When it isn't the event loop
Explore this workspace.
Technical background
Nothing blocks, the loop stays free, and the screen still lags — because frames are being dropped, or because xterm is taking a long time to lay out what was written to it. Both are timed and tallied into the same log, and a healthy hour writes no lines at all.
The problem
The stall recorder covers the event loop. It says nothing about the other way a terminal feels slow: nothing blocks, the loop stays free, and the screen still lags — because frames are being dropped, or because xterm is taking a long time to parse and lay out what was written to it.
How it works
tabby-render-timing is a builtin that times both and writes records into the same log. It is a TerminalDecorator, not an edit to the frontend — add-only, so it costs nothing at the next rebase, and it reaches any frontend exposing an xterm without knowing which. It wraps xterm.write and measures call → callback, which is the interval that matters: the caller's await returns long before the screen reflects anything.
A render-timing record
render-timing frames: 327, slowFrames 3, jankFrames 2, worst 150ms, p50 8.3, p95 8.5
term1: 7 writes, mean 22.7ms, worst 81ms, 2 slowDetails
- Tallied, never streamed. A busy terminal writes thousands of times a second; the finding is a distribution.
- The rAF loop only runs while something is writing, and stops two seconds after. A permanent one would keep the compositor awake on an idle window — a poor trade for a diagnostic.
- Gaps over 500 ms are not counted as dropped frames. An idle tab produces one enormous gap, and counting it would make every summary look catastrophic.
- Reports only when there is something to say: a healthy hour writes no lines.
What this does not claim
- The first version reported nothing at all, because it used the diagnostics
note()API — which only appends a breadcrumb shown as context when a stall is reported, and is invisible otherwise.report()was added alongside it for records that are the finding rather than context for one.