test: add remote control stress budgets

This commit is contained in:
AI Bot
2026-05-11 23:25:52 +08:00
parent 9c8ffebb92
commit bc199dcf5c
4 changed files with 133 additions and 3 deletions

View File

@@ -20,6 +20,9 @@ function parseArgs(argv) {
timeoutMs: 45_000,
skipChain: false,
skipRuntime: false,
reportJson: null,
maxChainP95Ms: null,
maxRuntimeP95Ms: null,
};
for (const arg of argv) {
@@ -31,7 +34,12 @@ function parseArgs(argv) {
options.runtimeConcurrency = positiveInt(arg.split("=")[1], options.runtimeConcurrency);
} else if (arg.startsWith("--poll-ms=")) options.pollMs = positiveInt(arg.split("=")[1], options.pollMs);
else if (arg.startsWith("--timeout-ms=")) options.timeoutMs = positiveInt(arg.split("=")[1], options.timeoutMs);
else if (arg === "--help" || arg === "-h") {
else if (arg.startsWith("--report-json=")) options.reportJson = arg.slice("--report-json=".length);
else if (arg.startsWith("--max-chain-p95-ms=")) {
options.maxChainP95Ms = positiveInt(arg.split("=")[1], options.maxChainP95Ms);
} else if (arg.startsWith("--max-runtime-p95-ms=")) {
options.maxRuntimeP95Ms = positiveInt(arg.split("=")[1], options.maxRuntimeP95Ms);
} else if (arg === "--help" || arg === "-h") {
options.help = true;
}
}
@@ -453,6 +461,29 @@ function hasFailure(summary) {
return summary.failed > 0;
}
function findThresholdFailures(summaries, options) {
const failures = [];
const chain = summaries.find((summary) => summary.name === "chain");
if (chain && options.maxChainP95Ms && chain.latencyMs.p95 > options.maxChainP95Ms) {
failures.push({
name: "chain_p95_latency",
actualMs: chain.latencyMs.p95,
maxMs: options.maxChainP95Ms,
});
}
const runtime = summaries.find((summary) => summary.name === "runtime");
if (runtime && options.maxRuntimeP95Ms && runtime.latencyMs.p95 > options.maxRuntimeP95Ms) {
failures.push({
name: "runtime_p95_latency",
actualMs: runtime.latencyMs.p95,
maxMs: options.maxRuntimeP95Ms,
});
}
return failures;
}
function printHelp() {
console.log(`Usage: node scripts/stress-remote-control.mjs [options]
@@ -462,6 +493,9 @@ Options:
--runtime-concurrency=N direct runtime concurrency, default 24
--poll-ms=N local-agent task poll interval, default 5
--timeout-ms=N chain stress timeout, default 45000
--report-json=PATH write the full stress report to PATH
--max-chain-p95-ms=N fail when local-agent chain p95 latency is above N
--max-runtime-p95-ms=N fail when direct runtime p95 latency is above N
--skip-chain skip local-agent chain stress
--skip-runtime skip direct runtime stress
`);
@@ -473,6 +507,8 @@ if (options.help) {
process.exit(0);
}
const startedAt = new Date();
const startedMs = Date.now();
const summaries = [];
if (!options.skipChain) {
summaries.push(await runChainStress(options));
@@ -481,7 +517,23 @@ if (!options.skipRuntime) {
summaries.push(await runRuntimeStress(options));
}
console.log(JSON.stringify({ ok: summaries.every((summary) => !hasFailure(summary)), summaries }, null, 2));
if (summaries.some(hasFailure)) {
const thresholdFailures = findThresholdFailures(summaries, options);
const report = {
ok: summaries.every((summary) => !hasFailure(summary)) && thresholdFailures.length === 0,
startedAt: startedAt.toISOString(),
finishedAt: new Date().toISOString(),
durationMs: Date.now() - startedMs,
options,
summaries,
thresholdFailures,
};
if (options.reportJson) {
await mkdir(path.dirname(path.resolve(options.reportJson)), { recursive: true });
await writeFile(path.resolve(options.reportJson), `${JSON.stringify(report, null, 2)}\n`, "utf8");
}
console.log(JSON.stringify(report, null, 2));
if (!report.ok) {
process.exitCode = 1;
}