Providers emitted identifiers and field keys that decodeSnapshot never
looked up, leaving CPU/kernel/load/disk sections zeroed and memory%,
storage%, network, process RSS, and fan fields decoded as zero.
Store-side decode now reads the emitted schema; providers gain the
previously-missing fields: network isUp (IFF_UP via getifaddrs), process
isStopped/isZombie (pbi_status), fan name (F{i}ID). Memory utilization is
computed as used/total in the decoder. decodeSnapshot is internal for
@testable access; new MMSnapshotContractTests cover the emitted-key
contract with synthetic dictionaries (93 tests total, 0 failures).
Generated with [Devin](https://devin.ai)
Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
164 lines
5.8 KiB
Objective-C
164 lines
5.8 KiB
Objective-C
#import "MMProcessTelemetryProvider.h"
|
|
#import <libproc.h>
|
|
#import <sys/proc_info.h>
|
|
#import <sys/proc.h>
|
|
#import <pwd.h>
|
|
#import <signal.h>
|
|
#import <mach/mach_time.h>
|
|
#import <os/lock.h>
|
|
|
|
@interface MMProcessTelemetryProvider () {
|
|
os_unfair_lock _lock;
|
|
NSMutableDictionary<NSNumber *, NSNumber *> *_previousCPUTimes;
|
|
uint64_t _previousTimestamp;
|
|
mach_timebase_info_data_t _timebase;
|
|
NSMutableDictionary<NSNumber *, NSString *> *_usernameCache;
|
|
}
|
|
@end
|
|
|
|
@implementation MMProcessTelemetryProvider
|
|
|
|
- (instancetype)init {
|
|
self = [super init];
|
|
if (self) {
|
|
_lock = OS_UNFAIR_LOCK_INIT;
|
|
_previousCPUTimes = [NSMutableDictionary dictionary];
|
|
_previousTimestamp = 0;
|
|
_usernameCache = [NSMutableDictionary dictionary];
|
|
mach_timebase_info(&_timebase);
|
|
}
|
|
return self;
|
|
}
|
|
|
|
- (MMTelemetryDomain)domain {
|
|
return MMTelemetryDomainProcess;
|
|
}
|
|
|
|
- (NSString *)providerIdentifier {
|
|
return @"com.i3omb.macmonitor.telemetry.process";
|
|
}
|
|
|
|
- (BOOL)isAvailable {
|
|
return YES;
|
|
}
|
|
|
|
+ (BOOL)terminateProcessWithPID:(pid_t)pid force:(BOOL)force {
|
|
int sig = force ? SIGKILL : SIGTERM;
|
|
return kill(pid, sig) == 0;
|
|
}
|
|
|
|
+ (NSArray<NSDictionary<NSString *, id> *> *)calculateProcessListWithRawProcesses:(NSArray<NSDictionary<NSString *, id> *> *)rawProcesses
|
|
previousCPUTimes:(nullable NSDictionary<NSNumber *, NSNumber *> *)previousCPUTimes
|
|
timeDelta:(NSTimeInterval)timeDelta {
|
|
NSMutableArray<NSDictionary<NSString *, id> *> *processed = [NSMutableArray arrayWithCapacity:rawProcesses.count];
|
|
|
|
for (NSDictionary<NSString *, id> *raw in rawProcesses) {
|
|
NSNumber *pidNum = raw[@"pid"];
|
|
uint64_t currCPUTime = [raw[@"cpuTimeNs"] unsignedLongLongValue];
|
|
|
|
double cpuPercent = 0.0;
|
|
if (previousCPUTimes && previousCPUTimes[pidNum]) {
|
|
uint64_t prevCPUTime = [previousCPUTimes[pidNum] unsignedLongLongValue];
|
|
if (currCPUTime >= prevCPUTime && timeDelta > 0.001) {
|
|
uint64_t deltaNs = currCPUTime - prevCPUTime;
|
|
double deltaSec = (double)deltaNs / 1e9;
|
|
cpuPercent = (deltaSec / timeDelta) * 100.0;
|
|
}
|
|
}
|
|
|
|
NSMutableDictionary<NSString *, id> *entry = [raw mutableCopy];
|
|
entry[@"cpuPercent"] = @(cpuPercent);
|
|
[processed addObject:entry];
|
|
}
|
|
|
|
// Sort descending by cpuPercent
|
|
[processed sortUsingComparator:^NSComparisonResult(NSDictionary<NSString *, id> *obj1, NSDictionary<NSString *, id> *obj2) {
|
|
NSNumber *c1 = obj1[@"cpuPercent"];
|
|
NSNumber *c2 = obj2[@"cpuPercent"];
|
|
return [c2 compare:c1];
|
|
}];
|
|
|
|
return processed;
|
|
}
|
|
|
|
- (nullable NSDictionary<NSString *, id> *)sampleTelemetryWithError:(NSError **)error {
|
|
uint64_t now = mach_absolute_time();
|
|
|
|
int pids[4096];
|
|
int bytes = proc_listpids(PROC_ALL_PIDS, 0, pids, sizeof(pids));
|
|
int pidCount = bytes / sizeof(int);
|
|
|
|
NSMutableArray<NSDictionary<NSString *, id> *> *rawList = [NSMutableArray arrayWithCapacity:pidCount];
|
|
NSMutableDictionary<NSNumber *, NSNumber *> *currentCPUTimes = [NSMutableDictionary dictionaryWithCapacity:pidCount];
|
|
|
|
NSUInteger totalThreads = 0;
|
|
|
|
for (int i = 0; i < pidCount; i++) {
|
|
pid_t pid = pids[i];
|
|
if (pid <= 0) continue;
|
|
|
|
struct proc_taskallinfo tai;
|
|
if (proc_pidinfo(pid, PROC_PIDTASKALLINFO, 0, &tai, sizeof(tai)) != sizeof(tai)) {
|
|
continue;
|
|
}
|
|
|
|
uint64_t cpuTimeNs = tai.ptinfo.pti_total_user + tai.ptinfo.pti_total_system;
|
|
currentCPUTimes[@(pid)] = @(cpuTimeNs);
|
|
|
|
totalThreads += tai.ptinfo.pti_threadnum;
|
|
|
|
// Resolve username
|
|
NSNumber *uidNum = @(tai.pbsd.pbi_uid);
|
|
NSString *username = _usernameCache[uidNum];
|
|
if (!username) {
|
|
struct passwd *pw = getpwuid(tai.pbsd.pbi_uid);
|
|
username = pw ? [NSString stringWithUTF8String:pw->pw_name] : [uidNum stringValue];
|
|
_usernameCache[uidNum] = username;
|
|
}
|
|
|
|
NSString *pname = [NSString stringWithUTF8String:tai.pbsd.pbi_name];
|
|
|
|
[rawList addObject:@{
|
|
@"pid": @(pid),
|
|
@"ppid": @(tai.pbsd.pbi_ppid),
|
|
@"uid": uidNum,
|
|
@"username": username,
|
|
@"name": pname,
|
|
@"cpuTimeNs": @(cpuTimeNs),
|
|
@"residentBytes": @(tai.ptinfo.pti_resident_size),
|
|
@"virtualBytes": @(tai.ptinfo.pti_virtual_size),
|
|
@"threadCount": @(tai.ptinfo.pti_threadnum),
|
|
@"runningThreads": @(tai.ptinfo.pti_numrunning),
|
|
@"isStopped": @(tai.pbsd.pbi_status == SSTOP),
|
|
@"isZombie": @(tai.pbsd.pbi_status == SZOMB)
|
|
}];
|
|
}
|
|
|
|
os_unfair_lock_lock(&_lock);
|
|
NSDictionary<NSNumber *, NSNumber *> *prev = [_previousCPUTimes copy];
|
|
uint64_t prevTime = _previousTimestamp;
|
|
|
|
_previousCPUTimes = currentCPUTimes;
|
|
_previousTimestamp = now;
|
|
os_unfair_lock_unlock(&_lock);
|
|
|
|
NSTimeInterval timeDelta = 1.0;
|
|
if (prevTime > 0) {
|
|
uint64_t elapsed = now - prevTime;
|
|
timeDelta = (double)elapsed * _timebase.numer / _timebase.denom / 1e9;
|
|
}
|
|
|
|
NSArray<NSDictionary<NSString *, id> *> *processed = [MMProcessTelemetryProvider calculateProcessListWithRawProcesses:rawList
|
|
previousCPUTimes:prev
|
|
timeDelta:timeDelta];
|
|
|
|
return @{
|
|
@"processCount": @(processed.count),
|
|
@"totalThreads": @(totalThreads),
|
|
@"processes": processed,
|
|
@"timeDelta": @(timeDelta)
|
|
};
|
|
}
|
|
|
|
@end
|