perf(desktop): baseline multitab + render-churn, leave idle-cost report-only

Captures medians of 5 runs for multitab and render-churn so tonight's
wins can't silently regress.

idle-cost is deliberately NOT gated. Its render attribution and idle
commit rate are trustworthy and are what the scenario exists for, but the
drag fps it reports (~0.6fps, p95 814ms) contradicts a direct
single-clock probe of the same gesture on the same build (57fps). I ruled
out sash selection, tile setup, render-counter residue, and a 20s soak,
and could not explain the gap — so the metric ships as a report, not a
gate. Gating CI on a number I can't defend would either fire on a phantom
or mask a real stall.

tier: 'report' is outside GATED ('ci','cold'), so the scenario still runs
and prints but neither compares nor writes a baseline.
This commit is contained in:
Brooklyn Nicholson 2026-07-26 19:55:55 -05:00
parent 45d4cf634d
commit a894879d28
3 changed files with 29 additions and 3 deletions

View File

@ -52,6 +52,7 @@ directly via `window.__PERF_DRIVE__`, so no LLM credits are spent.
| `keystroke` | ci | composer keystroke → paint latency | measure-latency, profile-typing, leak-typing |
| `transcript` | ci | large-transcript mount + paint cost | (new) |
| `render-churn` | ci | per-component render attribution + store churn while N tabs stream | (new) |
| `idle-cost` | report | busy-but-silent tiles: idle commit rate, + fps while resizing / typing | (new) |
| `cold-start` | cold | launch → CDP → driver → first paint (fresh spawn/run) | (new) |
| `first-token` | backend | Enter → first assistant token painted (TTFT) | (new) |
| `submit` | backend | Enter → cleared → user msg painted, scroll jump | measure-submit, measure-jump |

View File

@ -1,9 +1,9 @@
{
"_meta": {
"note": "Median of 5 runs, darwin-arm64, `--spawn --prod` (PRODUCTION minified renderer, real boot no fake-boot). Representative shipped numbers, not dev-inflated. cold-start reuses one profile so the V8 code cache is WARM (what users get after first launch, ~1.0s); a fresh-profile first launch is ~+400ms (measure with `--cold-fresh`). Marks are process-spawn wall clock (spawn_to_*) or renderer nav-relative (dom_*). Re-baseline per device with `--update-baseline`; tolerances loose for cross-machine/disk variance.",
"note": "Median of 5 runs, darwin-arm64, `--spawn --prod` (PRODUCTION minified renderer, real boot \u2014 no fake-boot). Representative shipped numbers, not dev-inflated. cold-start reuses one profile so the V8 code cache is WARM (what users get after first launch, ~1.0s); a fresh-profile first launch is ~+400ms (measure with `--cold-fresh`). Marks are process-spawn wall clock (spawn_to_*) or renderer nav-relative (dom_*). Re-baseline per device with `--update-baseline`; tolerances loose for cross-machine/disk variance.",
"platform": "darwin-arm64",
"node": "v24.11.0",
"updated": "2026-07-19T23:16:01.227Z"
"updated": "2026-07-27T00:30:11.290Z"
},
"scenarios": {
"stream": {
@ -55,6 +55,25 @@
"dom_content_loaded_ms": 574,
"nav_to_read_ms": 721
}
},
"multitab": {
"metrics": {
"longtasks_n": 0,
"longtask_max_ms": 0,
"frame_p95_ms": 29.1,
"frame_p99_ms": 36.2,
"slow_frames_33": 9
}
},
"render-churn": {
"metrics": {
"sidebar_renders": 0,
"sidebar_wasted": 0,
"wasted_renders": 1704,
"total_renders": 8221,
"commits": 1352,
"wasted_notifies": 0
}
}
}
}

View File

@ -202,7 +202,13 @@ const round = (n, places = 1) => Math.round(n * 10 ** places) / 10 ** places
export default {
name: 'idle-cost',
tier: 'ci',
// NOT 'ci': the drag fps this reports (~0.6fps, p95 814ms) contradicts a
// direct single-clock probe of the same gesture on the same build (57fps),
// and I could not reconcile the two — ruled out sash selection, tile setup,
// counter residue, and a 20s soak. Its RENDER attribution and idle commit
// rate are trustworthy and are what this scenario is for; the interaction
// fps is reported for investigation, not gated on, until that is explained.
tier: 'report',
description: 'Busy-but-silent tiles: idle commit rate, and fps while resizing / typing.',
async run(cdp, opts = {}) {
const tiles = Number(opts.tiles ?? 5)