Show how much moved, not only how fast - #36
Closed
evandhoffman wants to merge 4 commits into
Closed
Conversation
A rate sample is the mean over the gap before it, so multiplying each sample by the interval it measures and summing telescopes back to exactly the counter delta. No counter history is kept, no source changes, and nothing is read that the ring buffer does not hold. WindowTotal reports the span it actually covered alongside the value. Ten seconds after launch the buffer holds ten seconds, and putting "2 min" under a number covering a twelfth of that is the quiet kind of wrong. A gap wider than maximumGap is clipped rather than credited in full, so a slept laptop cannot invent traffic. MetricUnit.accumulation says what a rate adds up to, derived from the unit the way ChartMirror and ChartStack read direction and composition. Network totals in bytes and the divide by eight lives there: a link is quoted in bits, a volume in bytes. Claude-Session: https://claude.ai/code/session_01EqTTtmt4fyNtMxjc2ZVBzj
A second number per legend entry: how much moved, beside how fast it is moving. The span sits once beside the title rather than after every entry, since the History picker is global and repeating it down a legend is four copies of one fact. The span reads the window only when the card really has it. Ten seconds after launch it says ten seconds, because "2 min" over a twelfth of that looks exactly like the right answer and nobody would check it. An entry covering materially less than the card's stated span is dimmed — one source failed while its neighbour kept reading. AppModel.totalGap is four ticks of the master clock, so it follows the Sampling tab. A ceiling fixed for the 0.5 s default would clip every interval of a sampler slowed to 2 s and quietly report a quarter of the traffic. Off by default, like mirroring and stacking. Narrower reason: those change what the picture means and this only adds a number, but it adds one to a header already decided by ViewThatFits. Claude-Session: https://claude.ai/code/session_01EqTTtmt4fyNtMxjc2ZVBzj
The argument for matching mirroring and stacking lost on first contact: shipped off, the reaction to the finished feature was "I don't see it". Those two change what the picture means, so a reader who never asked for them deserves the chart they had. A total only adds a number beside one already there, and a number nobody can find is worth less than a header that reflows. Claude-Session: https://claude.ai/code/session_01EqTTtmt4fyNtMxjc2ZVBzj
This was referenced Aug 28, 2026
Wrapped entries put the totals at four different left edges down the same card. A card with totals now draws a small Grid instead of a FlowLayout: swatch, name, rate, total. Right-justified, because these are magnitudes and a magnitude is read by where its last digit sits — 104 MB over 48 MB aligned on the left puts the hundreds above the tens and hides the difference the column exists to show. Headed, because a second bare number beside a rate does not say what it is. The span beside the title says how long; the heading says of what. Under the title, never beside it. ViewThatFits is the right question for a wrapping row and the wrong one for a column of figures: pushed right of the title on a wide card, the heading floats in the middle of the header with nothing under it that reads as a table. Cards with nothing to total keep the flow — Memory's seven slices and five temperature sensors want wrapping, not a column. Claude-Session: https://claude.ai/code/session_01EqTTtmt4fyNtMxjc2ZVBzj
Contributor
Author
|
Folded into #38, which carries these commits unchanged. Splitting this off bought nothing: the stack was only ever mergeable in order, so it was three squash-merges and three releases for one change. |
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.
Closes #35.
A second number in every legend entry: how much moved over the window the
chart is drawing, beside how fast it is moving now. Network and disk in
bytes, packets and disk operations as counts.
The sum is already in the buffer
A rate sample is the mean over the gap before it —
RateTrackerproducesΔcounter / Δt— soΣ(rate × preceding gap)telescopes back to exactly thecounter delta.
WindowTotalis that sum. No counter history is kept, nosource changes, and nothing is read that the ring buffer does not already
hold, so this stays well inside the rule that nothing is written to disk.
The round-trip test is the whole premise: build a synthetic counter,
differentiate it with
RateTracker, integrate it back, expect the originaldelta. Deliberately uneven intervals, so it cannot pass by multiplying by a
constant it guessed right.
Three things it refuses to fake
holds ten seconds, and "2 min" over a twelfth of that looks exactly like the
right answer — which is why nobody would check it. The span sits beside the
title and reads the window only when the card really has it.
maximumGapis clipped and the remainder comes offcovered, so a laptopback from sleep cannot credit one sample with an hour that never crossed the
wire.
AppModel.totalGapis four ticks of the master clock, so it followsthe Sampling tab rather than assuming 0.5 s.
RateTrackergives on a first read, for the same reason.Network totals in bytes
The rate stays Mbit/s because a link is quoted in bits. A volume is quoted in
bytes everywhere it matters.
MetricUnit.accumulationholds the ÷8 as itsonly definition, the way
NetworkSourceholds the ×8 — derived from the unitlike
directionandcomposition, so a source added later gets a totalwithout anything being told it exists.
The one thing worth arguing about
showsTotalsis off by default, followingmirrorsPairsandstacksParts. The precedent's reason is that those change what the picturemeans; this only adds a number. The narrower reason it still applies: it
adds one to a header already decided by
ViewThatFits, so switching it onreflows cards onto two lines — a card changing shape on upgrade. Happy to
ship it on instead and say so in the release notes.
Non-goals, each deliberate
No totals row in the CSV (a spreadsheet's
SUM × intervalis this samearithmetic, and a "Total" row under the Time column is a type error waiting to
be pasted into a chart). No odometer tile — considered, and it is very much
this app's idiom, but it costs panel space and a third tile kind in
PanelArrangement. Nomonitorctltotals; no watt-hours; nothing past tenminutes until the store lands.
Verification
swift build && swift test(205 tests),swift build -c release, andswiftformat --lintall clean. Not yet looked at inswift run monitor— theopen question is whether a legend entry carrying two numbers still reads at a
glance, and no test can answer that.
https://claude.ai/code/session_01EqTTtmt4fyNtMxjc2ZVBzj