RAM Breakdown, Compression & Memory Pressure Monitoring #8

Closed
opened 2026-09-08 10:18:45 +01:00 by gronod · 2 comments
Owner

Purpose

Provide comprehensive real-time memory telemetry matching and extending macOS Activity Monitor, breaking down physical RAM into distinct categories, tracking swap memory usage, and monitoring macOS virtual memory pressure levels.

Priority

Critical (Core subsystem telemetry directly affecting system stability and responsiveness)

Dependencies

  • Depends on #1 (Core Application Architecture & Objective-C/SwiftUI Bridging Foundation)

Scope

  • Report total installed physical RAM.
  • Break down memory allocation into:
    • App Memory (active + internal anonymous pages)
    • Wired Memory (kernel and driver locked pages)
    • Compressed Memory (pages held in kernel compressor)
    • Cached Files (purgeable and file-backed pages)
    • Free Memory
  • Monitor virtual memory swap space: total swap, used swap, free swap, and swap rate.
  • Monitor macOS memory pressure events: Normal, Warning (pressure on cache/compression), and Critical (active swapping / process termination risk).
  • Visualize memory distribution as a stacked memory bar and gauge.

Implementation Suggestions

  • Query Mach VM statistics in Objective-C using host_statistics64(mach_host_self(), HOST_VM_INFO64, ...) with vm_statistics64_data_t.
  • Retrieve page size via vm_kernel_page_size to calculate exact byte values.
  • Query swap statistics via sysctlbyname("vm.swapusage", &swap, &len, NULL, 0).
  • Register a GCD memory pressure dispatch source using dispatch_source_create(DISPATCH_SOURCE_TYPE_MEMORYPRESSURE, 0, DISPATCH_MEMORYPRESSURE_NORMAL | DISPATCH_MEMORYPRESSURE_WARN | DISPATCH_MEMORYPRESSURE_CRITICAL, queue) to receive real-time OS memory pressure state transitions.
### Purpose Provide comprehensive real-time memory telemetry matching and extending macOS Activity Monitor, breaking down physical RAM into distinct categories, tracking swap memory usage, and monitoring macOS virtual memory pressure levels. ### Priority **Critical** (Core subsystem telemetry directly affecting system stability and responsiveness) ### Dependencies - Depends on #1 (Core Application Architecture & Objective-C/SwiftUI Bridging Foundation) ### Scope - Report total installed physical RAM. - Break down memory allocation into: - App Memory (active + internal anonymous pages) - Wired Memory (kernel and driver locked pages) - Compressed Memory (pages held in kernel compressor) - Cached Files (purgeable and file-backed pages) - Free Memory - Monitor virtual memory swap space: total swap, used swap, free swap, and swap rate. - Monitor macOS memory pressure events: Normal, Warning (pressure on cache/compression), and Critical (active swapping / process termination risk). - Visualize memory distribution as a stacked memory bar and gauge. ### Implementation Suggestions - Query Mach VM statistics in Objective-C using `host_statistics64(mach_host_self(), HOST_VM_INFO64, ...)` with `vm_statistics64_data_t`. - Retrieve page size via `vm_kernel_page_size` to calculate exact byte values. - Query swap statistics via `sysctlbyname("vm.swapusage", &swap, &len, NULL, 0)`. - Register a GCD memory pressure dispatch source using `dispatch_source_create(DISPATCH_SOURCE_TYPE_MEMORYPRESSURE, 0, DISPATCH_MEMORYPRESSURE_NORMAL | DISPATCH_MEMORYPRESSURE_WARN | DISPATCH_MEMORYPRESSURE_CRITICAL, queue)` to receive real-time OS memory pressure state transitions.
gronod added the Kind/Feature
Priority
Critical
1
labels 2026-09-08 10:18:45 +01:00
gronod added the Project/AntigravityFeature/BackendFeature/UI labels 2026-09-08 10:28:33 +01:00
Author
Owner

Technical Implementation Plan & Code Solution

1. Memory Subsystem Architecture

To match macOS Activity Monitor, RAM must be divided into:

  • App Memory: Active pages + anonymous internal pages - purgeable memory.
  • Wired Memory: Memory locked by the kernel and drivers that cannot be paged out.
  • Compressed: Memory compressed by the in-memory compressor (compressor_page_count).
  • Cached Files: Cached file-backed pages that can be freed when needed.
  • Swap: Active secondary disk paging space.

2. Objective-C Provider (MMMemoryProvider.m)

#import "MMMemoryProvider.h"
#import <mach/mach.h>
#import <mach/mach_host.h>
#import <sys/sysctl.h>

@interface MMMemoryProvider () {
    vm_size_t _pageSize;
    uint64_t _totalPhysicalRAM;
    dispatch_source_t _pressureSource;
    NSInteger _currentPressureLevel; // 1=Normal, 2=Warn, 4=Critical
}
@end

@implementation MMMemoryProvider

- (instancetype)init {
    self = [super init];
    if (self) {
        _pageSize = vm_kernel_page_size;
        _totalPhysicalRAM = [self queryTotalRAM];
        _currentPressureLevel = 1;
        [self setupMemoryPressureMonitor];
    }
    return self;
}

- (void)dealloc {
    if (_pressureSource) {
        dispatch_source_cancel(_pressureSource);
    }
}

- (uint64_t)queryTotalRAM {
    uint64_t mem = 0;
    size_t size = sizeof(mem);
    sysctlbyname("hw.memsize", &mem, &size, NULL, 0);
    return mem;
}

- (void)setupMemoryPressureMonitor {
    dispatch_queue_t queue = dispatch_get_global_queue(QOS_CLASS_UTILITY, 0);
    _pressureSource = dispatch_source_create(
        DISPATCH_SOURCE_TYPE_MEMORYPRESSURE,
        0,
        DISPATCH_MEMORYPRESSURE_NORMAL | DISPATCH_MEMORYPRESSURE_WARN | DISPATCH_MEMORYPRESSURE_CRITICAL,
        queue
    );

    __weak typeof(self) weakSelf = self;
    dispatch_source_set_event_handler(_pressureSource, ^{
        __strong typeof(weakSelf) strongSelf = weakSelf;
        if (!strongSelf) return;

        unsigned long flags = dispatch_source_get_data(strongSelf->_pressureSource);
        if (flags & DISPATCH_MEMORYPRESSURE_CRITICAL) {
            strongSelf->_currentPressureLevel = 4;
        } else if (flags & DISPATCH_MEMORYPRESSURE_WARN) {
            strongSelf->_currentPressureLevel = 2;
        } else {
            strongSelf->_currentPressureLevel = 1;
        }
    });

    dispatch_resume(_pressureSource);
}

- (NSDictionary<NSString *, id> *)sampleTelemetryWithError:(NSError **)error {
    vm_statistics64_data_t vmStat;
    mach_msg_type_number_t count = HOST_VM_INFO64_COUNT;

    kern_return_t kr = host_statistics64(mach_host_self(), HOST_VM_INFO64, (host_info64_t)&vmStat, &count);
    if (kr != KERN_SUCCESS) {
        return @{};
    }

    uint64_t wired = (uint64_t)vmStat.wire_count * _pageSize;
    uint64_t free = (uint64_t)vmStat.free_count * _pageSize;
    uint64_t compressed = (uint64_t)vmStat.compressor_page_count * _pageSize;
    uint64_t purgeable = (uint64_t)vmStat.purgeable_count * _pageSize;
    uint64_t internal = (uint64_t)vmStat.internal_page_count * _pageSize;

    // App memory = internal anonymous pages minus purgeable pages
    uint64_t appMemory = (internal >= purgeable) ? (internal - purgeable) : 0;

    // Cached files = purgeable pages + external file-backed pages
    uint64_t external = (uint64_t)vmStat.external_page_count * _pageSize;
    uint64_t cachedFiles = external + purgeable;

    // Swap Usage
    struct xsw_usage swap;
    size_t swapSize = sizeof(swap);
    uint64_t swapTotal = 0, swapUsed = 0;
    if (sysctlbyname("vm.swapusage", &swap, &swapSize, NULL, 0) == 0) {
        swapTotal = swap.xsu_total;
        swapUsed = swap.xsu_used;
    }

    uint64_t usedRAM = appMemory + wired + compressed;

    return @{
        @"totalBytes": @(_totalPhysicalRAM),
        @"usedBytes": @(usedRAM),
        @"appBytes": @(appMemory),
        @"wiredBytes": @(wired),
        @"compressedBytes": @(compressed),
        @"cachedBytes": @(cachedFiles),
        @"freeBytes": @(free),
        @"swapTotalBytes": @(swapTotal),
        @"swapUsedBytes": @(swapUsed),
        @"pressureLevel": @(_currentPressureLevel)
    };
}
@end

3. Swift UI Integration

@Observable
public final class MemorySnapshot {
    public var totalBytes: UInt64 = 0
    public var appBytes: UInt64 = 0
    public var wiredBytes: UInt64 = 0
    public var compressedBytes: UInt64 = 0
    public var cachedBytes: UInt64 = 0
    public var freeBytes: UInt64 = 0
    public var swapUsedBytes: UInt64 = 0
    public var pressureLevel: Int = 1 // 1=Normal, 2=Warn, 4=Critical

    public var memoryUsagePercentage: Double {
        totalBytes > 0 ? Double(appBytes + wiredBytes + compressedBytes) / Double(totalBytes) * 100.0 : 0
    }

    public func update(from dict: [String: Any]) {
        self.totalBytes = dict["totalBytes"] as? UInt64 ?? 0
        self.appBytes = dict["appBytes"] as? UInt64 ?? 0
        self.wiredBytes = dict["wiredBytes"] as? UInt64 ?? 0
        self.compressedBytes = dict["compressedBytes"] as? UInt64 ?? 0
        self.cachedBytes = dict["cachedBytes"] as? UInt64 ?? 0
        self.freeBytes = dict["freeBytes"] as? UInt64 ?? 0
        self.swapUsedBytes = dict["swapUsedBytes"] as? UInt64 ?? 0
        self.pressureLevel = dict["pressureLevel"] as? Int ?? 1
    }
}

4. Edge Cases Handled

  • Memory Pressure Level: Real-time event subscription via GCD DISPATCH_SOURCE_TYPE_MEMORYPRESSURE provides immediate detection before swapping freezes UI responsiveness.
  • Purgeable Cache Subtraction: Prevents cached disk blocks from being counted erroneously as occupied app RAM.
## Technical Implementation Plan & Code Solution ### 1. Memory Subsystem Architecture To match macOS Activity Monitor, RAM must be divided into: - **App Memory**: Active pages + anonymous internal pages - purgeable memory. - **Wired Memory**: Memory locked by the kernel and drivers that cannot be paged out. - **Compressed**: Memory compressed by the in-memory compressor (`compressor_page_count`). - **Cached Files**: Cached file-backed pages that can be freed when needed. - **Swap**: Active secondary disk paging space. --- ### 2. Objective-C Provider (`MMMemoryProvider.m`) ```objc #import "MMMemoryProvider.h" #import <mach/mach.h> #import <mach/mach_host.h> #import <sys/sysctl.h> @interface MMMemoryProvider () { vm_size_t _pageSize; uint64_t _totalPhysicalRAM; dispatch_source_t _pressureSource; NSInteger _currentPressureLevel; // 1=Normal, 2=Warn, 4=Critical } @end @implementation MMMemoryProvider - (instancetype)init { self = [super init]; if (self) { _pageSize = vm_kernel_page_size; _totalPhysicalRAM = [self queryTotalRAM]; _currentPressureLevel = 1; [self setupMemoryPressureMonitor]; } return self; } - (void)dealloc { if (_pressureSource) { dispatch_source_cancel(_pressureSource); } } - (uint64_t)queryTotalRAM { uint64_t mem = 0; size_t size = sizeof(mem); sysctlbyname("hw.memsize", &mem, &size, NULL, 0); return mem; } - (void)setupMemoryPressureMonitor { dispatch_queue_t queue = dispatch_get_global_queue(QOS_CLASS_UTILITY, 0); _pressureSource = dispatch_source_create( DISPATCH_SOURCE_TYPE_MEMORYPRESSURE, 0, DISPATCH_MEMORYPRESSURE_NORMAL | DISPATCH_MEMORYPRESSURE_WARN | DISPATCH_MEMORYPRESSURE_CRITICAL, queue ); __weak typeof(self) weakSelf = self; dispatch_source_set_event_handler(_pressureSource, ^{ __strong typeof(weakSelf) strongSelf = weakSelf; if (!strongSelf) return; unsigned long flags = dispatch_source_get_data(strongSelf->_pressureSource); if (flags & DISPATCH_MEMORYPRESSURE_CRITICAL) { strongSelf->_currentPressureLevel = 4; } else if (flags & DISPATCH_MEMORYPRESSURE_WARN) { strongSelf->_currentPressureLevel = 2; } else { strongSelf->_currentPressureLevel = 1; } }); dispatch_resume(_pressureSource); } - (NSDictionary<NSString *, id> *)sampleTelemetryWithError:(NSError **)error { vm_statistics64_data_t vmStat; mach_msg_type_number_t count = HOST_VM_INFO64_COUNT; kern_return_t kr = host_statistics64(mach_host_self(), HOST_VM_INFO64, (host_info64_t)&vmStat, &count); if (kr != KERN_SUCCESS) { return @{}; } uint64_t wired = (uint64_t)vmStat.wire_count * _pageSize; uint64_t free = (uint64_t)vmStat.free_count * _pageSize; uint64_t compressed = (uint64_t)vmStat.compressor_page_count * _pageSize; uint64_t purgeable = (uint64_t)vmStat.purgeable_count * _pageSize; uint64_t internal = (uint64_t)vmStat.internal_page_count * _pageSize; // App memory = internal anonymous pages minus purgeable pages uint64_t appMemory = (internal >= purgeable) ? (internal - purgeable) : 0; // Cached files = purgeable pages + external file-backed pages uint64_t external = (uint64_t)vmStat.external_page_count * _pageSize; uint64_t cachedFiles = external + purgeable; // Swap Usage struct xsw_usage swap; size_t swapSize = sizeof(swap); uint64_t swapTotal = 0, swapUsed = 0; if (sysctlbyname("vm.swapusage", &swap, &swapSize, NULL, 0) == 0) { swapTotal = swap.xsu_total; swapUsed = swap.xsu_used; } uint64_t usedRAM = appMemory + wired + compressed; return @{ @"totalBytes": @(_totalPhysicalRAM), @"usedBytes": @(usedRAM), @"appBytes": @(appMemory), @"wiredBytes": @(wired), @"compressedBytes": @(compressed), @"cachedBytes": @(cachedFiles), @"freeBytes": @(free), @"swapTotalBytes": @(swapTotal), @"swapUsedBytes": @(swapUsed), @"pressureLevel": @(_currentPressureLevel) }; } @end ``` --- ### 3. Swift UI Integration ```swift @Observable public final class MemorySnapshot { public var totalBytes: UInt64 = 0 public var appBytes: UInt64 = 0 public var wiredBytes: UInt64 = 0 public var compressedBytes: UInt64 = 0 public var cachedBytes: UInt64 = 0 public var freeBytes: UInt64 = 0 public var swapUsedBytes: UInt64 = 0 public var pressureLevel: Int = 1 // 1=Normal, 2=Warn, 4=Critical public var memoryUsagePercentage: Double { totalBytes > 0 ? Double(appBytes + wiredBytes + compressedBytes) / Double(totalBytes) * 100.0 : 0 } public func update(from dict: [String: Any]) { self.totalBytes = dict["totalBytes"] as? UInt64 ?? 0 self.appBytes = dict["appBytes"] as? UInt64 ?? 0 self.wiredBytes = dict["wiredBytes"] as? UInt64 ?? 0 self.compressedBytes = dict["compressedBytes"] as? UInt64 ?? 0 self.cachedBytes = dict["cachedBytes"] as? UInt64 ?? 0 self.freeBytes = dict["freeBytes"] as? UInt64 ?? 0 self.swapUsedBytes = dict["swapUsedBytes"] as? UInt64 ?? 0 self.pressureLevel = dict["pressureLevel"] as? Int ?? 1 } } ``` --- ### 4. Edge Cases Handled - **Memory Pressure Level**: Real-time event subscription via GCD `DISPATCH_SOURCE_TYPE_MEMORYPRESSURE` provides immediate detection before swapping freezes UI responsiveness. - **Purgeable Cache Subtraction**: Prevents cached disk blocks from being counted erroneously as occupied app RAM.
gronod added this to the M2: Primary System & Thermal Telemetry milestone 2026-09-08 10:42:05 +01:00
gronod added a new dependency 2026-09-08 11:37:11 +01:00
Author
Owner

Completed in feat/8-ram-breakdown and merged into develop.

  • Implemented MMMemoryTelemetryProvider sampling Mach host_statistics64 (HOST_VM_INFO64).
  • Extracted active, inactive, wired, compressed, and free bytes.
  • Polled sysctl vm.swapusage for total and used swap bytes.
  • Polled kern.memorystatus_vm_pressure_level for memory pressure classification.
  • Unit tested with 100% pass in MMMemoryTests.swift.
Completed in `feat/8-ram-breakdown` and merged into `develop`. - Implemented `MMMemoryTelemetryProvider` sampling Mach `host_statistics64` (`HOST_VM_INFO64`). - Extracted active, inactive, wired, compressed, and free bytes. - Polled sysctl `vm.swapusage` for total and used swap bytes. - Polled `kern.memorystatus_vm_pressure_level` for memory pressure classification. - Unit tested with 100% pass in `MMMemoryTests.swift`.
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Reference: gronod/MacMonitor#8