perf(cef): DevTools' Chromium creation runs after the key's frame committed (#32911)
Fixes #32911 (pacing triage, STATUS 8128).
What
DevTools open (Cmd-Opt-I, docked): Chromium's DevTools creation (CefBrowserHost::ShowDevTools, about 18 ms on the main thread, with DEVTOOLS_WILL_OPEN/OPENED handled inside it) now runs after the run loop committed the key's frame, not inside the key's commit.
Why
The stand-in (BROWSER VIDEO F3) laid out the docked frame in the key's turn, flushed, then asked Chromium from a main-actor Task. A main-actor Task runs from the main dispatch queue in the same run-loop pass as the key, before Core Animation's BeforeWaiting commit. So the measured commit of the key included ShowDevTools: commit_ms 43.2/50.7 ms (p50/max), 9/11 missed main vsyncs per press on feat 585171513e37 (profile pt-feat-5851715-p, three presses: ShowDevTools 11-25 samples of 1 ms each inside the key's pass).
As Chrome commits the frame an input changed before longer work, the key's turn now lays out the stand-in only; the run loop commits it with the turn's own commit, and Chromium is asked after that commit through AfterFrameCommit (the same seam PL-cef-tab uses for a new tab's browser). The mid-turn CATransaction.flush() (a perf-gate-ok exception) is gone: the turn has one commit again.
Not changed here, with evidence
- The CEF pump pass of 18-36 ms after the open: 93-100% of its samples are unsymbolicated Chromium frames under one
CefDoMessageLoopWork. CEF'sMessagePumpExternalchecks its 10 msmax_time_sliceonly between tasks, so such a pass is one or more single Chromium tasks of 8-26 ms (DevTools frontend creation and load). cmux cannot cut a single Chromium task; that needs a fork change, found with a symbolicated Chromium profile. The only cmux work inside those passes isCEFPaneHost.refreshExtensionActions(PNG encode of extension icons) andCEFExtensionStores.refresh, which BROWSER OPEN FRAMES owns (#32912). - Inside ShowDevTools, cmux's own callbacks (
openedfocus of DevTools,addChildWindowoverlay reorder) are 2-5 ms of the 18-35 ms; they stay (focus must move with the open).
Changelog
- Changed: Opening docked DevTools commits the docked layout frame before Chromium creates DevTools, so the key's frame is not held by the creation.
Tests
Regression: CEFDevToolsStandInTests.chromiumIsAskedOnlyAfterTheKeysFrameCommitted. The key arrives in a run-loop callout (as a real key does); the fake shim records the run-loop stage of each DevTools command: the turn (sources, main queue, timers) or after Core Animation's commit (before waiting, order past 2,000,000).
Focused command (fleet): cmux-ci run --class light --script scripts/ci/package-test-lane.sh --arg suite --arg Packages/macOS/CmuxNext --arg CEFDevToolsStandInTests --ref SHA
- Red at a10c495fe3f8 (branch test/ndf-dt-red-never-merge: base f338fc81fd35 + the test commits only), step de257191bdd97c720c09c0a0: 7 of 8 passed;
chromiumIsAskedOnlyAfterTheKeysFrameCommittedfailed:ShimLog.commandStages == [.turn], expected[.afterCommit]. - Green at f27bb84ffcba, step b98492edb39a933b5f4196c1: CEFDevToolsStandInTests 8 of 8 passed; CEFDevToolsCloseTests 13 of 13 passed. The lane exits 1 for the Close suite only because the XCTest phase (0 XCTest tests) crashed with signal 6 after CoreData 134020: the known fleet fault (BASE RED XCTEST, STATUS 8187/8195), also on base.
Measured (bench builds, bv-dt-bottom-light-dark, 120 Hz 1x, real HID Cmd-Opt-I, pacing-triage kit 0c43dd9dd50)
Base: feat f338fc81fd35, job ad0da90ae9ff261ab4051139. Head: 397489a8fc5c (the fix; later commits change tests only), job 42ad35c64ac9381e41e2dcb2. Rounds in order base, head, base, head (cmux18, cmux18, cmux18, cmux23). 10 opens per arm (5 films each with a real-key open).
| DevTools open | drops per press p50 / max | main missed vsyncs p50 / max | commit_ms p50 / p95 / max | start tier |
|---|---|---|---|---|
| base, all | 10 / 22 | 10 / 12 | 49.6 / 57.4 / 59.1 | E 7, S 3 |
| head, all | 5 / 6 | 9 / 10 | 6.5 / 10.1 / 10.3 | E 8, S 2 |
| base, E start | 20 / 22 | 10 / 10 | 50.3 / 58.0 / 59.1 | 7 |
| head, E start | 5.5 / 6 | 9 / 10 | 7.7 / 10.1 / 10.3 | 8 |
| base, S start | 5 / 14 | 10 / 12 | 45.9 / 49.2 / 49.6 | 3 |
| head, S start | 4.5 / 5 | 7 / 8 | 4.8 / 4.9 / 4.9 | 2 |
The scripted open (devtools.openDocked, debug.action_budget): base 43.5 and 47.5 ms, head 1.8 and 1.9 ms to commit. DevTools close (12 per arm) is unchanged: 0 drops p50, 3 missed vsyncs p50, commit 15.2 vs 14.3 ms p50.
Main missed vsyncs fall only a little: ShowDevTools (about 18 ms) and the CEF pump passes after it still run on the main thread, now after the key's frame. They are the next work (a fork change; see above).
Observed / Not run
Observed: the red and green steps above; 4 judged bench rounds. Rounds on cmux21 (base and head) fail the journey at step 26 (a real Cmd-Opt-I does not close DevTools) on both builds, as in 4 older rounds: a host fault, so those films are not counted. Rounds that did not run: cmux24 and cmux23 set-up faults (OCR).
Not run: a symbolicated Chromium profile of the long pump pass; Release compile on its own (the bench builds compiled the head).
No capture: the change is when Chromium is asked, not what is drawn; the stand-in frame and DevTools look the same. Films: https://cmux-orchestrator-1.tail137216.ts.net/resize-videos/ndf-dt-base-1-120x1/ , ndf-dt-head-5-120x1/, ndf-dt-base-5-120x1/, ndf-dt-head-6-120x1/.