claude-code-router/packages/core/benchmark/request-log-web-load.mjs
musi 30d1f35926
Some checks failed
CI / Typecheck (push) Has been cancelled
CI / Test architecture (push) Has been cancelled
CI / Test cli (push) Has been cancelled
CI / Test core (push) Has been cancelled
CI / Test electron (push) Has been cancelled
CI / Test ui (push) Has been cancelled
CI / CLI web E2E (push) Has been cancelled
CI / Docker system smoke (push) Has been cancelled
Refactor request routing and provider coordination
2026-07-15 13:32:14 +08:00

100 lines
3.3 KiB
JavaScript

import process from "node:process";
import { performance } from "node:perf_hooks";
import { Pool } from "undici";
void main();
async function main() {
const options = parseArgs(process.argv.slice(2));
const target = new URL(options.url);
const pool = new Pool(target.origin, {
connections: options.concurrency,
pipelining: 1
});
const body = Buffer.alloc(options.bodyBytes, "x");
const latencies = new Float64Array(options.requests);
let nextRequest = 0;
let completed = 0;
let errors = 0;
const startedAt = performance.now();
try {
await Promise.all(Array.from({ length: options.concurrency }, async () => {
while (true) {
const index = nextRequest;
nextRequest += 1;
if (index >= options.requests) return;
const requestStartedAt = performance.now();
try {
const response = await pool.request({
body,
headers: {
"content-type": "application/octet-stream",
"x-benchmark-request-id": `${options.prefix}-${index}`
},
method: "POST",
path: target.pathname
});
await response.body.dump();
if (response.statusCode < 200 || response.statusCode >= 300) errors += 1;
} catch {
errors += 1;
} finally {
latencies[index] = performance.now() - requestStartedAt;
completed += 1;
}
}
}));
} finally {
await pool.close();
}
const durationMs = performance.now() - startedAt;
const sorted = [...latencies].sort((left, right) => left - right);
process.stdout.write(JSON.stringify({
completed,
concurrency: options.concurrency,
durationMs: round(durationMs),
errors,
latencyAvgMs: round(sorted.reduce((total, value) => total + value, 0) / Math.max(1, sorted.length)),
latencyMaxMs: round(sorted.at(-1) ?? 0),
latencyP50Ms: round(percentile(sorted, 0.5)),
latencyP95Ms: round(percentile(sorted, 0.95)),
latencyP99Ms: round(percentile(sorted, 0.99)),
qps: round(completed / (durationMs / 1_000)),
requests: options.requests
}));
}
function parseArgs(values) {
const options = {
bodyBytes: 1_024,
concurrency: 64,
prefix: "benchmark",
requests: 10_000,
url: ""
};
for (let index = 0; index < values.length; index += 1) {
if (values[index] === "--body-bytes") options.bodyBytes = positiveInteger(values[index + 1], options.bodyBytes);
if (values[index] === "--concurrency") options.concurrency = positiveInteger(values[index + 1], options.concurrency);
if (values[index] === "--prefix") options.prefix = values[index + 1] || options.prefix;
if (values[index] === "--requests") options.requests = positiveInteger(values[index + 1], options.requests);
if (values[index] === "--url") options.url = values[index + 1] || "";
}
if (!options.url) throw new Error("--url is required");
return options;
}
function positiveInteger(value, fallback) {
const parsed = Number(value);
return Number.isFinite(parsed) && parsed > 0 ? Math.floor(parsed) : fallback;
}
function percentile(sorted, fraction) {
if (sorted.length === 0) return 0;
return sorted[Math.min(sorted.length - 1, Math.floor((sorted.length - 1) * fraction))];
}
function round(value) {
return Math.round(value * 1_000) / 1_000;
}