-
Notifications
You must be signed in to change notification settings - Fork 10
ci(bench): gate release benchmark on engine parity thresholds #1014
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 1 commit
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,121 @@ | ||||||||||||||||||||||||||||
| #!/usr/bin/env node | ||||||||||||||||||||||||||||
| /** | ||||||||||||||||||||||||||||
| * Engine parity gate — runs after the release build benchmark. | ||||||||||||||||||||||||||||
| * | ||||||||||||||||||||||||||||
| * Reads the merged benchmark-result.json (contains `wasm` and `native` blocks) | ||||||||||||||||||||||||||||
| * and fails the workflow if the gap between engines breaches a documented | ||||||||||||||||||||||||||||
| * threshold. A failure here doesn't block the release (benchmark runs *after* | ||||||||||||||||||||||||||||
| * Publish completes); it surfaces regressions to maintainers via the workflow's | ||||||||||||||||||||||||||||
| * red status and writes a summary to $GITHUB_STEP_SUMMARY. | ||||||||||||||||||||||||||||
| * | ||||||||||||||||||||||||||||
| * Thresholds reference the parity bugs open against v3.9.5: | ||||||||||||||||||||||||||||
| * - #1010 DB size / excess ast_nodes | ||||||||||||||||||||||||||||
| * - #1011 Native orchestrator drops files | ||||||||||||||||||||||||||||
| * - #1012 Native 1-file incremental runs globally | ||||||||||||||||||||||||||||
| * - #1013 Native full-build edges/roles phases | ||||||||||||||||||||||||||||
| * | ||||||||||||||||||||||||||||
| * Each threshold fires only when BOTH engines produced results. If one engine | ||||||||||||||||||||||||||||
| * failed, we leave the gate passing so the rest of the workflow (doc PR, | ||||||||||||||||||||||||||||
| * artifact upload) still runs, and a separate "both engines ran" check flags | ||||||||||||||||||||||||||||
| * the missing engine. | ||||||||||||||||||||||||||||
| */ | ||||||||||||||||||||||||||||
| import fs from 'node:fs'; | ||||||||||||||||||||||||||||
| import path from 'node:path'; | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| const resultFile = process.argv[2]; | ||||||||||||||||||||||||||||
| if (!resultFile) { | ||||||||||||||||||||||||||||
| console.error('Usage: benchmark-parity-gate.mjs <benchmark-result.json>'); | ||||||||||||||||||||||||||||
| process.exit(2); | ||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| const result = JSON.parse(fs.readFileSync(resultFile, 'utf8')); | ||||||||||||||||||||||||||||
| const { wasm, native, version } = result; | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| const summaryFile = process.env.GITHUB_STEP_SUMMARY; | ||||||||||||||||||||||||||||
| const writeSummary = (text) => { | ||||||||||||||||||||||||||||
| if (summaryFile) fs.appendFileSync(summaryFile, text); | ||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| function line(s = '') { | ||||||||||||||||||||||||||||
| console.log(s); | ||||||||||||||||||||||||||||
| writeSummary(`${s}\n`); | ||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| line(`## Engine parity gate — v${version}`); | ||||||||||||||||||||||||||||
| line(''); | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| if (!wasm || !native) { | ||||||||||||||||||||||||||||
| const missing = [!wasm && 'wasm', !native && 'native'].filter(Boolean).join(', '); | ||||||||||||||||||||||||||||
| line(`**FAIL:** missing engine result for: ${missing}. Benchmark cannot assert parity.`); | ||||||||||||||||||||||||||||
| process.exit(1); | ||||||||||||||||||||||||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
The script-level JSDoc on line 19 explicitly states: "If one engine failed, we leave the gate passing…" but
Suggested change
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Fixed in b789411 — missing-engine branch now logs SKIP and exits 0, matching the documented pass-through intent in the file-level JSDoc. |
||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| // ── Thresholds ───────────────────────────────────────────────────────── | ||||||||||||||||||||||||||||
| // Each entry: | ||||||||||||||||||||||||||||
| // name — human-readable label | ||||||||||||||||||||||||||||
| // actual — computed metric | ||||||||||||||||||||||||||||
| // limit — ceiling; actual must be ≤ limit | ||||||||||||||||||||||||||||
| // formatter — how to render the value | ||||||||||||||||||||||||||||
| // tracks — related issue link shown on failure | ||||||||||||||||||||||||||||
| const checks = [ | ||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||
|
Comment on lines
+62
to
+66
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
If either
Suggested change
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Fixed in b789411 — file-set gap now uses |
||||||||||||||||||||||||||||
| name: 'File-set gap (|wasm − native|)', | ||||||||||||||||||||||||||||
| actual: Math.abs(wasm.files - native.files), | ||||||||||||||||||||||||||||
| limit: 2, | ||||||||||||||||||||||||||||
| formatter: (v) => String(v), | ||||||||||||||||||||||||||||
| tracks: '#1011', | ||||||||||||||||||||||||||||
|
Comment on lines
+67
to
+71
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
All four timing-based ratio checks defensively use
Suggested change
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Fixed in b789411 — DB size ratio now uses |
||||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||
| name: 'DB size ratio (native / wasm)', | ||||||||||||||||||||||||||||
| actual: native.dbSizeBytes / wasm.dbSizeBytes, | ||||||||||||||||||||||||||||
| limit: 1.02, | ||||||||||||||||||||||||||||
| formatter: (v) => v.toFixed(3), | ||||||||||||||||||||||||||||
| tracks: '#1010', | ||||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||
| name: 'Full-build edges-phase ratio', | ||||||||||||||||||||||||||||
| actual: (native.phases?.edgesMs ?? 0) / Math.max(wasm.phases?.edgesMs ?? 1, 1), | ||||||||||||||||||||||||||||
| limit: 1.3, | ||||||||||||||||||||||||||||
| formatter: (v) => v.toFixed(2), | ||||||||||||||||||||||||||||
| tracks: '#1013', | ||||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||
| name: 'Full-build roles-phase ratio', | ||||||||||||||||||||||||||||
| actual: (native.phases?.rolesMs ?? 0) / Math.max(wasm.phases?.rolesMs ?? 1, 1), | ||||||||||||||||||||||||||||
| limit: 1.3, | ||||||||||||||||||||||||||||
| formatter: (v) => v.toFixed(2), | ||||||||||||||||||||||||||||
| tracks: '#1013', | ||||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||
| name: '1-file incremental ratio', | ||||||||||||||||||||||||||||
| actual: | ||||||||||||||||||||||||||||
| (native.oneFileRebuildMs ?? 0) / | ||||||||||||||||||||||||||||
| Math.max(wasm.oneFileRebuildMs ?? 1, 1), | ||||||||||||||||||||||||||||
| limit: 1.5, | ||||||||||||||||||||||||||||
| formatter: (v) => v.toFixed(2), | ||||||||||||||||||||||||||||
| tracks: '#1012', | ||||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||||
| ]; | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| line('| Check | Actual | Limit | Status | Tracks |'); | ||||||||||||||||||||||||||||
| line('|---|---:|---:|---|---|'); | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| let failed = 0; | ||||||||||||||||||||||||||||
| for (const c of checks) { | ||||||||||||||||||||||||||||
| const ok = c.actual <= c.limit; | ||||||||||||||||||||||||||||
| if (!ok) failed++; | ||||||||||||||||||||||||||||
| const status = ok ? ':white_check_mark: pass' : ':x: **fail**'; | ||||||||||||||||||||||||||||
| line( | ||||||||||||||||||||||||||||
| `| ${c.name} | ${c.formatter(c.actual)} | ${c.formatter(c.limit)} | ${status} | ${c.tracks} |`, | ||||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| line(''); | ||||||||||||||||||||||||||||
| if (failed > 0) { | ||||||||||||||||||||||||||||
| line( | ||||||||||||||||||||||||||||
| `**${failed} parity check(s) failed.** See linked issues for root-cause tracking; the benchmark doc PR (if opened) captures the raw numbers.`, | ||||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||||
| process.exit(1); | ||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| line('All parity checks passed.'); | ||||||||||||||||||||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
readFileSyncthrows an opaque stack trace on missing fileIf
benchmark-result.jsondoesn't exist (e.g., the build benchmark step was skipped by a prior failure), Node throws a rawENOENTstack trace. Wrapping this in a try/catch gives maintainers a cleaner diagnostic message that matches the script's own error-reporting style.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed in b789411 —
readFileSync/JSON.parsewrapped in try/catch, exits 2 with a cleanFailed to read <file>: <err.message>diagnostic instead of a raw ENOENT stack trace.