diff --git a/CHANGELOG.md b/CHANGELOG.md index b8c101da5d..2a580b14ce 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,7 @@ ### Fixes +- fix: Normalize profiling cpu usage to percent (#7798) - Detect development builds via provisioning profile and debugger attachment (#7702) ## 9.10.0 diff --git a/Sources/Sentry/SentrySystemWrapper.mm b/Sources/Sentry/SentrySystemWrapper.mm index 78bca77cdf..b9c0c5437a 100644 --- a/Sources/Sentry/SentrySystemWrapper.mm +++ b/Sources/Sentry/SentrySystemWrapper.mm @@ -6,6 +6,10 @@ # import # include +@interface SentrySystemWrapper () ++ (float)normalizeCPUUsage:(integer_t)threadCPUUsage processorCount:(long)processorCount; +@end + @implementation SentrySystemWrapper { float processorCount; } @@ -42,6 +46,11 @@ - (SentryRAMBytes)memoryFootprintBytes:(NSError *__autoreleasing _Nullable *)err return footprintBytes; } ++ (float)normalizeCPUUsage:(integer_t)threadCPUUsage processorCount:(long)processorCount +{ + return (static_cast(threadCPUUsage) / TH_USAGE_SCALE) * 100.f / processorCount; +} + - (NSNumber *)cpuUsageWithError:(NSError **)error { mach_msg_type_number_t count; @@ -76,7 +85,8 @@ - (NSNumber *)cpuUsageWithError:(NSError **)error return nil; } - usage += data.cpu_usage / processorCount; + usage += [SentrySystemWrapper normalizeCPUUsage:data.cpu_usage + processorCount:static_cast(processorCount)]; } vm_deallocate(mach_task_self(), reinterpret_cast(list), sizeof(*list) * count); diff --git a/Sources/Sentry/include/SentrySystemWrapper.h b/Sources/Sentry/include/SentrySystemWrapper.h index 90e2a96275..e6ae8a6b03 100644 --- a/Sources/Sentry/include/SentrySystemWrapper.h +++ b/Sources/Sentry/include/SentrySystemWrapper.h @@ -23,12 +23,19 @@ typedef mach_vm_size_t SentryRAMBytes; - (SentryRAMBytes)memoryFootprintBytes:(NSError **)error; /** - * @return The CPU usage per core, where the order of results corresponds to the core number as - * returned by the underlying system call, e.g. @c @[ @c , @c , - * @c ...] . + * @return The CPU usage of this process as a percentage of the device's total CPU capacity, + * normalized to a range from @c 0.0 to @c 100.0. */ - (nullable NSNumber *)cpuUsageWithError:(NSError **)error; +# if defined(SENTRY_TEST) || defined(SENTRY_TEST_CI) +/** + * Test-only helper that normalizes a thread CPU usage value returned by Mach to the + * process's percentage of the device's total CPU capacity. + */ ++ (float)normalizeCPUUsage:(integer_t)threadCPUUsage processorCount:(long)processorCount; +# endif + // Only some architectures support reading energy. # if defined(__arm__) || defined(__arm64__) /** diff --git a/Tests/SentryProfilerTests/SentrySystemWrapperTests.swift b/Tests/SentryProfilerTests/SentrySystemWrapperTests.swift index 1353dde711..ce39a40e1a 100644 --- a/Tests/SentryProfilerTests/SentrySystemWrapperTests.swift +++ b/Tests/SentryProfilerTests/SentrySystemWrapperTests.swift @@ -1,3 +1,4 @@ +import Foundation import XCTest #if os(iOS) || os(macOS) @@ -7,11 +8,16 @@ class SentrySystemWrapperTests: XCTestCase { } lazy private var fixture = Fixture() - func testCPUUsageReportsData() throws { - XCTAssertNoThrow({ - let cpuUsage = try XCTUnwrap(self.fixture.systemWrapper.cpuUsage()) - XCTAssertTrue((0.0 ... 100.0).contains(cpuUsage.doubleValue)) - }) + func testCPUUsage_whenIdle_shouldReportNormalizedPercent() throws { + let cpuUsage = try XCTUnwrap(fixture.systemWrapper.cpuUsage()) + + XCTAssertTrue((0.0 ... 100.0).contains(cpuUsage.doubleValue)) + } + + func testNormalizeCPUUsage_shouldConvertMachScaleToPercentOfTotalCapacity() { + XCTAssertEqual(SentrySystemWrapper.normalizeCPUUsage(1_000, processorCount: 4), 25.0, accuracy: 0.001) + XCTAssertEqual(SentrySystemWrapper.normalizeCPUUsage(500, processorCount: 8), 6.25, accuracy: 0.001) + XCTAssertEqual(SentrySystemWrapper.normalizeCPUUsage(1_000, processorCount: 10), 10.0, accuracy: 0.001) } func testMemoryFootprint() {