From 4beeeff531620774ce72d0df05a617b6d347898f Mon Sep 17 00:00:00 2001 From: Andrew McKnight Date: Wed, 16 Aug 2023 10:19:22 -0800 Subject: [PATCH] feat: measure cpu energy usage estimates (#3217) --- CHANGELOG.md | 5 +++ Sources/Sentry/SentryMetricProfiler.mm | 36 +++++++++++++++++++ Sources/Sentry/SentrySystemWrapper.mm | 18 ++++++++++ Sources/Sentry/include/SentryMetricProfiler.h | 1 + Sources/Sentry/include/SentrySystemWrapper.h | 6 ++++ 5 files changed, 66 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 61b7f79cc1d..d5ce568ccef 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,10 @@ # Changelog +## Unreleased + +### Features + +- Record energy usage estimates for profiling (#3217) ## 8.9.4 diff --git a/Sources/Sentry/SentryMetricProfiler.mm b/Sources/Sentry/SentryMetricProfiler.mm index 879776afe46..7f8d9eb35ad 100644 --- a/Sources/Sentry/SentryMetricProfiler.mm +++ b/Sources/Sentry/SentryMetricProfiler.mm @@ -29,6 +29,7 @@ @implementation SentryMetricReading NSString *const kSentryMetricProfilerSerializationKeyMemoryFootprint = @"memory_footprint"; NSString *const kSentryMetricProfilerSerializationKeyCPUUsageFormat = @"cpu_usage_%d"; +NSString *const kSentryMetricProfilerSerializationKeyCPUEnergyUsage = @"cpu_energy_usage"; NSString *const kSentryMetricProfilerSerializationUnitBytes = @"byte"; NSString *const kSentryMetricProfilerSerializationUnitPercentage = @"percent"; @@ -82,6 +83,9 @@ @implementation SentryMetricProfiler { NSMutableDictionary *> *_cpuUsage; NSMutableArray *_memoryFootprint; + + NSNumber *previousEnergyReading; + NSMutableArray *_cpuEnergyUsage; } - (instancetype)init @@ -98,6 +102,7 @@ - (instancetype)init } _memoryFootprint = [NSMutableArray array]; + _cpuEnergyUsage = [NSMutableArray array]; } return self; } @@ -123,8 +128,10 @@ - (void)stop and:(uint64_t)endSystemTime; { NSArray *memoryFootprint; + NSArray *cpuEnergyUsage; NSDictionary *> *cpuUsage; @synchronized(self) { + cpuEnergyUsage = [NSArray arrayWithArray:_cpuEnergyUsage]; memoryFootprint = [NSArray arrayWithArray:_memoryFootprint]; cpuUsage = [NSDictionary *> dictionaryWithDictionary:_cpuUsage]; @@ -136,6 +143,11 @@ - (void)stop = serializeValuesWithNormalizedTime(memoryFootprint, kSentryMetricProfilerSerializationUnitBytes, startSystemTime, endSystemTime); } + if (cpuEnergyUsage.count > 0) { + dict[kSentryMetricProfilerSerializationKeyCPUEnergyUsage] + = serializeValuesWithNormalizedTime(cpuEnergyUsage, + kSentryMetricProfilerSerializationUnitBytes, startSystemTime, endSystemTime); + } [cpuUsage enumerateKeysAndObjectsUsingBlock:^(NSNumber *_Nonnull core, NSArray *_Nonnull readings, BOOL *_Nonnull stop) { @@ -167,6 +179,7 @@ - (void)registerSampler eventHandler:^{ [weakSelf recordCPUPercentagePerCore]; [weakSelf recordMemoryFootprint]; + [weakSelf recordEnergyUsageEstimate]; }]; } @@ -205,6 +218,29 @@ - (void)recordCPUPercentagePerCore } } +- (void)recordEnergyUsageEstimate +{ + NSError *error; + const auto reading = + [SentryDependencyContainer.sharedInstance.systemWrapper cpuEnergyUsageWithError:&error]; + if (error) { + SENTRY_LOG_ERROR(@"Failed to read CPU energy usage: %@", error); + return; + } + + if (previousEnergyReading == nil) { + previousEnergyReading = reading; + return; + } + + const auto value = reading.unsignedIntegerValue - previousEnergyReading.unsignedIntegerValue; + previousEnergyReading = reading; + + @synchronized(self) { + [_cpuEnergyUsage addObject:[self metricReadingForValue:@(value)]]; + } +} + - (SentryMetricReading *)metricReadingForValue:(NSNumber *)value { const auto reading = [[SentryMetricReading alloc] init]; diff --git a/Sources/Sentry/SentrySystemWrapper.mm b/Sources/Sentry/SentrySystemWrapper.mm index 6e626700e8e..47264a7afdc 100644 --- a/Sources/Sentry/SentrySystemWrapper.mm +++ b/Sources/Sentry/SentrySystemWrapper.mm @@ -59,4 +59,22 @@ - (SentryRAMBytes)memoryFootprintBytes:(NSError *__autoreleasing _Nullable *)err return result; } +- (NSNumber *)cpuEnergyUsageWithError:(NSError **)error +{ + struct task_power_info_v2 powerInfo; + + mach_msg_type_number_t size = TASK_POWER_INFO_V2_COUNT; + + task_t task = mach_task_self(); + kern_return_t kr = task_info(task, TASK_POWER_INFO_V2, (task_info_t)&powerInfo, &size); + if (kr != KERN_SUCCESS) { + if (error) { + *error = NSErrorFromSentryErrorWithKernelError( + kSentryErrorKernel, @"Error with task_info(…TASK_POWER_INFO_V2…).", kr); + ; + } + } + return @(powerInfo.cpu_energy.total_system + powerInfo.cpu_energy.total_user); +} + @end diff --git a/Sources/Sentry/include/SentryMetricProfiler.h b/Sources/Sentry/include/SentryMetricProfiler.h index cdeedec7ea1..0182ddccd26 100644 --- a/Sources/Sentry/include/SentryMetricProfiler.h +++ b/Sources/Sentry/include/SentryMetricProfiler.h @@ -10,6 +10,7 @@ NS_ASSUME_NONNULL_BEGIN SENTRY_EXTERN NSString *const kSentryMetricProfilerSerializationKeyMemoryFootprint; SENTRY_EXTERN NSString *const kSentryMetricProfilerSerializationKeyCPUUsageFormat; +SENTRY_EXTERN NSString *const kSentryMetricProfilerSerializationKeyCPUEnergyUsage; SENTRY_EXTERN NSString *const kSentryMetricProfilerSerializationUnitBytes; SENTRY_EXTERN NSString *const kSentryMetricProfilerSerializationUnitPercentage; diff --git a/Sources/Sentry/include/SentrySystemWrapper.h b/Sources/Sentry/include/SentrySystemWrapper.h index 09d01a67d14..c98a26f30ae 100644 --- a/Sources/Sentry/include/SentrySystemWrapper.h +++ b/Sources/Sentry/include/SentrySystemWrapper.h @@ -26,6 +26,12 @@ typedef mach_vm_size_t SentryRAMBytes; */ - (nullable NSArray *)cpuUsagePerCore:(NSError **)error; +/** + * @return The cumulative amount of nanojoules expended by the CPU for this task since process + * start. + */ +- (nullable NSNumber *)cpuEnergyUsageWithError:(NSError **)error; + @end NS_ASSUME_NONNULL_END