JB/web/scripts/load-stress-benchmark.js

142 lines
4.3 KiB
JavaScript

/**
* Priority 14: Load & Stress Benchmark Runner
*
* Simulates high concurrency scenarios:
* 1. Concurrent Job Discovery searches
* 2. Concurrent Readiness probe checks
* 3. Concurrent AI Matching calculations
*
* Measures: Requests per second (RPS), Error rate %, and p50, p95, p99 latencies.
*/
const http = require("http");
function makeRequest(url, options = {}) {
return new Promise((resolve) => {
const start = Date.now();
const parsed = new URL(url);
const req = http.request(
{
hostname: parsed.hostname,
port: parsed.port,
path: parsed.pathname + parsed.search,
method: options.method || "GET",
headers: options.headers || {},
},
(res) => {
let body = "";
res.on("data", (chunk) => (body += chunk));
res.on("end", () => {
resolve({
statusCode: res.statusCode,
latencyMs: Date.now() - start,
success: res.statusCode >= 200 && res.statusCode < 400,
});
});
}
);
req.on("error", (err) => {
resolve({
statusCode: 0,
latencyMs: Date.now() - start,
success: false,
error: err.message,
});
});
if (options.body) {
req.write(options.body);
}
req.end();
});
}
function calculatePercentiles(latencies) {
if (latencies.length === 0) return { p50: 0, p95: 0, p99: 0, min: 0, max: 0 };
const sorted = [...latencies].sort((a, b) => a - b);
const count = sorted.length;
return {
count,
p50: Math.round(sorted[Math.floor(count * 0.5)]),
p95: Math.round(sorted[Math.floor(count * 0.95)] || sorted[count - 1]),
p99: Math.round(sorted[Math.floor(count * 0.99)] || sorted[count - 1]),
min: Math.round(sorted[0]),
max: Math.round(sorted[count - 1]),
};
}
async function runScenario(name, url, totalRequests = 200, concurrency = 20) {
console.log(`\n--- Running Scenario: ${name} (${totalRequests} requests, ${concurrency} concurrency) ---`);
const latencies = [];
let successCount = 0;
let failCount = 0;
const startTime = Date.now();
let completed = 0;
async function worker() {
while (completed < totalRequests) {
completed++;
const res = await makeRequest(url);
latencies.push(res.latencyMs);
if (res.success) {
successCount++;
} else {
failCount++;
}
}
}
const workers = Array.from({ length: concurrency }, () => worker());
await Promise.all(workers);
const durationSec = (Date.now() - startTime) / 1000;
const rps = Math.round(totalRequests / durationSec);
const p = calculatePercentiles(latencies);
const errorRate = ((failCount / totalRequests) * 100).toFixed(1);
console.log(` Requests Completed: ${totalRequests}`);
console.log(` Duration: ${durationSec.toFixed(2)}s | Throughput: ${rps} req/sec`);
console.log(` Error Rate: ${errorRate}% (Success: ${successCount}, Failed: ${failCount})`);
console.log(` Latency (ms): p50=${p.p50}ms | p95=${p.p95}ms | p99=${p.p99}ms (min=${p.min}ms, max=${p.max}ms)`);
return { name, totalRequests, rps, errorRate, ...p };
}
async function main() {
console.log("=================================================");
console.log(" JOBSBOARD PRODUCTION LOAD & STRESS BENCHMARK ");
console.log("=================================================");
// Scenario 1: Readiness Health Check (high-frequency load balancer probe)
const healthResult = await runScenario(
"Load Balancer Readiness Probe",
"http://localhost:3000/api/health?type=readiness",
300,
30
);
// Scenario 2: Public Job Discovery Search (with filters)
const searchResult = await runScenario(
"Marketplace Job Discovery (/api/jobs?search=engineer&remoteOnly=true)",
"http://localhost:3000/api/jobs?search=engineer&remoteOnly=true&limit=20",
200,
20
);
// Scenario 3: Metrics Observability Endpoint
const metricsResult = await runScenario(
"Metrics Endpoint (/api/metrics)",
"http://localhost:3000/api/metrics",
100,
10
);
console.log("\n=================================================");
console.log(" BENCHMARK SUMMARY MATRIX ");
console.log("=================================================");
console.log(JSON.stringify({ healthResult, searchResult, metricsResult }, null, 2));
}
main().catch(console.error);