AirStats shipped with a bug I could not reproduce on demand.
On a machine doing nothing at all, the first GPU sample after launch would sometimes come back at 98%. The second sample, one second later, was 0%, and it stayed there. Relaunch and it was usually fine.
Those are the worst ones. Not the bugs that fail, the bugs that fail one time in five and behave perfectly while you are watching.
The number was never wrong
Utilisation comes from the IOAccelerator service's performance statistics dictionary. I had assumed, as most people do, that the percentage it hands you is an instantaneous reading.
It is not. It is an average over the interval since the counter was last read. Not since you last read it. Since any process on the machine last read it.
Whose window is it anyway
That distinction is the whole bug. When you take your first reading, you are handed an average over a window whose start time was chosen by whichever process happened to touch that node last.
If nothing has read it since boot, your window is hours long. If a game or a benchmark read it four minutes ago, your window is four minutes long and covers everything that ran in those four minutes.
Which is how a completely idle GPU reports 98%. A benchmark had read the node minutes earlier, the driver had been accumulating busy time across a window that included it, and AirStats' first read collected the whole thing and divided it by an interval it never chose. The reading was accurate. It was answering a question I had not asked.
The fix is a read you throw away
There is no API for asking about a specific window, so the only correct move is to close the window you inherited and open your own. On every device rescan, AirStats reads the statistics for each accelerator and discards the result:
// Prime each accumulator with a discarded read. The driver reports
// utilisation over the interval since the last read *by any process*,
// so without this the first sample inherits a window of unknown length.
for device in devices where device.service != 0 {
_ = Self.performanceStatistics(device.service)
}
lastReadInstant = Monotonic.nowFrom then on every window is bounded by two of AirStats' own reads, at a cadence AirStats picked.
The priming has to happen on rescan rather than once at launch. External GPUs and display changes rebuild the device list, and every new service handle arrives with the same inherited window problem, brand new.
The general shape of this bug
Counters that report since the last read are everywhere in systems programming, and they all carry this hazard. The rule I took away from it is that the first read of an interval counter is never a measurement, it is a timestamp.
The same reasoning is why the AirStats benchmark samples with top -l 2 instead of top -l 1. The first sample of any top run reports CPU cumulative since process launch rather than over the interval, so reading it would have quietly flattered whichever app had been running longest. Same bug, different counter.
There is a second rule the collectors are tested against, which covers the other half of this: never return a fabricated or zeroed value to paper over a failed syscall, and never compute a rate from a zero elapsed time. A monitor that prints nothing is annoying. A monitor that quietly prints a plausible wrong number is worse, because you will believe it.