perf: stop re-probing the terminal width on every line of every render#501
Open
elhoim wants to merge 6 commits into
Open
perf: stop re-probing the terminal width on every line of every render#501elhoim wants to merge 6 commits into
elhoim wants to merge 6 commits into
Conversation
Read ancestry from /proc/<pid>/stat, locate the pty by readlinking
/proc/<pid>/fd/{0,1,2}, and get columns via TIOCGWINSZ (tty.WriteStream)
instead of spawning sh+ps / sh+stty+awk / sh+tput. Returns null off Linux
so the portable walk remains the fallback for macOS/BSD.
Verified against a real pty: probeWidthNative() returns 209, matching
'stty -F /dev/pts/7 size' (88 209), with zero spawns.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Persists probed widths to ~/.cache/ccstatusline/terminal-width.json, keyed
by session_id (width is per-terminal, so a global value would be wrong).
Atomic tmp+rename writes, entries pruned after an hour, corrupt/unwritable
cache treated as a miss and never fatal.
readCachedWidth returns a wrapper ({width} | null) so a cached null -- 'we
probed, there is no TTY' -- is a HIT, not a miss. Collapsing that would make
the no-TTY case re-probe forever, which is the bug this exists to fix.
ttlSeconds 0 disables caching (always probe). Note this deliberately differs
from gitCacheTtlSeconds, where 0 means 'never expire'.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude Code spawns the statusline without a TTY, so probeTerminalWidth() returns null. Callers read it as 'context.terminalWidth ?? getTerminalWidth()', so the null re-triggered the full ps/stty ancestor walk once per configured line. Cache the probe behind an explicit hasProbed flag so the negative result is memoized too. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
execSync with shell:'/bin/sh' spawned a shell in addition to the command, doubling every probe; the stty|awk pipe cost three processes. Call the binaries directly and parse 'rows cols' in JS. Drops the legacy 'stty size < /dev/tty' redirect form, which required a shell and is redundant with the -F/-f forms. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Chain is now: CCSTATUSLINE_WIDTH override -> in-process memo -> shared L2 disk cache -> probe (native /proc+TIOCGWINSZ on Linux, ps/stty/tput elsewhere). Only the entry point supplies a session_id, so only it reads or writes the shared cache; renderer.ts and widgets/TerminalWidth.ts keep their zero-arg calls and hit the memo. Adds terminalWidthCacheTtlSeconds (default 5, 0 disables) to Settings and RenderContext, and backfills it in two widget test fixtures that build a full Settings literal (zod .default() makes the key required on the output type). Reads process.platform at call time in terminal-native's default deps: a module-load snapshot ignored the per-test platform pin and ran the real Linux probe under darwin-pinned tests. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
Claude Code spawns the statusline with no TTY, so
probeTerminalWidth()returnsnull. Because every caller reads the width ascontext.terminalWidth ?? getTerminalWidth(), thatnullwas never cached — it re-ran aps/stty/tputancestor walk once per configured line, on every render.Measured on a 6-core Linux box: 122
execveper render, with 6.5s of a 9.9s CPU profile insidespawnSync. Sustained, this was 11.2% of the machine across active sessions.It also produced the wrong answer: the
tput colsfallback reported its no-TTY default of 80 on a 209-column terminal.Fix
nullresult (an explicithasProbedflag — the actual bug)./proc/<pid>/statfor the ancestor chain,readlinkits fds to find a/dev/pts/*, thenTIOCGWINSZviatty.WriteStream. Spawns nothing.execFileSyncinstead ofexecSync+ shell in the remaining fallbacks (no shell process, and non-zero exits propagate instead of being swallowed by an| awkpipeline).session_id, atomictmp+rename, corruption treated as a miss. New settingterminalWidthCacheTtlSeconds(default 5s, mirroringgitCacheTtlSeconds).Existing behaviour is preserved: the
stty/tputfallbacks remain for non-Linux, andCCSTATUSLINE_WIDTHstill wins.Results
execveper rendernodeitself)sysper renderCold and warm cache both cost 1
execve— the native probe spawns nothing, so even a cache miss is free.Tests
32 new/updated tests across
terminal.test.ts(15),terminal-native.test.ts(7),terminal-width-cache.test.ts(10).bun run lintclean.🤖 Generated with Claude Code's help