bench-terminal-resize: a drag or key resize fails when a terminal does not change size with its pane
Summary
The terminal-resize bench (#35407) passed a divider drag that held every terminal at its old size: a held drag only needed laid-out views. Now a drag or key round fails when any terminal does not change size with its pane in a frame of the motion.
debug.terminal_display now also samples each terminal that is shown when start runs. It samples once per display frame, after that frame's Core Animation commit, until stop. Each sample reads:
area: the size the surface takes in its host now (TerminalHostView.surfaceSize(forHostSize: bounds.size)), which is the pane's size.asked: the frame the host asked for. This is the frame owner'swantedframe when the window commit owns the frame, else the surface view's frame.shown: the surface layer's bounds.
The stop reply adds follow_frames and terminals_followed. For each terminal it has frames, held (a frame where asked differs from area by more than 0.5 pt on either axis), lagging (a frame where shown differs from area), and the first 3 of each, with their sizes. The existing keys stay as they were. The sampler is a FrameClient plus an after-commit MainRunLoopObserver, which only exist between start and stop, with a 600 s expiry. Each frame costs O(terminals shown at start).
The probe moves from the App to CmuxNextTerminal (TerminalDisplayProbe, TerminalPaneFollow.swift), next to the geometry it reads. The App keeps only the route, so app_lines goes down by 91.
The rule
terminal_resize_verdict.problems fails a drag or key round that has a held frame. The message names the terminal, its held frames out of the sampled frames, and the first sizes. Example: light round 1: 2 of 2 terminals did not change size with their pane in the drag: <tab> held 239 of 240 frames (frame 3: pane 590x400, asked 600x400, shown 600x400, ...).
- A round with no pane check, or one that sampled no frame, also fails, because it judged nothing.
- The held-drag excuse is gone.
holds_terminalsanddrag_holds_terminalsare still reported as data. - Lagging frames are data only. The frame owner keeps a frame for 1-4 refreshes by design, and films judge that part.
- With
--ab, the arm summaries addheld_frames,held_roundsandlagging_frames.
This bench is run by hand on the fleet. This PR adds no CI job, step, gate, perf-gate row or budget.
Owners: the probe is in CmuxNextTerminal, which belongs to PL-ghostty (session cmux-b6) under plans/cmux-next/OWNERS.md. The bench scripts under scripts/cmux-next/ have no owner row.
Observed / Not run
-
Fleet proof at this exact head
135c1834597e02183315113de5a302c8ad3545a2(display session, gui-session-v1): stepd6c0c389361d5fe5b6ab31f1: TerminalPaneFollowTallyTests, TerminalKeyResizeFrameTests and TerminalFrameOwnerMainThreadTests, all 3 suites passed (exit 0). -
python3 scripts/cmux-next/tests/test_bench_terminal_resize.py: 26 tests pass, including the newPaneCheckcases (a held drag fails and names the terminal and sizes, a held key round fails per arm, lagging frames do not fail, a round with no pane check fails). -
Fleet step
7cf90adc43694e7eb24733cd(package-test-lane.sh suite Packages/macOS/CmuxNext TerminalPaneFollowTallyTestsat 135c1834597e): CmuxNext and its test targets compiled, and the 3TerminalPaneFollowTallyTestspass. -
prepush-gate.py: no new violations. -
Red bench run on cmux24-perf, at 135c1834597e and the build's defaults (
terminalDrag.holdSurfaceon,motion.frameOwner.terminalsoff): step00bc034a339af6293b883460exits 1 with the new "did not change size with their pane" problem in all 8 rounds. Frames are the display frames the pane check sampled in the motion:layout round frames terminals held / shown held frames per held terminal lagging frames light 1 241 2 / 2 241 482 light 2 243 2 / 2 242 484 stress 1 239 7 / 14 238 1666 stress 2 238 7 / 14 238 1666 keys 1 169 4 / 4 37 148 keys 2 163 4 / 4 39 156 keys-stress 1 171 7 / 14 32 224 keys-stress 2 165 7 / 14 31 217 In the drags, every terminal beside the divider holds its start size in nearly every frame (light: pane 442x692, asked and shown 446x692). In the stress layouts, the 7 terminals in the two columns beside the divider are held and the other 7 have 0 held frames. In the key layouts, each moved terminal holds its spring's end size for about 32-39 frames (keys: pane 699x472, then 715 and 724, while asked and shown stay 765x472). In this run, lagging frames equal held frames: the layer shows what the host asked for. The light drags also fail the older "no terminal resized" and "no terminal drew a frame" checks, which the held excuse used to skip.
-
A/B run
--ab=motion.frameOwner.terminals, same head: step36c89a7955f0804e66a66166. It shows the rule is not always red. The step exits 1 because of the drags and the OFF key rounds. Held frames per arm (2 rounds each):layout OFF held frames (rounds held) ON held frames (rounds held) ON lagging frames keys 308 (2 of 2) 0 (0 of 2) 0 keys-stress 441 (2 of 2) 0 (0 of 2) 0 light 962 (2 of 2) 958 (2 of 2) 958 stress 3332 (2 of 2) 3339 (2 of 2) 3339 With the frame owner on, every key-resized terminal asks for its pane's size in every sampled frame. The drags stay held in both arms because
terminalDrag.holdSurfaceis a separate switch, still on. Note: the ON key rounds also show 0 lagging frames, so in every frame sampled after a commit, the layer already had the pane's size. I expected some lagging frames from the 1-4 refresh design. Either each new size's frame was ready inside its own commit, or the layer's bounds after the commit do not show the lag the screen shows. Not answered yet; my earlier note here was wrong. The follow-up #36223 moved the sampler to each window's frame clock and the ON key rounds still show 0 lagging frames (step d557f84a, cmux23-perf, --config=bench). Two counters that do not depend on when the sampler reads agree: the window's frame presenter counted 0 keeps and 0 fallbacks (112 or 140 presented per round), and Ghostty counted host_late_presents 0. So in this bench every key-spring frame was drawn and presented in its own commit. pixel-perfect's film trace (STATUS 13764) showed the surface one tick behind in spring frames; that was a sampling artifact of the trace, not the product (STATUS 14047): its frame client fires before or after the layout's client depending on dictionary order in FrameScheduler, and always before the presenter's pass, so in each film the moving ticks read either all one behind or all on time (ab-m14c single-3k 76/76 behind, held-2 162/162 on time; ab-m14d the reverse). The films used the same bench configuration. So the bench result stands. One limit of this probe: its "pane" is the terminal host's bounds, so a host that lags its pane is not seen here. -
Not run: films or pixel checks of lagging frames.
Changelog
none