scripts/bench-stats.mjs
scripts/bench-stats.mjsBrowse 13 files
2,867 tokens
8,940 bytes
Token encoding: o200k_base
Snapshot bfcf687
← Back to SKILL.md
1// Shared statistics for the sandbox bench harnesses.2//3// Unit of analysis: the VM BOOT. Iterations within a boot share JIT4// state, heap layout, host, and phase-sequence position, so they are5// not independent samples (Kalibera & Jones, "Rigorous Benchmarking in6// Reasonable Time"; JMH forks). Each boot contributes ONE delta per7// metric (the mean of its paired within-boot deltas); confidence8// intervals and p-values are computed across boots. Within-boot9// statistics are printed as a diagnostic only — never as the claim.10 11// Two-sided critical values of Student's t at 97.5% (df 1..30).12const T975 = [13 12.706, 4.303, 3.182, 2.776, 2.571, 2.447, 2.365, 2.306, 2.262, 2.228, 2.201,14 2.179, 2.16, 2.145, 2.131, 2.12, 2.11, 2.101, 2.093, 2.086, 2.08, 2.074,15 2.069, 2.064, 2.06, 2.056, 2.052, 2.048, 2.045, 2.042,16]17 18function tCritical975(df) {19 if (df < 1) return Infinity20 if (df <= 30) return T975[df - 1]21 return 1.96 + 2.4 / df // adequate approximation past df=3022}23 24// Two-sided p for a one-sample t test against zero, via numerical25// integration of the t pdf (small-df accuracy is what matters here —26// boots are few).27export function tTestP(values) {28 const n = values.length29 if (n < 2) return 130 if (values.some((v) => !Number.isFinite(v))) return 131 const mean = values.reduce((a, b) => a + b, 0) / n32 const sd = Math.sqrt(33 values.reduce((a, b) => a + (b - mean) ** 2, 0) / (n - 1)34 )35 if (sd === 0) return mean === 0 ? 1 : 036 const t = Math.abs(mean / (sd / Math.sqrt(n)))37 const df = n - 138 if (df === 1) {39 // Student t with df=1 is Cauchy; the closed form avoids the fat40 // tail truncating a numerical integration.41 return Math.min(1, Math.max(0, 1 - (2 / Math.PI) * Math.atan(t)))42 }43 if (!Number.isFinite(t) || t > 45) {44 // p underflows well past any claim threshold; also guards the45 // integration loop below, whose step size vanishes against huge t.46 return 047 }48 const pdf = (x) => Math.exp(-((df + 1) / 2) * Math.log(1 + (x * x) / df))49 let integral = 050 const STEP = 0.00151 for (let x = t; x < t + 60; x += STEP) integral += pdf(x + STEP / 2) * STEP52 let norm = 053 for (let x = 0; x < 80; x += STEP) norm += pdf(x + STEP / 2) * STEP54 return Math.min(1, integral / norm)55}56 57// Anytime-valid confidence sequence for a running mean (asymptotic CS,58// Waudby-Smith & Ramdas). Unlike a t-CI, this interval is valid at59// EVERY peek simultaneously, so interim displays built on it cannot60// manufacture significance through repeated looking. Tuned to be61// tightest around ~12 boots.62export function confidenceSeq(values, alpha = 0.05) {63 const n = values.length64 // Below 6 samples the estimated variance is too unstable for the65 // asymptotic guarantee; show nothing rather than something wrong.66 if (n < 6) return null67 const mean = values.reduce((a, b) => a + b, 0) / n68 let sd = Math.sqrt(values.reduce((a, b) => a + (b - mean) ** 2, 0) / (n - 1))69 if (!Number.isFinite(sd)) return null70 // Small-sample variance inflation (t-style): keeps the sequence71 // honest at the n this harness actually runs (6..32 boots).72 sd *= Math.sqrt((n - 1) / Math.max(1, n - 3))73 const nOpt = 1674 const rho2 = (2 * Math.log(2 / alpha)) / nOpt75 const width =76 sd *77 Math.sqrt(78 ((2 * (n * rho2 + 1)) / (n * n * rho2)) *79 Math.log((Math.sqrt(n * rho2 + 1) * 2) / alpha)80 )81 return { mean, lo: mean - width, hi: mean + width, n }82}83 84// What each metric measures and which direction is an improvement.85// Deltas are always relative (%), so the unit answers "% of what".86export const METRICS = {87 rps: { unit: 'req/s', better: 'higher' },88 docKb: { unit: 'KB', better: 'lower' },89 gzipKb: { unit: 'KB', better: 'lower' },90 flightKb: { unit: 'KB', better: 'lower' },91 median: { unit: 'ms', better: 'lower' },92 mean: { unit: 'ms', better: 'lower' },93 p95: { unit: 'ms', better: 'lower' },94 p99: { unit: 'ms', better: 'lower' },95 ttfb: { unit: 'ms', better: 'lower' },96 gcMs: { unit: 'ms', better: 'lower' },97 p50: { unit: 'ms', better: 'lower' },98 rss: { unit: 'MB', better: 'lower' },99 heapMb: { unit: 'MB', better: 'lower' },100 rssHw: { unit: 'MB peak', better: 'lower' },101}102 103// One metric in one phase: per-boot arrays of paired deltas in, verdict out.104export function bootLevelStats(perBootDeltas) {105 const boots = perBootDeltas.filter((d) => d.length > 0)106 const bootMeans = boots.map((d) => d.reduce((a, b) => a + b, 0) / d.length)107 const n = bootMeans.length108 if (n === 0) return null109 const mean = bootMeans.reduce((a, b) => a + b, 0) / n110 const all = boots.flat()111 const result = {112 mean,113 boots: n,114 bootMeans,115 pairs: all.length,116 withinP: tTestP(all),117 }118 if (n >= 2) {119 const sd = Math.sqrt(120 bootMeans.reduce((a, b) => a + (b - mean) ** 2, 0) / (n - 1)121 )122 result.ci95 = (tCritical975(n - 1) * sd) / Math.sqrt(n)123 result.p = tTestP(bootMeans)124 } else {125 result.ci95 = Infinity126 result.p = 1127 }128 return result129}130 131export function formatStat(name, candName, baseName, s) {132 if (s === null) return ` ${name.padEnd(6)} (no data)`133 const pct = (x) => `${(x * 100).toFixed(1)}%`134 const ci = s.ci95 === Infinity ? '±∞' : `±${(s.ci95 * 100).toFixed(1)}`135 const m = METRICS[name]136 const meta = m137 ? ` [${m.unit}; ${s.mean > 0 === (m.better === 'higher') ? 'IMPROVEMENT' : 'regression'} if real]`138 : ''139 // Byte metrics are deterministic per build, so a ±0.0 near-zero cell140 // with p=0 is normal; the absolute values say whether it matters.141 const abs =142 s.absBase !== undefined && m?.unit === 'KB'143 ? ` [${s.absBase.toFixed(1)}KB -> ${s.absCand.toFixed(1)}KB]`144 : ''145 return (146 ` ${name.padEnd(6)} ${candName} vs ${baseName}: ${pct(s.mean)} ${ci}${meta}${abs} ` +147 `(boots=${s.boots}${s.boots < 3 ? ' — TOO FEW FOR CLAIMS' : ''}, p=${s.p.toFixed(4)})` +148 ` perBoot ${s.bootMeans.map((m) => pct(m)).join(' ')}` +149 ` [pairs=${s.pairs} within-run p=${s.withinP.toFixed(4)} — diagnostic only]`150 )151}152 153// E2e rows: {vm, arm, block, run, route, phase, <metrics...>}. Pairs are154// (vm, block, run); the boot is the vm. Returns nothing; prints.155export function analyzeE2eRows(rows, baseName, candName, metrics) {156 const fps = {}157 for (const r of rows)158 (fps[r.arm] ||= new Set()).add(r.ver ? `${r.fp}/${r.ver}` : `${r.fp}`)159 console.log(160 `\nfingerprints: ${baseName}=${[...(fps[baseName] ?? [])].join(',')} ` +161 `${candName}=${[...(fps[candName] ?? [])].join(',')}`162 )163 if ((fps[baseName]?.size ?? 0) !== 1 || (fps[candName]?.size ?? 0) !== 1) {164 // A VM measured the wrong build; numbers would look official and be165 // meaningless. Refuse instead of printing stats with a warning.166 console.log(167 '!! inconsistent fingerprints within an arm — RESULTS INVALID, no stats'168 )169 process.exitCode = 1170 return false171 }172 const baseFp = [...fps[baseName]][0]?.split('/')[0]173 const candFp = [...fps[candName]][0]?.split('/')[0]174 if (baseFp !== undefined && candFp !== undefined) {175 console.log(176 baseFp === candFp177 ? 'fingerprint files byte-identical between arms (A/A for those files; ' +178 'arms may still differ elsewhere — check version strings)'179 : 'arms differ (A/B mode)'180 )181 }182 183 // "Absent" must be distinguishable from "identical": metrics the run184 // never captured are named at the end instead of silently missing.185 const captured = new Set()186 for (const phase of [187 ...new Set(rows.map((r) => `${r.route ?? ''} ${r.phase}`)),188 ].sort()) {189 console.log(`\n${phase}`)190 const inPhase = (r) => `${r.route ?? ''} ${r.phase}` === phase191 for (const metric of metrics) {192 const perBoot = []193 const baseVals = []194 const candVals = []195 for (const vm of [...new Set(rows.map((r) => r.vm))]) {196 const deltas = []197 for (const r of rows.filter(198 (x) => x.vm === vm && inPhase(x) && x.arm === candName199 )) {200 const b = rows.find(201 (x) =>202 x.vm === vm &&203 inPhase(x) &&204 x.arm === baseName &&205 x.block === r.block &&206 x.run === r.run207 )208 if (b && b[metric] > 0 && r[metric] > 0) {209 deltas.push((r[metric] - b[metric]) / b[metric])210 baseVals.push(b[metric])211 candVals.push(r[metric])212 }213 }214 perBoot.push(deltas)215 }216 const s = bootLevelStats(perBoot)217 if (s !== null && s.pairs > 0) {218 captured.add(metric)219 s.absBase = baseVals.reduce((a, b) => a + b, 0) / baseVals.length220 s.absCand = candVals.reduce((a, b) => a + b, 0) / candVals.length221 console.log(formatStat(metric, candName, baseName, s))222 }223 }224 }225 const absent = metrics.filter((m) => !captured.has(m))226 if (absent.length > 0) {227 console.log(`\nnot captured on this run: ${absent.join(', ')}`)228 }229 return true230}231