Skip to content

System Monitor: add history sparklines and a top-process list - #25

Open
havokentity wants to merge 2 commits into
mainfrom
feat/sysmon-sparklines
Open

System Monitor: add history sparklines and a top-process list#25
havokentity wants to merge 2 commits into
mainfrom
feat/sysmon-sparklines

Conversation

@havokentity

Copy link
Copy Markdown
Owner

Wave 2 Phase A.

Implementation: System Monitor: add history sparklines and a top-process list
Review fixes: System Monitor: stop running ps while the panel is closed

Built by an isolated agent, then adversarially reviewed by an independent agent that checked the branch out and ran swift build + the full suite itself. Every review finding was fixed, with a regression test added for each high and medium.

🤖 Generated with Claude Code

havokentity and others added 2 commits July 21, 2026 13:27
The panel could say the CPU was at 40% but not whether that was a spike
settling or a climb, and never said which process was responsible — the
two questions anyone actually opens a system monitor to answer. Each of
the CPU, memory and network cards now carries a sparkline of the last
minute, and a card at the foot lists the five busiest processes ranked
by CPU or by memory.

The history is a fixed-capacity ring rather than an array that grows.
This tool is a login item that stays up for months, and it has already
shipped one bug of exactly the long-uptime shape — the kernel's
integer_t CPU tick counters read back negative past 2^31 and made the
load report zero — so the storage is allocated once at sixty samples,
the write cursor is taken modulo the capacity every append, and the
fill level saturates. Nothing added here counts upwards without a
bound, including the process-refresh cadence, which is a countdown that
resets rather than a tick total.

The series-to-points mapping is a pure nonisolated function separate
from the Shape, because its failure mode is invisible rather than loud:
an idle machine reports the same figure for a minute straight, and
normalising against that zero range puts a NaN in the Path, which
CoreGraphics answers by silently dropping the whole shape. So a flat
series falls back to a line down the middle, out-of-domain values are
clamped, and a stray non-finite sample is neutralised before it can
poison the series minimum and maximum for every other point.

CPU and memory are plotted against a fixed 0...1 axis so an idle
machine looks idle instead of having its 2% jitter redrawn as a
mountain range; the two network traces share one axis derived from
their combined peak, since scaled independently the upstream line would
draw just as tall as a downstream forty times its size.

The process list shells out to ps, which is far too expensive to do on
the one-second poll and would reshuffle faster than anyone can read.
Rather than add a second timer it divides the existing one, running
every fifth poll, and the subprocess and its exit wait happen in a
detached task so the main actor never blocks. A run that fails leaves
the previous list on screen rather than blanking it, and rows are
padded to a fixed five so the card cannot resize the panel underneath
the user.

Making room for all of this meant the panel grew, so its height is now
derived from the same constants the popover lays its content out with
instead of being a separate literal, and the settings sheet is capped
against it. That cap fixes a latent 4pt overhang: the sheet was a
hard-coded 338 against a 370pt panel, and PreferencesOverlay adds 18pt
of padding per side.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The top-process list rides on the metric poll at a fifth of its rate, but
it rode on it unconditionally: `start` is called at launch and `stop` only
at termination, so the five-second `ps -A` ran whether or not anything was
displaying the result. This tool ships as a login item, which means the
common case is a machine where the panel is opened rarely or never — and
there the cost was 17,280 subprocess spawns a day, around a tenth of a
second of mostly system time apiece enumerating every process on the box,
to compute a snapshot nobody could see. On battery that is timer-driven
wakeups and roughly a percent of a core burned forever for nothing.

Gate the listing on whether the list is on screen. The panel's own
onDidShow/onDidClose hooks drive the flag rather than togglePopover, so a
dismissal by outside click, by a display-parameter change or by a Space
switch stops the sampling exactly as a deliberate close does. A show
primes the list immediately and zeroes the countdown, so opening the panel
does not sit in front of an empty or stale list while a leftover count
drains; a closed panel parks the countdown at zero for the same reason.
The metrics themselves are Mach counters costing next to nothing, so they
keep polling every second and the tray strip is unaffected.

The sampler is injectable so the cadence can be proved without launching a
subprocess, and the tests assert call counts on the real poll path: zero
listings across a closed panel's polls, one on show, one per divider while
open, none again after close.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant