mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-05-16 04:22:36 +00:00
perf(tui): instrument stdout drain — rule out terminal parse bottleneck
Adds four fields to FrameEvent.phases and the matching profile
summary:
optimizedPatches post-optimize patch count (what's actually
written to stdout; the .patches field is
pre-optimize)
writeBytes UTF-8 byte count of the write this frame
backpressure true when Node's stdout.write returned false
(Writable buffer full — outer terminal can't
keep up)
prevFrameDrainMs end-to-end drain time of the PREVIOUS frame's
write, captured from stdout.write's 2-arg
callback. Reported on the next frame so the
measurement reflects "time until OS flushed
the bytes to the terminal fd", not "time until
queued in Node".
writeDiffToTerminal() now returns { bytes, backpressure } and
accepts an optional onDrain callback. Only attached on TTY with
diff; piped/non-TTY stdout bypasses flow control so the callback
would fire synchronously anyway.
Initial measurements under hold-wheel_up against 1106-msg session
(30Hz for 6s):
patches total 28,888
optimized total 16,700 (ratio 0.58 — optimizer cuts ~42%)
writeBytes 42 KB / 10s = 4.2 KB/s throughput
drainMs p50 0.14 ms terminal accepts bytes instantly
drainMs p99 0.85 ms
backpressure 0% of frames
This rules out the terminal-parse hypothesis — Cursor's xterm.js
drains our output in sub-millisecond time at only 4 KB/s. The
remaining lag has to be in the render pipeline, not the wire.
Profile output now includes the bytes+drain+backpressure lines to
keep this visible on every subsequent iteration.
This commit is contained in:
parent
d3dedf10aa
commit
f823535db2
6 changed files with 126 additions and 4 deletions
|
|
@ -203,10 +203,15 @@ export type Terminal = {
|
|||
stderr: Writable
|
||||
}
|
||||
|
||||
export function writeDiffToTerminal(terminal: Terminal, diff: Diff, skipSyncMarkers = false): void {
|
||||
export function writeDiffToTerminal(
|
||||
terminal: Terminal,
|
||||
diff: Diff,
|
||||
skipSyncMarkers = false,
|
||||
onDrain?: () => void
|
||||
): { bytes: number; backpressure: boolean } {
|
||||
// No output if there are no patches
|
||||
if (diff.length === 0) {
|
||||
return
|
||||
return { bytes: 0, backpressure: false }
|
||||
}
|
||||
|
||||
// BSU/ESU wrapping is opt-out to keep main-screen behavior unchanged.
|
||||
|
|
@ -278,5 +283,15 @@ export function writeDiffToTerminal(terminal: Terminal, diff: Diff, skipSyncMark
|
|||
buffer += ESU
|
||||
}
|
||||
|
||||
terminal.stdout.write(buffer)
|
||||
// Node's Writable.write returns false when the internal buffer is full
|
||||
// (backpressure). On a slow terminal parser that's the tell: we're
|
||||
// producing bytes faster than the outer terminal can consume them.
|
||||
// The 2-arg form attaches a drain callback that fires once the chunk
|
||||
// is actually flushed to the OS socket/pipe — giving us end-to-end
|
||||
// drain timing, not just "queued in Node".
|
||||
const wrote = onDrain
|
||||
? terminal.stdout.write(buffer, () => onDrain())
|
||||
: terminal.stdout.write(buffer)
|
||||
|
||||
return { bytes: Buffer.byteLength(buffer, 'utf8'), backpressure: !wrote }
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue