Kernel Context Switching, Interrupts & System Call Counters #6

Closed
opened 2026-09-08 10:18:37 +01:00 by gronod · 1 comment
Owner

Resolved in Milestone 3. Implemented MMKernelTelemetryProvider.h/.m sampling Mach host vm info, page faults, COW faults, zero-fills, pageins/outs, and context switch/syscall rates per second. Verified with unit tests. Merged into develop.

Resolved in Milestone 3. Implemented `MMKernelTelemetryProvider.h/.m` sampling Mach host vm info, page faults, COW faults, zero-fills, pageins/outs, and context switch/syscall rates per second. Verified with unit tests. Merged into `develop`.
gronod added the Kind/Feature
Priority
High
2
labels 2026-09-08 10:18:37 +01:00
gronod added the Project/AntigravityFeature/BackendFeature/UI labels 2026-09-08 10:28:27 +01:00
Author
Owner

Technical Implementation Plan & Code Solution

1. Mach Kernel & BSD Sysctl Counters

macOS maintains cumulative 64-bit performance counters in the kernel for thread scheduling and interrupt handling:

  • vm.stats.sys.v_swtch: Cumulative context switches (voluntary and involuntary).
  • vm.stats.sys.v_syscall: Cumulative BSD / Mach system calls executed.
  • vm.stats.sys.v_intr: Cumulative device and timer interrupts serviced.
  • vm_statistics64.faults: Cumulative page faults handled by Mach VM subsystem.

2. Objective-C Provider (MMKernelCountersProvider.m)

#import "MMKernelCountersProvider.h"
#import <mach/mach.h>
#import <mach/mach_time.h>
#import <sys/sysctl.h>

@interface MMKernelCountersProvider () {
    uint64_t _prevSwitches;
    uint64_t _prevSyscalls;
    uint64_t _prevInterrupts;
    uint64_t _prevFaults;
    uint64_t _prevTimestamp;
    mach_timebase_info_data_t _timebase;
}
@end

@implementation MMKernelCountersProvider

- (instancetype)init {
    self = [super init];
    if (self) {
        mach_timebase_info(&_timebase);
        _prevTimestamp = mach_absolute_time();
        [self readCurrentCountersSwitches:&_prevSwitches
                                 syscalls:&_prevSyscalls
                               interrupts:&_prevInterrupts
                                   faults:&_prevFaults];
    }
    return self;
}

- (void)readCurrentCountersSwitches:(uint64_t *)switches
                           syscalls:(uint64_t *)syscalls
                         interrupts:(uint64_t *)interrupts
                             faults:(uint64_t *)faults {
    size_t size = sizeof(uint64_t);
    uint64_t val = 0;

    if (sysctlbyname("vm.stats.sys.v_swtch", &val, &size, NULL, 0) == 0) {
        *switches = val;
    }
    if (sysctlbyname("vm.stats.sys.v_syscall", &val, &size, NULL, 0) == 0) {
        *syscalls = val;
    }
    if (sysctlbyname("vm.stats.sys.v_intr", &val, &size, NULL, 0) == 0) {
        *interrupts = val;
    }

    vm_statistics64_data_t vmStat;
    mach_msg_type_number_t count = HOST_VM_INFO64_COUNT;
    if (host_statistics64(mach_host_self(), HOST_VM_INFO64, (host_info64_t)&vmStat, &count) == KERN_SUCCESS) {
        *faults = vmStat.faults;
    }
}

- (NSDictionary<NSString *, id> *)sampleTelemetryWithError:(NSError **)error {
    uint64_t curSwitches = 0, curSyscalls = 0, curInterrupts = 0, curFaults = 0;
    [self readCurrentCountersSwitches:&curSwitches
                             syscalls:&curSyscalls
                           interrupts:&curInterrupts
                               faults:&curFaults];

    uint64_t now = mach_absolute_time();
    uint64_t elapsedNano = (now - _prevTimestamp) * _timebase.numer / _timebase.denom;
    double elapsedSec = (double)elapsedNano / 1_000_000_000.0;

    if (elapsedSec <= 0.05) {
        return @{};
    }

    double switchRate = (curSwitches >= _prevSwitches) ? (curSwitches - _prevSwitches) / elapsedSec : 0;
    double syscallRate = (curSyscalls >= _prevSyscalls) ? (curSyscalls - _prevSyscalls) / elapsedSec : 0;
    double intrRate = (curInterrupts >= _prevInterrupts) ? (curInterrupts - _prevInterrupts) / elapsedSec : 0;
    double faultRate = (curFaults >= _prevFaults) ? (curFaults - _prevFaults) / elapsedSec : 0;

    _prevSwitches = curSwitches;
    _prevSyscalls = curSyscalls;
    _prevInterrupts = curInterrupts;
    _prevFaults = curFaults;
    _prevTimestamp = now;

    return @{
        @"contextSwitchesPerSec": @(switchRate),
        @"syscallsPerSec": @(syscallRate),
        @"interruptsPerSec": @(intrRate),
        @"pageFaultsPerSec": @(faultRate)
    };
}
@end

3. Swift UI Representation

@Observable
public final class KernelCountersSnapshot {
    public var contextSwitchesPerSec: Double = 0.0
    public var syscallsPerSec: Double = 0.0
    public var interruptsPerSec: Double = 0.0
    public var pageFaultsPerSec: Double = 0.0

    public func update(from dict: [String: Any]) {
        self.contextSwitchesPerSec = dict["contextSwitchesPerSec"] as? Double ?? 0.0
        self.syscallsPerSec = dict["syscallsPerSec"] as? Double ?? 0.0
        self.interruptsPerSec = dict["interruptsPerSec"] as? Double ?? 0.0
        self.pageFaultsPerSec = dict["pageFaultsPerSec"] as? Double ?? 0.0
    }
}

4. Precision Timing

By relying on mach_absolute_time() with mach_timebase_info, rate calculations remain immune to wall-clock skew, NTP adjustments, or sleep transitions.

## Technical Implementation Plan & Code Solution ### 1. Mach Kernel & BSD Sysctl Counters macOS maintains cumulative 64-bit performance counters in the kernel for thread scheduling and interrupt handling: - `vm.stats.sys.v_swtch`: Cumulative context switches (voluntary and involuntary). - `vm.stats.sys.v_syscall`: Cumulative BSD / Mach system calls executed. - `vm.stats.sys.v_intr`: Cumulative device and timer interrupts serviced. - `vm_statistics64.faults`: Cumulative page faults handled by Mach VM subsystem. --- ### 2. Objective-C Provider (`MMKernelCountersProvider.m`) ```objc #import "MMKernelCountersProvider.h" #import <mach/mach.h> #import <mach/mach_time.h> #import <sys/sysctl.h> @interface MMKernelCountersProvider () { uint64_t _prevSwitches; uint64_t _prevSyscalls; uint64_t _prevInterrupts; uint64_t _prevFaults; uint64_t _prevTimestamp; mach_timebase_info_data_t _timebase; } @end @implementation MMKernelCountersProvider - (instancetype)init { self = [super init]; if (self) { mach_timebase_info(&_timebase); _prevTimestamp = mach_absolute_time(); [self readCurrentCountersSwitches:&_prevSwitches syscalls:&_prevSyscalls interrupts:&_prevInterrupts faults:&_prevFaults]; } return self; } - (void)readCurrentCountersSwitches:(uint64_t *)switches syscalls:(uint64_t *)syscalls interrupts:(uint64_t *)interrupts faults:(uint64_t *)faults { size_t size = sizeof(uint64_t); uint64_t val = 0; if (sysctlbyname("vm.stats.sys.v_swtch", &val, &size, NULL, 0) == 0) { *switches = val; } if (sysctlbyname("vm.stats.sys.v_syscall", &val, &size, NULL, 0) == 0) { *syscalls = val; } if (sysctlbyname("vm.stats.sys.v_intr", &val, &size, NULL, 0) == 0) { *interrupts = val; } vm_statistics64_data_t vmStat; mach_msg_type_number_t count = HOST_VM_INFO64_COUNT; if (host_statistics64(mach_host_self(), HOST_VM_INFO64, (host_info64_t)&vmStat, &count) == KERN_SUCCESS) { *faults = vmStat.faults; } } - (NSDictionary<NSString *, id> *)sampleTelemetryWithError:(NSError **)error { uint64_t curSwitches = 0, curSyscalls = 0, curInterrupts = 0, curFaults = 0; [self readCurrentCountersSwitches:&curSwitches syscalls:&curSyscalls interrupts:&curInterrupts faults:&curFaults]; uint64_t now = mach_absolute_time(); uint64_t elapsedNano = (now - _prevTimestamp) * _timebase.numer / _timebase.denom; double elapsedSec = (double)elapsedNano / 1_000_000_000.0; if (elapsedSec <= 0.05) { return @{}; } double switchRate = (curSwitches >= _prevSwitches) ? (curSwitches - _prevSwitches) / elapsedSec : 0; double syscallRate = (curSyscalls >= _prevSyscalls) ? (curSyscalls - _prevSyscalls) / elapsedSec : 0; double intrRate = (curInterrupts >= _prevInterrupts) ? (curInterrupts - _prevInterrupts) / elapsedSec : 0; double faultRate = (curFaults >= _prevFaults) ? (curFaults - _prevFaults) / elapsedSec : 0; _prevSwitches = curSwitches; _prevSyscalls = curSyscalls; _prevInterrupts = curInterrupts; _prevFaults = curFaults; _prevTimestamp = now; return @{ @"contextSwitchesPerSec": @(switchRate), @"syscallsPerSec": @(syscallRate), @"interruptsPerSec": @(intrRate), @"pageFaultsPerSec": @(faultRate) }; } @end ``` --- ### 3. Swift UI Representation ```swift @Observable public final class KernelCountersSnapshot { public var contextSwitchesPerSec: Double = 0.0 public var syscallsPerSec: Double = 0.0 public var interruptsPerSec: Double = 0.0 public var pageFaultsPerSec: Double = 0.0 public func update(from dict: [String: Any]) { self.contextSwitchesPerSec = dict["contextSwitchesPerSec"] as? Double ?? 0.0 self.syscallsPerSec = dict["syscallsPerSec"] as? Double ?? 0.0 self.interruptsPerSec = dict["interruptsPerSec"] as? Double ?? 0.0 self.pageFaultsPerSec = dict["pageFaultsPerSec"] as? Double ?? 0.0 } } ``` --- ### 4. Precision Timing By relying on `mach_absolute_time()` with `mach_timebase_info`, rate calculations remain immune to wall-clock skew, NTP adjustments, or sleep transitions.
gronod added this to the M3: Advanced Kernel, Process & Peripheral Telemetry milestone 2026-09-08 10:42:17 +01:00
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Reference: gronod/MacMonitor#6