Count memory the way Activity Monitor does - #17
Open
jondkinney wants to merge 1 commit into
Open
Conversation
The gauge treated every page that was not free or speculative as used, so the file cache counted against it. On a 128 GB machine that reads 116 GB — 91% — while Activity Monitor and iStat Menus both say 63%. Used memory is now app (anonymous) + wired + compressed, the same three figures Activity Monitor adds up. Pressure came off that same free-page count, which made it a second copy of the used percentage: both gauges sat at 91%, and a real pressure spike would have had nowhere to go. It now counts only what the kernel cannot reclaim — wired + compressed — which tracks what `memory_pressure` reports, 5% against iStat's 7% on the sample above. The arithmetic moves into MemoryReading so it can be tested away from the host: the tests run the page counts from that 128 GB machine through it and pin both figures to what Activity Monitor showed.
jondkinney
force-pushed
the
fix-memory-gauge
branch
from
August 20, 2026 16:13
775da48 to
9dcc207
Compare
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.
The bug
The memory gauge counts every page that is not free or speculative as used, which sweeps in the file cache. On a 128 GB machine it reads 116 GB / 91% while Activity Monitor and iStat Menus both report 63%:
The error scales with how much RAM the machine has spare to cache with, so it's worst on exactly the machines this dashboard is aimed at.
Memory pressure has a second problem: it's derived from the same free-page count, so it is effectively a duplicate of the used percentage. Both gauges move together and sit pinned near the top, which means a genuine pressure spike — the thing that gauge exists to show — has nowhere left to travel.
The fix
SystemMetricsService.currentMemorySnapshot():internal_page_count − purgeable_count) + wired + compressed, the three figures Activity Monitor adds up. File-backed and purgeable pages are cache the kernel hands back on demand, so they aren't counted.memory_pressurereports as unavailable. On the sample above that gives 5% against iStat's 7%.Incidentally this also drops a small double-count:
vm_statistics64.free_countalready includes speculative pages, so the oldfree_count + speculative_countcounted them twice.Tests
The arithmetic moves into a
MemoryReadingstruct so it can be exercised without reading the live host.Tests/EdgeControlTests/MemoryReadingTests.swiftruns thevm_statpage counts from that 128 GB machine through it and pins used and pressure to what Activity Monitor showed, plus the edge cases (clamping,purgeable_count > internal_page_count, zero physical size). Reverting the source to the old formula fails them with exactly the 116.1 GB / 90.7% from the screenshot.Behaviour is unchanged otherwise: same struct fields, same call site, same fallback to the previous sample when
host_statistics64fails.One note
I could not run the suite on a clean
main— on Xcode 16.4 / Swift 6,mainfails to compile before any of this (PluginWidgetProvider.swift:58, non-SendableNSImagein aSendablestruct, and main-actor isolation errors inLayoutEngineTests). I verified these tests with those build errors patched locally. Happy to send that as a separate PR if it would be useful.