185 lines
6.4 KiB
Objective-C
185 lines
6.4 KiB
Objective-C
#import "MMGPUTelemetryProvider.h"
|
|
#import <IOKit/IOKitLib.h>
|
|
#import <Metal/Metal.h>
|
|
|
|
@interface MMGPUTelemetryProvider () {
|
|
MMAppleSMCClient *_smcClient;
|
|
}
|
|
@end
|
|
|
|
@implementation MMGPUTelemetryProvider
|
|
|
|
- (instancetype)init {
|
|
return [self initWithSMCClient:[MMAppleSMCClient sharedClient]];
|
|
}
|
|
|
|
- (instancetype)initWithSMCClient:(MMAppleSMCClient *)client {
|
|
self = [super init];
|
|
if (self) {
|
|
_smcClient = client;
|
|
}
|
|
return self;
|
|
}
|
|
|
|
- (MMTelemetryDomain)domain {
|
|
return MMTelemetryDomainGPU;
|
|
}
|
|
|
|
- (NSString *)providerIdentifier {
|
|
return @"com.i3omb.macmonitor.telemetry.gpu";
|
|
}
|
|
|
|
- (BOOL)isAvailable {
|
|
return YES;
|
|
}
|
|
|
|
- (MMAppleSMCClient *)smcClient {
|
|
return _smcClient;
|
|
}
|
|
|
|
+ (NSDictionary<NSString *, id> *)calculateGPUMetricsWithDevices:(NSArray<NSDictionary<NSString *, id> *> *)gpuDevices {
|
|
double totalUtil = 0.0;
|
|
double peakUtil = 0.0;
|
|
NSString *activeGPU = @"None";
|
|
|
|
for (NSDictionary<NSString *, id> *gpu in gpuDevices) {
|
|
double util = [gpu[@"utilization"] doubleValue];
|
|
totalUtil += util;
|
|
if (util > peakUtil) {
|
|
peakUtil = util;
|
|
activeGPU = gpu[@"name"] ?: @"GPU";
|
|
}
|
|
}
|
|
|
|
double avgUtil = gpuDevices.count > 0 ? (totalUtil / (double)gpuDevices.count) : 0.0;
|
|
if ([activeGPU isEqualToString:@"None"] && gpuDevices.count > 0) {
|
|
activeGPU = gpuDevices[0][@"name"] ?: @"GPU";
|
|
}
|
|
|
|
return @{
|
|
@"gpuCount": @(gpuDevices.count),
|
|
@"averageUtilization": @(avgUtil),
|
|
@"peakUtilization": @(peakUtil),
|
|
@"activeGPUName": activeGPU,
|
|
@"devices": gpuDevices
|
|
};
|
|
}
|
|
|
|
- (nullable NSDictionary<NSString *, id> *)sampleTelemetryWithError:(NSError **)error {
|
|
// 1. Query IOKit IOAccelerator performance statistics
|
|
NSMutableDictionary<NSString *, NSDictionary *> *accelStatsByName = [NSMutableDictionary dictionary];
|
|
|
|
CFMutableDictionaryRef matching = IOServiceMatching("IOAccelerator");
|
|
io_iterator_t iterator = IO_OBJECT_NULL;
|
|
if (IOServiceGetMatchingServices(kIOMainPortDefault, matching, &iterator) == KERN_SUCCESS && iterator != IO_OBJECT_NULL) {
|
|
io_registry_entry_t entry;
|
|
while ((entry = IOIteratorNext(iterator)) != IO_OBJECT_NULL) {
|
|
CFMutableDictionaryRef props = NULL;
|
|
if (IORegistryEntryCreateCFProperties(entry, &props, kCFAllocatorDefault, 0) == KERN_SUCCESS && props) {
|
|
NSDictionary *dict = (__bridge NSDictionary *)props;
|
|
NSDictionary *stats = dict[@"PerformanceStatistics"];
|
|
io_name_t entryName;
|
|
if (IORegistryEntryGetName(entry, entryName) == KERN_SUCCESS && stats) {
|
|
accelStatsByName[[NSString stringWithUTF8String:entryName]] = stats;
|
|
}
|
|
CFRelease(props);
|
|
}
|
|
IOObjectRelease(entry);
|
|
}
|
|
IOObjectRelease(iterator);
|
|
}
|
|
|
|
// 2. Query SMC for GPU temperature fallback
|
|
NSNumber *smcGpuTemp = nil;
|
|
if (_smcClient.isAvailable) {
|
|
smcGpuTemp = [_smcClient readNumericValueForKey:@"TG0D" error:nil];
|
|
if (!smcGpuTemp) {
|
|
smcGpuTemp = [_smcClient readNumericValueForKey:@"TG0P" error:nil];
|
|
}
|
|
}
|
|
|
|
// 3. Enumerate Metal devices
|
|
NSArray<id<MTLDevice>> *metalDevices = MTLCopyAllDevices();
|
|
NSMutableArray<NSDictionary<NSString *, id> *> *deviceList = [NSMutableArray array];
|
|
|
|
for (id<MTLDevice> dev in metalDevices) {
|
|
NSString *name = dev.name;
|
|
BOOL isLowPower = dev.isLowPower;
|
|
BOOL isRemovable = dev.isRemovable;
|
|
uint64_t maxMem = dev.recommendedMaxWorkingSetSize;
|
|
|
|
// Find matching stats
|
|
double util = 0.0;
|
|
uint64_t vramUsed = 0;
|
|
uint64_t vramTotal = maxMem;
|
|
double temp = smcGpuTemp ? [smcGpuTemp doubleValue] : 0.0;
|
|
|
|
for (NSString *accelName in accelStatsByName) {
|
|
NSDictionary *stats = accelStatsByName[accelName];
|
|
|
|
// Check Intel stats
|
|
if ([name containsString:@"Intel"] || [accelName containsString:@"Intel"]) {
|
|
if (stats[@"Device Utilization %"]) {
|
|
util = [stats[@"Device Utilization %"] doubleValue];
|
|
}
|
|
if (stats[@"gartUsedBytes"]) {
|
|
vramUsed = [stats[@"gartUsedBytes"] unsignedLongLongValue];
|
|
}
|
|
if (stats[@"gartSizeBytes"]) {
|
|
vramTotal = [stats[@"gartSizeBytes"] unsignedLongLongValue];
|
|
}
|
|
} else {
|
|
// AMD / Discrete stats
|
|
if (stats[@"GPU Activity(%)"]) {
|
|
util = [stats[@"GPU Activity(%)"] doubleValue];
|
|
} else if (stats[@"Device Utilization %"]) {
|
|
util = [stats[@"Device Utilization %"] doubleValue];
|
|
}
|
|
if (stats[@"vramUsedBytes"]) {
|
|
vramUsed = [stats[@"vramUsedBytes"] unsignedLongLongValue];
|
|
}
|
|
if (stats[@"vramFreeBytes"]) {
|
|
uint64_t freeBytes = [stats[@"vramFreeBytes"] unsignedLongLongValue];
|
|
vramTotal = vramUsed + freeBytes;
|
|
}
|
|
if (stats[@"Temperature(C)"]) {
|
|
temp = [stats[@"Temperature(C)"] doubleValue];
|
|
}
|
|
}
|
|
}
|
|
|
|
NSString *gpuType = isLowPower || [name containsString:@"Intel"] ? @"Integrated" : @"Discrete";
|
|
|
|
[deviceList addObject:@{
|
|
@"name": name,
|
|
@"type": gpuType,
|
|
@"utilization": @(util),
|
|
@"vramUsedBytes": @(vramUsed),
|
|
@"vramTotalBytes": @(vramTotal),
|
|
@"temperature": @(temp),
|
|
@"isLowPower": @(isLowPower),
|
|
@"isRemovable": @(isRemovable),
|
|
@"recommendedMaxWorkingSetBytes": @(maxMem)
|
|
}];
|
|
}
|
|
|
|
// Fallback if no Metal devices enumerated (headless / virtualization)
|
|
if (deviceList.count == 0) {
|
|
[deviceList addObject:@{
|
|
@"name": @"Default GPU",
|
|
@"type": @"Integrated",
|
|
@"utilization": @(0.0),
|
|
@"vramUsedBytes": @(0),
|
|
@"vramTotalBytes": @(1024 * 1024 * 1024),
|
|
@"temperature": @(0.0),
|
|
@"isLowPower": @(YES),
|
|
@"isRemovable": @(NO),
|
|
@"recommendedMaxWorkingSetBytes": @(1024 * 1024 * 1024)
|
|
}];
|
|
}
|
|
|
|
return [MMGPUTelemetryProvider calculateGPUMetricsWithDevices:deviceList];
|
|
}
|
|
|
|
@end
|