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>
160 lines
5.1 KiB
Objective-C
160 lines
5.1 KiB
Objective-C
#import "MMFanTelemetryProvider.h"
|
|
|
|
@interface MMFanTelemetryProvider () {
|
|
MMAppleSMCClient *_smcClient;
|
|
NSInteger _probedFanCount;
|
|
}
|
|
@end
|
|
|
|
@implementation MMFanTelemetryProvider
|
|
|
|
- (instancetype)init {
|
|
return [self initWithSMCClient:[MMAppleSMCClient sharedClient]];
|
|
}
|
|
|
|
- (instancetype)initWithSMCClient:(MMAppleSMCClient *)client {
|
|
self = [super init];
|
|
if (self) {
|
|
_smcClient = client;
|
|
_probedFanCount = -1;
|
|
[self probeFans];
|
|
}
|
|
return self;
|
|
}
|
|
|
|
- (void)probeFans {
|
|
if (!_smcClient.isAvailable) return;
|
|
|
|
NSNumber *num = [_smcClient readNumericValueForKey:@"FNum" error:nil];
|
|
if (num) {
|
|
_probedFanCount = [num integerValue];
|
|
} else {
|
|
// Fallback probe F0Ac
|
|
NSNumber *f0 = [_smcClient readNumericValueForKey:@"F0Ac" error:nil];
|
|
if (f0 && [f0 doubleValue] >= 0.0) {
|
|
_probedFanCount = 1;
|
|
NSNumber *f1 = [_smcClient readNumericValueForKey:@"F1Ac" error:nil];
|
|
if (f1 && [f1 doubleValue] >= 0.0) {
|
|
_probedFanCount = 2;
|
|
}
|
|
} else {
|
|
_probedFanCount = 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
- (MMTelemetryDomain)domain {
|
|
return MMTelemetryDomainFan;
|
|
}
|
|
|
|
- (NSString *)providerIdentifier {
|
|
return @"com.i3omb.macmonitor.telemetry.fan";
|
|
}
|
|
|
|
- (BOOL)isAvailable {
|
|
return _smcClient.isAvailable;
|
|
}
|
|
|
|
- (MMAppleSMCClient *)smcClient {
|
|
return _smcClient;
|
|
}
|
|
|
|
- (NSInteger)fanCount {
|
|
return (_probedFanCount >= 0) ? _probedFanCount : 0;
|
|
}
|
|
|
|
+ (NSDictionary<NSString *, id> *)calculateFanMetricsFromFanList:(NSArray<NSDictionary<NSString *, id> *> *)fanList {
|
|
if (!fanList || fanList.count == 0) {
|
|
return @{
|
|
@"fanCount": @(0),
|
|
@"fans": @[],
|
|
@"averageRPM": @(0.0),
|
|
@"peakRPM": @(0.0)
|
|
};
|
|
}
|
|
|
|
NSMutableArray<NSDictionary<NSString *, id> *> *resultList = [NSMutableArray arrayWithCapacity:fanList.count];
|
|
double sumRPM = 0.0;
|
|
double peakRPM = 0.0;
|
|
|
|
for (NSUInteger i = 0; i < fanList.count; i++) {
|
|
NSDictionary<NSString *, id> *raw = fanList[i];
|
|
double current = [raw[@"currentRPM"] doubleValue];
|
|
double min = [raw[@"minRPM"] doubleValue];
|
|
double max = [raw[@"maxRPM"] doubleValue];
|
|
double target = [raw[@"targetRPM"] doubleValue];
|
|
|
|
double util = 0.0;
|
|
if (max > min && current >= min) {
|
|
util = ((current - min) / (max - min)) * 100.0;
|
|
if (util < 0.0) util = 0.0;
|
|
if (util > 100.0) util = 100.0;
|
|
}
|
|
|
|
if (current > peakRPM) peakRPM = current;
|
|
sumRPM += current;
|
|
|
|
[resultList addObject:@{
|
|
@"fanIndex": @(i),
|
|
@"name": raw[@"name"] ?: [NSString stringWithFormat:@"Fan %lu", (unsigned long)(i + 1)],
|
|
@"currentRPM": @(current),
|
|
@"minRPM": @(min),
|
|
@"maxRPM": @(max),
|
|
@"targetRPM": @(target),
|
|
@"utilizationPercent": @(util)
|
|
}];
|
|
}
|
|
|
|
double avgRPM = fanList.count > 0 ? (sumRPM / fanList.count) : 0.0;
|
|
|
|
return @{
|
|
@"fanCount": @(fanList.count),
|
|
@"fans": [resultList copy],
|
|
@"averageRPM": @(avgRPM),
|
|
@"peakRPM": @(peakRPM)
|
|
};
|
|
}
|
|
|
|
- (nullable NSDictionary<NSString *, id> *)sampleTelemetryWithError:(NSError **)error {
|
|
if (_probedFanCount < 0 && _smcClient.isAvailable) {
|
|
[self probeFans];
|
|
}
|
|
|
|
NSInteger count = [self fanCount];
|
|
NSMutableArray<NSDictionary<NSString *, id> *> *rawList = [NSMutableArray arrayWithCapacity:count];
|
|
|
|
for (NSInteger i = 0; i < count; i++) {
|
|
NSString *acKey = [NSString stringWithFormat:@"F%ldAc", (long)i];
|
|
NSString *mnKey = [NSString stringWithFormat:@"F%ldMn", (long)i];
|
|
NSString *mxKey = [NSString stringWithFormat:@"F%ldMx", (long)i];
|
|
NSString *tgKey = [NSString stringWithFormat:@"F%ldTg", (long)i];
|
|
NSString *idKey = [NSString stringWithFormat:@"F%ldID", (long)i];
|
|
|
|
NSNumber *ac = [_smcClient readNumericValueForKey:acKey error:nil] ?: @(0.0);
|
|
NSNumber *mn = [_smcClient readNumericValueForKey:mnKey error:nil] ?: @(0.0);
|
|
NSNumber *mx = [_smcClient readNumericValueForKey:mxKey error:nil] ?: @(6000.0);
|
|
NSNumber *tg = [_smcClient readNumericValueForKey:tgKey error:nil] ?: ac;
|
|
|
|
NSString *fanName = [NSString stringWithFormat:@"Fan %ld", (long)(i + 1)];
|
|
NSData *idData = [_smcClient readBytesForKey:idKey error:nil];
|
|
if (idData.length > 0) {
|
|
NSString *raw = [[NSString alloc] initWithData:idData encoding:NSASCIIStringEncoding];
|
|
NSString *trimmed = [raw stringByTrimmingCharactersInSet:
|
|
[NSCharacterSet characterSetWithCharactersInString:@"\0 "]];
|
|
if (trimmed.length > 0) fanName = trimmed;
|
|
}
|
|
|
|
[rawList addObject:@{
|
|
@"name": fanName,
|
|
@"currentRPM": ac,
|
|
@"minRPM": mn,
|
|
@"maxRPM": mx,
|
|
@"targetRPM": tg
|
|
}];
|
|
}
|
|
|
|
return [MMFanTelemetryProvider calculateFanMetricsFromFanList:rawList];
|
|
}
|
|
|
|
@end
|