Files
orca/config/scripts/capture-live-input-lag.mjs
Jinjing 89e7667161 perf(persistence): add pty-binding fast lane to skip redundant flushes
Terminal pane reattachment currently clones the session and serializes the
entire 9.2 MB app state even when the binding is already in place and durable.
Add an early-return fast path that skips this work when all nine predicates
hold: no split, binding matches in-memory and on-disk, incarnation matches,
no tombstone, and generation counter proves durability.

Includes one-line fix in `writeToDiskSync` to record hash-matched sync flushes
as durable, so the fast path doesn't stay parked behind a stale generation.

Adds `persistence.pty-binding` observability spans (local NDJSON, unsampled for
mutations, budgeted for fast-lane hits) to measure eligibility rates before
and after. Includes ratchet test to ensure every binding writer bumps the
generation. Diagnostic tools and full investigation notes from September 7,
2026 capture that identified the 59–100 ms no-op binds and measured a real
terminal keystroke queued 117 ms behind one such call.
2026-09-13 17:37:16 -04:00

185 lines
6.1 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { mkdtemp, writeFile } from 'node:fs/promises'
import { tmpdir } from 'node:os'
import { join } from 'node:path'
import {
startRendererTimingProbe,
stopRendererTimingProbe
} from './idle-cpu-renderer-timing-probe.mjs'
// Called with an already-attached main Orca page; never launches, focuses, or reloads it.
export async function captureLiveInputLag(page, durationMs = 30_000) {
if (!Number.isFinite(durationMs) || durationMs < 1_000 || durationMs > 60_000) {
throw new Error('Capture duration must be 1–60 seconds')
}
const identity = await page.evaluate(async () => {
if (!window.api?.app?.getIdentity) {
throw new Error('Target is not the main Orca renderer')
}
if (window.__orcaLiveInputLag || window.__orcaIdleCpuTimingProbe) {
throw new Error('A renderer timing probe already exists; stop it before capturing')
}
return window.api.app.getIdentity()
})
const directory = await mkdtemp(join(tmpdir(), 'orca-input-lag-'))
const cdp = await page.context().newCDPSession(page)
let timingStarted = false
let inputStarted = false
let profilingStarted = false
try {
await startRendererTimingProbe(page)
timingStarted = true
await page.evaluate(() => {
const events = []
const frames = []
const observers = []
const maxEntries = 3_000
let dropped = 0
const retain = (list, value) => {
if (list.length < maxEntries) {
list.push(value)
} else {
dropped++
}
}
const surface = (target) => {
if (!(target instanceof Element)) {
return 'other'
}
if (target.closest('.xterm')) {
return 'terminal'
}
if (target.closest('.monaco-editor')) {
return 'editor'
}
if (target.closest('[contenteditable="true"]')) {
return 'contenteditable'
}
return target.matches('input, textarea') ? 'text-input' : 'other'
}
const onInput = (event) => {
retain(events, {
kind: 'listener',
type: event.type,
surface: surface(event.target),
eventAt: event.timeStamp,
handlerAt: performance.now(),
trusted: event.isTrusted
})
}
const types = ['keydown', 'beforeinput', 'input', 'compositionstart', 'compositionend']
for (const type of types) {
document.addEventListener(type, onInput, true)
}
const supported = PerformanceObserver.supportedEntryTypes ?? []
if (supported.includes('event')) {
const observer = new PerformanceObserver((list) => {
for (const event of list.getEntries()) {
if (!types.includes(event.name)) {
continue
}
retain(events, {
kind: 'event-timing',
type: event.name,
surface: surface(event.target),
eventAt: event.startTime,
processingStart: event.processingStart,
processingEnd: event.processingEnd,
duration: event.duration,
interactionId: event.interactionId
})
}
})
observer.observe({ type: 'event', durationThreshold: 16 })
observers.push(observer)
}
let last = performance.now()
let frameId
const frame = (now) => {
if (now - last > 32) {
retain(frames, { at: now, gapMs: now - last })
}
last = now
frameId = requestAnimationFrame(frame)
}
frameId = requestAnimationFrame(frame)
const startedAt = performance.now()
const startedAtIso = new Date().toISOString()
window.__orcaLiveInputLag = {
stop: () => {
cancelAnimationFrame(frameId)
for (const type of types) {
document.removeEventListener(type, onInput, true)
}
for (const observer of observers) {
observer.disconnect()
}
delete window.__orcaLiveInputLag
return {
startedAt,
startedAtIso,
endedAt: performance.now(),
visibility: document.visibilityState,
events,
frames,
dropped,
eventTimingSupported: supported.includes('event')
}
}
}
})
inputStarted = true
await cdp.send('Profiler.enable')
const profileStartWindow = [await page.evaluate(() => performance.now())]
await cdp.send('Profiler.start')
profilingStarted = true
profileStartWindow.push(await page.evaluate(() => performance.now()))
await new Promise((resolve) => setTimeout(resolve, durationMs))
const { profile } = await cdp.send('Profiler.stop')
profilingStarted = false
const input = await page.evaluate(() => window.__orcaLiveInputLag.stop())
inputStarted = false
const timing = await stopRendererTimingProbe(page)
await page.evaluate(() => {
delete window.__orcaIdleCpuTimingProbe
})
timingStarted = false
await writeFile(join(directory, 'renderer.cpuprofile'), JSON.stringify(profile), {
mode: 0o600
})
await writeFile(
join(directory, 'input-timing.json'),
JSON.stringify(
{
identity,
input,
timing,
profileStartWindow,
limitation:
'Keyboard dispatch, handlers and frames only; not PTY echo latency. Event Timing omits short events and rounds durations.'
},
null,
2
),
{ mode: 0o600 }
)
return { directory, eventRecords: input.events.length, slowFrames: input.frames.length, timing }
} finally {
if (profilingStarted) {
await cdp.send('Profiler.stop').catch(() => {})
}
if (inputStarted) {
await page.evaluate(() => window.__orcaLiveInputLag?.stop()).catch(() => {})
}
if (timingStarted) {
await stopRendererTimingProbe(page).catch(() => {})
await page
.evaluate(() => {
delete window.__orcaIdleCpuTimingProbe
})
.catch(() => {})
}
await cdp.send('Profiler.disable').catch(() => {})
await cdp.detach().catch(() => {})
}
}