diff --git a/apps/desktop/scripts/perf/baseline.json b/apps/desktop/scripts/perf/baseline.json index 1de3fea43b90..e24e85fb9d59 100644 --- a/apps/desktop/scripts/perf/baseline.json +++ b/apps/desktop/scripts/perf/baseline.json @@ -1,9 +1,9 @@ { "_meta": { - "note": "Median of 5 runs, darwin-arm64, `npm run perf -- cold-start stream keystroke transcript --spawn --prod` — a PRODUCTION renderer (minified React), so these are representative shipped numbers, not dev-inflated. Re-baseline per device with the same command + `--update-baseline`. Tolerances are loose to absorb cross-machine variance; cold-start especially varies with disk/OS state.", + "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 uses a fresh unique-port spawn per run and its marks are process-spawn wall clock (spawn_to_*) or renderer nav-relative (dom_*). Re-baseline per device with `--update-baseline`; tolerances are loose for cross-machine/disk variance.", "platform": "darwin-arm64", "node": "v24.11.0", - "updated": "2026-07-19T21:38:19.701Z" + "updated": "2026-07-19T22:59:21.605Z" }, "scenarios": { "stream": { @@ -49,9 +49,11 @@ "tolAbs": 150 }, "metrics": { - "spawn_to_cdp_ms": 326, - "spawn_to_driver_ms": 1647, - "fcp_ms": 500 + "spawn_to_cdp_ms": 1098, + "spawn_to_driver_ms": 1482, + "dom_interactive_ms": 794, + "dom_content_loaded_ms": 1057, + "nav_to_read_ms": 1209 } } } diff --git a/apps/desktop/scripts/perf/lib/launch.mjs b/apps/desktop/scripts/perf/lib/launch.mjs index c95da3f28545..46a9cba7e1e9 100644 --- a/apps/desktop/scripts/perf/lib/launch.mjs +++ b/apps/desktop/scripts/perf/lib/launch.mjs @@ -170,7 +170,6 @@ export async function startIsolatedInstance({ hermesHome, userDataDir, seedConfig = true, - bootFakeStepMs = 120, settleMs = 2500, connectTimeoutMs = 90000 } = {}) { @@ -232,11 +231,13 @@ export async function startIsolatedInstance({ // Isolated Electron: own --user-data-dir (single-instance lock scope) + own // HERMES_HOME (backend + sessions). No DEV_SERVER env in prod → dist load. const electronBin = require('electron') + // NB: do NOT set HERMES_DESKTOP_BOOT_FAKE here — it injects artificial + // per-phase sleeps into the boot overlay, which inflates cold-start timing + // (and adds pointless startup latency to the steady-state runs). We want the + // real boot sequence. const env = { ...process.env, HERMES_HOME: home, - HERMES_DESKTOP_BOOT_FAKE: '1', - HERMES_DESKTOP_BOOT_FAKE_STEP_MS: String(bootFakeStepMs), XCURSOR_SIZE: '24' } @@ -332,17 +333,26 @@ async function readBootMarks(cdp) { return await cdp.eval(`(() => { const paints = performance.getEntriesByType('paint') const fcp = paints.find(p => p.name === 'first-contentful-paint') + const nav = performance.getEntriesByType('navigation')[0] const composer = document.querySelector('[data-slot="composer-rich-input"]') + // Largest script resource ≈ the (intentionally single) renderer bundle. + // responseEnd → the script's own decode; the eval cost shows up as the gap + // between the bundle's responseEnd and domInteractive. + const scripts = performance.getEntriesByType('resource').filter(r => r.initiatorType === 'script') + const mainScript = scripts.sort((a, b) => (b.encodedBodySize || 0) - (a.encodedBodySize || 0))[0] + const round = n => (typeof n === 'number' ? Math.round(n) : null) return { - fcp_ms: fcp ? Math.round(fcp.startTime) : null, - // performance.now() at read time ≈ time since nav start; only meaningful - // right after boot (cold-start reads it immediately). - nav_to_read_ms: Math.round(performance.now()), + fcp_ms: fcp ? round(fcp.startTime) : null, + dom_interactive_ms: nav ? round(nav.domInteractive) : null, + dom_content_loaded_ms: nav ? round(nav.domContentLoadedEventEnd) : null, + main_script_kb: mainScript ? round((mainScript.encodedBodySize || 0) / 1024) : null, + main_script_response_end_ms: mainScript ? round(mainScript.responseEnd) : null, + nav_to_read_ms: round(performance.now()), composer_present: !!composer } })()`) } catch { - return { fcp_ms: null, nav_to_read_ms: null, composer_present: false } + return { fcp_ms: null, dom_interactive_ms: null, composer_present: false } } } diff --git a/apps/desktop/scripts/perf/run.mjs b/apps/desktop/scripts/perf/run.mjs index 2bf0c0649a2f..16db25f05090 100644 --- a/apps/desktop/scripts/perf/run.mjs +++ b/apps/desktop/scripts/perf/run.mjs @@ -152,9 +152,13 @@ async function main() { const perRun = [] for (let i = 0; i < runs; i++) { - const inst = await startIsolatedInstance({ port, devPort, prod, coldStart: true }) - const t = inst.timings - perRun.push({ spawn_to_cdp_ms: t.spawn_to_cdp_ms, spawn_to_driver_ms: t.spawn_to_driver_ms, fcp_ms: t.fcp_ms }) + // Unique debug + dev port per run: a just-killed instance can keep :9222 + // held for a beat, and reusing it makes the next CDP.connect attach to the + // dying instance (garbage timings). Stepping the port sidesteps the race. + const inst = await startIsolatedInstance({ port: port + i, devPort: devPort + i, prod, coldStart: true }) + // Forward every numeric boot mark; only the baseline keys are gated, the + // rest (dom_interactive, main_script_kb, …) are reported for composition. + perRun.push(Object.fromEntries(Object.entries(inst.timings).filter(([, v]) => typeof v === 'number'))) inst.teardown() }