Repository navigation
cmux-next: keystroke echo no longer waits for vsync (20.5 → 5.4 ms); typing probe - #17494
Conversation
CMUX_NEXT_TYPING_PROBE=<csv> records, per terminal key, when AppKit dispatch, Ghostty io_write, the attach socket submit, the echo decode, main-actor hop, lane parse and the next IOSurface layer contents ran, relative to the NSEvent timestamp. scripts/cmux-next/bench-typing.py drives it with CuaDriver key presses and summarizes each hop. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Raw probe data from fleet build probe-v2.summary.json{
"rows": 66,
"complete": 66,
"measured": 61,
"io_write_on_main": 61,
"hops": {
"dispatch_start_ms": {
"n": 61,
"median": 0.59,
"p95": 1.13,
"min": 0.19,
"max": 1.55
},
"dispatch_end_ms": {
"n": 61,
"median": 1.55,
"p95": 2.25,
"min": 0.55,
"max": 2.55
},
"io_write_ms": {
"n": 61,
"median": 1.47,
"p95": 2.15,
"min": 0.53,
"max": 2.49
},
"socket_submit_ms": {
"n": 61,
"median": 1.73,
"p95": 2.42,
"min": 0.63,
"max": 2.78
},
"output_decoded_ms": {
"n": 61,
"median": 2.2,
"p95": 3.08,
"min": 0.85,
"max": 3.23
},
"output_main_ms": {
"n": 61,
"median": 2.33,
"p95": 3.24,
"min": 0.88,
"max": 3.45
},
"output_parsed_ms": {
"n": 61,
"median": 2.36,
"p95": 3.27,
"min": 0.9,
"max": 3.5
},
"contents_ms": {
"n": 61,
"median": 20.46,
"p95": 28.16,
"min": 12.34,
"max": 30.38
}
},
"segments": {
"event to dispatch": {
"n": 61,
"median": 0.59,
"p95": 1.13,
"min": 0.19,
"max": 1.55
},
"dispatch to io_write": {
"n": 61,
"median": 0.71,
"p95": 1.09,
"min": 0.34,
"max": 1.47
},
"io_write to socket": {
"n": 61,
"median": 0.22,
"p95": 0.35,
"min": 0.1,
"max": 0.42
},
"socket to echo decoded (daemon, host, shell)": {
"n": 61,
"median": 0.44,
"p95": 0.79,
"min": 0.21,
"max": 0.81
},
"decoded to main actor": {
"n": 61,
"median": 0.11,
"p95": 0.19,
"min": 0.04,
"max": 0.25
},
"main actor to parsed": {
"n": 61,
"median": 0.04,
"p95": 0.05,
"min": 0.02,
"max": 0.07
},
"parsed to frame contents": {
"n": 61,
"median": 18.34,
"p95": 25.65,
"min": 9.41,
"max": 26.89
}
}
}probe-v2-novsync.summary.json{
"rows": 66,
"complete": 66,
"measured": 61,
"io_write_on_main": 61,
"hops": {
"dispatch_start_ms": {
"n": 61,
"median": 0.56,
"p95": 1.57,
"min": 0.34,
"max": 2.29
},
"dispatch_end_ms": {
"n": 61,
"median": 1.41,
"p95": 2.22,
"min": 0.87,
"max": 3.53
},
"io_write_ms": {
"n": 61,
"median": 1.34,
"p95": 2.15,
"min": 0.83,
"max": 3.44
},
"socket_submit_ms": {
"n": 61,
"median": 1.59,
"p95": 2.58,
"min": 0.99,
"max": 3.83
},
"output_decoded_ms": {
"n": 61,
"median": 2.2,
"p95": 3.49,
"min": 1.3,
"max": 5.71
},
"output_main_ms": {
"n": 61,
"median": 2.29,
"p95": 3.6,
"min": 1.38,
"max": 5.89
},
"output_parsed_ms": {
"n": 61,
"median": 2.34,
"p95": 3.64,
"min": 1.4,
"max": 5.97
},
"contents_ms": {
"n": 61,
"median": 4.37,
"p95": 6.85,
"min": 3.02,
"max": 7.8
}
},
"segments": {
"event to dispatch": {
"n": 61,
"median": 0.56,
"p95": 1.57,
"min": 0.34,
"max": 2.29
},
"dispatch to io_write": {
"n": 61,
"median": 0.77,
"p95": 1.22,
"min": 0.48,
"max": 1.38
},
"io_write to socket": {
"n": 61,
"median": 0.26,
"p95": 0.41,
"min": 0.15,
"max": 0.47
},
"socket to echo decoded (daemon, host, shell)": {
"n": 61,
"median": 0.53,
"p95": 0.95,
"min": 0.28,
"max": 1.87
},
"decoded to main actor": {
"n": 61,
"median": 0.1,
"p95": 0.23,
"min": 0.06,
"max": 0.47
},
"main actor to parsed": {
"n": 61,
"median": 0.04,
"p95": 0.06,
"min": 0.02,
"max": 0.08
},
"parsed to frame contents": {
"n": 61,
"median": 2.0,
"p95": 3.95,
"min": 0.72,
"max": 4.18
}
}
} |
|
Important Review skippedAuto reviews are disabled on base/target branches other than the default branch. Please check the settings in the CodeRabbit UI or the ⚙️ Run configuration
You can disable this status message by setting the Use the checkbox below for a quick retry:
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
|
Not verified: macOS jobs did not run: compile and app tests were skipped on CI passes on Written by |
…ng for vsync Move the CmuxGhosttyKit pin (apple-v6, 8ef3906625d2) and the ghostty-next gitlink to 2199dc93e (ghostty-next PR 28): a keystroke echo on an idle terminal no longer waits for the next display-link tick. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
After the fix: fleet build probe-v3.summary.json{
"rows": 66,
"complete": 66,
"measured": 61,
"io_write_on_main": 61,
"hops": {
"dispatch_start_ms": {
"n": 61,
"median": 0.64,
"p95": 1.68,
"min": 0.38,
"max": 2.43
},
"dispatch_end_ms": {
"n": 61,
"median": 1.67,
"p95": 2.85,
"min": 0.98,
"max": 3.73
},
"io_write_ms": {
"n": 61,
"median": 1.59,
"p95": 2.8,
"min": 0.94,
"max": 3.65
},
"socket_submit_ms": {
"n": 61,
"median": 1.9,
"p95": 2.96,
"min": 1.1,
"max": 4.03
},
"output_decoded_ms": {
"n": 61,
"median": 2.51,
"p95": 4.08,
"min": 1.48,
"max": 6.88
},
"output_main_ms": {
"n": 61,
"median": 2.68,
"p95": 4.22,
"min": 1.56,
"max": 6.99
},
"output_parsed_ms": {
"n": 61,
"median": 2.74,
"p95": 4.26,
"min": 1.6,
"max": 7.03
},
"contents_ms": {
"n": 61,
"median": 5.39,
"p95": 7.14,
"min": 3.24,
"max": 7.59
}
},
"segments": {
"event to dispatch": {
"n": 61,
"median": 0.64,
"p95": 1.68,
"min": 0.38,
"max": 2.43
},
"dispatch to io_write": {
"n": 61,
"median": 0.93,
"p95": 1.22,
"min": 0.54,
"max": 1.44
},
"io_write to socket": {
"n": 61,
"median": 0.31,
"p95": 0.4,
"min": 0.15,
"max": 0.56
},
"socket to echo decoded (daemon, host, shell)": {
"n": 61,
"median": 0.54,
"p95": 1.11,
"min": 0.32,
"max": 2.85
},
"decoded to main actor": {
"n": 61,
"median": 0.12,
"p95": 0.21,
"min": 0.07,
"max": 0.26
},
"main actor to parsed": {
"n": 61,
"median": 0.04,
"p95": 0.08,
"min": 0.02,
"max": 0.1
},
"parsed to frame contents": {
"n": 61,
"median": 2.23,
"p95": 4.13,
"min": 0.56,
"max": 4.28
}
}
} |
… 2199dc93e; bench-typing targets an exact window ghosttykit_repin.py with source-archive run 37406288001 and cmux-ci iOS job 2c4987f6b499; ios_notices check-repo passes. bench-typing.py needs --window: CuaDriver press_key posts nothing without an exact window. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
ios-simulator-build failed on a stale explicit-module cache on the persistent runner, not on code: cmux-b2 (unregistered) |
…bench counts only its own rows Review nits on #17494: the setContents: block is built in a nonisolated helper so a contents set off the main thread cannot trap, and bench-typing.py skips rows an earlier run left in the same CSV. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Pre-merge review (subagent, correctness first): ship with nits, nothing blocking. With the probe off, nothing changes: the hot paths do no allocation, locking or environment reads, and the layer hook is never installed. Fixed in 819c867:
Left as is (probe-on only):
The merge is still blocked on cmux-b2 (unregistered) |
Bugbot is paused — on-demand spend limit reachedBugbot uses usage-based billing for this team and has hit its on-demand spend limit. A team admin can raise the spend limit in the Cursor dashboard, or wait for the next billing cycle to continue. |
In cmux-next, a typed character took a median 20.5 ms from the key event until the frame showing it was handed to Core Animation (60 Hz). About 18 ms of that was Ghostty's renderer waiting for the next display-link tick. This PR bumps GhosttyNextKit to
xcframework-2199dc93…-apple-v6(manaflow-ai/ghostty-next#28), which draws the first frame after idle immediately. That brings the median to 5.4 ms withwindow-vsyncstill on. The PR also adds the opt-in probe and bench that measured it.Before and after
Same host as hq#1373:
a capture Mac(M4, macOS 26.4, 60 Hz), Debug fleet builds. Each arm is 61 real key-downs posted by CuaDriver to a zsh prompt, 300 ms apart. Times are in ms afterNSEvent.timestamp.eb550eec(median / p95)2d003cfc(median / p95)IOSurfaceLayer.contentsEverything before the frame (AppKit dispatch, Ghostty
io_write, attach socket,cmux-tuidaemon, terminal host, shell echo, main-actor hop, parse) totals about 2.5 ms. The photon follows at the compositor's next vsync, at most one more refresh (16.7 ms). So the worst case on screen is now under about 25 ms, against 30 + 16.7 ms before. For comparison,window-vsync = falseon the old build gave 4.37 / 6.85 ms; the fix gets nearly all of that while frames stay vsync-paced during streaming output.hq#1373's method (
terminal-render-soak.py: CLIwrite, then CLIscreen readpolling every 50 ms), on the fixed build: ack median 23.1 ms, visible median 39.2 ms (p95 67.4, range 33.6–71.8), from 64 samples. 12 more samples were spaces, which this harness can't detect, because screen reads trim trailing blanks. That number is the harness's floor: a baseline read, the ~23 ms CLI write and at least one poll. It isn't a frame time. Classic's 146 ms from 4 samples has the same structure.Classic shares the cause. Its Ghostty fork (
324c027) keeps the display link running while a surface is focused and defers every draw to vsync (Thread.zig:1788). Classic's fix would be the same idea, in that fork.What's here
Packages/Shared/CmuxGhosttyKit/Package.swift(checksum8ef39066…, attested release) and theghostty-nextgitlink, with notices regenerated byghosttykit_repin.py.TypingLatencyProbe(CMUX_NEXT_TYPING_PROBE=<csv>): one row per key across 8 hops. When unset, each hop costs one load.TypingFrameProbeadds asetContents:override to Ghostty's layer class only while the probe is on.scripts/cmux-next/bench-typing.py: posts keys with CuaDriverpress_keyand prints the per-hop and per-segment median, p95 and range.TypingLatencyProbeTests: hop ordering, and the probe is a no-op when disabled.Verification
cbfbe5e17ef2(before) and9896631dd64f(after). Per-arm summaries are in the PR comments.cmux-next swift testruns them.Changelog
🤖 Generated with Claude Code