Skip to content

Commit 912c130

Browse files
authored
test(e2e): measure typing-time renderer stalls with long tasks, not interval drift (#30971)
In the hidden E2E renderer the 16ms interval probe measured timer scheduling: a CI trace shows a 268ms interval gap while every keystroke echoed within 45ms. Long-task timing reports how long the main thread was actually blocked, which is what the 250ms and 3.5s budgets are meant to bound. The report field keeps its maxTimerDrift name so the perf report pipeline still reads and budgets it.
1 parent 3ce8012 commit 912c130

1 file changed

Lines changed: 20 additions & 14 deletions

File tree

‎tests/e2e/artificial-opencode-terminal-load.spec.ts‎

Lines changed: 20 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -120,7 +120,6 @@ const DEFAULT_HIDDEN_PRESSURE_PANES = 17
120120
const HIDDEN_PRESSURE_START_DELAY_MS = 1200
121121
const DEFAULT_FRAME_COUNT = 180
122122
const DEFAULT_FRAME_INTERVAL_MS = 6
123-
const TIMER_SAMPLE_MS = 16
124123
const MAIN_RENDERER_PRESSURE_TARGET_CHARS = 2 * 1024 * 1024
125124
// Why: these are regression budgets, not observed baselines. Repeated local
126125
// 100-pane OpenCode-scale runs are below 50ms worst-key latency; keep enough
@@ -139,8 +138,8 @@ const MAX_WORST_KEY_LATENCY_UNDER_LOAD_MS = 3_500
139138
// same environment-dominated worst-case as typing under load; the unloaded
140139
// 300ms budget has no margin for this switch-and-focus moment.
141140
const MAX_REVISIT_LATENCY_UNDER_LOAD_MS = 3_000
142-
// Why: GitHub's two-worker Electron shards can briefly starve renderer timers
143-
// without visible typing lag. Keep this as a smoke gate, not a CPU lottery.
141+
// Why: bounds the longest renderer task while typing and the scroll probe's interval drift; a
142+
// smoke gate for main-thread stalls, not a CPU lottery.
144143
const MAX_TIMER_DRIFT_MS = 250
145144
// Why: under injected multi-pane redraw load the renderer event loop is
146145
// environment-dominated (seen at ~3.1s on a CPU-starved OSS shard) even when
@@ -252,21 +251,28 @@ async function measureTypingDuringLoad(
252251
await waitForTerminalOutputForPtyId(page, ptyId, `OPENCODE_TYPING_READY_${runId}`, 10_000)
253252
await focusActiveTerminalInput(page)
254253

255-
const eventLoop = await page.evaluateHandle((sampleMs) => {
256-
let maxTimerDriftMs = 0
257-
let lastTick = performance.now()
258-
const timer = window.setInterval(() => {
259-
const now = performance.now()
260-
maxTimerDriftMs = Math.max(maxTimerDriftMs, now - lastTick - sampleMs)
261-
lastTick = now
262-
}, sampleMs)
254+
// Why: in the hidden E2E renderer a 16ms interval showed 268ms gaps while every key echoed in
255+
// <45ms, so drift measured timer scheduling; long tasks measure how long the main thread blocked.
256+
const eventLoop = await page.evaluateHandle(() => {
257+
if (!PerformanceObserver.supportedEntryTypes.includes('longtask')) {
258+
throw new Error('Long-task timing is unavailable in this renderer')
259+
}
260+
let longestTaskMs = 0
261+
const record = (entries: PerformanceEntryList): void => {
262+
for (const entry of entries) {
263+
longestTaskMs = Math.max(longestTaskMs, entry.duration)
264+
}
265+
}
266+
const observer = new PerformanceObserver((list) => record(list.getEntries()))
267+
observer.observe({ type: 'longtask' })
263268
return {
264269
stop: () => {
265-
window.clearInterval(timer)
266-
return maxTimerDriftMs
270+
record(observer.takeRecords())
271+
observer.disconnect()
272+
return longestTaskMs
267273
}
268274
}
269-
}, TIMER_SAMPLE_MS)
275+
})
270276

271277
const latencies: number[] = []
272278
for (const [index, char] of [...KEY_LATENCY_SAMPLES].entries()) {

0 commit comments

Comments
 (0)