Compare commits
2
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
91d50dcbae | ||
|
|
36c7ea2319 |
@@ -18,5 +18,6 @@
|
||||
#import "MMCPUThermalProvider.h"
|
||||
#import "MMFanTelemetryProvider.h"
|
||||
#import "MMComponentThermalProvider.h"
|
||||
#import "MMPowerTelemetryProvider.h"
|
||||
|
||||
#endif /* MacMonitor_Bridging_Header_h */
|
||||
|
||||
@@ -40,6 +40,13 @@
|
||||
(uint32_t)bytes[3];
|
||||
}
|
||||
|
||||
static int hexDigitValue(unichar c) {
|
||||
if (c >= '0' && c <= '9') return c - '0';
|
||||
if (c >= 'a' && c <= 'f') return c - 'a' + 10;
|
||||
if (c >= 'A' && c <= 'F') return c - 'A' + 10;
|
||||
return -1;
|
||||
}
|
||||
|
||||
+ (nullable NSNumber *)decodeValueWithDataType:(NSString *)dataType bytes:(const uint8_t *)bytes size:(NSUInteger)size {
|
||||
if (!bytes || size == 0) return nil;
|
||||
|
||||
@@ -49,7 +56,7 @@
|
||||
if (size >= 2) return @([self decodeSP78:bytes]);
|
||||
} else if ([trimmedType isEqualToString:@"fpe2"]) {
|
||||
if (size >= 2) return @([self decodeFPE2:bytes]);
|
||||
} else if ([trimmedType isEqualToString:@"flt"]) {
|
||||
} else if ([trimmedType isEqualToString:@"flt"] || [trimmedType isEqualToString:@"ioft"]) {
|
||||
if (size >= 4) return @([self decodeFLT:bytes]);
|
||||
} else if ([trimmedType isEqualToString:@"ui8"]) {
|
||||
if (size >= 1) return @([self decodeUI8:bytes]);
|
||||
@@ -59,6 +66,18 @@
|
||||
if (size >= 4) return @([self decodeUI32:bytes]);
|
||||
} else if ([trimmedType isEqualToString:@"flag"]) {
|
||||
if (size >= 1) return @(bytes[0] != 0);
|
||||
} else if (trimmedType.length == 4 && [trimmedType hasPrefix:@"sp"] && size >= 2) {
|
||||
int fracBits = hexDigitValue([trimmedType characterAtIndex:3]);
|
||||
if (fracBits >= 0 && fracBits <= 15) {
|
||||
int16_t raw = (int16_t)(((uint16_t)bytes[0] << 8) | (uint16_t)bytes[1]);
|
||||
return @((float)raw / (float)(1 << fracBits));
|
||||
}
|
||||
} else if (trimmedType.length == 4 && [trimmedType hasPrefix:@"fp"] && size >= 2) {
|
||||
int fracBits = hexDigitValue([trimmedType characterAtIndex:3]);
|
||||
if (fracBits >= 0 && fracBits <= 15) {
|
||||
uint16_t raw = ((uint16_t)bytes[0] << 8) | (uint16_t)bytes[1];
|
||||
return @((float)raw / (float)(1 << fracBits));
|
||||
}
|
||||
}
|
||||
|
||||
return nil;
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
#ifndef MMPowerTelemetryProvider_h
|
||||
#define MMPowerTelemetryProvider_h
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
#import "MMTelemetryProvider.h"
|
||||
#import "MMAppleSMCClient.h"
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
/**
|
||||
* MMPowerTelemetryProvider
|
||||
* Samples system power draw, CPU/GPU package wattage, rail voltages, and currents
|
||||
* via AppleSMC and AppleSmartBattery IOKit interfaces.
|
||||
*/
|
||||
@interface MMPowerTelemetryProvider : NSObject <MMTelemetryProvider>
|
||||
|
||||
@property (nonatomic, strong, readonly) MMAppleSMCClient *smcClient;
|
||||
@property (nonatomic, readonly) NSArray<NSString *> *discoveredPowerKeys;
|
||||
@property (nonatomic, readonly) NSArray<NSString *> *discoveredVoltageKeys;
|
||||
@property (nonatomic, readonly) NSArray<NSString *> *discoveredCurrentKeys;
|
||||
|
||||
- (instancetype)init;
|
||||
- (instancetype)initWithSMCClient:(MMAppleSMCClient *)client;
|
||||
|
||||
/**
|
||||
* Friendly name for power, voltage, and current SMC keys.
|
||||
*/
|
||||
+ (NSString *)humanReadableNameForKey:(NSString *)key;
|
||||
|
||||
/**
|
||||
* Unit of measurement for SMC key ("W", "V", "A").
|
||||
*/
|
||||
+ (NSString *)unitForKey:(NSString *)key;
|
||||
|
||||
/**
|
||||
* Pure calculator method for deterministic unit testing.
|
||||
*/
|
||||
+ (NSDictionary<NSString *, id> *)calculatePowerMetricsWithReadings:(NSDictionary<NSString *, NSNumber *> *)readings
|
||||
batteryInfo:(nullable NSDictionary<NSString *, id> *)batteryInfo;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
|
||||
#endif /* MMPowerTelemetryProvider_h */
|
||||
@@ -0,0 +1,314 @@
|
||||
#import "MMPowerTelemetryProvider.h"
|
||||
#import <IOKit/IOKitLib.h>
|
||||
#import <IOKit/ps/IOPowerSources.h>
|
||||
#import <IOKit/ps/IOPSKeys.h>
|
||||
|
||||
@interface MMPowerTelemetryProvider () {
|
||||
MMAppleSMCClient *_smcClient;
|
||||
NSMutableArray<NSString *> *_discoveredPowerKeys;
|
||||
NSMutableArray<NSString *> *_discoveredVoltageKeys;
|
||||
NSMutableArray<NSString *> *_discoveredCurrentKeys;
|
||||
BOOL _probed;
|
||||
}
|
||||
@end
|
||||
|
||||
@implementation MMPowerTelemetryProvider
|
||||
|
||||
- (instancetype)init {
|
||||
return [self initWithSMCClient:[MMAppleSMCClient sharedClient]];
|
||||
}
|
||||
|
||||
- (instancetype)initWithSMCClient:(MMAppleSMCClient *)client {
|
||||
self = [super init];
|
||||
if (self) {
|
||||
_smcClient = client;
|
||||
_discoveredPowerKeys = [NSMutableArray array];
|
||||
_discoveredVoltageKeys = [NSMutableArray array];
|
||||
_discoveredCurrentKeys = [NSMutableArray array];
|
||||
[self probeSensors];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)probeSensors {
|
||||
if (!_smcClient.isAvailable) return;
|
||||
|
||||
NSArray<NSString *> *powerCandidates = @[
|
||||
@"PSTR", @"PDTR", @"PCPR", @"PCTR", @"PC0C", @"PCPT",
|
||||
@"PG0R", @"PGTR", @"PMSR", @"PM0R", @"PCPC"
|
||||
];
|
||||
|
||||
NSArray<NSString *> *voltageCandidates = @[
|
||||
@"VC0C", @"VD0R", @"VN0C", @"VM0R", @"VP0R"
|
||||
];
|
||||
|
||||
NSArray<NSString *> *currentCandidates = @[
|
||||
@"IC0C", @"ID0R", @"IG0C", @"IM0R"
|
||||
];
|
||||
|
||||
[_discoveredPowerKeys removeAllObjects];
|
||||
for (NSString *key in powerCandidates) {
|
||||
NSNumber *val = [_smcClient readNumericValueForKey:key error:nil];
|
||||
if (val && [val doubleValue] > 0.0 && [val doubleValue] < 5000.0) {
|
||||
[_discoveredPowerKeys addObject:key];
|
||||
}
|
||||
}
|
||||
|
||||
[_discoveredVoltageKeys removeAllObjects];
|
||||
for (NSString *key in voltageCandidates) {
|
||||
NSNumber *val = [_smcClient readNumericValueForKey:key error:nil];
|
||||
if (val && [val doubleValue] > 0.0) {
|
||||
[_discoveredVoltageKeys addObject:key];
|
||||
}
|
||||
}
|
||||
|
||||
[_discoveredCurrentKeys removeAllObjects];
|
||||
for (NSString *key in currentCandidates) {
|
||||
NSNumber *val = [_smcClient readNumericValueForKey:key error:nil];
|
||||
if (val && [val doubleValue] > 0.0) {
|
||||
[_discoveredCurrentKeys addObject:key];
|
||||
}
|
||||
}
|
||||
|
||||
_probed = YES;
|
||||
}
|
||||
|
||||
- (MMTelemetryDomain)domain {
|
||||
return MMTelemetryDomainPower;
|
||||
}
|
||||
|
||||
- (NSString *)providerIdentifier {
|
||||
return @"com.i3omb.macmonitor.telemetry.power";
|
||||
}
|
||||
|
||||
- (BOOL)isAvailable {
|
||||
return _smcClient.isAvailable;
|
||||
}
|
||||
|
||||
- (MMAppleSMCClient *)smcClient {
|
||||
return _smcClient;
|
||||
}
|
||||
|
||||
- (NSArray<NSString *> *)discoveredPowerKeys {
|
||||
return [_discoveredPowerKeys copy];
|
||||
}
|
||||
|
||||
- (NSArray<NSString *> *)discoveredVoltageKeys {
|
||||
return [_discoveredVoltageKeys copy];
|
||||
}
|
||||
|
||||
- (NSArray<NSString *> *)discoveredCurrentKeys {
|
||||
return [_discoveredCurrentKeys copy];
|
||||
}
|
||||
|
||||
+ (NSString *)unitForKey:(NSString *)key {
|
||||
if ([key hasPrefix:@"P"]) return @"W";
|
||||
if ([key hasPrefix:@"V"]) return @"V";
|
||||
if ([key hasPrefix:@"I"]) return @"A";
|
||||
return @"";
|
||||
}
|
||||
|
||||
+ (NSString *)humanReadableNameForKey:(NSString *)key {
|
||||
static NSDictionary<NSString *, NSString *> *names = nil;
|
||||
static dispatch_once_t onceToken;
|
||||
dispatch_once(&onceToken, ^{
|
||||
names = @{
|
||||
@"PSTR": @"Total System Power",
|
||||
@"PDTR": @"DC-In Total Power",
|
||||
@"PCPR": @"CPU Package Power",
|
||||
@"PCTR": @"CPU Total Power",
|
||||
@"PC0C": @"CPU Core Power",
|
||||
@"PCPT": @"CPU Package Total Power",
|
||||
@"PG0R": @"GPU 0 Power",
|
||||
@"PGTR": @"GPU Total Power",
|
||||
@"PMSR": @"Memory Subsystem Power",
|
||||
@"PM0R": @"Memory Rail Power",
|
||||
@"PCPC": @"Power Supply In / AC",
|
||||
@"VC0C": @"CPU Core Voltage",
|
||||
@"VD0R": @"DC-In Rail Voltage",
|
||||
@"VN0C": @"PCH Rail Voltage",
|
||||
@"VM0R": @"Memory Rail Voltage",
|
||||
@"VP0R": @"Power Supply Voltage",
|
||||
@"IC0C": @"CPU Core Current",
|
||||
@"ID0R": @"DC-In Rail Current",
|
||||
@"IG0C": @"GPU Rail Current",
|
||||
@"IM0R": @"Memory Rail Current"
|
||||
};
|
||||
});
|
||||
|
||||
NSString *match = names[key];
|
||||
return match ?: key;
|
||||
}
|
||||
|
||||
+ (NSDictionary<NSString *, id> *)calculatePowerMetricsWithReadings:(NSDictionary<NSString *, NSNumber *> *)readings
|
||||
batteryInfo:(nullable NSDictionary<NSString *, id> *)batteryInfo {
|
||||
// Determine system total power
|
||||
double systemTotal = 0.0;
|
||||
if (readings[@"PSTR"]) {
|
||||
systemTotal = [readings[@"PSTR"] doubleValue];
|
||||
} else if (readings[@"PDTR"]) {
|
||||
systemTotal = [readings[@"PDTR"] doubleValue];
|
||||
} else if (batteryInfo[@"batteryWatts"]) {
|
||||
systemTotal = [batteryInfo[@"batteryWatts"] doubleValue];
|
||||
}
|
||||
|
||||
// CPU wattage
|
||||
double cpuWatts = 0.0;
|
||||
if (readings[@"PCPR"]) {
|
||||
cpuWatts = [readings[@"PCPR"] doubleValue];
|
||||
} else if (readings[@"PCTR"]) {
|
||||
cpuWatts = [readings[@"PCTR"] doubleValue];
|
||||
} else if (readings[@"PC0C"]) {
|
||||
cpuWatts = [readings[@"PC0C"] doubleValue];
|
||||
} else if (readings[@"PCPT"]) {
|
||||
cpuWatts = [readings[@"PCPT"] doubleValue];
|
||||
}
|
||||
|
||||
// Voltage & Current
|
||||
double cpuVoltage = readings[@"VC0C"] ? [readings[@"VC0C"] doubleValue] : 0.0;
|
||||
double cpuCurrent = readings[@"IC0C"] ? [readings[@"IC0C"] doubleValue] : 0.0;
|
||||
|
||||
// Fallback: calculate cpuWatts from V * I if SMC didn't provide direct wattage
|
||||
if (cpuWatts == 0.0 && cpuVoltage > 0.0 && cpuCurrent > 0.0) {
|
||||
cpuWatts = cpuVoltage * cpuCurrent;
|
||||
}
|
||||
|
||||
// GPU wattage
|
||||
double gpuWatts = 0.0;
|
||||
if (readings[@"PG0R"]) {
|
||||
gpuWatts = [readings[@"PG0R"] doubleValue];
|
||||
} else if (readings[@"PGTR"]) {
|
||||
gpuWatts = [readings[@"PGTR"] doubleValue];
|
||||
}
|
||||
|
||||
// Memory wattage
|
||||
double memoryWatts = 0.0;
|
||||
if (readings[@"PMSR"]) {
|
||||
memoryWatts = [readings[@"PMSR"] doubleValue];
|
||||
} else if (readings[@"PM0R"]) {
|
||||
memoryWatts = [readings[@"PM0R"] doubleValue];
|
||||
}
|
||||
|
||||
// Collect all individual sensor entries
|
||||
NSMutableArray<NSDictionary<NSString *, id> *> *sensors = [NSMutableArray arrayWithCapacity:readings.count];
|
||||
NSArray<NSString *> *sortedKeys = [readings.allKeys sortedArrayUsingSelector:@selector(compare:)];
|
||||
for (NSString *k in sortedKeys) {
|
||||
double val = [readings[k] doubleValue];
|
||||
[sensors addObject:@{
|
||||
@"key": k,
|
||||
@"name": [self humanReadableNameForKey:k],
|
||||
@"value": @(val),
|
||||
@"unit": [self unitForKey:k]
|
||||
}];
|
||||
}
|
||||
|
||||
NSString *powerSource = batteryInfo[@"powerSource"] ?: @"AC Power";
|
||||
BOOL isCharging = [batteryInfo[@"isCharging"] boolValue];
|
||||
double batteryLevel = [batteryInfo[@"batteryLevel"] doubleValue];
|
||||
|
||||
return @{
|
||||
@"systemTotalWatts": @(systemTotal),
|
||||
@"cpuWatts": @(cpuWatts),
|
||||
@"gpuWatts": @(gpuWatts),
|
||||
@"memoryWatts": @(memoryWatts),
|
||||
@"cpuVoltage": @(cpuVoltage),
|
||||
@"cpuCurrent": @(cpuCurrent),
|
||||
@"powerSource": powerSource,
|
||||
@"isCharging": @(isCharging),
|
||||
@"batteryLevel": @(batteryLevel),
|
||||
@"sensors": sensors
|
||||
};
|
||||
}
|
||||
|
||||
- (nullable NSDictionary<NSString *, id> *)sampleBatteryInfo {
|
||||
CFTypeRef psInfo = IOPSCopyPowerSourcesInfo();
|
||||
if (!psInfo) return nil;
|
||||
|
||||
CFArrayRef psList = IOPSCopyPowerSourcesList(psInfo);
|
||||
if (!psList) {
|
||||
CFRelease(psInfo);
|
||||
return nil;
|
||||
}
|
||||
|
||||
NSString *stateStr = @"AC Power";
|
||||
BOOL isCharging = NO;
|
||||
double capacity = 100.0;
|
||||
|
||||
if (CFArrayGetCount(psList) > 0) {
|
||||
CFTypeRef ps = CFArrayGetValueAtIndex(psList, 0);
|
||||
CFDictionaryRef desc = IOPSGetPowerSourceDescription(psInfo, ps);
|
||||
if (desc) {
|
||||
NSDictionary *dict = (__bridge NSDictionary *)desc;
|
||||
stateStr = dict[@"Power Source State"] ?: @"AC Power";
|
||||
isCharging = [dict[@"Is Charging"] boolValue];
|
||||
if (dict[@"Current Capacity"]) {
|
||||
capacity = [dict[@"Current Capacity"] doubleValue];
|
||||
}
|
||||
}
|
||||
}
|
||||
CFRelease(psList);
|
||||
CFRelease(psInfo);
|
||||
|
||||
// Sample AppleSmartBattery for instantaneous voltage and current
|
||||
double batteryWatts = 0.0;
|
||||
double batteryVoltage = 0.0;
|
||||
double batteryAmperage = 0.0;
|
||||
|
||||
io_service_t service = IOServiceGetMatchingService(kIOMainPortDefault, IOServiceMatching("AppleSmartBattery"));
|
||||
if (service != IO_OBJECT_NULL) {
|
||||
CFMutableDictionaryRef props = NULL;
|
||||
if (IORegistryEntryCreateCFProperties(service, &props, kCFAllocatorDefault, 0) == KERN_SUCCESS && props) {
|
||||
NSDictionary *dict = (__bridge NSDictionary *)props;
|
||||
double v_mV = [dict[@"Voltage"] doubleValue];
|
||||
double a_mA = dict[@"InstantAmperage"] ? [dict[@"InstantAmperage"] doubleValue] : [dict[@"Amperage"] doubleValue];
|
||||
|
||||
batteryVoltage = v_mV / 1000.0;
|
||||
batteryAmperage = fabs(a_mA) / 1000.0;
|
||||
batteryWatts = batteryVoltage * batteryAmperage;
|
||||
|
||||
CFRelease(props);
|
||||
}
|
||||
IOObjectRelease(service);
|
||||
}
|
||||
|
||||
return @{
|
||||
@"powerSource": stateStr,
|
||||
@"isCharging": @(isCharging),
|
||||
@"batteryLevel": @(capacity),
|
||||
@"batteryVoltage": @(batteryVoltage),
|
||||
@"batteryAmperage": @(batteryAmperage),
|
||||
@"batteryWatts": @(batteryWatts)
|
||||
};
|
||||
}
|
||||
|
||||
- (nullable NSDictionary<NSString *, id> *)sampleTelemetryWithError:(NSError **)error {
|
||||
if (!_probed && _smcClient.isAvailable) {
|
||||
[self probeSensors];
|
||||
}
|
||||
|
||||
NSMutableDictionary<NSString *, NSNumber *> *readings = [NSMutableDictionary dictionary];
|
||||
|
||||
NSMutableArray<NSString *> *allKeys = [NSMutableArray arrayWithArray:_discoveredPowerKeys];
|
||||
[allKeys addObjectsFromArray:_discoveredVoltageKeys];
|
||||
[allKeys addObjectsFromArray:_discoveredCurrentKeys];
|
||||
|
||||
for (NSString *key in allKeys) {
|
||||
NSNumber *val = [_smcClient readNumericValueForKey:key error:nil];
|
||||
if (val) {
|
||||
double v = [val doubleValue];
|
||||
// Normalize mV or mA if values appear raw
|
||||
if ([key hasPrefix:@"V"] && v > 100.0) {
|
||||
v /= 1000.0;
|
||||
} else if ([key hasPrefix:@"I"] && v > 500.0) {
|
||||
v /= 1000.0;
|
||||
}
|
||||
readings[key] = @(v);
|
||||
}
|
||||
}
|
||||
|
||||
NSDictionary<NSString *, id> *batteryInfo = [self sampleBatteryInfo];
|
||||
return [MMPowerTelemetryProvider calculatePowerMetricsWithReadings:readings
|
||||
batteryInfo:batteryInfo];
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,101 @@
|
||||
import XCTest
|
||||
@testable import MacMonitor
|
||||
|
||||
final class MMPowerTests: XCTestCase {
|
||||
|
||||
func testUnitsAndNames() {
|
||||
XCTAssertEqual(MMPowerTelemetryProvider.unit(forKey: "PSTR"), "W")
|
||||
XCTAssertEqual(MMPowerTelemetryProvider.unit(forKey: "VC0C"), "V")
|
||||
XCTAssertEqual(MMPowerTelemetryProvider.unit(forKey: "IC0C"), "A")
|
||||
XCTAssertEqual(MMPowerTelemetryProvider.unit(forKey: "UNKNOWN"), "")
|
||||
|
||||
XCTAssertEqual(MMPowerTelemetryProvider.humanReadableName(forKey: "PSTR"), "Total System Power")
|
||||
XCTAssertEqual(MMPowerTelemetryProvider.humanReadableName(forKey: "VC0C"), "CPU Core Voltage")
|
||||
XCTAssertEqual(MMPowerTelemetryProvider.humanReadableName(forKey: "IC0C"), "CPU Core Current")
|
||||
}
|
||||
|
||||
func testPowerMetricsCalculationFromSMC() {
|
||||
let readings: [String: NSNumber] = [
|
||||
"PSTR": NSNumber(value: 45.2),
|
||||
"PCPR": NSNumber(value: 28.4),
|
||||
"PG0R": NSNumber(value: 8.5),
|
||||
"PMSR": NSNumber(value: 3.1),
|
||||
"VC0C": NSNumber(value: 1.15),
|
||||
"IC0C": NSNumber(value: 24.5)
|
||||
]
|
||||
|
||||
let batteryInfo: [String: Any] = [
|
||||
"powerSource": "AC Power",
|
||||
"isCharging": false,
|
||||
"batteryLevel": 100.0,
|
||||
"batteryWatts": 0.0
|
||||
]
|
||||
|
||||
let metrics = MMPowerTelemetryProvider.calculatePowerMetrics(
|
||||
withReadings: readings,
|
||||
batteryInfo: batteryInfo
|
||||
)
|
||||
|
||||
let systemWatts = metrics["systemTotalWatts"] as? Double ?? 0
|
||||
let cpuWatts = metrics["cpuWatts"] as? Double ?? 0
|
||||
let gpuWatts = metrics["gpuWatts"] as? Double ?? 0
|
||||
let memWatts = metrics["memoryWatts"] as? Double ?? 0
|
||||
let cpuV = metrics["cpuVoltage"] as? Double ?? 0
|
||||
let cpuI = metrics["cpuCurrent"] as? Double ?? 0
|
||||
let powerSource = metrics["powerSource"] as? String ?? ""
|
||||
let sensors = metrics["sensors"] as? [[String: Any]] ?? []
|
||||
|
||||
XCTAssertEqual(systemWatts, 45.2, accuracy: 0.1)
|
||||
XCTAssertEqual(cpuWatts, 28.4, accuracy: 0.1)
|
||||
XCTAssertEqual(gpuWatts, 8.5, accuracy: 0.1)
|
||||
XCTAssertEqual(memWatts, 3.1, accuracy: 0.1)
|
||||
XCTAssertEqual(cpuV, 1.15, accuracy: 0.01)
|
||||
XCTAssertEqual(cpuI, 24.5, accuracy: 0.1)
|
||||
XCTAssertEqual(powerSource, "AC Power")
|
||||
XCTAssertEqual(sensors.count, 6)
|
||||
}
|
||||
|
||||
func testBatteryFallbackCalculation() {
|
||||
// No PSTR, no PCPR, but VC0C and IC0C are present
|
||||
let readings: [String: NSNumber] = [
|
||||
"VC0C": NSNumber(value: 1.2),
|
||||
"IC0C": NSNumber(value: 10.0)
|
||||
]
|
||||
|
||||
let batteryInfo: [String: Any] = [
|
||||
"powerSource": "Battery Power",
|
||||
"isCharging": false,
|
||||
"batteryLevel": 75.0,
|
||||
"batteryWatts": 18.5
|
||||
]
|
||||
|
||||
let metrics = MMPowerTelemetryProvider.calculatePowerMetrics(
|
||||
withReadings: readings,
|
||||
batteryInfo: batteryInfo
|
||||
)
|
||||
|
||||
let systemWatts = metrics["systemTotalWatts"] as? Double ?? 0
|
||||
let cpuWatts = metrics["cpuWatts"] as? Double ?? 0
|
||||
let powerSource = metrics["powerSource"] as? String ?? ""
|
||||
let isCharging = metrics["isCharging"] as? Bool ?? true
|
||||
|
||||
// System watts falls back to batteryWatts (18.5)
|
||||
XCTAssertEqual(systemWatts, 18.5, accuracy: 0.1)
|
||||
// CPU watts calculated from V * I = 1.2 * 10.0 = 12.0 W
|
||||
XCTAssertEqual(cpuWatts, 12.0, accuracy: 0.1)
|
||||
XCTAssertEqual(powerSource, "Battery Power")
|
||||
XCTAssertFalse(isCharging)
|
||||
}
|
||||
|
||||
func testLivePowerProvider() throws {
|
||||
let provider = MMPowerTelemetryProvider()
|
||||
XCTAssertEqual(provider.domain, .power)
|
||||
XCTAssertEqual(provider.providerIdentifier, "com.i3omb.macmonitor.telemetry.power")
|
||||
|
||||
let sample = try provider.sampleTelemetry()
|
||||
XCTAssertNotNil(sample)
|
||||
XCTAssertNotNil(sample["systemTotalWatts"])
|
||||
XCTAssertNotNil(sample["powerSource"])
|
||||
XCTAssertNotNil(sample["sensors"])
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user