perf(tooling): reach a fully populated sidebar in both profilers

Idle and streaming cost both depend on how much of the sidebar is mounted, so
the scenario setup is now shared. --expand-projects seeds the persisted collapse
state; --expand-sessions clicks every "Show more sessions" control, which
cannot be seeded because pagination is component state. Both run before the
measured window, so it stays input-free. Session expansion must run after the
sidebar has populated, not straight after the load event, or the controls do
not exist yet.

Also replaces a spread push over collected trace events, which overflowed the
call stack once a populated sidebar produced chunks of hundreds of thousands of
events, and the equivalent spread in the heap-maximum calculation.
This commit is contained in:
Bohdan Triapitsyn
2026-08-03 18:01:52 +03:00
parent b2cac4d242
commit 632fc09e18
3 changed files with 80 additions and 19 deletions
+12 -17
View File
@@ -29,6 +29,7 @@ import process from "node:process"
import { CdpClient, createPageTarget, evaluateValue, launchChrome, reservePort, resolveChrome, wait } from "./perf/cdp.mjs"
import { buildIdleProbeSource, IDLE_PROBE_GLOBAL } from "./perf/idle-probe.mjs"
import { summarizeCpuProfile } from "./perf/cpu-profile.mjs"
import { expandProjects, expandSessionLists } from "./perf/scenario.mjs"
import { growthPerSecond, metricMap, round } from "./perf/metrics.mjs"
const HELP = `Usage: bun run profile:idle -- [options]
@@ -42,6 +43,10 @@ Options:
--then-tab <name> After settling, navigate to this tab without a
reload, then record. Use it to measure what a
surface keeps doing after the user leaves it.
--expand-sessions Click every "Show more sessions" control until the
sidebar has no collapsed session lists left, so all
session rows are mounted. Clicks happen before the
recording window, which stays input-free.
--expand-projects Expand every project in the sidebar before
recording, which mounts a row per worktree and
session directory
@@ -74,6 +79,7 @@ const parseArgs = (argv) => {
thenTab: null,
panels: [],
expandProjects: false,
expandSessions: false,
duration: 30,
settle: 15,
output: null,
@@ -99,6 +105,7 @@ const parseArgs = (argv) => {
else if (value === "--then-tab") options.thenTab = argv[++index]
else if (value === "--panel") options.panels.push(argv[++index])
else if (value === "--expand-projects") options.expandProjects = true
else if (value === "--expand-sessions") options.expandSessions = true
else if (value === "--label") options.label = argv[++index]
else if (value === "--duration") options.duration = Number(argv[++index])
else if (value === "--settle") options.settle = Number(argv[++index])
@@ -243,7 +250,7 @@ const buildSummary = ({ options, before, after, samples, cpu, probe, elapsedSeco
frames: Number(after.Frames ?? 0),
heapStartMb: round(heapSamples.at(0) ?? 0),
heapEndMb: round(heapSamples.at(-1) ?? 0),
heapMaxMb: round(Math.max(0, ...heapSamples)),
heapMaxMb: round(heapSamples.reduce((max, value) => Math.max(max, value), 0)),
heapGrowthMbPerSecond: growthPerSecond(samples, "jsHeapUsedMb"),
},
cpuProfile: cpu,
@@ -357,10 +364,7 @@ const main = async () => {
await loaded
if (options.expandProjects) {
// The sidebar persists the ids of collapsed projects, so an empty list
// expands everything. This is the state in issue #1472: one mounted
// directory-bound row per worktree and session.
await evaluateValue(client, `localStorage.setItem("oc.sessions.projectCollapse", "[]")`)
await expandProjects(client)
console.log("Expanded every project in the sidebar.")
}
@@ -374,18 +378,9 @@ const main = async () => {
console.log(`Loaded ${options.url}; settling for ${options.settle}s before recording.`)
await wait(options.settle * 1000)
if (options.thenTab) {
// Route changes normally come from clicks; driving history directly
// reaches the same router path without generating input work inside the
// recorded window, and leaves already-mounted surfaces mounted.
await evaluateValue(client, `(() => {
const url = new URL(window.location.href)
url.searchParams.set("tab", ${JSON.stringify(options.thenTab)})
window.history.pushState({}, "", url.toString())
window.dispatchEvent(new PopStateEvent("popstate", { state: {} }))
return true
})()`)
console.log(`Navigated to tab "${options.thenTab}" without reloading; settling ${options.settle}s again.`)
if (options.expandSessions) {
const expanded = await expandSessionLists(client)
console.log(`Expanded ${expanded} collapsed session lists; settling ${options.settle}s again.`)
await wait(options.settle * 1000)
}