perf(web): stop paying a process-table scan on every agent-browser call

Merged
TH
12d ago
+1004-169

Summary

Every managed web call re-derived the daemon's owned-process record with a whole-process-table scan (ps -ax), for a browser fleet that only changes when open launches it, close ends it, record starts or stops the encoder, or a failure finds it died or restarted.

Plain successful commands now pay one batched same-pid identity probe. The gate still refreshes on any failure and on open/close/record, and stays fail-closed: an empty, undecodable, unreadable, or unanswered record falls back to the scan, so a fleet adopted from a predecessor daemon becomes owned through the record again. The probe reads the host off the event loop, so the daemon no longer blocks other requests, timers, and abort handling behind a spawnSync. 13 files.

agent-device open http://localhost:3000 --platform web
agent-device snapshot -i --platform web     # no process-table scan

Refs #3503 — requirement 2 (classifying details.dispatched for web failures) is #3542.

Validation

Tested a1fa5ae70. pnpm check:affected --run passed on that SHA (format, lint, typecheck, layering, fallow, build, related vitest; 868 files / 7,157 tests); coverage and device lanes are GitHub-authoritative.

Refs-mode per-op latency through the CLI against one local page, 5 interleaved runs per side, median of per-run medians (base 46dae0f2e → head 5446b9c67, load average 10–20):

opbasehead
snapshot -i624315
fill @ref630317
click @ref652318
screenshot585347
mean/op1131856

The saving scales with the process table: measured directly, ps -ax costs 378 ms median on a 2,563-row table against 4–15 ms for the probe, and it hit its 1,500 ms budget under parallel-agent load. At load average 90–230 the same ops measured 1.3–1.9 s/op faster (7cb2c3d21, this change before its review fixes). wait text polls (~3.4 s) and open is Chrome-startup-bound, so both stay backend-bound; close clears the record without scanning and is unchanged. close clearing and a failed close not clearing are asserted; the reaper and leak oracle read the same record shape.

The head after 5446b9c67 adds no measured hot-path cost — its only extra scans are one per record boundary, in code paths the table above does not time — so those numbers still describe the measured ops.

Findings applied: probe moved off the event loop, oversized test file split, unused re-export removed, unreadable record store fails closed to a scan (86b161d81), windowsPlatform no longer leaks win32 to its worker (6943e284c), and record added to the lifecycle set (2ed0820fa): the pinned agent-browser 0.27.1 mints an ffmpeg child of the agent-browser daemon on record start, and that encoder outlives the CLI call which started it, so it sits outside Chrome's tree and only a scan reached by a lifecycle boundary can put it in the record the startup reaper signals — a probe over already-recorded pids cannot learn of a process the fleet minted after the last scan.

That head also adds a gate test over a real record store, the real probe, and real ps answers: a run of plain commands leaves the recorded fleet in the file untouched and mints nothing, and a fleet the host no longer names sends every later plain command back to the scan (a1fa5ae70 keeps those cases from passing without a subject).