import fs from 'node:fs'; import path from 'node:path'; const [baselineDir, candidateDir] = process.argv.slice(2); if (!baselineDir || !candidateDir) { throw new Error('usage: node compare-baseline.mjs '); } function median(values) { const sorted = [...values].sort((left, right) => left - right); return sorted[Math.floor(sorted.length / 2)]; } function read(directory) { const summaries = [1, 2, 3].map((run) => JSON.parse(fs.readFileSync(path.join(directory, `summary-run-${run}.json`), 'utf8'))); return { throughput: median(summaries.map((summary) => summary.metrics.http_reqs.values.rate)), p95: median(summaries.map((summary) => summary.metrics.http_req_duration.values['p(95)'])), p99: median(summaries.map((summary) => summary.metrics.http_req_duration.values['p(99)'])), failures: summaries.reduce((total, summary) => total + (summary.metrics.http_req_failed?.values?.passes || 0), 0), dropped: summaries.reduce((total, summary) => total + (summary.metrics.dropped_iterations?.values?.count || 0), 0), }; } const baseline = read(baselineDir); const candidate = read(candidateDir); const throughputChange = (candidate.throughput - baseline.throughput) / baseline.throughput; const p95Change = (candidate.p95 - baseline.p95) / baseline.p95; console.log(JSON.stringify({ baseline, candidate, throughputChange, p95Change }, null, 2)); if (candidate.failures > 0 || candidate.dropped > 0 || throughputChange < -0.05 || p95Change > 0.05) process.exitCode = 1;