perf(cef): DevTools' Chromium creation runs after the key's frame committed (#32911)

LA
3d ago
+89-22

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's MessagePumpExternal checks its 10 ms max_time_slice only 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 is CEFPaneHost.refreshExtensionActions (PNG encode of extension icons) and CEFExtensionStores.refresh, which BROWSER OPEN FRAMES owns (#32912).
  • Inside ShowDevTools, cmux's own callbacks (opened focus of DevTools, addChildWindow overlay 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; chromiumIsAskedOnlyAfterTheKeysFrameCommitted failed: 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 opendrops per press p50 / maxmain missed vsyncs p50 / maxcommit_ms p50 / p95 / maxstart tier
base, all10 / 2210 / 1249.6 / 57.4 / 59.1E 7, S 3
head, all5 / 69 / 106.5 / 10.1 / 10.3E 8, S 2
base, E start20 / 2210 / 1050.3 / 58.0 / 59.17
head, E start5.5 / 69 / 107.7 / 10.1 / 10.38
base, S start5 / 1410 / 1245.9 / 49.2 / 49.63
head, S start4.5 / 57 / 84.8 / 4.9 / 4.92

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/.