a73x

683a882f

test: a throughput leg that can see a detached session rendering

a73x   2026-08-21 18:19

Commit message
test: a throughput leg that can see a detached session rendering

The gate could not see the regression the previous commit fixed. Both
existing legs flood `yes`, and a one-character row is nearly free to
render, so they stay flat straight through it. Same box, either side of
the fix:

    solo    (yes, no client)        5ms  vs   6ms
    client  (yes, attached)        48ms  vs  55ms
    repaint (full width, detached) 87ms  vs 479ms

The first two are noise. The third is the bug.

So: a third leg that repaints a full screen with nobody attached — 15MB
of cursor-home plus 23 rows of 79 columns, best-of-5 like the others.
23 rows and not 24 so the screen is repainted rather than scrolled;
scrolling costs memset and cursorScrollAbove, which would drown the cost
being measured.

The ceiling is 200ms, sitting near the good number (87ms, readings
87-90) rather than under the bad one. At 400ms a machine twice this fast
runs the BROKEN build in 240ms and the gate says nothing.

The frame file is built by doubling — 13 cats rather than 8192.

Every arm was watched to fire: the pre-fix binaries trip the ceiling at
497ms, a mutated needle trips the empty-grid guard, and one rep instead
of five trips the reading count. Two of those mutations silently failed
to apply first, and only a diff line count caught it.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JDnJ6v15RvwZ2Y3zvfrWpR

test/throughput.sh
Old New
@@ -43,6 +43,23 @@ SOLO_MAX_MS=10
43 CLIENT_LINES=200000 43 CLIENT_LINES=200000
44 CLIENT_WARN_MS=60 44 CLIENT_WARN_MS=60
45 CLIENT_MAX_MS=75 45 CLIENT_MAX_MS=75
46 # Leg 3 repaints a full screen over and over with NOBODY attached. Its
47 # own leg because `yes` cannot see what it sees: a one-character row is
48 # nearly free to render, so the solo leg above stays flat through a
49 # regression that makes full-width output 10x slower. Real output —
50 # vim, logs, a build — is full-width, and the daemon renders every row
51 # of it to hash it.
52 # 2^REPAINT_DOUBLINGS frames of ~1.8KB; 13 is ~15MB.
53 REPAINT_DOUBLINGS=13
54 # Measured either side of the fix, same box, best-of-5: 87ms with the
55 # detached check in place, 479ms without it. The ceiling sits near the
56 # good number rather than under the bad one — put it at 400 and a machine
57 # twice this fast runs the BROKEN build in 240ms and the gate says
58 # nothing. 2.3x over nominal, and the readings span 87-90.
59 #
60 # 5.5x here against 27x at 200x50: the penalty is per rendered row, and
61 # this leg renders 23 of 80 columns rather than 49 of 200.
62 REPAINT_MAX_MS=200
46 # Repeats per leg; the fastest one is the reading. See the header. 63 # Repeats per leg; the fastest one is the reading. See the header.
47 REPS=5 64 REPS=5
48 65
@@ -148,4 +165,61 @@ echo "client: $CLIENT_LINES lines, attached: ${CLIENT_MS}ms (target ${CLIENT_WA
148 echo " Not a failure. It is the number that decides whether this feels good to use." 165 echo " Not a failure. It is the number that decides whether this feels good to use."
149 } 166 }
150 167
168 # ---- leg 3: full-width repaint, nobody attached ------------------------
169 # The client from leg 2 has quit, so this is a detached session again.
170 #
171 # One frame: cursor home, then 23 rows of 79 columns. 23 and not 24 so the
172 # screen is REPAINTED rather than scrolled — scrolling is a different cost
173 # (memset and cursorScrollAbove) and it would drown the one being measured.
174 {
175 printf '\033[H'
176 i=0
177 while [ "$i" -lt 23 ]; do
178 printf '%79s\n' '' | tr ' ' x
179 i=$((i+1))
180 done
181 } > "$TMPD/frame"
182
183 # Doubling rather than a loop of appends: 13 cats instead of 8192.
184 cp "$TMPD/frame" "$TMPD/big"
185 i=0
186 while [ "$i" -lt "$REPAINT_DOUBLINGS" ]; do
187 cat "$TMPD/big" "$TMPD/big" > "$TMPD/big2"
188 mv "$TMPD/big2" "$TMPD/big"
189 i=$((i+1))
190 done
191 REPAINT_BYTES=$(wc -c < "$TMPD/big")
192
193 rm -f "$TMPD/repaint" "$TMPD/repaint.done"
194 "$MUXA" send --sock "$SOCK" \
195 "for k in \$(seq $REPS); do A=\$(date +%s%N); cat $TMPD/big; B=\$(date +%s%N); echo \$(( (B-A)/1000000 )) >> $TMPD/repaint; done; echo ok > $TMPD/repaint.done\n" \
196 > /dev/null
197 i=0
198 while [ ! -s "$TMPD/repaint.done" ] && [ "$i" -lt 120 ]; do sleep 0.5; i=$((i+1)); done
199 [ -s "$TMPD/repaint.done" ] || {
200 echo "throughput FAIL: $REPS repaints of $REPAINT_BYTES bytes unfinished after 60s"
201 exit 1
202 }
203 REPAINT_ALL="$(cat "$TMPD/repaint")"
204 REPAINT_MS="$(echo "$REPAINT_ALL" | sort -n | head -1)"
205 [ "$(echo "$REPAINT_ALL" | wc -l)" -eq "$REPS" ] || {
206 echo "throughput FAIL: wanted $REPS repaint readings, got: $REPAINT_ALL"
207 exit 1
208 }
209 # The screen must actually be full of the frame, or a shell that never ran
210 # the cat would score a perfect time for doing nothing.
211 "$MUXA" capture --sock "$SOCK" | grep -qF 'xxxxxxxx' || {
212 echo "throughput FAIL: the repaint left no output on the grid"
213 "$MUXA" capture --sock "$SOCK"
214 exit 1
215 }
216
217 echo "repaint: $REPAINT_BYTES bytes, detached: ${REPAINT_MS}ms (max ${REPAINT_MAX_MS}ms) [$(echo "$REPAINT_ALL" | tr '\n' ' ')]"
218 [ "$REPAINT_MS" -lt "$REPAINT_MAX_MS" ] || {
219 echo "throughput FAIL: repaint leg took ${REPAINT_MS}ms, ceiling is ${REPAINT_MAX_MS}ms"
220 echo " A detached session should not be rendering rows at all. Check that"
221 echo " sendUpdate still asks hasClientsIn BEFORE it diffs."
222 exit 1
223 }
224
151 echo "throughput OK" 225 echo "throughput OK"