soak: what the long-running modes cost after the first minute, measured - #300
Merged
Conversation
Everything the harness measured before this commit was one call. The listener modes and the council room do not stop after one call, and no gate measured whether an idle process holds a constant amount of memory across a day. tools/soak.ps1 is the instrument. Residency mode samples one process tree at an interval and records the working set, the private bytes, the handle count, the live CPU and the child count. Per-fire mode starts a short-lived process many times, because the statusline has no residency to sample. Summary mode renders a finished JSONL file again, so the samples stay the artifact. The script uses Win32_Process rather than Get-Counter, because the counter set names of Get-Counter are localized and its instances carry process names instead of pids. It guards against pid reuse on every sample. It prints a median-based drift beside the least-squares slope, and it prints an absent figure as `--` rather than as 0. The first measurement is recorded in design.md, in the section 5 area. Both idle listeners held 128 handles and 1 process across 210 samples over 34.8 min, with a robust drift of 0.00 MiB and idle CPU under the measurement floor. The statusline ran 1000 fires at a median of 20.9 ms with no drift. Its tail is the finding: 6 fires of 1000 blocked for about 2.03 s while their CPU stayed normal, so the process waited and did not compute. The arm records that result and does not name a cause. The council arm is owed. A room needs a real terminal, and its seats read credentials from the real home directory, so it cannot run headlessly. The amendment states the operator command. Co-Authored-By: Claude Fable 5 <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.
Everything the harness measured before this PR was one call.
ci.ymltimes 15statusline samples, and every eval fixture asserts a single render. The listener
modes and the council room do not stop after one call, so no gate answered the
question they raise: does an idle process hold a constant amount of memory across
a day? This PR adds the instrument and records the first measurement.
The instrument
tools/soak.ps1has three modes. Residency mode samples one process tree at aninterval, and it records the working set, the private bytes, the handle count,
the live CPU and the child count. Per-fire mode starts a short-lived process many
times, because
telltale statuslinehas no residency to sample. Summary moderenders a finished JSONL file again, so the samples stay the artifact and a later
reader needs no second soak.
Three decisions in the script are load-bearing:
Win32_Processrather thanGet-Counter. The counter set names ofGet-Counterare localized, and its instances carry process names rather thanpids, so it cannot identify a tree.
outlier moves a slope and no outlier moves a median.
--and never as0, which is the zero-vs-absentrule from ADR-001 applied to the instrument itself.
Conditions
Intel i7-7700K, Windows 11, Windows PowerShell 5.1, Go 1.26.5. Both listeners
bound non-default loopback ports (14519 and 14318), and every arm ran with
USERPROFILEredirected to a scratch directory. The isolation is verified in bothdirections: the listeners wrote their stores under the scratch home, and
~/.telltaleheld no file newer than the previous day when the arms ended.What the arms found
Both idle listeners held 128 handles and 1 process across 210 samples over
34.8 min. The robust drift is 0.00 MiB on every metric, and idle CPU sat under the
measurement floor at 0.02 s and 0.00 s of total CPU. Neither listener leaks.
The statusline ran 1000 fires at a median of 20.9 ms, all exiting 0 and all
rendering the
Opusmarker, with no drift in cost. Its tail is the finding:6 fires of 1000 blocked for about 2.03 s while their CPU stayed at the usual
15-31 ms, so the process waited and did not compute. The arm records that result
and deliberately does not name a cause, because two residency arms polled the same
machine throughout.
The full tables, and an explicit list of what the measurement does not assert,
are in the
docs/design.md§5 amendment.Owed
The council arm cannot run headlessly. The room is a TUI that needs a real
terminal, and its seats are live vendor CLIs that read credentials from the real
home directory, so it cannot use the redirected
USERPROFILEthat isolated theother three arms. The amendment names it as owed and states the exact two-terminal
operator command.
Verification
go vet ./...passes on the merged tree.go test ./internal/hud/... ./internal/council/...passes, which is what covers the doc/code sync assertionsover
docs/design.md. The soak is an operator instrument and not a CI gate; noworkflow runs it.
🤖 Generated with Claude Code