From bd8f15845fba804448ef6398e72d5fece068df87 Mon Sep 17 00:00:00 2001 From: Andrew McKnight Date: Mon, 5 Dec 2022 15:51:51 -0900 Subject: [PATCH 01/70] feat: monitor for new metric values via sampling and notifications --- Sentry.xcodeproj/project.pbxproj | 8 ++ Sources/Sentry/SentryMetricProfiler.h | 24 ++++ Sources/Sentry/SentryMetricProfiler.mm | 156 +++++++++++++++++++++++++ 3 files changed, 188 insertions(+) create mode 100644 Sources/Sentry/SentryMetricProfiler.h create mode 100644 Sources/Sentry/SentryMetricProfiler.mm diff --git a/Sentry.xcodeproj/project.pbxproj b/Sentry.xcodeproj/project.pbxproj index 1bb1b09f0e5..89e271cfcd2 100644 --- a/Sentry.xcodeproj/project.pbxproj +++ b/Sentry.xcodeproj/project.pbxproj @@ -601,6 +601,8 @@ 8419C0C428C1889D001C8259 /* SentryProfilerSwiftTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8419C0C328C1889D001C8259 /* SentryProfilerSwiftTests.swift */; }; 8453421228BE855D00C22EEC /* SentrySampleDecision.m in Sources */ = {isa = PBXBuildFile; fileRef = 8453421128BE855D00C22EEC /* SentrySampleDecision.m */; }; 8453421628BE8A9500C22EEC /* SentrySpanStatus.m in Sources */ = {isa = PBXBuildFile; fileRef = 8453421528BE8A9500C22EEC /* SentrySpanStatus.m */; }; + 8454CF8C293EAF9A006AC140 /* SentryMetricProfiler.h in Headers */ = {isa = PBXBuildFile; fileRef = 8454CF8A293EAF9A006AC140 /* SentryMetricProfiler.h */; }; + 8454CF8D293EAF9A006AC140 /* SentryMetricProfiler.mm in Sources */ = {isa = PBXBuildFile; fileRef = 8454CF8B293EAF9A006AC140 /* SentryMetricProfiler.mm */; }; 84A888FD28D9B11700C51DFD /* SentryProfiler+Test.h in Headers */ = {isa = PBXBuildFile; fileRef = 84A888FC28D9B11700C51DFD /* SentryProfiler+Test.h */; }; 84A8891C28DBD28900C51DFD /* SentryDevice.h in Headers */ = {isa = PBXBuildFile; fileRef = 84A8891A28DBD28900C51DFD /* SentryDevice.h */; }; 84A8891D28DBD28900C51DFD /* SentryDevice.mm in Sources */ = {isa = PBXBuildFile; fileRef = 84A8891B28DBD28900C51DFD /* SentryDevice.mm */; }; @@ -1412,6 +1414,8 @@ 844DA81F28246DE300E6B62E /* scripts */ = {isa = PBXFileReference; lastKnownFileType = folder; path = scripts; sourceTree = ""; }; 8453421128BE855D00C22EEC /* SentrySampleDecision.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SentrySampleDecision.m; sourceTree = ""; }; 8453421528BE8A9500C22EEC /* SentrySpanStatus.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SentrySpanStatus.m; sourceTree = ""; }; + 8454CF8A293EAF9A006AC140 /* SentryMetricProfiler.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = SentryMetricProfiler.h; path = Sources/Sentry/SentryMetricProfiler.h; sourceTree = SOURCE_ROOT; }; + 8454CF8B293EAF9A006AC140 /* SentryMetricProfiler.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; name = SentryMetricProfiler.mm; path = Sources/Sentry/SentryMetricProfiler.mm; sourceTree = SOURCE_ROOT; }; 84A888FC28D9B11700C51DFD /* SentryProfiler+Test.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = "SentryProfiler+Test.h"; path = "Sources/Sentry/include/SentryProfiler+Test.h"; sourceTree = SOURCE_ROOT; }; 84A8891A28DBD28900C51DFD /* SentryDevice.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = SentryDevice.h; path = include/SentryDevice.h; sourceTree = ""; }; 84A8891B28DBD28900C51DFD /* SentryDevice.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = SentryDevice.mm; sourceTree = ""; }; @@ -2803,6 +2807,8 @@ 03F84D1B27DD414C008FE43F /* SentryMachLogging.hpp */, 03F84D2C27DD4191008FE43F /* SentryMachLogging.cpp */, 03F84D1127DD414C008FE43F /* SentryProfiler.h */, + 8454CF8A293EAF9A006AC140 /* SentryMetricProfiler.h */, + 8454CF8B293EAF9A006AC140 /* SentryMetricProfiler.mm */, 84A888FC28D9B11700C51DFD /* SentryProfiler+Test.h */, 03F84D2B27DD4191008FE43F /* SentryProfiler.mm */, 03BCC38D27E2A377003232C7 /* SentryProfilingConditionals.h */, @@ -3098,6 +3104,7 @@ 63FE711F20DA4C1000CDBAE8 /* SentryCrashObjC.h in Headers */, 7BC3936825B1AB3E004F03D3 /* SentryLevelMapper.h in Headers */, 8E4E7C6E25DAAAFE006AB9E2 /* SentrySpan.h in Headers */, + 8454CF8C293EAF9A006AC140 /* SentryMetricProfiler.h in Headers */, D8ACE3CE2762187D00F5A213 /* SentryNSDataTracker.h in Headers */, 03F84D2427DD414C008FE43F /* SentryCompiler.h in Headers */, 631E6D331EBC679C00712345 /* SentryQueueableRequestManager.h in Headers */, @@ -3563,6 +3570,7 @@ 7BD729982463E93500EA3610 /* SentryDateUtil.m in Sources */, 639FCF9D1EBC7F9500778193 /* SentryThread.m in Sources */, 8E8C57A225EEFC07001CEEFA /* SentryTracesSampler.m in Sources */, + 8454CF8D293EAF9A006AC140 /* SentryMetricProfiler.mm in Sources */, 63FE714120DA4C1100CDBAE8 /* SentryCrashDate.c in Sources */, 63FE70DB20DA4C1000CDBAE8 /* SentryCrashMonitor_System.m in Sources */, 7BA61CBB247BC5D800C130A8 /* SentryCrashDefaultBinaryImageProvider.m in Sources */, diff --git a/Sources/Sentry/SentryMetricProfiler.h b/Sources/Sentry/SentryMetricProfiler.h new file mode 100644 index 00000000000..2f538d2443e --- /dev/null +++ b/Sources/Sentry/SentryMetricProfiler.h @@ -0,0 +1,24 @@ +#import + +@class SentryNSNotificationCenterWrapper; + +NS_ASSUME_NONNULL_BEGIN + +/** + * A profiler that gathers various time-series and event-based metrics on the app process, such as + * CPU and memory usage timeseries and thermal and memory pressure warning notifications. + */ +@interface SentryMetricProfiler : NSObject + +- (instancetype)initWithNotificationCenterWrapper: + (SentryNSNotificationCenterWrapper *)notificationCenterWrapper + profileStartTime:(uint64_t)profileStartTime; +- (void)start; +- (void)stop; + +/** @return All data gathered during the profiling run. */ +- (NSData *)serialize; + +@end + +NS_ASSUME_NONNULL_END diff --git a/Sources/Sentry/SentryMetricProfiler.mm b/Sources/Sentry/SentryMetricProfiler.mm new file mode 100644 index 00000000000..17d83264092 --- /dev/null +++ b/Sources/Sentry/SentryMetricProfiler.mm @@ -0,0 +1,156 @@ +#import "SentryMetricProfiler.h" +#import "SentryMachLogging.hpp" +#import "SentryNSNotificationCenterWrapper.h" +#import "SentryTime.h" +#include + +const NSTimeInterval kSentryMetricProfilerInterval = 0.1; // 10 Hz + +@implementation SentryMetricProfiler { + NSTimer *_timer; + SentryNSNotificationCenterWrapper *_notificationCenter; + dispatch_source_t _memoryWarningSource; + dispatch_queue_t _memoryWarningQueue; + NSMutableArray *> *_cpuTimeSeries; + NSMutableArray *> *_memoryFootprintTimeSeries; + NSMutableArray *> *_thermalStateChanges; + NSMutableArray *> *_memoryPressureStateChanges; + uint64_t _profileStartTime; +} + +- (instancetype)initWithNotificationCenterWrapper: + (SentryNSNotificationCenterWrapper *)notificationCenterWrapper + profileStartTime:(uint64_t)profileStartTime +{ + if (self = [super init]) { + _cpuTimeSeries = [NSMutableArray *> array]; + _memoryFootprintTimeSeries = [NSMutableArray *> array]; + _thermalStateChanges = [NSMutableArray *> array]; + _memoryPressureStateChanges = + [NSMutableArray *> array]; + _notificationCenter = notificationCenterWrapper; + _profileStartTime = profileStartTime; + } + return self; +} + +- (void)dealloc +{ + [self stop]; +} + +#pragma mark - Public + +- (void)start +{ + [self registerTimeSeriesHandler]; + [self registerMemoryPressureWarningHandler]; +} + +- (void)stop +{ + [_timer invalidate]; + dispatch_source_cancel(_memoryWarningSource); + [_notificationCenter removeObserver:self name:NSProcessInfoThermalStateDidChangeNotification]; +} + +- (NSData *)serialize +{ + // TODO: implement + return [[NSData alloc] init]; +} + +#pragma mark - Private + +- (void)registerTimeSeriesHandler +{ + _timer = [NSTimer scheduledTimerWithTimeInterval:kSentryMetricProfilerInterval + repeats:YES + block:^(NSTimer *_Nonnull timer) { + [self recordCPUPercentage]; + [self recordMemoryFootprint]; + }]; +} + +/** + * This is a more fine-grained API, providing normal/warn/critical levels of memory usage, versus + * using `UIApplicationDidReceiveMemoryWarningNotification` which does not provide any additional + * information ("This notification does not contain a userInfo dictionary." from + * https://developer.apple.com/documentation/uikit/uiapplication/1622920-didreceivememorywarningnotificat). + */ +- (void)registerMemoryPressureWarningHandler +{ + const auto queueAttributes + = dispatch_queue_attr_make_with_qos_class(DISPATCH_QUEUE_SERIAL, QOS_CLASS_UTILITY, 0); + _memoryWarningQueue = dispatch_queue_create("io.sentry.queue.memory-warnings", queueAttributes); + _memoryWarningSource = dispatch_source_create(DISPATCH_SOURCE_TYPE_MEMORYPRESSURE, 0, + DISPATCH_MEMORYPRESSURE_NORMAL | DISPATCH_MEMORYPRESSURE_WARN + | DISPATCH_MEMORYPRESSURE_CRITICAL, + _memoryWarningQueue); + dispatch_source_set_event_handler(_memoryWarningSource, ^{ + [self recordMemoryPressureState:dispatch_source_get_data(self->_memoryWarningSource)]; + }); + dispatch_resume(_memoryWarningSource); +} + +- (void)registerStateChangeNotifications +{ + // According to Apple docs: "To receive NSProcessInfoThermalStateDidChangeNotification, you must + // access the thermalState prior to registering for the notification." (from + // https://developer.apple.com/documentation/foundation/nsprocessinfothermalstatedidchangenotification/) + [self recordThermalState]; + + // According to Apple docs: "This notification is posted on the global dispatch queue." + [_notificationCenter addObserver:self + selector:@selector(handleThermalStateChangeNotification:) + name:NSProcessInfoThermalStateDidChangeNotification]; +} + +- (void)handleThermalStateChangeNotification:(NSNotification *)note +{ + [self recordThermalState]; +} + +- (void)recordThermalState +{ + [_thermalStateChanges + addObject:[self metricEntryForValue:@(NSProcessInfo.processInfo.thermalState)]]; +} + +- (void)recordMemoryPressureState:(uintptr_t)memoryPressureState +{ + [_memoryPressureStateChanges addObject:[self metricEntryForValue:@(memoryPressureState)]]; +} + +- (void)recordMemoryFootprint +{ + task_vm_info_data_t info; + mach_msg_type_number_t count = TASK_VM_INFO_COUNT; + if (SENTRY_PROF_LOG_KERN_RETURN( + task_info(mach_task_self(), TASK_VM_INFO, (task_info_t)&info, &count)) + == KERN_SUCCESS) { + mach_vm_size_t footprintBytes; + if (count >= TASK_VM_INFO_REV1_COUNT) { + footprintBytes = info.phys_footprint; + } else { + footprintBytes = info.resident_size; + } + + [_memoryFootprintTimeSeries addObject:[self metricEntryForValue:@(footprintBytes)]]; + } +} + +- (void)recordCPUPercentage +{ + // TODO: implement +} + +- (NSDictionary *)metricEntryForValue:(NSNumber *)value +{ + return @{ + @"value" : value, + @"elapsed_since_start_ns" : @(getDurationNs(_profileStartTime, getAbsoluteTime())) + }; +} + +@end From 1337fd248f11dcf21566efadabca67d761f88216 Mon Sep 17 00:00:00 2001 From: Andrew McKnight Date: Mon, 5 Dec 2022 15:56:35 -0900 Subject: [PATCH 02/70] changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 217d2db7524..4863196f34a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ This version adds a dependency on Swift. ### Features - Properly demangle Swift class name (#2162) +- Gather profiling timeseries metrics for CPU usage and memory footprint, and thermal and memory pressure events (#2493) ### Fixes From 8a87e632235282fa24daddc24caa19ced1f854bd Mon Sep 17 00:00:00 2001 From: Andrew McKnight Date: Mon, 5 Dec 2022 15:58:36 -0900 Subject: [PATCH 03/70] fix changelog --- CHANGELOG.md | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4863196f34a..125652d9368 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## Unreleased + +### Features + +- Gather profiling timeseries metrics for CPU usage and memory footprint, and thermal and memory pressure events (#2493) + ## 8.0.0-beta.4 This version adds a dependency on Swift. @@ -7,7 +13,6 @@ This version adds a dependency on Swift. ### Features - Properly demangle Swift class name (#2162) -- Gather profiling timeseries metrics for CPU usage and memory footprint, and thermal and memory pressure events (#2493) ### Fixes From da9e2871a74be53e8a1e648b517b9b17123f0210 Mon Sep 17 00:00:00 2001 From: Andrew McKnight Date: Tue, 6 Dec 2022 13:20:19 -0900 Subject: [PATCH 04/70] register for low power mode notifications; improve notification wrapper api to accept associated objects --- Sources/Sentry/SentryMetricProfiler.mm | 39 ++++++++++++++++--- .../SentryNSNotificationCenterWrapper.m | 16 ++++++++ .../SentryNSNotificationCenterWrapper.h | 7 ++++ 3 files changed, 57 insertions(+), 5 deletions(-) diff --git a/Sources/Sentry/SentryMetricProfiler.mm b/Sources/Sentry/SentryMetricProfiler.mm index 17d83264092..32193443f72 100644 --- a/Sources/Sentry/SentryMetricProfiler.mm +++ b/Sources/Sentry/SentryMetricProfiler.mm @@ -14,6 +14,7 @@ @implementation SentryMetricProfiler { NSMutableArray *> *_cpuTimeSeries; NSMutableArray *> *_memoryFootprintTimeSeries; NSMutableArray *> *_thermalStateChanges; + NSMutableArray *> *_powerLevelStateChanges; NSMutableArray *> *_memoryPressureStateChanges; uint64_t _profileStartTime; } @@ -26,8 +27,10 @@ - (instancetype)initWithNotificationCenterWrapper: _cpuTimeSeries = [NSMutableArray *> array]; _memoryFootprintTimeSeries = [NSMutableArray *> array]; _thermalStateChanges = [NSMutableArray *> array]; + _powerLevelStateChanges = [NSMutableArray *> array]; _memoryPressureStateChanges = [NSMutableArray *> array]; + _notificationCenter = notificationCenterWrapper; _profileStartTime = profileStartTime; } @@ -43,7 +46,8 @@ - (void)dealloc - (void)start { - [self registerTimeSeriesHandler]; + [self registerSampler]; + [self registerStateChangeNotifications]; [self registerMemoryPressureWarningHandler]; } @@ -51,7 +55,12 @@ - (void)stop { [_timer invalidate]; dispatch_source_cancel(_memoryWarningSource); - [_notificationCenter removeObserver:self name:NSProcessInfoThermalStateDidChangeNotification]; + [_notificationCenter removeObserver:self + name:NSProcessInfoThermalStateDidChangeNotification + object:NSProcessInfo.processInfo]; + [_notificationCenter removeObserver:self + name:NSProcessInfoPowerStateDidChangeNotification + object:NSProcessInfo.processInfo]; } - (NSData *)serialize @@ -62,7 +71,7 @@ - (NSData *)serialize #pragma mark - Private -- (void)registerTimeSeriesHandler +- (void)registerSampler { _timer = [NSTimer scheduledTimerWithTimeInterval:kSentryMetricProfilerInterval repeats:YES @@ -100,10 +109,19 @@ - (void)registerStateChangeNotifications // https://developer.apple.com/documentation/foundation/nsprocessinfothermalstatedidchangenotification/) [self recordThermalState]; - // According to Apple docs: "This notification is posted on the global dispatch queue." + // According to Apple docs: "This notification is posted on the global dispatch queue. The + // object associated with the notification is NSProcessInfo.processInfo." [_notificationCenter addObserver:self selector:@selector(handleThermalStateChangeNotification:) - name:NSProcessInfoThermalStateDidChangeNotification]; + name:NSProcessInfoThermalStateDidChangeNotification + object:NSProcessInfo.processInfo]; + + // According to Apple docs: "This notification is posted on the global dispatch queue. The + // object associated with the notification is NSProcessInfo.processInfo." + [_notificationCenter addObserver:self + selector:@selector(handleThermalStateChangeNotification:) + name:NSProcessInfoPowerStateDidChangeNotification + object:NSProcessInfo.processInfo]; } - (void)handleThermalStateChangeNotification:(NSNotification *)note @@ -111,12 +129,23 @@ - (void)handleThermalStateChangeNotification:(NSNotification *)note [self recordThermalState]; } +- (void)handlePowerLevelStateChangeNotification:(NSNotification *)note +{ + [self recordPowerLevelState]; +} + - (void)recordThermalState { [_thermalStateChanges addObject:[self metricEntryForValue:@(NSProcessInfo.processInfo.thermalState)]]; } +- (void)recordPowerLevelState +{ + [_powerLevelStateChanges + addObject:[self metricEntryForValue:@(NSProcessInfo.processInfo.lowPowerModeEnabled)]]; +} + - (void)recordMemoryPressureState:(uintptr_t)memoryPressureState { [_memoryPressureStateChanges addObject:[self metricEntryForValue:@(memoryPressureState)]]; diff --git a/Sources/Sentry/SentryNSNotificationCenterWrapper.m b/Sources/Sentry/SentryNSNotificationCenterWrapper.m index d658f727c98..7afa353f1ac 100644 --- a/Sources/Sentry/SentryNSNotificationCenterWrapper.m +++ b/Sources/Sentry/SentryNSNotificationCenterWrapper.m @@ -43,6 +43,17 @@ + (NSNotificationName)willTerminateNotificationName } #endif +- (void)addObserver:(id)observer + selector:(SEL)aSelector + name:(NSNotificationName)aName + object:(id)anObject +{ + [NSNotificationCenter.defaultCenter addObserver:observer + selector:aSelector + name:aName + object:anObject]; +} + - (void)addObserver:(id)observer selector:(SEL)aSelector name:(NSNotificationName)aName { [NSNotificationCenter.defaultCenter addObserver:observer @@ -56,6 +67,11 @@ - (void)removeObserver:(id)observer name:(NSNotificationName)aName [NSNotificationCenter.defaultCenter removeObserver:observer name:aName object:nil]; } +- (void)removeObserver:(id)observer name:(NSNotificationName)aName object:(id)anObject +{ + [NSNotificationCenter.defaultCenter removeObserver:observer name:aName object:anObject]; +} + - (void)removeObserver:(id)observer { [NSNotificationCenter.defaultCenter removeObserver:observer]; diff --git a/Sources/Sentry/include/SentryNSNotificationCenterWrapper.h b/Sources/Sentry/include/SentryNSNotificationCenterWrapper.h index 2c6dd57ae86..b9bb252ffd4 100644 --- a/Sources/Sentry/include/SentryNSNotificationCenterWrapper.h +++ b/Sources/Sentry/include/SentryNSNotificationCenterWrapper.h @@ -18,8 +18,15 @@ NS_ASSUME_NONNULL_BEGIN @property (nonatomic, readonly, copy, class) NSNotificationName willTerminateNotificationName; #endif +- (void)addObserver:(id)observer + selector:(SEL)aSelector + name:(NSNotificationName)aName + object:(id)anObject; + - (void)addObserver:(id)observer selector:(SEL)aSelector name:(NSNotificationName)aName; +- (void)removeObserver:(id)observer name:(NSNotificationName)aName object:(id)anObject; + - (void)removeObserver:(id)observer name:(NSNotificationName)aName; - (void)removeObserver:(id)observer; From 5f7412be771fc29e1a5b763acde089c9297b9beb Mon Sep 17 00:00:00 2001 From: Andrew McKnight Date: Wed, 7 Dec 2022 18:13:42 -0900 Subject: [PATCH 05/70] extract syscalls and NSProcess work to wrappers with test mocks; lots of improvements; convert frame render info to new schema --- Sentry.xcodeproj/project.pbxproj | 32 +- Sources/Sentry/Public/SentryError.h | 3 + .../Sentry/{SentryError.m => SentryError.mm} | 10 + Sources/Sentry/SentryMachLogging.cpp | 404 +++++++++--------- Sources/Sentry/SentryMetricProfiler.h | 11 +- Sources/Sentry/SentryMetricProfiler.mm | 183 ++++---- .../SentryNSNotificationCenterWrapper.m | 5 + Sources/Sentry/SentryNSProcessInfoWrapper.h | 16 + Sources/Sentry/SentryNSProcessInfoWrapper.mm | 50 +++ Sources/Sentry/SentryProfiler.mm | 52 ++- Sources/Sentry/SentrySystemWrapper.h | 26 ++ Sources/Sentry/SentrySystemWrapper.mm | 91 ++++ Sources/Sentry/include/SentryMachLogging.hpp | 39 +- .../SentryNSNotificationCenterWrapper.h | 2 + Sources/Sentry/include/SentryProfiler+Test.h | 17 + Sources/Sentry/include/SentryProfiler.h | 2 + .../TestSentryNSProcessInfoWrapper.swift | 26 ++ .../Helper/TestSentrySystemWrapper.swift | 28 ++ Tests/SentryTests/SentryClientTests.swift | 2 +- .../TestSentryUIDeviceWrapper.swift | 4 +- .../SentryTests/SentryTests-Bridging-Header.h | 2 + 21 files changed, 675 insertions(+), 330 deletions(-) rename Sources/Sentry/{SentryError.m => SentryError.mm} (70%) create mode 100644 Sources/Sentry/SentryNSProcessInfoWrapper.h create mode 100644 Sources/Sentry/SentryNSProcessInfoWrapper.mm create mode 100644 Sources/Sentry/SentrySystemWrapper.h create mode 100644 Sources/Sentry/SentrySystemWrapper.mm create mode 100644 Tests/SentryTests/Helper/TestSentryNSProcessInfoWrapper.swift create mode 100644 Tests/SentryTests/Helper/TestSentrySystemWrapper.swift diff --git a/Sentry.xcodeproj/project.pbxproj b/Sentry.xcodeproj/project.pbxproj index 89e271cfcd2..89e2d72f516 100644 --- a/Sentry.xcodeproj/project.pbxproj +++ b/Sentry.xcodeproj/project.pbxproj @@ -144,7 +144,7 @@ 63AA76991EB9C1C200D153DE /* SentryDefines.h in Headers */ = {isa = PBXBuildFile; fileRef = 63AA76951EB9C1C200D153DE /* SentryDefines.h */; settings = {ATTRIBUTES = (Public, ); }; }; 63AA769A1EB9C1C200D153DE /* SentryLog.h in Headers */ = {isa = PBXBuildFile; fileRef = 63AA76961EB9C1C200D153DE /* SentryLog.h */; settings = {ATTRIBUTES = (Private, ); }; }; 63AA769D1EB9C57A00D153DE /* SentryError.h in Headers */ = {isa = PBXBuildFile; fileRef = 63AA769B1EB9C57A00D153DE /* SentryError.h */; settings = {ATTRIBUTES = (Public, ); }; }; - 63AA769E1EB9C57A00D153DE /* SentryError.m in Sources */ = {isa = PBXBuildFile; fileRef = 63AA769C1EB9C57A00D153DE /* SentryError.m */; }; + 63AA769E1EB9C57A00D153DE /* SentryError.mm in Sources */ = {isa = PBXBuildFile; fileRef = 63AA769C1EB9C57A00D153DE /* SentryError.mm */; }; 63AA76A31EB9CBAA00D153DE /* SentryDsn.m in Sources */ = {isa = PBXBuildFile; fileRef = 63AA76A11EB9CBAA00D153DE /* SentryDsn.m */; }; 63AA76A51EB9CBC200D153DE /* SentryDsn.h in Headers */ = {isa = PBXBuildFile; fileRef = 63AA76A41EB9CBC200D153DE /* SentryDsn.h */; settings = {ATTRIBUTES = (Public, ); }; }; 63AF656C1ED87B8C00EBCFF7 /* libz.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = 6304360D1EC05CEF00C4D3FA /* libz.tbd */; }; @@ -599,6 +599,12 @@ 7DC8310A2398283C0043DD9A /* SentryCrashIntegration.h in Headers */ = {isa = PBXBuildFile; fileRef = 7DC831082398283C0043DD9A /* SentryCrashIntegration.h */; }; 7DC8310C2398283C0043DD9A /* SentryCrashIntegration.m in Sources */ = {isa = PBXBuildFile; fileRef = 7DC831092398283C0043DD9A /* SentryCrashIntegration.m */; }; 8419C0C428C1889D001C8259 /* SentryProfilerSwiftTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8419C0C328C1889D001C8259 /* SentryProfilerSwiftTests.swift */; }; + 844EDC6F294143B900C86F34 /* SentryNSProcessInfoWrapper.h in Headers */ = {isa = PBXBuildFile; fileRef = 844EDC6D294143B900C86F34 /* SentryNSProcessInfoWrapper.h */; }; + 844EDC70294143B900C86F34 /* SentryNSProcessInfoWrapper.mm in Sources */ = {isa = PBXBuildFile; fileRef = 844EDC6E294143B900C86F34 /* SentryNSProcessInfoWrapper.mm */; }; + 844EDC73294144B200C86F34 /* TestSentryNSProcessInfoWrapper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 844EDC712941442200C86F34 /* TestSentryNSProcessInfoWrapper.swift */; }; + 844EDC76294144DB00C86F34 /* SentrySystemWrapper.h in Headers */ = {isa = PBXBuildFile; fileRef = 844EDC74294144DB00C86F34 /* SentrySystemWrapper.h */; }; + 844EDC77294144DB00C86F34 /* SentrySystemWrapper.mm in Sources */ = {isa = PBXBuildFile; fileRef = 844EDC75294144DB00C86F34 /* SentrySystemWrapper.mm */; }; + 844EDC7A29415AE800C86F34 /* TestSentrySystemWrapper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 844EDC7829415AB300C86F34 /* TestSentrySystemWrapper.swift */; }; 8453421228BE855D00C22EEC /* SentrySampleDecision.m in Sources */ = {isa = PBXBuildFile; fileRef = 8453421128BE855D00C22EEC /* SentrySampleDecision.m */; }; 8453421628BE8A9500C22EEC /* SentrySpanStatus.m in Sources */ = {isa = PBXBuildFile; fileRef = 8453421528BE8A9500C22EEC /* SentrySpanStatus.m */; }; 8454CF8C293EAF9A006AC140 /* SentryMetricProfiler.h in Headers */ = {isa = PBXBuildFile; fileRef = 8454CF8A293EAF9A006AC140 /* SentryMetricProfiler.h */; }; @@ -904,7 +910,7 @@ 63AA76951EB9C1C200D153DE /* SentryDefines.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = SentryDefines.h; path = Public/SentryDefines.h; sourceTree = ""; }; 63AA76961EB9C1C200D153DE /* SentryLog.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = SentryLog.h; path = include/SentryLog.h; sourceTree = ""; }; 63AA769B1EB9C57A00D153DE /* SentryError.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = SentryError.h; path = Public/SentryError.h; sourceTree = ""; }; - 63AA769C1EB9C57A00D153DE /* SentryError.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SentryError.m; sourceTree = ""; }; + 63AA769C1EB9C57A00D153DE /* SentryError.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = SentryError.mm; sourceTree = ""; }; 63AA76A11EB9CBAA00D153DE /* SentryDsn.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SentryDsn.m; sourceTree = ""; }; 63AA76A41EB9CBC200D153DE /* SentryDsn.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = SentryDsn.h; path = Public/SentryDsn.h; sourceTree = ""; }; 63AA76AE1EB9D5CD00D153DE /* SentryTests.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = SentryTests.xcconfig; sourceTree = ""; }; @@ -1412,6 +1418,12 @@ 844DA81D28246DAE00E6B62E /* develop-docs */ = {isa = PBXFileReference; lastKnownFileType = folder; path = "develop-docs"; sourceTree = ""; }; 844DA81E28246DB900E6B62E /* fastlane */ = {isa = PBXFileReference; lastKnownFileType = folder; path = fastlane; sourceTree = ""; }; 844DA81F28246DE300E6B62E /* scripts */ = {isa = PBXFileReference; lastKnownFileType = folder; path = scripts; sourceTree = ""; }; + 844EDC6D294143B900C86F34 /* SentryNSProcessInfoWrapper.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SentryNSProcessInfoWrapper.h; sourceTree = ""; }; + 844EDC6E294143B900C86F34 /* SentryNSProcessInfoWrapper.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = SentryNSProcessInfoWrapper.mm; sourceTree = ""; }; + 844EDC712941442200C86F34 /* TestSentryNSProcessInfoWrapper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TestSentryNSProcessInfoWrapper.swift; sourceTree = ""; }; + 844EDC74294144DB00C86F34 /* SentrySystemWrapper.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SentrySystemWrapper.h; sourceTree = ""; }; + 844EDC75294144DB00C86F34 /* SentrySystemWrapper.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = SentrySystemWrapper.mm; sourceTree = ""; }; + 844EDC7829415AB300C86F34 /* TestSentrySystemWrapper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TestSentrySystemWrapper.swift; sourceTree = ""; }; 8453421128BE855D00C22EEC /* SentrySampleDecision.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SentrySampleDecision.m; sourceTree = ""; }; 8453421528BE8A9500C22EEC /* SentrySpanStatus.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SentrySpanStatus.m; sourceTree = ""; }; 8454CF8A293EAF9A006AC140 /* SentryMetricProfiler.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = SentryMetricProfiler.h; path = Sources/Sentry/SentryMetricProfiler.h; sourceTree = SOURCE_ROOT; }; @@ -1872,7 +1884,7 @@ 63AA76951EB9C1C200D153DE /* SentryDefines.h */, D8BD2E67293619F600D96C6A /* PrivatesHeader.h */, 63AA769B1EB9C57A00D153DE /* SentryError.h */, - 63AA769C1EB9C57A00D153DE /* SentryError.m */, + 63AA769C1EB9C57A00D153DE /* SentryError.mm */, 7B42C47F27E08F33009B58C2 /* SentryDependencyContainer.h */, 7B42C48127E08F4B009B58C2 /* SentryDependencyContainer.m */, 7BC8522E24581096005A70F0 /* SentryFileContents.h */, @@ -1920,6 +1932,10 @@ 84A8891A28DBD28900C51DFD /* SentryDevice.h */, 84A8891B28DBD28900C51DFD /* SentryDevice.mm */, 0A9E917028DC7E7000FB4182 /* SentryInternalDefines.h */, + 844EDC6D294143B900C86F34 /* SentryNSProcessInfoWrapper.h */, + 844EDC6E294143B900C86F34 /* SentryNSProcessInfoWrapper.mm */, + 844EDC74294144DB00C86F34 /* SentrySystemWrapper.h */, + 844EDC75294144DB00C86F34 /* SentrySystemWrapper.mm */, ); name = Helper; sourceTree = ""; @@ -2523,6 +2539,8 @@ 7B18DE4328D9F8F6004845C6 /* TestNSNotificationCenterWrapper.swift */, 7B18DE4928DA0C8B004845C6 /* SentryNSNotificationCenterWrapperTests.swift */, 84A8892028DBD8D600C51DFD /* SentryDeviceTests.mm */, + 844EDC712941442200C86F34 /* TestSentryNSProcessInfoWrapper.swift */, + 844EDC7829415AB300C86F34 /* TestSentrySystemWrapper.swift */, ); path = Helper; sourceTree = ""; @@ -3093,10 +3111,12 @@ 635B3F381EBC6E2500A6176D /* SentryAsynchronousOperation.h in Headers */, 7BD4BD4727EB2A3D0071F4FF /* SentryDiscardedEvent.h in Headers */, 7BBD18972449DC1D00427C76 /* SentryRateLimits.h in Headers */, + 844EDC76294144DB00C86F34 /* SentrySystemWrapper.h in Headers */, 7B3B473025D6CBFC00D01640 /* SentryNSError.h in Headers */, 630436101EC0600A00C4D3FA /* SentrySerializable.h in Headers */, 63FE70DD20DA4C1000CDBAE8 /* SentryCrashMonitor_Signal.h in Headers */, 63FE710320DA4C1000CDBAE8 /* SentryCrashMachineContext_Apple.h in Headers */, + 844EDC6F294143B900C86F34 /* SentryNSProcessInfoWrapper.h in Headers */, D8479328278873A100BE8E99 /* SentryByteCountFormatter.h in Headers */, 63AA76981EB9C1C200D153DE /* SentryClient.h in Headers */, 63AA76971EB9C1C200D153DE /* Sentry.h in Headers */, @@ -3465,7 +3485,7 @@ 7BCFA71627D0BB50008C662C /* SentryANRTracker.m in Sources */, 63EED6C02237923600E02400 /* SentryOptions.m in Sources */, D84793262788737D00BE8E99 /* SentryByteCountFormatter.m in Sources */, - 63AA769E1EB9C57A00D153DE /* SentryError.m in Sources */, + 63AA769E1EB9C57A00D153DE /* SentryError.mm in Sources */, 7B8713B026415B22006D6004 /* SentryAppStartTrackingIntegration.m in Sources */, 8E133FA225E72DEF00ABD0BF /* SentrySamplingContext.m in Sources */, 7B6C5F8726034395007F7DFF /* SentryOutOfMemoryLogic.m in Sources */, @@ -3482,6 +3502,7 @@ D8370B6A273DF1E900F66E2D /* SentryNSURLSessionTaskSearch.m in Sources */, 7B30B67E26527894006B2752 /* SentryDisplayLinkWrapper.m in Sources */, 63FE711D20DA4C1000CDBAE8 /* SentryCrashCPU_arm64.c in Sources */, + 844EDC77294144DB00C86F34 /* SentrySystemWrapper.mm in Sources */, 630435FF1EBCA9D900C4D3FA /* SentryNSURLRequest.m in Sources */, 7B5CAF7727F5A68C00ED0DB6 /* SentryNSURLRequestBuilder.m in Sources */, 639FCFA11EBC804600778193 /* SentryException.m in Sources */, @@ -3562,6 +3583,7 @@ 6344DDB11EC308E400D9160D /* SentryCrashInstallationReporter.m in Sources */, D85596F3280580F10041FF8B /* SentryScreenshotIntegration.m in Sources */, 7BAF3DCE243DCBFE008A5414 /* SentryTransportFactory.m in Sources */, + 844EDC70294143B900C86F34 /* SentryNSProcessInfoWrapper.mm in Sources */, 7BB65501253DC1B500887E87 /* SentryUserFeedback.m in Sources */, 7D5C441A237C2E1F00DAB0A3 /* SentrySDK.m in Sources */, 7D65260E237F649E00113EA2 /* SentryScope.m in Sources */, @@ -3765,6 +3787,7 @@ 7B7725D8292F5DC20015BBF9 /* SentryCrashInstallationTests.m in Sources */, 7B82D54924E2A2D400EE670F /* SentryIdTests.swift in Sources */, 7BD47B4E268F0B470076A663 /* ClearTestState.swift in Sources */, + 844EDC7A29415AE800C86F34 /* TestSentrySystemWrapper.swift in Sources */, 7B6D98ED24C703F8005502FA /* Async.swift in Sources */, 7BA0C04C28056556003E0326 /* SentryTransportAdapterTests.swift in Sources */, 7BE0DC29272A9E1C004FA8B7 /* SentryBreadcrumbTrackerTests.swift in Sources */, @@ -3832,6 +3855,7 @@ 7BC6EC14255C415E0059822A /* SentryExceptionTests.swift in Sources */, 7B82722927A319E900F4BFF4 /* SentryAutoSessionTrackingIntegrationTests.swift in Sources */, D875ED0B276CC84700422FAC /* SentryNSDataTrackerTests.swift in Sources */, + 844EDC73294144B200C86F34 /* TestSentryNSProcessInfoWrapper.swift in Sources */, 8ED2D28026A6581C00CA8329 /* NSURLProtocolSwizzle.m in Sources */, D808FB92281BF6EC009A2A33 /* SentryUIEventTrackingIntegrationTests.swift in Sources */, 7BC6EC04255C235F0059822A /* SentryFrameTests.swift in Sources */, diff --git a/Sources/Sentry/Public/SentryError.h b/Sources/Sentry/Public/SentryError.h index 442a098ec5e..2dc46decb53 100644 --- a/Sources/Sentry/Public/SentryError.h +++ b/Sources/Sentry/Public/SentryError.h @@ -13,6 +13,7 @@ typedef NS_ENUM(NSInteger, SentryError) { kSentryErrorRequestError = 106, kSentryErrorEventNotSent = 107, kSentryErrorFileIO = 108, + kSentryErrorKernel = 109, }; SENTRY_EXTERN NSError *_Nullable NSErrorFromSentryError(SentryError error, NSString *description); @@ -20,6 +21,8 @@ SENTRY_EXTERN NSError *_Nullable NSErrorFromSentryErrorWithUnderlyingError( SentryError error, NSString *description, NSError *underlyingError); SENTRY_EXTERN NSError *_Nullable NSErrorFromSentryErrorWithException( SentryError error, NSString *description, NSException *exception); +SENTRY_EXTERN NSError *_Nullable NSErrorFromSentryErrorWithKernelError( + SentryError error, NSString *description, kern_return_t kernelErrorCode); SENTRY_EXTERN NSString *const SentryErrorDomain; diff --git a/Sources/Sentry/SentryError.m b/Sources/Sentry/SentryError.mm similarity index 70% rename from Sources/Sentry/SentryError.m rename to Sources/Sentry/SentryError.mm index d00e8a51f5c..998f8220984 100644 --- a/Sources/Sentry/SentryError.m +++ b/Sources/Sentry/SentryError.mm @@ -1,4 +1,5 @@ #import "SentryError.h" +#import "SentryMachLogging.hpp" NS_ASSUME_NONNULL_BEGIN @@ -25,6 +26,15 @@ }); } +SENTRY_EXTERN NSError *_Nullable NSErrorFromSentryErrorWithKernelError( + SentryError error, NSString *description, kern_return_t kernelErrorCode) +{ + return _SentryError(error, @ { + NSLocalizedDescriptionKey : [NSString stringWithFormat:@"%@ (%s)", description, + sentry::kernelReturnCodeDescription(kernelErrorCode)], + }); +} + NSError *_Nullable NSErrorFromSentryError(SentryError error, NSString *description) { return _SentryError(error, @ { NSLocalizedDescriptionKey : description }); diff --git a/Sources/Sentry/SentryMachLogging.cpp b/Sources/Sentry/SentryMachLogging.cpp index a35dce1214d..af35c5f6d9c 100644 --- a/Sources/Sentry/SentryMachLogging.cpp +++ b/Sources/Sentry/SentryMachLogging.cpp @@ -1,212 +1,210 @@ #include "SentryMachLogging.hpp" namespace sentry { -namespace profiling { - const char * - kernelReturnCodeDescription(kern_return_t kr) noexcept - { - switch (kr) { - case KERN_SUCCESS: - return "Success."; - case KERN_INVALID_ADDRESS: - return "Specified address is not currently valid."; - case KERN_PROTECTION_FAILURE: - return "Specified memory is valid, but does not permit the required forms of access."; - case KERN_NO_SPACE: - return "The address range specified is already in use, or no address range of the size " - "specified could be found."; - case KERN_INVALID_ARGUMENT: - return "The function requested was not applicable to this type of argument, or an " - "argument is invalid."; - case KERN_FAILURE: - return "The function could not be performed."; - case KERN_RESOURCE_SHORTAGE: - return "A system resource could not be allocated to fulfill this request."; - case KERN_NOT_RECEIVER: - return "The task in question does not hold receive rights for the port argument."; - case KERN_NO_ACCESS: - return "Bogus access restriction."; - case KERN_MEMORY_FAILURE: - return "During a page fault, the target address refers to a memory object that has " - "been destroyed."; - case KERN_MEMORY_ERROR: - return "During a page fault, the memory object indicated that the data could not be " - "returned."; - case KERN_ALREADY_IN_SET: - return "The receive right is already a member of the portset."; - case KERN_NOT_IN_SET: - return "The receive right is not a member of a port set."; - case KERN_NAME_EXISTS: - return "The name already denotes a right in the task."; - case KERN_ABORTED: - return "The operation was aborted."; - case KERN_INVALID_NAME: - return "The name doesn't denote a right in the task."; - case KERN_INVALID_TASK: - return "Target task isn't an active task."; - case KERN_INVALID_RIGHT: - return "The name denotes a right, but not an appropriate right."; - case KERN_INVALID_VALUE: - return "A blatant range error."; - case KERN_UREFS_OVERFLOW: - return "Operation would overflow limit on user-references."; - case KERN_INVALID_CAPABILITY: - return "The supplied (port) capability is improper."; - case KERN_RIGHT_EXISTS: - return "The task already has send or receive rights for the port under another name."; - case KERN_INVALID_HOST: - return "Target host isn't actually a host."; - case KERN_MEMORY_PRESENT: - return "An attempt was made to supply \"precious\" data for memory that is already " - "present in a memory object."; - case KERN_MEMORY_DATA_MOVED: - return "See code documentation for KERN_MEMORY_DATA_MOVED"; - case KERN_MEMORY_RESTART_COPY: - return "See code documentation for KERN_MEMORY_RESTART_COPY"; - case KERN_INVALID_PROCESSOR_SET: - return "An argument applied to assert processor set privilege was not a processor set " - "control port."; - case KERN_POLICY_LIMIT: - return "The specified scheduling attributes exceed the thread's limits."; - case KERN_INVALID_POLICY: - return "The specified scheduling policy is not currently enabled for the processor " - "set."; - case KERN_INVALID_OBJECT: - return "The external memory manager failed to initialize the memory object."; - case KERN_ALREADY_WAITING: - return "A thread is attempting to wait for an event for which there is already a " - "waiting thread."; - case KERN_DEFAULT_SET: - return "An attempt was made to destroy the default processor set"; - case KERN_EXCEPTION_PROTECTED: - return "An attempt was made to fetch an exception port that is protected, or to abort " - "a thread while processing a protected exception."; - case KERN_INVALID_LEDGER: - return "A ledger was required but not supplied."; - case KERN_INVALID_MEMORY_CONTROL: - return "The port was not a memory cache control port."; - case KERN_INVALID_SECURITY: - return "An argument supplied to assert security privilege was not a host security " - "port."; - case KERN_NOT_DEPRESSED: - return "thread_depress_abort was called on a thread which was not currently depressed."; - case KERN_TERMINATED: - return "Object has been terminated and is no longer available"; - case KERN_LOCK_SET_DESTROYED: - return "Lock set has been destroyed and is no longer available."; - case KERN_LOCK_UNSTABLE: - return "The thread holding the lock terminated before releasing"; - case KERN_LOCK_OWNED: - return "The lock is already owned by another thread"; - case KERN_LOCK_OWNED_SELF: - return "The lock is already owned by the calling thread"; - case KERN_SEMAPHORE_DESTROYED: - return "Semaphore has been destroyed and is no longer available."; - case KERN_RPC_SERVER_TERMINATED: - return "Return from RPC indicating the target server was terminated before it " - "successfully replied."; - case KERN_RPC_TERMINATE_ORPHAN: - return "Terminate an orphaned activation."; - case KERN_RPC_CONTINUE_ORPHAN: - return "Allow an orphaned activation to continue executing."; - case KERN_NOT_SUPPORTED: - return "Empty thread activation (No thread linked to it)"; - case KERN_NODE_DOWN: - return "Remote node down or inaccessible."; - case KERN_NOT_WAITING: - return "A signalled thread was not actually waiting."; - case KERN_OPERATION_TIMED_OUT: - return "Some thread-oriented operation (semaphore_wait) timed out"; - case KERN_CODESIGN_ERROR: - return "During a page fault, indicates that the page was rejected as a result of a " - "signature check."; - case KERN_POLICY_STATIC: - return "The requested property cannot be changed at this time."; - case KERN_INSUFFICIENT_BUFFER_SIZE: - return "The provided buffer is of insufficient size for the requested data."; - default: - return "Unknown error."; - } +const char * +kernelReturnCodeDescription(kern_return_t kr) noexcept +{ + switch (kr) { + case KERN_SUCCESS: + return "Success."; + case KERN_INVALID_ADDRESS: + return "Specified address is not currently valid."; + case KERN_PROTECTION_FAILURE: + return "Specified memory is valid, but does not permit the required forms of access."; + case KERN_NO_SPACE: + return "The address range specified is already in use, or no address range of the size " + "specified could be found."; + case KERN_INVALID_ARGUMENT: + return "The function requested was not applicable to this type of argument, or an " + "argument is invalid."; + case KERN_FAILURE: + return "The function could not be performed."; + case KERN_RESOURCE_SHORTAGE: + return "A system resource could not be allocated to fulfill this request."; + case KERN_NOT_RECEIVER: + return "The task in question does not hold receive rights for the port argument."; + case KERN_NO_ACCESS: + return "Bogus access restriction."; + case KERN_MEMORY_FAILURE: + return "During a page fault, the target address refers to a memory object that has " + "been destroyed."; + case KERN_MEMORY_ERROR: + return "During a page fault, the memory object indicated that the data could not be " + "returned."; + case KERN_ALREADY_IN_SET: + return "The receive right is already a member of the portset."; + case KERN_NOT_IN_SET: + return "The receive right is not a member of a port set."; + case KERN_NAME_EXISTS: + return "The name already denotes a right in the task."; + case KERN_ABORTED: + return "The operation was aborted."; + case KERN_INVALID_NAME: + return "The name doesn't denote a right in the task."; + case KERN_INVALID_TASK: + return "Target task isn't an active task."; + case KERN_INVALID_RIGHT: + return "The name denotes a right, but not an appropriate right."; + case KERN_INVALID_VALUE: + return "A blatant range error."; + case KERN_UREFS_OVERFLOW: + return "Operation would overflow limit on user-references."; + case KERN_INVALID_CAPABILITY: + return "The supplied (port) capability is improper."; + case KERN_RIGHT_EXISTS: + return "The task already has send or receive rights for the port under another name."; + case KERN_INVALID_HOST: + return "Target host isn't actually a host."; + case KERN_MEMORY_PRESENT: + return "An attempt was made to supply \"precious\" data for memory that is already " + "present in a memory object."; + case KERN_MEMORY_DATA_MOVED: + return "See code documentation for KERN_MEMORY_DATA_MOVED"; + case KERN_MEMORY_RESTART_COPY: + return "See code documentation for KERN_MEMORY_RESTART_COPY"; + case KERN_INVALID_PROCESSOR_SET: + return "An argument applied to assert processor set privilege was not a processor set " + "control port."; + case KERN_POLICY_LIMIT: + return "The specified scheduling attributes exceed the thread's limits."; + case KERN_INVALID_POLICY: + return "The specified scheduling policy is not currently enabled for the processor " + "set."; + case KERN_INVALID_OBJECT: + return "The external memory manager failed to initialize the memory object."; + case KERN_ALREADY_WAITING: + return "A thread is attempting to wait for an event for which there is already a " + "waiting thread."; + case KERN_DEFAULT_SET: + return "An attempt was made to destroy the default processor set"; + case KERN_EXCEPTION_PROTECTED: + return "An attempt was made to fetch an exception port that is protected, or to abort " + "a thread while processing a protected exception."; + case KERN_INVALID_LEDGER: + return "A ledger was required but not supplied."; + case KERN_INVALID_MEMORY_CONTROL: + return "The port was not a memory cache control port."; + case KERN_INVALID_SECURITY: + return "An argument supplied to assert security privilege was not a host security " + "port."; + case KERN_NOT_DEPRESSED: + return "thread_depress_abort was called on a thread which was not currently depressed."; + case KERN_TERMINATED: + return "Object has been terminated and is no longer available"; + case KERN_LOCK_SET_DESTROYED: + return "Lock set has been destroyed and is no longer available."; + case KERN_LOCK_UNSTABLE: + return "The thread holding the lock terminated before releasing"; + case KERN_LOCK_OWNED: + return "The lock is already owned by another thread"; + case KERN_LOCK_OWNED_SELF: + return "The lock is already owned by the calling thread"; + case KERN_SEMAPHORE_DESTROYED: + return "Semaphore has been destroyed and is no longer available."; + case KERN_RPC_SERVER_TERMINATED: + return "Return from RPC indicating the target server was terminated before it " + "successfully replied."; + case KERN_RPC_TERMINATE_ORPHAN: + return "Terminate an orphaned activation."; + case KERN_RPC_CONTINUE_ORPHAN: + return "Allow an orphaned activation to continue executing."; + case KERN_NOT_SUPPORTED: + return "Empty thread activation (No thread linked to it)"; + case KERN_NODE_DOWN: + return "Remote node down or inaccessible."; + case KERN_NOT_WAITING: + return "A signalled thread was not actually waiting."; + case KERN_OPERATION_TIMED_OUT: + return "Some thread-oriented operation (semaphore_wait) timed out"; + case KERN_CODESIGN_ERROR: + return "During a page fault, indicates that the page was rejected as a result of a " + "signature check."; + case KERN_POLICY_STATIC: + return "The requested property cannot be changed at this time."; + case KERN_INSUFFICIENT_BUFFER_SIZE: + return "The provided buffer is of insufficient size for the requested data."; + default: + return "Unknown error."; } +} - const char * - machMessageReturnCodeDescription(mach_msg_return_t mr) noexcept - { - switch (mr) { - case MACH_MSG_SUCCESS: - return "Success."; - case MACH_SEND_NO_BUFFER: - return "A resource shortage prevented the kernel from allocating a message buffer."; - case MACH_SEND_INVALID_DATA: - return "The supplied message buffer was not readable."; - case MACH_SEND_INVALID_HEADER: - return "The msgh_bits value was invalid."; - case MACH_SEND_INVALID_DEST: - return "The msgh_remote_port value was invalid."; - case MACH_SEND_INVALID_NOTIFY: - return "When using MACH_SEND_CANCEL, the notify argument did not denote a valid " - "receive right."; - case MACH_SEND_INVALID_REPLY: - return "The msgh_local_port value was invalid."; - case MACH_SEND_INVALID_TRAILER: - return "The trailer to be sent does not correspond to the current kernel format, or " - "the sending task does not have the privilege to supply the message attributes."; - case MACH_SEND_INVALID_MEMORY: - return "The message body specified out-of-line data that was not readable."; - case MACH_SEND_INVALID_RIGHT: - return "The message body specified a port right which the caller didn't possess."; - case MACH_SEND_INVALID_TYPE: - return "A kernel processed descriptor was invalid."; - case MACH_SEND_MSG_TOO_SMALL: - return "The last data item in the message ran over the end of the message."; - case MACH_SEND_TIMED_OUT: - return "The timeout interval expired."; - case MACH_SEND_INTERRUPTED: - return "A software interrupt occurred."; - case MACH_RCV_INVALID_NAME: - return "The specified receive_name was invalid."; - case MACH_RCV_IN_SET: - return "The specified port was a member of a port set."; - case MACH_RCV_TIMED_OUT: - return "The timeout interval expired."; - case MACH_RCV_INTERRUPTED: - return "A software interrupt occurred."; - case MACH_RCV_PORT_DIED: - return "The caller lost the rights specified by receive_name."; - case MACH_RCV_PORT_CHANGED: - return "receive_name specified a receive right which was moved into a port set during " - "the call."; - case MACH_RCV_TOO_LARGE: - return "When using MACH_RCV_LARGE, the message was larger than receive_limit. The " - "message is left queued, and its actual size is returned in the message " - "header/message body."; - case MACH_RCV_INVALID_TRAILER: - return "The trailer type desired, or the number of trailer elements desired, is not " - "supported by the kernel."; - case MACH_RCV_HEADER_ERROR: - return "A resource shortage prevented the reception of the port rights in the message " - "header."; - case MACH_RCV_INVALID_NOTIFY: - return "When using MACH_RCV_NOTIFY, the notify argument did not denote a valid receive " - "right."; - case MACH_RCV_INVALID_DATA: - return "The specified message buffer was not writable."; - case MACH_RCV_SCATTER_SMALL: - return "When not using MACH_RCV_LARGE with MACH_RCV_OVERWRITE, one or more scatter " - "list descriptors specified an overwrite region smaller than the corresponding " - "incoming region. The message was de-queued and destroyed."; - case MACH_RCV_INVALID_TYPE: - return "When using MACH_RCV_OVERWRITE, one or more scatter list descriptors did not " - "have the type matching the corresponding incoming message descriptor or had an " - "invalid copy (disposition) field."; - case MACH_RCV_BODY_ERROR: - return "A resource shortage prevented the reception of a port right or out-of- line " - "memory region in the message body."; - default: - return "Unknown error."; - } +const char * +machMessageReturnCodeDescription(mach_msg_return_t mr) noexcept +{ + switch (mr) { + case MACH_MSG_SUCCESS: + return "Success."; + case MACH_SEND_NO_BUFFER: + return "A resource shortage prevented the kernel from allocating a message buffer."; + case MACH_SEND_INVALID_DATA: + return "The supplied message buffer was not readable."; + case MACH_SEND_INVALID_HEADER: + return "The msgh_bits value was invalid."; + case MACH_SEND_INVALID_DEST: + return "The msgh_remote_port value was invalid."; + case MACH_SEND_INVALID_NOTIFY: + return "When using MACH_SEND_CANCEL, the notify argument did not denote a valid " + "receive right."; + case MACH_SEND_INVALID_REPLY: + return "The msgh_local_port value was invalid."; + case MACH_SEND_INVALID_TRAILER: + return "The trailer to be sent does not correspond to the current kernel format, or " + "the sending task does not have the privilege to supply the message attributes."; + case MACH_SEND_INVALID_MEMORY: + return "The message body specified out-of-line data that was not readable."; + case MACH_SEND_INVALID_RIGHT: + return "The message body specified a port right which the caller didn't possess."; + case MACH_SEND_INVALID_TYPE: + return "A kernel processed descriptor was invalid."; + case MACH_SEND_MSG_TOO_SMALL: + return "The last data item in the message ran over the end of the message."; + case MACH_SEND_TIMED_OUT: + return "The timeout interval expired."; + case MACH_SEND_INTERRUPTED: + return "A software interrupt occurred."; + case MACH_RCV_INVALID_NAME: + return "The specified receive_name was invalid."; + case MACH_RCV_IN_SET: + return "The specified port was a member of a port set."; + case MACH_RCV_TIMED_OUT: + return "The timeout interval expired."; + case MACH_RCV_INTERRUPTED: + return "A software interrupt occurred."; + case MACH_RCV_PORT_DIED: + return "The caller lost the rights specified by receive_name."; + case MACH_RCV_PORT_CHANGED: + return "receive_name specified a receive right which was moved into a port set during " + "the call."; + case MACH_RCV_TOO_LARGE: + return "When using MACH_RCV_LARGE, the message was larger than receive_limit. The " + "message is left queued, and its actual size is returned in the message " + "header/message body."; + case MACH_RCV_INVALID_TRAILER: + return "The trailer type desired, or the number of trailer elements desired, is not " + "supported by the kernel."; + case MACH_RCV_HEADER_ERROR: + return "A resource shortage prevented the reception of the port rights in the message " + "header."; + case MACH_RCV_INVALID_NOTIFY: + return "When using MACH_RCV_NOTIFY, the notify argument did not denote a valid receive " + "right."; + case MACH_RCV_INVALID_DATA: + return "The specified message buffer was not writable."; + case MACH_RCV_SCATTER_SMALL: + return "When not using MACH_RCV_LARGE with MACH_RCV_OVERWRITE, one or more scatter " + "list descriptors specified an overwrite region smaller than the corresponding " + "incoming region. The message was de-queued and destroyed."; + case MACH_RCV_INVALID_TYPE: + return "When using MACH_RCV_OVERWRITE, one or more scatter list descriptors did not " + "have the type matching the corresponding incoming message descriptor or had an " + "invalid copy (disposition) field."; + case MACH_RCV_BODY_ERROR: + return "A resource shortage prevented the reception of a port right or out-of- line " + "memory region in the message body."; + default: + return "Unknown error."; } +} -} // namespace profiling } // namespace sentry diff --git a/Sources/Sentry/SentryMetricProfiler.h b/Sources/Sentry/SentryMetricProfiler.h index 2f538d2443e..742b3098f90 100644 --- a/Sources/Sentry/SentryMetricProfiler.h +++ b/Sources/Sentry/SentryMetricProfiler.h @@ -1,6 +1,7 @@ #import -@class SentryNSNotificationCenterWrapper; +@class SentryNSProcessInfoWrapper; +@class SentrySystemWrapper; NS_ASSUME_NONNULL_BEGIN @@ -10,14 +11,14 @@ NS_ASSUME_NONNULL_BEGIN */ @interface SentryMetricProfiler : NSObject -- (instancetype)initWithNotificationCenterWrapper: - (SentryNSNotificationCenterWrapper *)notificationCenterWrapper - profileStartTime:(uint64_t)profileStartTime; +- (instancetype)initWithProfileStartTime:(uint64_t)profileStartTime + processInfoWrapper:(SentryNSProcessInfoWrapper *)processInfoWrapper + systemWrapper:(SentrySystemWrapper *)systemWrapper; - (void)start; - (void)stop; /** @return All data gathered during the profiling run. */ -- (NSData *)serialize; +- (NSMutableDictionary *)serialize; @end diff --git a/Sources/Sentry/SentryMetricProfiler.mm b/Sources/Sentry/SentryMetricProfiler.mm index 32193443f72..d02b221b6ed 100644 --- a/Sources/Sentry/SentryMetricProfiler.mm +++ b/Sources/Sentry/SentryMetricProfiler.mm @@ -1,37 +1,62 @@ #import "SentryMetricProfiler.h" +#import "SentryDependencyContainer.h" +#import "SentryLog.h" #import "SentryMachLogging.hpp" #import "SentryNSNotificationCenterWrapper.h" +#import "SentryNSProcessInfoWrapper.h" +#import "SentrySystemWrapper.h" #import "SentryTime.h" -#include -const NSTimeInterval kSentryMetricProfilerInterval = 0.1; // 10 Hz +const NSTimeInterval kSentryMetricProfilerTimeseriesInterval = 0.1; // 10 Hz + +namespace { +NSDictionary * +serializedValues(NSArray *> *values, NSString *unit) +{ + return @ { @"unit" : unit, @"values" : values }; +} +} // namespace @implementation SentryMetricProfiler { NSTimer *_timer; - SentryNSNotificationCenterWrapper *_notificationCenter; dispatch_source_t _memoryWarningSource; dispatch_queue_t _memoryWarningQueue; - NSMutableArray *> *_cpuTimeSeries; - NSMutableArray *> *_memoryFootprintTimeSeries; - NSMutableArray *> *_thermalStateChanges; - NSMutableArray *> *_powerLevelStateChanges; - NSMutableArray *> *_memoryPressureStateChanges; + + SentryNSProcessInfoWrapper *_processInfoWrapper; + SentrySystemWrapper *_systemWrapper; + + /// arrays of readings keyed on NSNumbers representing the core number for the set of readings + NSMutableDictionary *> *> + *_cpuUsage; + + NSMutableArray *> *_memoryFootprint; + NSMutableArray *> *_thermalState; + NSMutableArray *> *_powerLevelState; + NSMutableArray *> *_memoryPressureState; uint64_t _profileStartTime; } -- (instancetype)initWithNotificationCenterWrapper: - (SentryNSNotificationCenterWrapper *)notificationCenterWrapper - profileStartTime:(uint64_t)profileStartTime +- (instancetype)initWithProfileStartTime:(uint64_t)profileStartTime + processInfoWrapper:(SentryNSProcessInfoWrapper *)processInfoWrapper + systemWrapper:(SentrySystemWrapper *)systemWrapper { if (self = [super init]) { - _cpuTimeSeries = [NSMutableArray *> array]; - _memoryFootprintTimeSeries = [NSMutableArray *> array]; - _thermalStateChanges = [NSMutableArray *> array]; - _powerLevelStateChanges = [NSMutableArray *> array]; - _memoryPressureStateChanges = - [NSMutableArray *> array]; - - _notificationCenter = notificationCenterWrapper; + _cpuUsage = [NSMutableDictionary *> *> + dictionary]; + const auto processorCount = NSProcessInfo.processInfo.processorCount; + for (NSUInteger core = 0; core < processorCount; core++) { + _cpuUsage[@(core)] = [NSMutableArray *> array]; + } + + _systemWrapper = systemWrapper; + _processInfoWrapper = processInfoWrapper; + + _memoryFootprint = [NSMutableArray *> array]; + _thermalState = [NSMutableArray *> array]; + _powerLevelState = [NSMutableArray *> array]; + _memoryPressureState = [NSMutableArray *> array]; + _profileStartTime = profileStartTime; } return self; @@ -55,29 +80,35 @@ - (void)stop { [_timer invalidate]; dispatch_source_cancel(_memoryWarningSource); - [_notificationCenter removeObserver:self - name:NSProcessInfoThermalStateDidChangeNotification - object:NSProcessInfo.processInfo]; - [_notificationCenter removeObserver:self - name:NSProcessInfoPowerStateDidChangeNotification - object:NSProcessInfo.processInfo]; + [_processInfoWrapper stopMonitoring:self]; } -- (NSData *)serialize +- (NSMutableDictionary *)serialize { - // TODO: implement - return [[NSData alloc] init]; + const auto dict = [NSMutableDictionary + dictionaryWithObjectsAndKeys:serializedValues( + _memoryPressureState, @"memory-pressure-enum"), + @"memory-pressure", serializedValues(_powerLevelState, @"bool"), @"is-low-power-mode", + serializedValues(_memoryFootprint, @"bytes"), @"memory-footprint", + serializedValues(_thermalState, @"thermal-state-enum"), @"thermal-state", nil]; + [_cpuUsage enumerateKeysAndObjectsUsingBlock:^(NSNumber *_Nonnull core, + NSMutableArray *> *_Nonnull readings, + BOOL *_Nonnull stop) { + dict[[NSString stringWithFormat:@"cpu-usage-%d", core.intValue]] = readings; + }]; + return dict; } #pragma mark - Private - (void)registerSampler { - _timer = [NSTimer scheduledTimerWithTimeInterval:kSentryMetricProfilerInterval + __weak auto weakSelf = self; + _timer = [NSTimer scheduledTimerWithTimeInterval:kSentryMetricProfilerTimeseriesInterval repeats:YES block:^(NSTimer *_Nonnull timer) { - [self recordCPUPercentage]; - [self recordMemoryFootprint]; + [weakSelf recordCPUPercentagePerCore]; + [weakSelf recordMemoryFootprint]; }]; } @@ -89,17 +120,15 @@ - (void)registerSampler */ - (void)registerMemoryPressureWarningHandler { - const auto queueAttributes - = dispatch_queue_attr_make_with_qos_class(DISPATCH_QUEUE_SERIAL, QOS_CLASS_UTILITY, 0); - _memoryWarningQueue = dispatch_queue_create("io.sentry.queue.memory-warnings", queueAttributes); - _memoryWarningSource = dispatch_source_create(DISPATCH_SOURCE_TYPE_MEMORYPRESSURE, 0, - DISPATCH_MEMORYPRESSURE_NORMAL | DISPATCH_MEMORYPRESSURE_WARN - | DISPATCH_MEMORYPRESSURE_CRITICAL, - _memoryWarningQueue); - dispatch_source_set_event_handler(_memoryWarningSource, ^{ - [self recordMemoryPressureState:dispatch_source_get_data(self->_memoryWarningSource)]; - }); - dispatch_resume(_memoryWarningSource); + __weak auto weakSelf = self; + [_systemWrapper registerMemoryPressureNotifications:^(uintptr_t memoryPressureState) { + const auto strongSelf = weakSelf; + if (!strongSelf) { + return; + } + [strongSelf->_memoryPressureState + addObject:[strongSelf metricEntryForValue:@(memoryPressureState)]]; + }]; } - (void)registerStateChangeNotifications @@ -109,69 +138,47 @@ - (void)registerStateChangeNotifications // https://developer.apple.com/documentation/foundation/nsprocessinfothermalstatedidchangenotification/) [self recordThermalState]; - // According to Apple docs: "This notification is posted on the global dispatch queue. The - // object associated with the notification is NSProcessInfo.processInfo." - [_notificationCenter addObserver:self - selector:@selector(handleThermalStateChangeNotification:) - name:NSProcessInfoThermalStateDidChangeNotification - object:NSProcessInfo.processInfo]; - - // According to Apple docs: "This notification is posted on the global dispatch queue. The - // object associated with the notification is NSProcessInfo.processInfo." - [_notificationCenter addObserver:self - selector:@selector(handleThermalStateChangeNotification:) - name:NSProcessInfoPowerStateDidChangeNotification - object:NSProcessInfo.processInfo]; -} - -- (void)handleThermalStateChangeNotification:(NSNotification *)note -{ - [self recordThermalState]; -} - -- (void)handlePowerLevelStateChangeNotification:(NSNotification *)note -{ - [self recordPowerLevelState]; + [_processInfoWrapper monitorForThermalStateChanges:self callback:@selector(recordThermalState)]; + [_processInfoWrapper monitorForPowerStateChanges:self + callback:@selector(recordPowerLevelState)]; } - (void)recordThermalState { - [_thermalStateChanges - addObject:[self metricEntryForValue:@(NSProcessInfo.processInfo.thermalState)]]; + [_thermalState addObject:[self metricEntryForValue:@(_processInfoWrapper.thermalState)]]; } - (void)recordPowerLevelState { - [_powerLevelStateChanges - addObject:[self metricEntryForValue:@(NSProcessInfo.processInfo.lowPowerModeEnabled)]]; -} - -- (void)recordMemoryPressureState:(uintptr_t)memoryPressureState -{ - [_memoryPressureStateChanges addObject:[self metricEntryForValue:@(memoryPressureState)]]; + [_powerLevelState + addObject:[self metricEntryForValue:@(_processInfoWrapper.isLowPowerModeEnabled)]]; } - (void)recordMemoryFootprint { - task_vm_info_data_t info; - mach_msg_type_number_t count = TASK_VM_INFO_COUNT; - if (SENTRY_PROF_LOG_KERN_RETURN( - task_info(mach_task_self(), TASK_VM_INFO, (task_info_t)&info, &count)) - == KERN_SUCCESS) { - mach_vm_size_t footprintBytes; - if (count >= TASK_VM_INFO_REV1_COUNT) { - footprintBytes = info.phys_footprint; - } else { - footprintBytes = info.resident_size; - } + NSError *error; + const auto footprintBytes = [_systemWrapper memoryFootprintBytes:&error]; - [_memoryFootprintTimeSeries addObject:[self metricEntryForValue:@(footprintBytes)]]; + if (error) { + SENTRY_LOG_ERROR(@"Failed to read memory footprint: %@", error); + return; } + + [_memoryFootprint addObject:[self metricEntryForValue:@(footprintBytes)]]; } -- (void)recordCPUPercentage +- (void)recordCPUPercentagePerCore { - // TODO: implement + NSError *error; + const auto result = [_systemWrapper cpuUsagePerCore:&error]; + + if (error) { + SENTRY_LOG_ERROR(@"Failed to read CPU usages: %@", error); + return; + } + + [result enumerateObjectsUsingBlock:^(NSNumber *_Nonnull usage, NSUInteger core, + BOOL *_Nonnull stop) { [_cpuUsage[@(core)] addObject:[self metricEntryForValue:usage]]; }]; } - (NSDictionary *)metricEntryForValue:(NSNumber *)value diff --git a/Sources/Sentry/SentryNSNotificationCenterWrapper.m b/Sources/Sentry/SentryNSNotificationCenterWrapper.m index 7afa353f1ac..f235f2de55f 100644 --- a/Sources/Sentry/SentryNSNotificationCenterWrapper.m +++ b/Sources/Sentry/SentryNSNotificationCenterWrapper.m @@ -77,6 +77,11 @@ - (void)removeObserver:(id)observer [NSNotificationCenter.defaultCenter removeObserver:observer]; } +- (void)postNotificationName:(NSNotificationName)aName object:(id)anObject +{ + [NSNotificationCenter.defaultCenter postNotificationName:aName object:anObject]; +} + @end NS_ASSUME_NONNULL_END diff --git a/Sources/Sentry/SentryNSProcessInfoWrapper.h b/Sources/Sentry/SentryNSProcessInfoWrapper.h new file mode 100644 index 00000000000..3d456cea520 --- /dev/null +++ b/Sources/Sentry/SentryNSProcessInfoWrapper.h @@ -0,0 +1,16 @@ +#import + +NS_ASSUME_NONNULL_BEGIN + +@interface SentryNSProcessInfoWrapper : NSObject + +@property (readonly) NSProcessInfoThermalState thermalState; +@property (readonly, getter=isLowPowerModeEnabled) BOOL lowPowerModeEnabled; + +- (void)monitorForPowerStateChanges:(id)target callback:(SEL)callback; +- (void)monitorForThermalStateChanges:(id)target callback:(SEL)callback; +- (void)stopMonitoring:(id)target; + +@end + +NS_ASSUME_NONNULL_END diff --git a/Sources/Sentry/SentryNSProcessInfoWrapper.mm b/Sources/Sentry/SentryNSProcessInfoWrapper.mm new file mode 100644 index 00000000000..3d600fabda0 --- /dev/null +++ b/Sources/Sentry/SentryNSProcessInfoWrapper.mm @@ -0,0 +1,50 @@ +#import "SentryNSProcessInfoWrapper.h" +#import "SentryDependencyContainer.h" +#import "SentryNSNotificationCenterWrapper.h" + +@implementation SentryNSProcessInfoWrapper + +- (NSProcessInfoThermalState)thermalState +{ + return NSProcessInfo.processInfo.thermalState; +} + +- (BOOL)isLowPowerModeEnabled +{ + return NSProcessInfo.processInfo.isLowPowerModeEnabled; +} + +- (void)monitorForPowerStateChanges:(id)target callback:(SEL)callback +{ + // According to Apple docs: "This notification is posted on the global dispatch queue. The + // object associated with the notification is NSProcessInfo.processInfo." + [SentryDependencyContainer.sharedInstance.notificationCenterWrapper + addObserver:target + selector:callback + name:NSProcessInfoPowerStateDidChangeNotification + object:NSProcessInfo.processInfo]; +} + +- (void)monitorForThermalStateChanges:(id)target callback:(SEL)callback +{ + // According to Apple docs: "This notification is posted on the global dispatch queue. The + // object associated with the notification is NSProcessInfo.processInfo." + [SentryDependencyContainer.sharedInstance.notificationCenterWrapper + addObserver:target + selector:callback + name:NSProcessInfoThermalStateDidChangeNotification + object:NSProcessInfo.processInfo]; +} + +- (void)stopMonitoring:(id)target +{ + const auto notifier = SentryDependencyContainer.sharedInstance.notificationCenterWrapper; + [notifier removeObserver:target + name:NSProcessInfoThermalStateDidChangeNotification + object:NSProcessInfo.processInfo]; + [notifier removeObserver:target + name:NSProcessInfoPowerStateDidChangeNotification + object:NSProcessInfo.processInfo]; +} + +@end diff --git a/Sources/Sentry/SentryProfiler.mm b/Sources/Sentry/SentryProfiler.mm index 99d0c7ad6ee..f7ba7ae3368 100644 --- a/Sources/Sentry/SentryProfiler.mm +++ b/Sources/Sentry/SentryProfiler.mm @@ -17,11 +17,14 @@ # import "SentryHub+Private.h" # import "SentryId.h" # import "SentryLog.h" +# import "SentryMetricProfiler.h" +# import "SentryNSProcessInfoWrapper.h" # import "SentrySamplingProfiler.hpp" # import "SentryScope+Private.h" # import "SentryScreenFrames.h" # import "SentrySerialization.h" # import "SentrySpanId.h" +# import "SentrySystemWrapper.h" # import "SentryThread.h" # import "SentryTime.h" # import "SentryTransaction.h" @@ -168,6 +171,7 @@ @implementation SentryProfiler { uint64_t _endTimestamp; NSDate *_endDate; std::shared_ptr _profiler; + SentryMetricProfiler *_metricProfiler; SentryDebugImageProvider *_debugImageProvider; thread::TIDType _mainThreadID; @@ -177,6 +181,9 @@ @implementation SentryProfiler { SentryScreenFrames *_frameInfo; NSTimer *_timeoutTimer; SentryHub *__weak _hub; + + SentryNSProcessInfoWrapper *_processInfoWrapper; + SentrySystemWrapper *_systemWrapper; } + (void)initialize @@ -319,6 +326,20 @@ + (BOOL)isRunning # endif // SENTRY_TARGET_PROFILING_SUPPORTED } +# pragma mark - Testing + ++ (void)useSystemWrapper:(SentrySystemWrapper *)systemWrapper +{ + std::lock_guard l(_gProfilerLock); + _gCurrentProfiler->_systemWrapper = systemWrapper; +} + ++ (void)useProcessInfoWrapper:(SentryNSProcessInfoWrapper *)processInfoWrapper +{ + std::lock_guard l(_gProfilerLock); + _gCurrentProfiler->_processInfoWrapper = processInfoWrapper; +} + # pragma mark - Private + (void)captureEnvelopeIfFinished:(SentryProfiler *)profiler spanID:(SentrySpanId *)spanID @@ -463,6 +484,17 @@ - (void)start }, kSentryProfilerFrequencyHz); _profiler->startSampling(); + + if (_systemWrapper == nil) { + _systemWrapper = [[SentrySystemWrapper alloc] init]; + } + if (_processInfoWrapper == nil) { + _processInfoWrapper = [[SentryNSProcessInfoWrapper alloc] init]; + } + _metricProfiler = [[SentryMetricProfiler alloc] initWithProfileStartTime:_startTimestamp + processInfoWrapper:_processInfoWrapper + systemWrapper:_systemWrapper]; + [_metricProfiler start]; } } @@ -492,6 +524,7 @@ - (void)stop _profiler->stopSampling(); _endTimestamp = getAbsoluteTime(); _endDate = [SentryCurrentDate date]; + [_metricProfiler stop]; SENTRY_LOG_DEBUG(@"Stopped profiler %@ at system time: %llu.", self, _endTimestamp); } } @@ -499,8 +532,10 @@ - (void)stop - (void)captureEnvelope { NSMutableDictionary *profile = nil; + NSMutableDictionary *metrics; @synchronized(self) { profile = [_profile mutableCopy]; + metrics = [_metricProfiler serialize]; } if ([((NSArray *)profile[@"profile"][@"samples"]) count] < 2) { @@ -550,6 +585,8 @@ - (void)captureEnvelope profile[@"timestamp"] = [[SentryCurrentDate date] sentry_toIso8601String]; profile[@"release"] = _hub.getClient.options.releaseName; + profile[@"measurements"] = metrics; + # if SENTRY_HAS_UIKIT auto relativeFrameTimestampsNs = [NSMutableArray array]; [_frameInfo.frameTimestamps enumerateObjectsUsingBlock:^( @@ -565,12 +602,15 @@ - (void)captureEnvelope @"will not report it."); return; } + const auto relativeStart = getDurationNs(_startTimestamp, begin); + const auto frameDuration = relativeEnd - relativeStart; [relativeFrameTimestampsNs addObject:@{ - @"start_timestamp_relative_ns" : @(getDurationNs(_startTimestamp, begin)), - @"end_timestamp_relative_ns" : @(relativeEnd), + @"elapsed_since_start_ns" : @(relativeStart), + @"value" : @(frameDuration), }]; }]; - profile[@"adverse_frame_render_timestamps"] = relativeFrameTimestampsNs; + metrics[@"slow_frame_renders"] = + @{ @"unit" : @"nanoseconds", @"values" : relativeFrameTimestampsNs }; relativeFrameTimestampsNs = [NSMutableArray array]; [_frameInfo.frameRateTimestamps enumerateObjectsUsingBlock:^( @@ -582,11 +622,11 @@ - (void)captureEnvelope relativeTimestamp = getDurationNs(_startTimestamp, timestamp); } [relativeFrameTimestampsNs addObject:@{ - @"start_timestamp_relative_ns" : @(relativeTimestamp), - @"frame_rate" : refreshRate, + @"elapsed_since_start_ns" : @(relativeTimestamp), + @"value" : refreshRate, }]; }]; - profile[@"screen_frame_rates"] = relativeFrameTimestampsNs; + metrics[@"screen_frame_rates"] = @{ @"unit" : @"hz", @"values" : relativeFrameTimestampsNs }; # endif // SENTRY_HAS_UIKIT // populate info from all transactions that occurred while profiler was running diff --git a/Sources/Sentry/SentrySystemWrapper.h b/Sources/Sentry/SentrySystemWrapper.h new file mode 100644 index 00000000000..0a1f8b0e875 --- /dev/null +++ b/Sources/Sentry/SentrySystemWrapper.h @@ -0,0 +1,26 @@ +#import + +NS_ASSUME_NONNULL_BEGIN + +typedef void (^SentryMemoryPressureNotification)(uintptr_t); + +/** + * A wrapper around low-level system APIs that are found in headers such as @c and @c + * . + */ +@interface SentrySystemWrapper : NSObject + +- (mach_vm_size_t)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 ...] . + */ +- (nullable NSArray *)cpuUsagePerCore:(NSError **)error; + +- (void)registerMemoryPressureNotifications:(SentryMemoryPressureNotification)handler; + +@end + +NS_ASSUME_NONNULL_END diff --git a/Sources/Sentry/SentrySystemWrapper.mm b/Sources/Sentry/SentrySystemWrapper.mm new file mode 100644 index 00000000000..2d72c11467c --- /dev/null +++ b/Sources/Sentry/SentrySystemWrapper.mm @@ -0,0 +1,91 @@ +#import "SentrySystemWrapper.h" +#import "SentryError.h" +#import "SentryMachLogging.hpp" +#import + +@implementation SentrySystemWrapper { + dispatch_source_t _memoryWarningSource; + dispatch_queue_t _memoryWarningQueue; +} + +- (mach_vm_size_t)memoryFootprintBytes:(NSError *__autoreleasing _Nullable *)error +{ + task_vm_info_data_t info; + mach_msg_type_number_t count = TASK_VM_INFO_COUNT; + + const auto status = task_info(mach_task_self(), TASK_VM_INFO, (task_info_t)&info, &count); + if (status != KERN_SUCCESS) { + if (error) { + *error = NSErrorFromSentryErrorWithKernelError( + kSentryErrorKernel, @"task_info reported an error.", status); + } + return 0; + } + + mach_vm_size_t footprintBytes; + if (count >= TASK_VM_INFO_REV1_COUNT) { + footprintBytes = info.phys_footprint; + } else { + footprintBytes = info.resident_size; + } + + return footprintBytes; +} + +- (NSArray *)cpuUsagePerCore:(NSError **)error +{ + natural_t numCPUs = 0U; + processor_info_array_t cpuInfo; + mach_msg_type_number_t numCPUInfo; + const auto status = host_processor_info( + mach_host_self(), PROCESSOR_CPU_LOAD_INFO, &numCPUs, &cpuInfo, &numCPUInfo); + if (status != KERN_SUCCESS) { + if (error) { + *error = NSErrorFromSentryErrorWithKernelError( + kSentryErrorKernel, @"host_processor_info reported an error.", status); + } + return nil; + } + + const auto cpuUsageLock = [[NSLock alloc] init]; + [cpuUsageLock lock]; + + NSMutableArray *result = [NSMutableArray arrayWithCapacity:numCPUs]; + for (natural_t core = 0U; core < numCPUs; ++core) { + const auto indexBase = CPU_STATE_MAX * core; + const float user = cpuInfo[indexBase + CPU_STATE_USER]; + const float sys = cpuInfo[indexBase + CPU_STATE_SYSTEM]; + const float nice = cpuInfo[indexBase + CPU_STATE_NICE]; + const float idle = cpuInfo[indexBase + CPU_STATE_IDLE]; + const auto inUse = user + sys + nice; + const auto total = inUse + idle; + const auto usagePercent = inUse / total * 100.f; + [result addObject:@(usagePercent)]; + } + + [cpuUsageLock unlock]; + + return result; +} + +- (void)registerMemoryPressureNotifications:(SentryMemoryPressureNotification)handler +{ + const auto queueAttributes + = dispatch_queue_attr_make_with_qos_class(DISPATCH_QUEUE_SERIAL, QOS_CLASS_UTILITY, 0); + _memoryWarningQueue = dispatch_queue_create("io.sentry.queue.memory-warnings", queueAttributes); + _memoryWarningSource = dispatch_source_create(DISPATCH_SOURCE_TYPE_MEMORYPRESSURE, 0, + DISPATCH_MEMORYPRESSURE_NORMAL | DISPATCH_MEMORYPRESSURE_WARN + | DISPATCH_MEMORYPRESSURE_CRITICAL, + _memoryWarningQueue); + __weak auto weakSelf = self; + dispatch_source_set_event_handler(_memoryWarningSource, ^{ + const auto strongSelf = weakSelf; + if (!strongSelf) { + return; + } + handler(dispatch_source_get_data(strongSelf->_memoryWarningSource)); + }); + dispatch_resume(_memoryWarningSource); +} + +@end diff --git a/Sources/Sentry/include/SentryMachLogging.hpp b/Sources/Sentry/include/SentryMachLogging.hpp index 39ce68186c3..625f2e065de 100644 --- a/Sources/Sentry/include/SentryMachLogging.hpp +++ b/Sources/Sentry/include/SentryMachLogging.hpp @@ -6,27 +6,25 @@ #include namespace sentry { -namespace profiling { - /** - * Returns a human readable description string for a kernel return code. - * - * @param kr The kernel return code to get a description for. - * @return A string containing the description, or an unknown error message if - * the error code is not known. - */ - const char *kernelReturnCodeDescription(kern_return_t kr) noexcept; +/** + * Returns a human readable description string for a kernel return code. + * + * @param kr The kernel return code to get a description for. + * @return A string containing the description, or an unknown error message if + * the error code is not known. + */ +const char *kernelReturnCodeDescription(kern_return_t kr) noexcept; - /** - * Returns a human readable description string for a mach message return code. - * - * @param mr The mach message return code to get a description for. - * @return A string containing the description, or an unknown error message if - * the error code is not known. - */ - const char *machMessageReturnCodeDescription(mach_msg_return_t mr) noexcept; +/** + * Returns a human readable description string for a mach message return code. + * + * @param mr The mach message return code to get a description for. + * @return A string containing the description, or an unknown error message if + * the error code is not known. + */ +const char *machMessageReturnCodeDescription(mach_msg_return_t mr) noexcept; -} // namespace profiling } // namespace sentry #define SENTRY_PROF_LOG_KERN_RETURN(statement) \ @@ -34,7 +32,7 @@ namespace profiling { const kern_return_t __log_kr = statement; \ if (__log_kr != KERN_SUCCESS) { \ SENTRY_PROF_LOG_ERROR("%s failed with kern return code: %d, description: %s", \ - #statement, __log_kr, sentry::profiling::kernelReturnCodeDescription(__log_kr)); \ + #statement, __log_kr, sentry::kernelReturnCodeDescription(__log_kr)); \ } \ __log_kr; \ }) @@ -44,8 +42,7 @@ namespace profiling { const mach_msg_return_t __log_mr = statement; \ if (__log_mr != MACH_MSG_SUCCESS) { \ SENTRY_PROF_LOG_ERROR("%s failed with mach_msg return code: %d, description: %s", \ - #statement, __log_mr, \ - sentry::profiling::machMessageReturnCodeDescription(__log_mr)); \ + #statement, __log_mr, sentry::machMessageReturnCodeDescription(__log_mr)); \ } \ __log_mr; \ }) diff --git a/Sources/Sentry/include/SentryNSNotificationCenterWrapper.h b/Sources/Sentry/include/SentryNSNotificationCenterWrapper.h index b9bb252ffd4..0bb69def33f 100644 --- a/Sources/Sentry/include/SentryNSNotificationCenterWrapper.h +++ b/Sources/Sentry/include/SentryNSNotificationCenterWrapper.h @@ -31,6 +31,8 @@ NS_ASSUME_NONNULL_BEGIN - (void)removeObserver:(id)observer; +- (void)postNotificationName:(NSNotificationName)aName object:(id)anObject; + NS_ASSUME_NONNULL_END @end diff --git a/Sources/Sentry/include/SentryProfiler+Test.h b/Sources/Sentry/include/SentryProfiler+Test.h index 8ad95a01fb0..bf9f2535ae4 100644 --- a/Sources/Sentry/include/SentryProfiler+Test.h +++ b/Sources/Sentry/include/SentryProfiler+Test.h @@ -12,3 +12,20 @@ void processBacktrace(const sentry::profiling::Backtrace &backtrace, NSMutableDictionary *frameIndexLookup, uint64_t startTimestamp, NSMutableDictionary *stackIndexLookup); #endif + +@interface +SentryProfiler () + +/** + * By default, the profiler will use an instance of @c SentrySystemWrapper. Use this method to swap + * out for a different instance, like @c TestSentrySystemWrapper. + */ ++ (void)useSystemWrapper:(SentrySystemWrapper *)systemWrapper; + +/** + * By default, the profiler will use an instance of @c SentrySystemWrapper. Use this method to swap + * out for a different instance, like @c TestSentrySystemWrapper. + */ ++ (void)useProcessInfoWrapper:(SentryNSProcessInfoWrapper *)processInfoWrapper; + +@end diff --git a/Sources/Sentry/include/SentryProfiler.h b/Sources/Sentry/include/SentryProfiler.h index 52316906529..08006b7f1ce 100644 --- a/Sources/Sentry/include/SentryProfiler.h +++ b/Sources/Sentry/include/SentryProfiler.h @@ -7,10 +7,12 @@ @class SentryFramesTracker; #endif // SENTRY_HAS_UIKIT @class SentryHub; +@class SentryNSProcessInfoWrapper; @class SentryProfilesSamplerDecision; @class SentryScreenFrames; @class SentryEnvelope; @class SentrySpanId; +@class SentrySystemWrapper; @class SentryTransaction; #if SENTRY_TARGET_PROFILING_SUPPORTED diff --git a/Tests/SentryTests/Helper/TestSentryNSProcessInfoWrapper.swift b/Tests/SentryTests/Helper/TestSentryNSProcessInfoWrapper.swift new file mode 100644 index 00000000000..81e24ec4cac --- /dev/null +++ b/Tests/SentryTests/Helper/TestSentryNSProcessInfoWrapper.swift @@ -0,0 +1,26 @@ +import Sentry + +class TestSentryNSProcessInfoWrapper: SentryNSProcessInfoWrapper { + struct Override { + var isLowPowerModeEnabled: Bool? + var thermalState: ProcessInfo.ThermalState? + } + + var overrides = Override() + + override var thermalState: ProcessInfo.ThermalState { + overrides.thermalState ?? super.thermalState + } + + override var isLowPowerModeEnabled: Bool { + overrides.isLowPowerModeEnabled ?? super.isLowPowerModeEnabled + } + + func sendPowerStateChangeNotification() { + SentryDependencyContainer.sharedInstance().notificationCenterWrapper.postNotificationName(NSNotification.Name.NSProcessInfoPowerStateDidChange, object: ProcessInfo.processInfo) + } + + func sendThermalStateChangeNotification() { + SentryDependencyContainer.sharedInstance().notificationCenterWrapper.postNotificationName(ProcessInfo.thermalStateDidChangeNotification, object: ProcessInfo.processInfo) + } +} diff --git a/Tests/SentryTests/Helper/TestSentrySystemWrapper.swift b/Tests/SentryTests/Helper/TestSentrySystemWrapper.swift new file mode 100644 index 00000000000..a25a2be2816 --- /dev/null +++ b/Tests/SentryTests/Helper/TestSentrySystemWrapper.swift @@ -0,0 +1,28 @@ +import Sentry + +class TestSentrySystemWrapper: SentrySystemWrapper { + struct Override { + var memoryFootprintError: NSError? + var memoryFootprintBytes: mach_vm_size_t? + + var cpuUsageError: NSError? + var cpuUsagePerCore: [NSNumber]? + } + + var overrides = Override() + + override func memoryFootprintBytes(_ error: NSErrorPointer) -> mach_vm_size_t { + if let errorOverride = overrides.memoryFootprintError { + error?.pointee = errorOverride + return 0 + } + return overrides.memoryFootprintBytes ?? super.memoryFootprintBytes(error) + } + + override func cpuUsagePerCore() throws -> [NSNumber] { + if let errorOverride = overrides.cpuUsageError { + throw errorOverride + } + return try overrides.cpuUsagePerCore ?? super.cpuUsagePerCore() + } +} diff --git a/Tests/SentryTests/SentryClientTests.swift b/Tests/SentryTests/SentryClientTests.swift index 86236d71dbb..58581f857e6 100644 --- a/Tests/SentryTests/SentryClientTests.swift +++ b/Tests/SentryTests/SentryClientTests.swift @@ -627,7 +627,7 @@ class SentryClientTest: XCTestCase { event.debugMeta = nil fixture.deviceWrapper.internalOrientation = .landscapeLeft - fixture.deviceWrapper.interalBatteryState = .full + fixture.deviceWrapper.internalBatteryState = .full fixture.getSut().captureCrash(event, with: fixture.scope) diff --git a/Tests/SentryTests/SentryCrash/TestSentryUIDeviceWrapper.swift b/Tests/SentryTests/SentryCrash/TestSentryUIDeviceWrapper.swift index c3caae43b64..dcb9a2378d2 100644 --- a/Tests/SentryTests/SentryCrash/TestSentryUIDeviceWrapper.swift +++ b/Tests/SentryTests/SentryCrash/TestSentryUIDeviceWrapper.swift @@ -5,7 +5,7 @@ class TestSentryUIDeviceWrapper: SentryUIDeviceWrapper { var internalOrientation = UIDeviceOrientation.portrait var internalIsBatteryMonitoringEnabled = true var internalBatteryLevel: Float = 0.6 - var interalBatteryState = UIDevice.BatteryState.charging + var internalBatteryState = UIDevice.BatteryState.charging override func orientation() -> UIDeviceOrientation { return internalOrientation @@ -20,7 +20,7 @@ class TestSentryUIDeviceWrapper: SentryUIDeviceWrapper { } override func batteryState() -> UIDevice.BatteryState { - return interalBatteryState + return internalBatteryState } #endif } diff --git a/Tests/SentryTests/SentryTests-Bridging-Header.h b/Tests/SentryTests/SentryTests-Bridging-Header.h index e8d251dc70a..252c95d0074 100644 --- a/Tests/SentryTests/SentryTests-Bridging-Header.h +++ b/Tests/SentryTests/SentryTests-Bridging-Header.h @@ -103,6 +103,7 @@ #import "SentryNSDataTracker.h" #import "SentryNSError.h" #import "SentryNSNotificationCenterWrapper.h" +#import "SentryNSProcessInfoWrapper.h" #import "SentryNSURLRequest.h" #import "SentryNSURLRequestBuilder.h" #import "SentryNSURLSessionTaskSearch.h" @@ -145,6 +146,7 @@ #import "SentrySwizzleWrapper.h" #import "SentrySysctl.h" #import "SentrySystemEventBreadcrumbs.h" +#import "SentrySystemWrapper.h" #import "SentryTestIntegration.h" #import "SentryTestObjCRuntimeWrapper.h" #import "SentryThread.h" From 288e63168971cbb58552a7fa5803a00770982ee7 Mon Sep 17 00:00:00 2001 From: Andrew McKnight Date: Thu, 8 Dec 2022 13:13:00 -0900 Subject: [PATCH 06/70] start writing the test; only add metrics to payload if any were gathered --- Sentry.xcodeproj/project.pbxproj | 2 + Sources/Sentry/SentryMetricProfiler.mm | 26 +++-- Sources/Sentry/include/SentryProfiler+Test.h | 17 ---- .../Helper/SentryProfiler+SwiftTest.h | 22 +++++ .../Profiling/SentryProfilerSwiftTests.swift | 97 +++++++++++++------ .../SentryTests/SentryTests-Bridging-Header.h | 3 +- 6 files changed, 114 insertions(+), 53 deletions(-) create mode 100644 Tests/SentryTests/Helper/SentryProfiler+SwiftTest.h diff --git a/Sentry.xcodeproj/project.pbxproj b/Sentry.xcodeproj/project.pbxproj index 89e2d72f516..6eb5c2d2b97 100644 --- a/Sentry.xcodeproj/project.pbxproj +++ b/Sentry.xcodeproj/project.pbxproj @@ -1424,6 +1424,7 @@ 844EDC74294144DB00C86F34 /* SentrySystemWrapper.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SentrySystemWrapper.h; sourceTree = ""; }; 844EDC75294144DB00C86F34 /* SentrySystemWrapper.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = SentrySystemWrapper.mm; sourceTree = ""; }; 844EDC7829415AB300C86F34 /* TestSentrySystemWrapper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TestSentrySystemWrapper.swift; sourceTree = ""; }; + 844EDC7B2942843400C86F34 /* SentryProfiler+SwiftTest.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = "SentryProfiler+SwiftTest.h"; path = "Tests/SentryTests/Helper/SentryProfiler+SwiftTest.h"; sourceTree = SOURCE_ROOT; }; 8453421128BE855D00C22EEC /* SentrySampleDecision.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SentrySampleDecision.m; sourceTree = ""; }; 8453421528BE8A9500C22EEC /* SentrySpanStatus.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SentrySpanStatus.m; sourceTree = ""; }; 8454CF8A293EAF9A006AC140 /* SentryMetricProfiler.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = SentryMetricProfiler.h; path = Sources/Sentry/SentryMetricProfiler.h; sourceTree = SOURCE_ROOT; }; @@ -2828,6 +2829,7 @@ 8454CF8A293EAF9A006AC140 /* SentryMetricProfiler.h */, 8454CF8B293EAF9A006AC140 /* SentryMetricProfiler.mm */, 84A888FC28D9B11700C51DFD /* SentryProfiler+Test.h */, + 844EDC7B2942843400C86F34 /* SentryProfiler+SwiftTest.h */, 03F84D2B27DD4191008FE43F /* SentryProfiler.mm */, 03BCC38D27E2A377003232C7 /* SentryProfilingConditionals.h */, 03F84D2927DD416B008FE43F /* SentryProfilingLogging.hpp */, diff --git a/Sources/Sentry/SentryMetricProfiler.mm b/Sources/Sentry/SentryMetricProfiler.mm index d02b221b6ed..59fc7f1d19b 100644 --- a/Sources/Sentry/SentryMetricProfiler.mm +++ b/Sources/Sentry/SentryMetricProfiler.mm @@ -85,17 +85,29 @@ - (void)stop - (NSMutableDictionary *)serialize { - const auto dict = [NSMutableDictionary - dictionaryWithObjectsAndKeys:serializedValues( - _memoryPressureState, @"memory-pressure-enum"), - @"memory-pressure", serializedValues(_powerLevelState, @"bool"), @"is-low-power-mode", - serializedValues(_memoryFootprint, @"bytes"), @"memory-footprint", - serializedValues(_thermalState, @"thermal-state-enum"), @"thermal-state", nil]; + const auto dict = [NSMutableDictionary dictionary]; + + if (_memoryFootprint.count > 0) { + dict[@"memory-footprint"] = serializedValues(_memoryFootprint, @"bytes"); + } + if (_memoryPressureState.count > 0) { + dict[@"memory-pressure"] = serializedValues(_memoryPressureState, @"memory-pressure-enum"); + } + if (_powerLevelState.count > 0) { + dict[@"is-low-power-mode"] = serializedValues(_powerLevelState, @"bool"); + } + if (_thermalState.count > 0) { + dict[@"thermal-state"] = serializedValues(_thermalState, @"thermal-state-enum"); + } + [_cpuUsage enumerateKeysAndObjectsUsingBlock:^(NSNumber *_Nonnull core, NSMutableArray *> *_Nonnull readings, BOOL *_Nonnull stop) { - dict[[NSString stringWithFormat:@"cpu-usage-%d", core.intValue]] = readings; + if (readings.count > 0) { + dict[[NSString stringWithFormat:@"cpu-usage-%d", core.intValue]] = readings; + } }]; + return dict; } diff --git a/Sources/Sentry/include/SentryProfiler+Test.h b/Sources/Sentry/include/SentryProfiler+Test.h index bf9f2535ae4..8ad95a01fb0 100644 --- a/Sources/Sentry/include/SentryProfiler+Test.h +++ b/Sources/Sentry/include/SentryProfiler+Test.h @@ -12,20 +12,3 @@ void processBacktrace(const sentry::profiling::Backtrace &backtrace, NSMutableDictionary *frameIndexLookup, uint64_t startTimestamp, NSMutableDictionary *stackIndexLookup); #endif - -@interface -SentryProfiler () - -/** - * By default, the profiler will use an instance of @c SentrySystemWrapper. Use this method to swap - * out for a different instance, like @c TestSentrySystemWrapper. - */ -+ (void)useSystemWrapper:(SentrySystemWrapper *)systemWrapper; - -/** - * By default, the profiler will use an instance of @c SentrySystemWrapper. Use this method to swap - * out for a different instance, like @c TestSentrySystemWrapper. - */ -+ (void)useProcessInfoWrapper:(SentryNSProcessInfoWrapper *)processInfoWrapper; - -@end diff --git a/Tests/SentryTests/Helper/SentryProfiler+SwiftTest.h b/Tests/SentryTests/Helper/SentryProfiler+SwiftTest.h new file mode 100644 index 00000000000..d9da73a2408 --- /dev/null +++ b/Tests/SentryTests/Helper/SentryProfiler+SwiftTest.h @@ -0,0 +1,22 @@ +// This is a separate header extension that's able to be included into the Swift bridging header for +// the tests. SentryProfiler+Test.h contains C++ symbols which are not able to be exported to Swift. + +#include "SentryProfiler.h" + +@interface +SentryProfiler () + +/** + * By default, the profiler will use an instance of @c SentrySystemWrapper. Use this method to swap + * out for a different instance, like @c TestSentrySystemWrapper. + */ ++ (void)useSystemWrapper:(SentrySystemWrapper *)systemWrapper NS_SWIFT_NAME(useSystemWrapper(_:)); + +/** + * By default, the profiler will use an instance of @c SentrySystemWrapper. Use this method to swap + * out for a different instance, like @c TestSentrySystemWrapper. + */ ++ (void)useProcessInfoWrapper:(SentryNSProcessInfoWrapper *)processInfoWrapper + NS_SWIFT_NAME(useProcessInfoWrapper(_:)); + +@end diff --git a/Tests/SentryTests/Profiling/SentryProfilerSwiftTests.swift b/Tests/SentryTests/Profiling/SentryProfilerSwiftTests.swift index 166ca2985e7..f320295c4f6 100644 --- a/Tests/SentryTests/Profiling/SentryProfilerSwiftTests.swift +++ b/Tests/SentryTests/Profiling/SentryProfilerSwiftTests.swift @@ -23,6 +23,12 @@ class SentryProfilerSwiftTests: XCTestCase { let message = "some message" let transactionName = "Some Transaction" let transactionOperation = "Some Operation" + lazy var systemWrapper = TestSentrySystemWrapper() + lazy var processInfoWrapper = TestSentryNSProcessInfoWrapper() + + func newTransaction() -> Span { + hub.startTransaction(name: transactionName, operation: transactionOperation) + } } private var fixture: Fixture! @@ -44,7 +50,41 @@ class SentryProfilerSwiftTests: XCTestCase { #endif } - func testConcurrentProfilingTransactions() { + func testMetricProfiler() { + SentryProfiler.useSystemWrapper(fixture.systemWrapper) + SentryProfiler.useProcessInfoWrapper(fixture.processInfoWrapper) + + let cpuUsages = [12.4, 63.5, 1.4, 4.6] + fixture.systemWrapper.overrides.cpuUsagePerCore = cpuUsages.map { NSNumber(value: $0) } + + let memoryFootprint: mach_vm_size_t = 123_455 + fixture.systemWrapper.overrides.memoryFootprintBytes = memoryFootprint + + let span = fixture.newTransaction() + forceProfilerSample() + + fixture.processInfoWrapper.overrides.isLowPowerModeEnabled = true + fixture.processInfoWrapper.sendThermalStateChangeNotification() + fixture.processInfoWrapper.overrides.isLowPowerModeEnabled = false + fixture.processInfoWrapper.sendThermalStateChangeNotification() + + fixture.processInfoWrapper.overrides.thermalState = .critical + fixture.processInfoWrapper.sendPowerStateChangeNotification() + fixture.processInfoWrapper.overrides.thermalState = .serious + fixture.processInfoWrapper.sendPowerStateChangeNotification() + fixture.processInfoWrapper.overrides.thermalState = .fair + fixture.processInfoWrapper.sendPowerStateChangeNotification() + fixture.processInfoWrapper.overrides.thermalState = .nominal + fixture.processInfoWrapper.sendPowerStateChangeNotification() + + try DispatchQueue.main.asyncAfter(deadline: .now() + 2) { + span.finish() + + let profileData = try getProfileData() + } + } + + func testConcurrentProfilingTransactions() throws { let options = fixture.options options.profilesSampleRate = 1.0 options.tracesSampleRate = 1.0 @@ -52,24 +92,15 @@ class SentryProfilerSwiftTests: XCTestCase { let numberOfTransactions = 10 var spans = [Span]() for _ in 0 ..< numberOfTransactions { - spans.append(fixture.hub.startTransaction(name: fixture.transactionName, operation: fixture.transactionOperation)) + spans.append(fixture.newTransaction()) } forceProfilerSample() spans.forEach { $0.finish() } - guard let envelope = self.fixture.client.captureEnvelopeInvocations.first else { - XCTFail("Expected to capture 1 event") - return - } - XCTAssertEqual(1, envelope.items.count) - guard let profileItem = envelope.items.first else { - XCTFail("Expected an envelope item") - return - } - XCTAssertEqual("profile", profileItem.header.type) - self.assertValidProfileData(data: profileItem.data, numberOfTransactions: numberOfTransactions) + let profileData = try getProfileData() + self.assertValidProfileData(data: profileData, numberOfTransactions: numberOfTransactions) } /// Test a situation where a long-running span starts the profiler, which winds up timing out, and then another span starts that begins a new profile, then finishes, and then the long-running span finishes; both profiles should be separately captured in envelopes. @@ -85,7 +116,7 @@ class SentryProfilerSwiftTests: XCTestCase { options.profilesSampleRate = 1.0 options.tracesSampleRate = 1.0 - let spanA = fixture.hub.startTransaction(name: fixture.transactionName, operation: fixture.transactionOperation) + let spanA = fixture.newTransaction() forceProfilerSample() @@ -96,7 +127,7 @@ class SentryProfilerSwiftTests: XCTestCase { } waitForExpectations(timeout: 3) - let spanB = self.fixture.hub.startTransaction(name: self.fixture.transactionName, operation: self.fixture.transactionOperation) + let spanB = fixture.newTransaction() forceProfilerSample() @@ -198,6 +229,25 @@ class SentryProfilerSwiftTests: XCTestCase { } private extension SentryProfilerSwiftTests { + enum TestError: String, Error { + case noEnvelopeCaptured = "Expected to capture 1 event" + case noProfileEnvelopeItem = "Expected an envelope item" + } + + func getProfileData() throws -> Data { + guard let envelope = self.fixture.client.captureEnvelopeInvocations.first else { + throw(TestError.noEnvelopeCaptured) + } + + XCTAssertEqual(1, envelope.items.count) + guard let profileItem = envelope.items.first else { + throw(TestError.noProfileEnvelopeItem) + } + + XCTAssertEqual("profile", profileItem.header.type) + return profileItem.data + } + /// Keep a thread busy over a long enough period of time (long enough for 3 samples) for the sampler to pick it up. func forceProfilerSample() { let str = "a" @@ -208,7 +258,7 @@ private extension SentryProfilerSwiftTests { } func performTest(transactionEnvironment: String = kSentryDefaultEnvironment, numberOfTransactions: Int = 1, shouldTimeOut: Bool = false) { - let span = fixture.hub.startTransaction(name: fixture.transactionName, operation: fixture.transactionOperation) + let span = fixture.newTransaction() forceProfilerSample() @@ -225,17 +275,8 @@ private extension SentryProfilerSwiftTests { waitForExpectations(timeout: 10) - guard let envelope = self.fixture.client.captureEnvelopeInvocations.first else { - XCTFail("Expected to capture at least 1 event") - return - } - XCTAssertEqual(1, envelope.items.count) - guard let profileItem = envelope.items.first else { - XCTFail("Expected an envelope item") - return - } - XCTAssertEqual("profile", profileItem.header.type) - self.assertValidProfileData(data: profileItem.data, transactionEnvironment: transactionEnvironment, numberOfTransactions: numberOfTransactions, shouldTimeout: shouldTimeOut) + let profileData = try getProfileData() + self.assertValidProfileData(data: profileData, transactionEnvironment: transactionEnvironment, numberOfTransactions: numberOfTransactions, shouldTimeout: shouldTimeOut) } @@ -362,7 +403,7 @@ private extension SentryProfilerSwiftTests { let hub = fixture.hub Dynamic(hub).tracesSampler.random = TestRandom(value: 1.0) - let span = hub.startTransaction(name: fixture.transactionName, operation: fixture.transactionOperation) + let span = fixture.newTransaction() let exp = expectation(description: "Span finishes") DispatchQueue.global().asyncAfter(deadline: .now() + 2.0) { span.finish() diff --git a/Tests/SentryTests/SentryTests-Bridging-Header.h b/Tests/SentryTests/SentryTests-Bridging-Header.h index 252c95d0074..998be6f9d3b 100644 --- a/Tests/SentryTests/SentryTests-Bridging-Header.h +++ b/Tests/SentryTests/SentryTests-Bridging-Header.h @@ -99,6 +99,7 @@ #import "SentryMechanism.h" #import "SentryMechanismMeta.h" #import "SentryMeta.h" +#import "SentryMetricProfiler.h" #import "SentryMigrateSessionInit.h" #import "SentryNSDataTracker.h" #import "SentryNSError.h" @@ -119,7 +120,7 @@ #import "SentryPerformanceTracker.h" #import "SentryPerformanceTrackingIntegration.h" #import "SentryPredicateDescriptor.h" -#import "SentryProfiler.h" +#import "SentryProfiler+SwiftTest.h" #import "SentryQueueableRequestManager.h" #import "SentryRandom.h" #import "SentryRateLimitParser.h" From bb77d06dfd2f48a43a891dd688a81a39cdbd28de Mon Sep 17 00:00:00 2001 From: Andrew McKnight Date: Thu, 8 Dec 2022 17:57:16 -0900 Subject: [PATCH 07/70] get test passing with various fixes --- Sources/Sentry/SentryMetricProfiler.h | 13 +++ Sources/Sentry/SentryMetricProfiler.mm | 34 ++++-- Sources/Sentry/SentryProfiler.mm | 24 ++-- Sources/Sentry/SentrySystemWrapper.h | 1 + Sources/Sentry/SentrySystemWrapper.mm | 5 + .../Profiling/SentryProfilerSwiftTests.swift | 110 +++++++++++++----- 6 files changed, 135 insertions(+), 52 deletions(-) diff --git a/Sources/Sentry/SentryMetricProfiler.h b/Sources/Sentry/SentryMetricProfiler.h index 742b3098f90..39653ac2bcd 100644 --- a/Sources/Sentry/SentryMetricProfiler.h +++ b/Sources/Sentry/SentryMetricProfiler.h @@ -1,3 +1,4 @@ +#import "SentryDefines.h" #import @class SentryNSProcessInfoWrapper; @@ -5,6 +6,18 @@ NS_ASSUME_NONNULL_BEGIN +SENTRY_EXTERN NSString *const kSentryMetricProfilerSerializationKeyMemoryFootprint; +SENTRY_EXTERN NSString *const kSentryMetricProfilerSerializationKeyMemoryPressure; +SENTRY_EXTERN NSString *const kSentryMetricProfilerSerializationKeyPowerState; +SENTRY_EXTERN NSString *const kSentryMetricProfilerSerializationKeyThermalState; +SENTRY_EXTERN NSString *const kSentryMetricProfilerSerializationKeyCPUUsageFormat; + +SENTRY_EXTERN NSString *const kSentryMetricProfilerSerializationUnitBytes; +SENTRY_EXTERN NSString *const kSentryMetricProfilerSerializationUnitBoolean; +SENTRY_EXTERN NSString *const kSentryMetricProfilerSerializationUnitMemoryPressureEnum; +SENTRY_EXTERN NSString *const kSentryMetricProfilerSerializationUnitThermalStateEnum; +SENTRY_EXTERN NSString *const kSentryMetricProfilerSerializationUnitPercentage; + /** * A profiler that gathers various time-series and event-based metrics on the app process, such as * CPU and memory usage timeseries and thermal and memory pressure warning notifications. diff --git a/Sources/Sentry/SentryMetricProfiler.mm b/Sources/Sentry/SentryMetricProfiler.mm index 59fc7f1d19b..1a33353dc04 100644 --- a/Sources/Sentry/SentryMetricProfiler.mm +++ b/Sources/Sentry/SentryMetricProfiler.mm @@ -7,7 +7,19 @@ #import "SentrySystemWrapper.h" #import "SentryTime.h" -const NSTimeInterval kSentryMetricProfilerTimeseriesInterval = 0.1; // 10 Hz +static const NSTimeInterval kSentryMetricProfilerTimeseriesInterval = 0.1; // 10 Hz + +NSString *const kSentryMetricProfilerSerializationKeyMemoryFootprint = @"memory-footprint"; +NSString *const kSentryMetricProfilerSerializationKeyMemoryPressure = @"memory-pressure"; +NSString *const kSentryMetricProfilerSerializationKeyPowerState = @"is-low-power-mode"; +NSString *const kSentryMetricProfilerSerializationKeyThermalState = @"thermal-state"; +NSString *const kSentryMetricProfilerSerializationKeyCPUUsageFormat = @"cpu-usage-%d"; + +NSString *const kSentryMetricProfilerSerializationUnitBytes = @"bytes"; +NSString *const kSentryMetricProfilerSerializationUnitBoolean = @"bool"; +NSString *const kSentryMetricProfilerSerializationUnitMemoryPressureEnum = @"memory-pressure-enum"; +NSString *const kSentryMetricProfilerSerializationUnitThermalStateEnum = @"thermal-state-enum"; +NSString *const kSentryMetricProfilerSerializationUnitPercentage = @"percent"; namespace { NSDictionary * @@ -19,8 +31,6 @@ @implementation SentryMetricProfiler { NSTimer *_timer; - dispatch_source_t _memoryWarningSource; - dispatch_queue_t _memoryWarningQueue; SentryNSProcessInfoWrapper *_processInfoWrapper; SentrySystemWrapper *_systemWrapper; @@ -79,7 +89,7 @@ - (void)start - (void)stop { [_timer invalidate]; - dispatch_source_cancel(_memoryWarningSource); + [_systemWrapper deregisterMemoryPressureNotifications]; [_processInfoWrapper stopMonitoring:self]; } @@ -88,23 +98,29 @@ - (void)stop const auto dict = [NSMutableDictionary dictionary]; if (_memoryFootprint.count > 0) { - dict[@"memory-footprint"] = serializedValues(_memoryFootprint, @"bytes"); + dict[kSentryMetricProfilerSerializationKeyMemoryFootprint] + = serializedValues(_memoryFootprint, kSentryMetricProfilerSerializationUnitBytes); } if (_memoryPressureState.count > 0) { - dict[@"memory-pressure"] = serializedValues(_memoryPressureState, @"memory-pressure-enum"); + dict[kSentryMetricProfilerSerializationKeyMemoryPressure] = serializedValues( + _memoryPressureState, kSentryMetricProfilerSerializationUnitMemoryPressureEnum); } if (_powerLevelState.count > 0) { - dict[@"is-low-power-mode"] = serializedValues(_powerLevelState, @"bool"); + dict[kSentryMetricProfilerSerializationKeyPowerState] + = serializedValues(_powerLevelState, kSentryMetricProfilerSerializationUnitBoolean); } if (_thermalState.count > 0) { - dict[@"thermal-state"] = serializedValues(_thermalState, @"thermal-state-enum"); + dict[kSentryMetricProfilerSerializationKeyThermalState] = serializedValues( + _thermalState, kSentryMetricProfilerSerializationUnitThermalStateEnum); } [_cpuUsage enumerateKeysAndObjectsUsingBlock:^(NSNumber *_Nonnull core, NSMutableArray *> *_Nonnull readings, BOOL *_Nonnull stop) { if (readings.count > 0) { - dict[[NSString stringWithFormat:@"cpu-usage-%d", core.intValue]] = readings; + dict[[NSString stringWithFormat:kSentryMetricProfilerSerializationKeyCPUUsageFormat, + core.intValue]] + = serializedValues(readings, kSentryMetricProfilerSerializationUnitPercentage); } }]; diff --git a/Sources/Sentry/SentryProfiler.mm b/Sources/Sentry/SentryProfiler.mm index f7ba7ae3368..fd19330e80d 100644 --- a/Sources/Sentry/SentryProfiler.mm +++ b/Sources/Sentry/SentryProfiler.mm @@ -150,6 +150,8 @@ std::mutex _gProfilerLock; NSMutableDictionary *_gProfilersPerSpanID; SentryProfiler *_Nullable _gCurrentProfiler; +SentryNSProcessInfoWrapper *_gCurrentProcessInfoWrapper; +SentrySystemWrapper *_gCurrentSystemWrapper; NSString * profilerTruncationReasonName(SentryProfilerTruncationReason reason) @@ -181,9 +183,6 @@ @implementation SentryProfiler { SentryScreenFrames *_frameInfo; NSTimer *_timeoutTimer; SentryHub *__weak _hub; - - SentryNSProcessInfoWrapper *_processInfoWrapper; - SentrySystemWrapper *_systemWrapper; } + (void)initialize @@ -331,13 +330,13 @@ + (BOOL)isRunning + (void)useSystemWrapper:(SentrySystemWrapper *)systemWrapper { std::lock_guard l(_gProfilerLock); - _gCurrentProfiler->_systemWrapper = systemWrapper; + _gCurrentSystemWrapper = systemWrapper; } + (void)useProcessInfoWrapper:(SentryNSProcessInfoWrapper *)processInfoWrapper { std::lock_guard l(_gProfilerLock); - _gCurrentProfiler->_processInfoWrapper = processInfoWrapper; + _gCurrentProcessInfoWrapper = processInfoWrapper; } # pragma mark - Private @@ -485,15 +484,16 @@ - (void)start kSentryProfilerFrequencyHz); _profiler->startSampling(); - if (_systemWrapper == nil) { - _systemWrapper = [[SentrySystemWrapper alloc] init]; + if (_gCurrentSystemWrapper == nil) { + _gCurrentSystemWrapper = [[SentrySystemWrapper alloc] init]; } - if (_processInfoWrapper == nil) { - _processInfoWrapper = [[SentryNSProcessInfoWrapper alloc] init]; + if (_gCurrentProcessInfoWrapper == nil) { + _gCurrentProcessInfoWrapper = [[SentryNSProcessInfoWrapper alloc] init]; } - _metricProfiler = [[SentryMetricProfiler alloc] initWithProfileStartTime:_startTimestamp - processInfoWrapper:_processInfoWrapper - systemWrapper:_systemWrapper]; + _metricProfiler = + [[SentryMetricProfiler alloc] initWithProfileStartTime:_startTimestamp + processInfoWrapper:_gCurrentProcessInfoWrapper + systemWrapper:_gCurrentSystemWrapper]; [_metricProfiler start]; } } diff --git a/Sources/Sentry/SentrySystemWrapper.h b/Sources/Sentry/SentrySystemWrapper.h index 0a1f8b0e875..3fae92c71e0 100644 --- a/Sources/Sentry/SentrySystemWrapper.h +++ b/Sources/Sentry/SentrySystemWrapper.h @@ -20,6 +20,7 @@ typedef void (^SentryMemoryPressureNotification)(uintptr_t); - (nullable NSArray *)cpuUsagePerCore:(NSError **)error; - (void)registerMemoryPressureNotifications:(SentryMemoryPressureNotification)handler; +- (void)deregisterMemoryPressureNotifications; @end diff --git a/Sources/Sentry/SentrySystemWrapper.mm b/Sources/Sentry/SentrySystemWrapper.mm index 2d72c11467c..09968c3b5be 100644 --- a/Sources/Sentry/SentrySystemWrapper.mm +++ b/Sources/Sentry/SentrySystemWrapper.mm @@ -88,4 +88,9 @@ - (void)registerMemoryPressureNotifications:(SentryMemoryPressureNotification)ha dispatch_resume(_memoryWarningSource); } +- (void)deregisterMemoryPressureNotifications +{ + dispatch_source_cancel(_memoryWarningSource); +} + @end diff --git a/Tests/SentryTests/Profiling/SentryProfilerSwiftTests.swift b/Tests/SentryTests/Profiling/SentryProfilerSwiftTests.swift index f320295c4f6..abce81f05c8 100644 --- a/Tests/SentryTests/Profiling/SentryProfilerSwiftTests.swift +++ b/Tests/SentryTests/Profiling/SentryProfilerSwiftTests.swift @@ -51,6 +51,9 @@ class SentryProfilerSwiftTests: XCTestCase { } func testMetricProfiler() { + let options = fixture.options + options.profilesSampleRate = 1.0 + options.tracesSampleRate = 1.0 SentryProfiler.useSystemWrapper(fixture.systemWrapper) SentryProfiler.useProcessInfoWrapper(fixture.processInfoWrapper) @@ -63,25 +66,28 @@ class SentryProfilerSwiftTests: XCTestCase { let span = fixture.newTransaction() forceProfilerSample() - fixture.processInfoWrapper.overrides.isLowPowerModeEnabled = true - fixture.processInfoWrapper.sendThermalStateChangeNotification() - fixture.processInfoWrapper.overrides.isLowPowerModeEnabled = false - fixture.processInfoWrapper.sendThermalStateChangeNotification() - - fixture.processInfoWrapper.overrides.thermalState = .critical - fixture.processInfoWrapper.sendPowerStateChangeNotification() - fixture.processInfoWrapper.overrides.thermalState = .serious - fixture.processInfoWrapper.sendPowerStateChangeNotification() - fixture.processInfoWrapper.overrides.thermalState = .fair - fixture.processInfoWrapper.sendPowerStateChangeNotification() - fixture.processInfoWrapper.overrides.thermalState = .nominal - fixture.processInfoWrapper.sendPowerStateChangeNotification() - - try DispatchQueue.main.asyncAfter(deadline: .now() + 2) { + [true, false].forEach { + fixture.processInfoWrapper.overrides.isLowPowerModeEnabled = $0 + fixture.processInfoWrapper.sendPowerStateChangeNotification() + } + + [ProcessInfo.ThermalState.critical, .serious, .fair, .nominal].forEach { + fixture.processInfoWrapper.overrides.thermalState = $0 + fixture.processInfoWrapper.sendThermalStateChangeNotification() + } + + let exp = expectation(description: "Receives profile payload") + DispatchQueue.main.asyncAfter(deadline: .now() + 2) { span.finish() - let profileData = try getProfileData() + do { + try self.assertMetricsPayload(thermalStateNotifications: 4, powerStateNotifications: 2, expectedCPUUsages: cpuUsages) + exp.fulfill() + } catch { + XCTFail("Encountered error: \(error)") + } } + waitForExpectations(timeout: 3) } func testConcurrentProfilingTransactions() throws { @@ -111,7 +117,7 @@ class SentryProfilerSwiftTests: XCTestCase { /// transaction B |-------| /// profiler B |-------| <- normal finish /// ``` - func testConcurrentSpansWithTimeout_disabled() { + func testConcurrentSpansWithTimeout_disabled() throws { let options = fixture.options options.profilesSampleRate = 1.0 options.tracesSampleRate = 1.0 @@ -139,8 +145,7 @@ class SentryProfilerSwiftTests: XCTestCase { for envelope in self.fixture.client.captureEnvelopeInvocations.invocations { XCTAssertEqual(1, envelope.items.count) guard let profileItem = envelope.items.first else { - XCTFail("Expected an envelope item") - return + throw TestError.noProfileEnvelopeItem } XCTAssertEqual("profile", profileItem.header.type) self.assertValidProfileData(data: profileItem.data, shouldTimeout: currentEnvelope == 1) @@ -148,34 +153,34 @@ class SentryProfilerSwiftTests: XCTestCase { } } - func testProfileTimeoutTimer_disabled() { + func testProfileTimeoutTimer_disabled() throws { fixture.options.profilesSampleRate = 1.0 fixture.options.tracesSampleRate = 1.0 - performTest(shouldTimeOut: true) + try performTest(shouldTimeOut: true) } - func testStartTransaction_ProfilingDataIsValid() { + func testStartTransaction_ProfilingDataIsValid() throws { fixture.options.profilesSampleRate = 1.0 fixture.options.tracesSampleRate = 1.0 - performTest() + try performTest() } - func testProfilingDataContainsEnvironmentSetFromOptions() { + func testProfilingDataContainsEnvironmentSetFromOptions() throws { fixture.options.profilesSampleRate = 1.0 fixture.options.tracesSampleRate = 1.0 let expectedEnvironment = "test-environment" fixture.options.environment = expectedEnvironment - performTest(transactionEnvironment: expectedEnvironment) + try performTest(transactionEnvironment: expectedEnvironment) } - func testProfilingDataContainsEnvironmentSetFromConfigureScope() { + func testProfilingDataContainsEnvironmentSetFromConfigureScope() throws { fixture.options.profilesSampleRate = 1.0 fixture.options.tracesSampleRate = 1.0 let expectedEnvironment = "test-environment" fixture.hub.configureScope { scope in scope.setEnvironment(expectedEnvironment) } - performTest(transactionEnvironment: expectedEnvironment) + try performTest(transactionEnvironment: expectedEnvironment) } func testStartTransaction_NotSamplingProfileUsingEnableProfiling() { @@ -229,9 +234,16 @@ class SentryProfilerSwiftTests: XCTestCase { } private extension SentryProfilerSwiftTests { - enum TestError: String, Error { - case noEnvelopeCaptured = "Expected to capture 1 event" - case noProfileEnvelopeItem = "Expected an envelope item" + enum TestError: Error { + case unexpectedProfileDeserializationType + case unexpectedMeasurementsDeserializationType + case noEnvelopeCaptured + case noProfileEnvelopeItem + case noPowerStateEvents + case noThermalStateEvents + case malformedMetricValueEntry + case noCPUUsageEvents + case noCPUUsageReported } func getProfileData() throws -> Data { @@ -257,7 +269,7 @@ private extension SentryProfilerSwiftTests { } } - func performTest(transactionEnvironment: String = kSentryDefaultEnvironment, numberOfTransactions: Int = 1, shouldTimeOut: Bool = false) { + func performTest(transactionEnvironment: String = kSentryDefaultEnvironment, numberOfTransactions: Int = 1, shouldTimeOut: Bool = false) throws { let span = fixture.newTransaction() forceProfilerSample() @@ -277,7 +289,43 @@ private extension SentryProfilerSwiftTests { let profileData = try getProfileData() self.assertValidProfileData(data: profileData, transactionEnvironment: transactionEnvironment, numberOfTransactions: numberOfTransactions, shouldTimeout: shouldTimeOut) + } + + func assertMetricsPayload(thermalStateNotifications: Int, powerStateNotifications: Int, expectedCPUUsages: [Double]) throws { + let profileData = try self.getProfileData() + guard let profile = try JSONSerialization.jsonObject(with: profileData) as? [String: Any] else { + throw TestError.unexpectedProfileDeserializationType + } + guard let measurements = profile["measurements"] as? [String: Any] else { + throw TestError.unexpectedMeasurementsDeserializationType + } + + guard let powerStateEntry = measurements[kSentryMetricProfilerSerializationKeyPowerState] as? [String: Any], let powerState = powerStateEntry["values"] as? [[String: Any]] else { + throw TestError.noPowerStateEvents + } + + XCTAssertEqual(powerState.count, powerStateNotifications) + + guard let thermalStateEntry = measurements[kSentryMetricProfilerSerializationKeyThermalState] as? [String: Any], let thermalState = thermalStateEntry["values"] as? [[String: Any]] else { + throw TestError.noThermalStateEvents + } + // one initial reading per API spec, then all the actual notifications sent + XCTAssertEqual(thermalState.count, thermalStateNotifications + 1) + + for (i, expectedUsage) in expectedCPUUsages.enumerated() { + guard let cpuUsage = measurements[NSString(format: kSentryMetricProfilerSerializationKeyCPUUsageFormat as NSString, i) as String] as? [String: Any] else { + throw TestError.noCPUUsageEvents + } + guard let values = cpuUsage["values"] as? [[String: Any]] else { + throw TestError.malformedMetricValueEntry + } + guard let firstReport = values[0]["value"] as? Double else { + throw TestError.noCPUUsageReported + } + + XCTAssertEqual(firstReport, expectedUsage) + } } func assertValidProfileData(data: Data, transactionEnvironment: String = kSentryDefaultEnvironment, numberOfTransactions: Int = 1, shouldTimeout: Bool = false) { From 92f1c09b7c120c22c69de78d92b7ee276bd09a60 Mon Sep 17 00:00:00 2001 From: Andrew McKnight Date: Mon, 12 Dec 2022 12:37:05 -0900 Subject: [PATCH 08/70] typedef system type for better understandability --- Sources/Sentry/SentrySystemWrapper.h | 8 +++++++- Sources/Sentry/SentrySystemWrapper.mm | 4 ++-- Tests/SentryTests/Helper/TestSentrySystemWrapper.swift | 4 ++-- .../SentryTests/Profiling/SentryProfilerSwiftTests.swift | 2 +- 4 files changed, 12 insertions(+), 6 deletions(-) diff --git a/Sources/Sentry/SentrySystemWrapper.h b/Sources/Sentry/SentrySystemWrapper.h index 3fae92c71e0..3f48a4bcbe6 100644 --- a/Sources/Sentry/SentrySystemWrapper.h +++ b/Sources/Sentry/SentrySystemWrapper.h @@ -4,13 +4,19 @@ NS_ASSUME_NONNULL_BEGIN typedef void (^SentryMemoryPressureNotification)(uintptr_t); +/** + * @c mach_vm_size_t Is a type defined in mach headers as an unsigned 64-bit type used to express + * the amount of working memory the process currently has allocated. + */ +typedef mach_vm_size_t SentryRAMBytes; + /** * A wrapper around low-level system APIs that are found in headers such as @c and @c * . */ @interface SentrySystemWrapper : NSObject -- (mach_vm_size_t)memoryFootprintBytes:(NSError **)error; +- (SentryRAMBytes)memoryFootprintBytes:(NSError **)error; /** * @return The CPU usage per core, where the order of results corresponds to the core number as diff --git a/Sources/Sentry/SentrySystemWrapper.mm b/Sources/Sentry/SentrySystemWrapper.mm index 09968c3b5be..3c9134b179a 100644 --- a/Sources/Sentry/SentrySystemWrapper.mm +++ b/Sources/Sentry/SentrySystemWrapper.mm @@ -8,7 +8,7 @@ @implementation SentrySystemWrapper { dispatch_queue_t _memoryWarningQueue; } -- (mach_vm_size_t)memoryFootprintBytes:(NSError *__autoreleasing _Nullable *)error +- (SentryRAMBytes)memoryFootprintBytes:(NSError *__autoreleasing _Nullable *)error { task_vm_info_data_t info; mach_msg_type_number_t count = TASK_VM_INFO_COUNT; @@ -22,7 +22,7 @@ - (mach_vm_size_t)memoryFootprintBytes:(NSError *__autoreleasing _Nullable *)err return 0; } - mach_vm_size_t footprintBytes; + SentryRAMBytes footprintBytes; if (count >= TASK_VM_INFO_REV1_COUNT) { footprintBytes = info.phys_footprint; } else { diff --git a/Tests/SentryTests/Helper/TestSentrySystemWrapper.swift b/Tests/SentryTests/Helper/TestSentrySystemWrapper.swift index a25a2be2816..5e515b79329 100644 --- a/Tests/SentryTests/Helper/TestSentrySystemWrapper.swift +++ b/Tests/SentryTests/Helper/TestSentrySystemWrapper.swift @@ -3,7 +3,7 @@ import Sentry class TestSentrySystemWrapper: SentrySystemWrapper { struct Override { var memoryFootprintError: NSError? - var memoryFootprintBytes: mach_vm_size_t? + var memoryFootprintBytes: SentryRAMBytes? var cpuUsageError: NSError? var cpuUsagePerCore: [NSNumber]? @@ -11,7 +11,7 @@ class TestSentrySystemWrapper: SentrySystemWrapper { var overrides = Override() - override func memoryFootprintBytes(_ error: NSErrorPointer) -> mach_vm_size_t { + override func memoryFootprintBytes(_ error: NSErrorPointer) -> SentryRAMBytes { if let errorOverride = overrides.memoryFootprintError { error?.pointee = errorOverride return 0 diff --git a/Tests/SentryTests/Profiling/SentryProfilerSwiftTests.swift b/Tests/SentryTests/Profiling/SentryProfilerSwiftTests.swift index abce81f05c8..8b671b70f7b 100644 --- a/Tests/SentryTests/Profiling/SentryProfilerSwiftTests.swift +++ b/Tests/SentryTests/Profiling/SentryProfilerSwiftTests.swift @@ -60,7 +60,7 @@ class SentryProfilerSwiftTests: XCTestCase { let cpuUsages = [12.4, 63.5, 1.4, 4.6] fixture.systemWrapper.overrides.cpuUsagePerCore = cpuUsages.map { NSNumber(value: $0) } - let memoryFootprint: mach_vm_size_t = 123_455 + let memoryFootprint: SentryRAMBytes = 123_455 fixture.systemWrapper.overrides.memoryFootprintBytes = memoryFootprint let span = fixture.newTransaction() From 5c94ce7c56816bab07e40a492e21d2edc351ee80 Mon Sep 17 00:00:00 2001 From: Andrew McKnight Date: Mon, 12 Dec 2022 12:57:00 -0900 Subject: [PATCH 09/70] add and use a wrapper method around NSProcessInfo to get core count --- Sources/Sentry/SentryMetricProfiler.mm | 2 +- Sources/Sentry/SentryNSProcessInfoWrapper.h | 1 + Sources/Sentry/SentryNSProcessInfoWrapper.mm | 5 +++++ 3 files changed, 7 insertions(+), 1 deletion(-) diff --git a/Sources/Sentry/SentryMetricProfiler.mm b/Sources/Sentry/SentryMetricProfiler.mm index 1a33353dc04..f175da5b5f0 100644 --- a/Sources/Sentry/SentryMetricProfiler.mm +++ b/Sources/Sentry/SentryMetricProfiler.mm @@ -54,7 +54,7 @@ - (instancetype)initWithProfileStartTime:(uint64_t)profileStartTime _cpuUsage = [NSMutableDictionary *> *> dictionary]; - const auto processorCount = NSProcessInfo.processInfo.processorCount; + const auto processorCount = processInfoWrapper.processorCount; for (NSUInteger core = 0; core < processorCount; core++) { _cpuUsage[@(core)] = [NSMutableArray *> array]; } diff --git a/Sources/Sentry/SentryNSProcessInfoWrapper.h b/Sources/Sentry/SentryNSProcessInfoWrapper.h index 3d456cea520..3d8a3ed82c0 100644 --- a/Sources/Sentry/SentryNSProcessInfoWrapper.h +++ b/Sources/Sentry/SentryNSProcessInfoWrapper.h @@ -6,6 +6,7 @@ NS_ASSUME_NONNULL_BEGIN @property (readonly) NSProcessInfoThermalState thermalState; @property (readonly, getter=isLowPowerModeEnabled) BOOL lowPowerModeEnabled; +@property (readonly) NSUInteger processorCount; - (void)monitorForPowerStateChanges:(id)target callback:(SEL)callback; - (void)monitorForThermalStateChanges:(id)target callback:(SEL)callback; diff --git a/Sources/Sentry/SentryNSProcessInfoWrapper.mm b/Sources/Sentry/SentryNSProcessInfoWrapper.mm index 3d600fabda0..4481cd9f9d4 100644 --- a/Sources/Sentry/SentryNSProcessInfoWrapper.mm +++ b/Sources/Sentry/SentryNSProcessInfoWrapper.mm @@ -14,6 +14,11 @@ - (BOOL)isLowPowerModeEnabled return NSProcessInfo.processInfo.isLowPowerModeEnabled; } +- (NSUInteger)processorCount +{ + return NSProcessInfo.processInfo.processorCount; +} + - (void)monitorForPowerStateChanges:(id)target callback:(SEL)callback { // According to Apple docs: "This notification is posted on the global dispatch queue. The From de82f6af1a6968956b71077774b1078c46cd3c30 Mon Sep 17 00:00:00 2001 From: Andrew McKnight Date: Mon, 12 Dec 2022 12:57:12 -0900 Subject: [PATCH 10/70] fix merge issue --- Sources/Sentry/SentryNSNotificationCenterWrapper.m | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/Sources/Sentry/SentryNSNotificationCenterWrapper.m b/Sources/Sentry/SentryNSNotificationCenterWrapper.m index c2cc42bacb3..f235f2de55f 100644 --- a/Sources/Sentry/SentryNSNotificationCenterWrapper.m +++ b/Sources/Sentry/SentryNSNotificationCenterWrapper.m @@ -62,17 +62,6 @@ - (void)addObserver:(id)observer selector:(SEL)aSelector name:(NSNotificationNam object:nil]; } -- (void)addObserver:(id)observer - selector:(SEL)aSelector - name:(NSNotificationName)aName - object:(id)anObject -{ - [NSNotificationCenter.defaultCenter addObserver:observer - selector:aSelector - name:aName - object:anObject]; -} - - (void)removeObserver:(id)observer name:(NSNotificationName)aName { [NSNotificationCenter.defaultCenter removeObserver:observer name:aName object:nil]; From 01ca6f50a7977723e81f3d9860b37f4a6233b827 Mon Sep 17 00:00:00 2001 From: Andrew McKnight Date: Mon, 12 Dec 2022 13:00:47 -0900 Subject: [PATCH 11/70] dont need lock around cpu usage gethering since its always on the same thread --- Sources/Sentry/SentrySystemWrapper.mm | 5 ----- 1 file changed, 5 deletions(-) diff --git a/Sources/Sentry/SentrySystemWrapper.mm b/Sources/Sentry/SentrySystemWrapper.mm index 3c9134b179a..7dcb592f5bd 100644 --- a/Sources/Sentry/SentrySystemWrapper.mm +++ b/Sources/Sentry/SentrySystemWrapper.mm @@ -47,9 +47,6 @@ - (SentryRAMBytes)memoryFootprintBytes:(NSError *__autoreleasing _Nullable *)err return nil; } - const auto cpuUsageLock = [[NSLock alloc] init]; - [cpuUsageLock lock]; - NSMutableArray *result = [NSMutableArray arrayWithCapacity:numCPUs]; for (natural_t core = 0U; core < numCPUs; ++core) { const auto indexBase = CPU_STATE_MAX * core; @@ -63,8 +60,6 @@ - (SentryRAMBytes)memoryFootprintBytes:(NSError *__autoreleasing _Nullable *)err [result addObject:@(usagePercent)]; } - [cpuUsageLock unlock]; - return result; } From ea2fde91f7f9fea4c400831a268b000d65d17959 Mon Sep 17 00:00:00 2001 From: Andrew McKnight Date: Tue, 13 Dec 2022 10:12:40 -0900 Subject: [PATCH 12/70] add NSTimer wrapper --- Sentry.xcodeproj/project.pbxproj | 14 +++++++++++ Sources/Sentry/SentryMetricProfiler.h | 4 +++- Sources/Sentry/SentryMetricProfiler.mm | 16 ++++++++----- Sources/Sentry/SentryNSTimerWrapper+Test.h | 8 +++++++ Sources/Sentry/SentryNSTimerWrapper.h | 13 ++++++++++ Sources/Sentry/SentryNSTimerWrapper.m | 19 +++++++++++++++ Sources/Sentry/SentryProfiler.mm | 11 ++++++++- .../Helper/SentryProfiler+SwiftTest.h | 2 ++ .../Helper/TestSentryNSTimerWrapper.swift | 24 +++++++++++++++++++ .../Profiling/SentryProfilerSwiftTests.swift | 6 +++++ .../SentryTests/SentryTests-Bridging-Header.h | 1 + 11 files changed, 110 insertions(+), 8 deletions(-) create mode 100644 Sources/Sentry/SentryNSTimerWrapper+Test.h create mode 100644 Sources/Sentry/SentryNSTimerWrapper.h create mode 100644 Sources/Sentry/SentryNSTimerWrapper.m create mode 100644 Tests/SentryTests/Helper/TestSentryNSTimerWrapper.swift diff --git a/Sentry.xcodeproj/project.pbxproj b/Sentry.xcodeproj/project.pbxproj index 0a7f2475b95..51c9aed7d8b 100644 --- a/Sentry.xcodeproj/project.pbxproj +++ b/Sentry.xcodeproj/project.pbxproj @@ -611,6 +611,9 @@ 844EDC76294144DB00C86F34 /* SentrySystemWrapper.h in Headers */ = {isa = PBXBuildFile; fileRef = 844EDC74294144DB00C86F34 /* SentrySystemWrapper.h */; }; 844EDC77294144DB00C86F34 /* SentrySystemWrapper.mm in Sources */ = {isa = PBXBuildFile; fileRef = 844EDC75294144DB00C86F34 /* SentrySystemWrapper.mm */; }; 844EDC7A29415AE800C86F34 /* TestSentrySystemWrapper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 844EDC7829415AB300C86F34 /* TestSentrySystemWrapper.swift */; }; + 844EDCE52947DC3100C86F34 /* SentryNSTimerWrapper.h in Headers */ = {isa = PBXBuildFile; fileRef = 844EDCE32947DC3100C86F34 /* SentryNSTimerWrapper.h */; }; + 844EDCE62947DC3100C86F34 /* SentryNSTimerWrapper.m in Sources */ = {isa = PBXBuildFile; fileRef = 844EDCE42947DC3100C86F34 /* SentryNSTimerWrapper.m */; }; + 844EDCE82947DCD700C86F34 /* TestSentryNSTimerWrapper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 844EDCE72947DCD700C86F34 /* TestSentryNSTimerWrapper.swift */; }; 8453421228BE855D00C22EEC /* SentrySampleDecision.m in Sources */ = {isa = PBXBuildFile; fileRef = 8453421128BE855D00C22EEC /* SentrySampleDecision.m */; }; 8453421628BE8A9500C22EEC /* SentrySpanStatus.m in Sources */ = {isa = PBXBuildFile; fileRef = 8453421528BE8A9500C22EEC /* SentrySpanStatus.m */; }; 8454CF8C293EAF9A006AC140 /* SentryMetricProfiler.h in Headers */ = {isa = PBXBuildFile; fileRef = 8454CF8A293EAF9A006AC140 /* SentryMetricProfiler.h */; }; @@ -1437,6 +1440,10 @@ 844EDC75294144DB00C86F34 /* SentrySystemWrapper.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = SentrySystemWrapper.mm; sourceTree = ""; }; 844EDC7829415AB300C86F34 /* TestSentrySystemWrapper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TestSentrySystemWrapper.swift; sourceTree = ""; }; 844EDC7B2942843400C86F34 /* SentryProfiler+SwiftTest.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = "SentryProfiler+SwiftTest.h"; path = "Tests/SentryTests/Helper/SentryProfiler+SwiftTest.h"; sourceTree = SOURCE_ROOT; }; + 844EDCE32947DC3100C86F34 /* SentryNSTimerWrapper.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SentryNSTimerWrapper.h; sourceTree = ""; }; + 844EDCE42947DC3100C86F34 /* SentryNSTimerWrapper.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SentryNSTimerWrapper.m; sourceTree = ""; }; + 844EDCE72947DCD700C86F34 /* TestSentryNSTimerWrapper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TestSentryNSTimerWrapper.swift; sourceTree = ""; }; + 844EDCE92947E78B00C86F34 /* SentryNSTimerWrapper+Test.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "SentryNSTimerWrapper+Test.h"; sourceTree = ""; }; 8453421128BE855D00C22EEC /* SentrySampleDecision.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SentrySampleDecision.m; sourceTree = ""; }; 8453421528BE8A9500C22EEC /* SentrySpanStatus.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SentrySpanStatus.m; sourceTree = ""; }; 8454CF8A293EAF9A006AC140 /* SentryMetricProfiler.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = SentryMetricProfiler.h; path = Sources/Sentry/SentryMetricProfiler.h; sourceTree = SOURCE_ROOT; }; @@ -1950,6 +1957,9 @@ 844EDC6E294143B900C86F34 /* SentryNSProcessInfoWrapper.mm */, 844EDC74294144DB00C86F34 /* SentrySystemWrapper.h */, 844EDC75294144DB00C86F34 /* SentrySystemWrapper.mm */, + 844EDCE32947DC3100C86F34 /* SentryNSTimerWrapper.h */, + 844EDCE92947E78B00C86F34 /* SentryNSTimerWrapper+Test.h */, + 844EDCE42947DC3100C86F34 /* SentryNSTimerWrapper.m */, ); name = Helper; sourceTree = ""; @@ -2565,6 +2575,7 @@ 84A8892028DBD8D600C51DFD /* SentryDeviceTests.mm */, 844EDC712941442200C86F34 /* TestSentryNSProcessInfoWrapper.swift */, 844EDC7829415AB300C86F34 /* TestSentrySystemWrapper.swift */, + 844EDCE72947DCD700C86F34 /* TestSentryNSTimerWrapper.swift */, ); path = Helper; sourceTree = ""; @@ -3258,6 +3269,7 @@ 7BD86EC5264A63F6005439DB /* SentrySysctl.h in Headers */, 63BE85701ECEC6DE00DC44F5 /* NSDate+SentryExtras.h in Headers */, 63FE709520DA4C1000CDBAE8 /* SentryCrashReportFilterBasic.h in Headers */, + 844EDCE52947DC3100C86F34 /* SentryNSTimerWrapper.h in Headers */, 63FE70A120DA4C1000CDBAE8 /* SentryCrashCString.h in Headers */, 7B63459D280EBA6300CFA05A /* SentryUIEventTracker.h in Headers */, 7B7D873424864C6600D2ECFF /* SentryCrashDefaultMachineContextWrapper.h in Headers */, @@ -3605,6 +3617,7 @@ A8F17B2E2901765900990B25 /* SentryRequest.m in Sources */, 7BE1E33424F7E3CB009D3AD0 /* SentryMigrateSessionInit.m in Sources */, 15E0A8F22411A45A00F044E3 /* SentrySession.m in Sources */, + 844EDCE62947DC3100C86F34 /* SentryNSTimerWrapper.m in Sources */, 7B6D1261265F784000C9BE4B /* PrivateSentrySDKOnly.m in Sources */, 63BE85711ECEC6DE00DC44F5 /* NSDate+SentryExtras.m in Sources */, 7BD4BD4927EB2A5D0071F4FF /* SentryDiscardedEvent.m in Sources */, @@ -3930,6 +3943,7 @@ 0A1B497328E597DD00D7BFA3 /* TestLogOutput.swift in Sources */, 7BA61CBD247BC6B900C130A8 /* TestSentryCrashBinaryImageProvider.swift in Sources */, 7BFA69F627E0840400233199 /* SentryANRTrackingIntegrationTests.swift in Sources */, + 844EDCE82947DCD700C86F34 /* TestSentryNSTimerWrapper.swift in Sources */, 7BBD18B62451807600427C76 /* SentryDefaultRateLimitsTests.swift in Sources */, 63FE720620DA66EC00CDBAE8 /* SentryCrashMonitor_AppState_Tests.m in Sources */, 7B4E375B2582313100059C93 /* SentryAttachmentTests.swift in Sources */, diff --git a/Sources/Sentry/SentryMetricProfiler.h b/Sources/Sentry/SentryMetricProfiler.h index 39653ac2bcd..1891b2db7fc 100644 --- a/Sources/Sentry/SentryMetricProfiler.h +++ b/Sources/Sentry/SentryMetricProfiler.h @@ -2,6 +2,7 @@ #import @class SentryNSProcessInfoWrapper; +@class SentryNSTimerWrapper; @class SentrySystemWrapper; NS_ASSUME_NONNULL_BEGIN @@ -26,7 +27,8 @@ SENTRY_EXTERN NSString *const kSentryMetricProfilerSerializationUnitPercentage; - (instancetype)initWithProfileStartTime:(uint64_t)profileStartTime processInfoWrapper:(SentryNSProcessInfoWrapper *)processInfoWrapper - systemWrapper:(SentrySystemWrapper *)systemWrapper; + systemWrapper:(SentrySystemWrapper *)systemWrapper + timerWrapper:(SentryNSTimerWrapper *)timerWrapper; - (void)start; - (void)stop; diff --git a/Sources/Sentry/SentryMetricProfiler.mm b/Sources/Sentry/SentryMetricProfiler.mm index f175da5b5f0..3f164adcb71 100644 --- a/Sources/Sentry/SentryMetricProfiler.mm +++ b/Sources/Sentry/SentryMetricProfiler.mm @@ -4,6 +4,7 @@ #import "SentryMachLogging.hpp" #import "SentryNSNotificationCenterWrapper.h" #import "SentryNSProcessInfoWrapper.h" +#import "SentryNSTimerWrapper.h" #import "SentrySystemWrapper.h" #import "SentryTime.h" @@ -34,6 +35,7 @@ @implementation SentryMetricProfiler { SentryNSProcessInfoWrapper *_processInfoWrapper; SentrySystemWrapper *_systemWrapper; + SentryNSTimerWrapper *_timerWrapper; /// arrays of readings keyed on NSNumbers representing the core number for the set of readings NSMutableDictionary *> *> @@ -49,6 +51,7 @@ @implementation SentryMetricProfiler { - (instancetype)initWithProfileStartTime:(uint64_t)profileStartTime processInfoWrapper:(SentryNSProcessInfoWrapper *)processInfoWrapper systemWrapper:(SentrySystemWrapper *)systemWrapper + timerWrapper:(SentryNSTimerWrapper *)timerWrapper { if (self = [super init]) { _cpuUsage = [NSMutableDictionary *> array]; _thermalState = [NSMutableArray *> array]; @@ -132,12 +136,12 @@ - (void)stop - (void)registerSampler { __weak auto weakSelf = self; - _timer = [NSTimer scheduledTimerWithTimeInterval:kSentryMetricProfilerTimeseriesInterval - repeats:YES - block:^(NSTimer *_Nonnull timer) { - [weakSelf recordCPUPercentagePerCore]; - [weakSelf recordMemoryFootprint]; - }]; + _timer = [_timerWrapper scheduledTimerWithTimeInterval:kSentryMetricProfilerTimeseriesInterval + repeats:YES + block:^(NSTimer *_Nonnull timer) { + [weakSelf recordCPUPercentagePerCore]; + [weakSelf recordMemoryFootprint]; + }]; } /** diff --git a/Sources/Sentry/SentryNSTimerWrapper+Test.h b/Sources/Sentry/SentryNSTimerWrapper+Test.h new file mode 100644 index 00000000000..245668340d7 --- /dev/null +++ b/Sources/Sentry/SentryNSTimerWrapper+Test.h @@ -0,0 +1,8 @@ +#import "SentryNSTimerWrapper.h" + +@interface +SentryNSTimerWrapper () + +- (void)fire; + +@end diff --git a/Sources/Sentry/SentryNSTimerWrapper.h b/Sources/Sentry/SentryNSTimerWrapper.h new file mode 100644 index 00000000000..32fbb51ca9d --- /dev/null +++ b/Sources/Sentry/SentryNSTimerWrapper.h @@ -0,0 +1,13 @@ +#import + +NS_ASSUME_NONNULL_BEGIN + +@interface SentryNSTimerWrapper : NSObject + +- (NSTimer *)scheduledTimerWithTimeInterval:(NSTimeInterval)interval + repeats:(BOOL)repeats + block:(void(NS_SWIFT_SENDABLE ^)(NSTimer *timer))block; + +@end + +NS_ASSUME_NONNULL_END diff --git a/Sources/Sentry/SentryNSTimerWrapper.m b/Sources/Sentry/SentryNSTimerWrapper.m new file mode 100644 index 00000000000..c8fef33530e --- /dev/null +++ b/Sources/Sentry/SentryNSTimerWrapper.m @@ -0,0 +1,19 @@ +#import "SentryNSTimerWrapper.h" + +@implementation SentryNSTimerWrapper + +- (NSTimer *)scheduledTimerWithTimeInterval:(NSTimeInterval)interval + repeats:(BOOL)repeats + block:(void(NS_SWIFT_SENDABLE ^)(NSTimer *timer))block +{ + return [NSTimer scheduledTimerWithTimeInterval:interval repeats:repeats block:block]; +} + +#pragma mark - Testing + +- (void)fire +{ + // no-op +} + +@end diff --git a/Sources/Sentry/SentryProfiler.mm b/Sources/Sentry/SentryProfiler.mm index fd19330e80d..d6b8c09440a 100644 --- a/Sources/Sentry/SentryProfiler.mm +++ b/Sources/Sentry/SentryProfiler.mm @@ -19,6 +19,7 @@ # import "SentryLog.h" # import "SentryMetricProfiler.h" # import "SentryNSProcessInfoWrapper.h" +# import "SentryNSTimerWrapper.h" # import "SentrySamplingProfiler.hpp" # import "SentryScope+Private.h" # import "SentryScreenFrames.h" @@ -152,6 +153,7 @@ SentryProfiler *_Nullable _gCurrentProfiler; SentryNSProcessInfoWrapper *_gCurrentProcessInfoWrapper; SentrySystemWrapper *_gCurrentSystemWrapper; +SentryNSTimerWrapper *_gCurrentTimerWrapper; NSString * profilerTruncationReasonName(SentryProfilerTruncationReason reason) @@ -339,6 +341,12 @@ + (void)useProcessInfoWrapper:(SentryNSProcessInfoWrapper *)processInfoWrapper _gCurrentProcessInfoWrapper = processInfoWrapper; } ++ (void)useTimerWrapper:(SentryNSTimerWrapper *)timerWrapper +{ + std::lock_guard l(_gProfilerLock); + _gCurrentTimerWrapper = timerWrapper; +} + # pragma mark - Private + (void)captureEnvelopeIfFinished:(SentryProfiler *)profiler spanID:(SentrySpanId *)spanID @@ -493,7 +501,8 @@ - (void)start _metricProfiler = [[SentryMetricProfiler alloc] initWithProfileStartTime:_startTimestamp processInfoWrapper:_gCurrentProcessInfoWrapper - systemWrapper:_gCurrentSystemWrapper]; + systemWrapper:_gCurrentSystemWrapper + timerWrapper:_gCurrentTimerWrapper]; [_metricProfiler start]; } } diff --git a/Tests/SentryTests/Helper/SentryProfiler+SwiftTest.h b/Tests/SentryTests/Helper/SentryProfiler+SwiftTest.h index d9da73a2408..b6b8b5efaa9 100644 --- a/Tests/SentryTests/Helper/SentryProfiler+SwiftTest.h +++ b/Tests/SentryTests/Helper/SentryProfiler+SwiftTest.h @@ -19,4 +19,6 @@ SentryProfiler () + (void)useProcessInfoWrapper:(SentryNSProcessInfoWrapper *)processInfoWrapper NS_SWIFT_NAME(useProcessInfoWrapper(_:)); ++ (void)useTimerWrapper:(SentryNSTimerWrapper *)timerWrapper NS_SWIFT_NAME(useTimerWrapper(_:)); + @end diff --git a/Tests/SentryTests/Helper/TestSentryNSTimerWrapper.swift b/Tests/SentryTests/Helper/TestSentryNSTimerWrapper.swift new file mode 100644 index 00000000000..d05719097e7 --- /dev/null +++ b/Tests/SentryTests/Helper/TestSentryNSTimerWrapper.swift @@ -0,0 +1,24 @@ +import Foundation +import Sentry + +class TestTimer: Timer {} + +class TestSentryNSTimerWrapper: SentryNSTimerWrapper { + struct Overrides { + var timer: TestTimer! + var block: ((Timer) -> Void)? + } + + lazy var overrides = Overrides() + + override func scheduledTimer(withTimeInterval interval: TimeInterval, repeats: Bool, block: @escaping (Timer) -> Void) -> Timer { + let timer = TestTimer() + overrides.timer = timer + overrides.block = block + return timer + } + + override func fire() { + overrides.block?(overrides.timer) + } +} diff --git a/Tests/SentryTests/Profiling/SentryProfilerSwiftTests.swift b/Tests/SentryTests/Profiling/SentryProfilerSwiftTests.swift index 8b671b70f7b..b6b1064fb86 100644 --- a/Tests/SentryTests/Profiling/SentryProfilerSwiftTests.swift +++ b/Tests/SentryTests/Profiling/SentryProfilerSwiftTests.swift @@ -25,6 +25,7 @@ class SentryProfilerSwiftTests: XCTestCase { let transactionOperation = "Some Operation" lazy var systemWrapper = TestSentrySystemWrapper() lazy var processInfoWrapper = TestSentryNSProcessInfoWrapper() + lazy var timerWrapper = TestSentryNSTimerWrapper() func newTransaction() -> Span { hub.startTransaction(name: transactionName, operation: transactionOperation) @@ -56,6 +57,7 @@ class SentryProfilerSwiftTests: XCTestCase { options.tracesSampleRate = 1.0 SentryProfiler.useSystemWrapper(fixture.systemWrapper) SentryProfiler.useProcessInfoWrapper(fixture.processInfoWrapper) + SentryProfiler.useTimerWrapper(fixture.timerWrapper) let cpuUsages = [12.4, 63.5, 1.4, 4.6] fixture.systemWrapper.overrides.cpuUsagePerCore = cpuUsages.map { NSNumber(value: $0) } @@ -76,6 +78,10 @@ class SentryProfilerSwiftTests: XCTestCase { fixture.processInfoWrapper.sendThermalStateChangeNotification() } + DispatchQueue.main.asyncAfter(deadline: .now() + 1) { + self.fixture.timerWrapper.fire() + } + let exp = expectation(description: "Receives profile payload") DispatchQueue.main.asyncAfter(deadline: .now() + 2) { span.finish() diff --git a/Tests/SentryTests/SentryTests-Bridging-Header.h b/Tests/SentryTests/SentryTests-Bridging-Header.h index 18a81a549d6..0df3e70d63b 100644 --- a/Tests/SentryTests/SentryTests-Bridging-Header.h +++ b/Tests/SentryTests/SentryTests-Bridging-Header.h @@ -106,6 +106,7 @@ #import "SentryNSError.h" #import "SentryNSNotificationCenterWrapper.h" #import "SentryNSProcessInfoWrapper.h" +#import "SentryNSTimerWrapper+Test.h" #import "SentryNSURLRequest.h" #import "SentryNSURLRequestBuilder.h" #import "SentryNSURLSessionTaskSearch.h" From 6e1aed92515a96e28f4c945c2a5cbc1fca49f0f3 Mon Sep 17 00:00:00 2001 From: Andrew McKnight Date: Tue, 13 Dec 2022 10:12:55 -0900 Subject: [PATCH 13/70] rename paramater target->observer --- Sources/Sentry/SentryNSProcessInfoWrapper.h | 6 +++--- Sources/Sentry/SentryNSProcessInfoWrapper.mm | 14 +++++++------- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/Sources/Sentry/SentryNSProcessInfoWrapper.h b/Sources/Sentry/SentryNSProcessInfoWrapper.h index 3d8a3ed82c0..c7f3391ac0e 100644 --- a/Sources/Sentry/SentryNSProcessInfoWrapper.h +++ b/Sources/Sentry/SentryNSProcessInfoWrapper.h @@ -8,9 +8,9 @@ NS_ASSUME_NONNULL_BEGIN @property (readonly, getter=isLowPowerModeEnabled) BOOL lowPowerModeEnabled; @property (readonly) NSUInteger processorCount; -- (void)monitorForPowerStateChanges:(id)target callback:(SEL)callback; -- (void)monitorForThermalStateChanges:(id)target callback:(SEL)callback; -- (void)stopMonitoring:(id)target; +- (void)monitorForPowerStateChanges:(id)observer callback:(SEL)callback; +- (void)monitorForThermalStateChanges:(id)observer callback:(SEL)callback; +- (void)stopMonitoring:(id)observer; @end diff --git a/Sources/Sentry/SentryNSProcessInfoWrapper.mm b/Sources/Sentry/SentryNSProcessInfoWrapper.mm index 4481cd9f9d4..fcd8bcd0a37 100644 --- a/Sources/Sentry/SentryNSProcessInfoWrapper.mm +++ b/Sources/Sentry/SentryNSProcessInfoWrapper.mm @@ -19,35 +19,35 @@ - (NSUInteger)processorCount return NSProcessInfo.processInfo.processorCount; } -- (void)monitorForPowerStateChanges:(id)target callback:(SEL)callback +- (void)monitorForPowerStateChanges:(id)observer callback:(SEL)callback { // According to Apple docs: "This notification is posted on the global dispatch queue. The // object associated with the notification is NSProcessInfo.processInfo." [SentryDependencyContainer.sharedInstance.notificationCenterWrapper - addObserver:target + addObserver:observer selector:callback name:NSProcessInfoPowerStateDidChangeNotification object:NSProcessInfo.processInfo]; } -- (void)monitorForThermalStateChanges:(id)target callback:(SEL)callback +- (void)monitorForThermalStateChanges:(id)observer callback:(SEL)callback { // According to Apple docs: "This notification is posted on the global dispatch queue. The // object associated with the notification is NSProcessInfo.processInfo." [SentryDependencyContainer.sharedInstance.notificationCenterWrapper - addObserver:target + addObserver:observer selector:callback name:NSProcessInfoThermalStateDidChangeNotification object:NSProcessInfo.processInfo]; } -- (void)stopMonitoring:(id)target +- (void)stopMonitoring:(id)observer { const auto notifier = SentryDependencyContainer.sharedInstance.notificationCenterWrapper; - [notifier removeObserver:target + [notifier removeObserver:observer name:NSProcessInfoThermalStateDidChangeNotification object:NSProcessInfo.processInfo]; - [notifier removeObserver:target + [notifier removeObserver:observer name:NSProcessInfoPowerStateDidChangeNotification object:NSProcessInfo.processInfo]; } From 762cd42ce1e0e997bc6f900bde357466e828b6e8 Mon Sep 17 00:00:00 2001 From: Andrew McKnight Date: Tue, 13 Dec 2022 10:23:03 -0900 Subject: [PATCH 14/70] add info on where to find doc info --- Sources/Sentry/SentryNSProcessInfoWrapper.mm | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/Sources/Sentry/SentryNSProcessInfoWrapper.mm b/Sources/Sentry/SentryNSProcessInfoWrapper.mm index fcd8bcd0a37..7a845e3ace5 100644 --- a/Sources/Sentry/SentryNSProcessInfoWrapper.mm +++ b/Sources/Sentry/SentryNSProcessInfoWrapper.mm @@ -22,7 +22,8 @@ - (NSUInteger)processorCount - (void)monitorForPowerStateChanges:(id)observer callback:(SEL)callback { // According to Apple docs: "This notification is posted on the global dispatch queue. The - // object associated with the notification is NSProcessInfo.processInfo." + // object associated with the notification is NSProcessInfo.processInfo." See the declaration + // for NSProcessInfoPowerStateDidChangeNotification in NSProcessInfo.h for more information. [SentryDependencyContainer.sharedInstance.notificationCenterWrapper addObserver:observer selector:callback @@ -33,7 +34,8 @@ - (void)monitorForPowerStateChanges:(id)observer callback:(SEL)callback - (void)monitorForThermalStateChanges:(id)observer callback:(SEL)callback { // According to Apple docs: "This notification is posted on the global dispatch queue. The - // object associated with the notification is NSProcessInfo.processInfo." + // object associated with the notification is NSProcessInfo.processInfo." See the declaration + // for NSProcessInfoThermalStateDidChangeNotification in NSProcessInfo.h for more information. [SentryDependencyContainer.sharedInstance.notificationCenterWrapper addObserver:observer selector:callback From 246d01b9985f6360a932608d4b3eb212995e335d Mon Sep 17 00:00:00 2001 From: Andrew McKnight Date: Tue, 13 Dec 2022 10:28:10 -0900 Subject: [PATCH 15/70] fix timer wrapper issue and assert number of times it "fired" --- .../SentryTests/Helper/TestSentryNSTimerWrapper.swift | 6 +++++- .../Profiling/SentryProfilerSwiftTests.swift | 10 +++++----- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/Tests/SentryTests/Helper/TestSentryNSTimerWrapper.swift b/Tests/SentryTests/Helper/TestSentryNSTimerWrapper.swift index d05719097e7..93eef16a47d 100644 --- a/Tests/SentryTests/Helper/TestSentryNSTimerWrapper.swift +++ b/Tests/SentryTests/Helper/TestSentryNSTimerWrapper.swift @@ -1,7 +1,11 @@ import Foundation import Sentry -class TestTimer: Timer {} +class TestTimer: Timer { + override func invalidate() { + // no-op as this timer doesn't actually schedule anything on a runloop + } +} class TestSentryNSTimerWrapper: SentryNSTimerWrapper { struct Overrides { diff --git a/Tests/SentryTests/Profiling/SentryProfilerSwiftTests.swift b/Tests/SentryTests/Profiling/SentryProfilerSwiftTests.swift index b6b1064fb86..1bdb3d618aa 100644 --- a/Tests/SentryTests/Profiling/SentryProfilerSwiftTests.swift +++ b/Tests/SentryTests/Profiling/SentryProfilerSwiftTests.swift @@ -78,16 +78,15 @@ class SentryProfilerSwiftTests: XCTestCase { fixture.processInfoWrapper.sendThermalStateChangeNotification() } - DispatchQueue.main.asyncAfter(deadline: .now() + 1) { - self.fixture.timerWrapper.fire() - } + self.fixture.timerWrapper.fire() + self.fixture.timerWrapper.fire() let exp = expectation(description: "Receives profile payload") DispatchQueue.main.asyncAfter(deadline: .now() + 2) { span.finish() do { - try self.assertMetricsPayload(thermalStateNotifications: 4, powerStateNotifications: 2, expectedCPUUsages: cpuUsages) + try self.assertMetricsPayload(thermalStateNotifications: 4, powerStateNotifications: 2, expectedCPUUsages: cpuUsages, cpuReadings: 2) exp.fulfill() } catch { XCTFail("Encountered error: \(error)") @@ -297,7 +296,7 @@ private extension SentryProfilerSwiftTests { self.assertValidProfileData(data: profileData, transactionEnvironment: transactionEnvironment, numberOfTransactions: numberOfTransactions, shouldTimeout: shouldTimeOut) } - func assertMetricsPayload(thermalStateNotifications: Int, powerStateNotifications: Int, expectedCPUUsages: [Double]) throws { + func assertMetricsPayload(thermalStateNotifications: Int, powerStateNotifications: Int, expectedCPUUsages: [Double], cpuReadings: Int) throws { let profileData = try self.getProfileData() guard let profile = try JSONSerialization.jsonObject(with: profileData) as? [String: Any] else { throw TestError.unexpectedProfileDeserializationType @@ -326,6 +325,7 @@ private extension SentryProfilerSwiftTests { guard let values = cpuUsage["values"] as? [[String: Any]] else { throw TestError.malformedMetricValueEntry } + XCTAssertEqual(values.count, cpuReadings) guard let firstReport = values[0]["value"] as? Double else { throw TestError.noCPUUsageReported } From aa4700401f5229919eec416baaaeb178fe932392 Mon Sep 17 00:00:00 2001 From: Andrew McKnight Date: Tue, 13 Dec 2022 11:52:48 -0900 Subject: [PATCH 16/70] fix ci build error --- Sources/Sentry/SentryNSTimerWrapper.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/Sentry/SentryNSTimerWrapper.h b/Sources/Sentry/SentryNSTimerWrapper.h index 32fbb51ca9d..0529b48e5ba 100644 --- a/Sources/Sentry/SentryNSTimerWrapper.h +++ b/Sources/Sentry/SentryNSTimerWrapper.h @@ -6,7 +6,7 @@ NS_ASSUME_NONNULL_BEGIN - (NSTimer *)scheduledTimerWithTimeInterval:(NSTimeInterval)interval repeats:(BOOL)repeats - block:(void(NS_SWIFT_SENDABLE ^)(NSTimer *timer))block; + block:(void (^)(NSTimer *timer))block; @end From a032fb015c23923e1597512f683209a7e04ba048 Mon Sep 17 00:00:00 2001 From: Andrew McKnight Date: Tue, 13 Dec 2022 12:12:19 -0900 Subject: [PATCH 17/70] again --- Sources/Sentry/SentryNSTimerWrapper.m | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/Sentry/SentryNSTimerWrapper.m b/Sources/Sentry/SentryNSTimerWrapper.m index c8fef33530e..d1e1202e325 100644 --- a/Sources/Sentry/SentryNSTimerWrapper.m +++ b/Sources/Sentry/SentryNSTimerWrapper.m @@ -4,7 +4,7 @@ @implementation SentryNSTimerWrapper - (NSTimer *)scheduledTimerWithTimeInterval:(NSTimeInterval)interval repeats:(BOOL)repeats - block:(void(NS_SWIFT_SENDABLE ^)(NSTimer *timer))block + block:(void (^)(NSTimer *timer))block { return [NSTimer scheduledTimerWithTimeInterval:interval repeats:repeats block:block]; } From e323e773dfd7720bf8eae8b5433fb9199268f873 Mon Sep 17 00:00:00 2001 From: Andrew McKnight Date: Tue, 13 Dec 2022 12:45:16 -0900 Subject: [PATCH 18/70] fix macos build --- Sources/Sentry/SentryMetricProfiler.mm | 13 +++++++--- Sources/Sentry/SentryNSProcessInfoWrapper.mm | 26 +++++++++++++------- 2 files changed, 26 insertions(+), 13 deletions(-) diff --git a/Sources/Sentry/SentryMetricProfiler.mm b/Sources/Sentry/SentryMetricProfiler.mm index 3f164adcb71..ab5e531c2ac 100644 --- a/Sources/Sentry/SentryMetricProfiler.mm +++ b/Sources/Sentry/SentryMetricProfiler.mm @@ -171,8 +171,11 @@ - (void)registerStateChangeNotifications [self recordThermalState]; [_processInfoWrapper monitorForThermalStateChanges:self callback:@selector(recordThermalState)]; - [_processInfoWrapper monitorForPowerStateChanges:self - callback:@selector(recordPowerLevelState)]; + + if (@available(macOS 12.0, *)) { + [_processInfoWrapper monitorForPowerStateChanges:self + callback:@selector(recordPowerLevelState)]; + } } - (void)recordThermalState @@ -182,8 +185,10 @@ - (void)recordThermalState - (void)recordPowerLevelState { - [_powerLevelState - addObject:[self metricEntryForValue:@(_processInfoWrapper.isLowPowerModeEnabled)]]; + if (@available(macOS 12.0, *)) { + [_powerLevelState + addObject:[self metricEntryForValue:@(_processInfoWrapper.isLowPowerModeEnabled)]]; + } } - (void)recordMemoryFootprint diff --git a/Sources/Sentry/SentryNSProcessInfoWrapper.mm b/Sources/Sentry/SentryNSProcessInfoWrapper.mm index 7a845e3ace5..886ef924504 100644 --- a/Sources/Sentry/SentryNSProcessInfoWrapper.mm +++ b/Sources/Sentry/SentryNSProcessInfoWrapper.mm @@ -11,7 +11,11 @@ - (NSProcessInfoThermalState)thermalState - (BOOL)isLowPowerModeEnabled { - return NSProcessInfo.processInfo.isLowPowerModeEnabled; + if (@available(macOS 12.0, *)) { + return NSProcessInfo.processInfo.isLowPowerModeEnabled; + } else { + return NO; + } } - (NSUInteger)processorCount @@ -24,11 +28,13 @@ - (void)monitorForPowerStateChanges:(id)observer callback:(SEL)callback // According to Apple docs: "This notification is posted on the global dispatch queue. The // object associated with the notification is NSProcessInfo.processInfo." See the declaration // for NSProcessInfoPowerStateDidChangeNotification in NSProcessInfo.h for more information. - [SentryDependencyContainer.sharedInstance.notificationCenterWrapper - addObserver:observer - selector:callback - name:NSProcessInfoPowerStateDidChangeNotification - object:NSProcessInfo.processInfo]; + if (@available(macOS 12.0, *)) { + [SentryDependencyContainer.sharedInstance.notificationCenterWrapper + addObserver:observer + selector:callback + name:NSProcessInfoPowerStateDidChangeNotification + object:NSProcessInfo.processInfo]; + } } - (void)monitorForThermalStateChanges:(id)observer callback:(SEL)callback @@ -49,9 +55,11 @@ - (void)stopMonitoring:(id)observer [notifier removeObserver:observer name:NSProcessInfoThermalStateDidChangeNotification object:NSProcessInfo.processInfo]; - [notifier removeObserver:observer - name:NSProcessInfoPowerStateDidChangeNotification - object:NSProcessInfo.processInfo]; + if (@available(macOS 12.0, *)) { + [notifier removeObserver:observer + name:NSProcessInfoPowerStateDidChangeNotification + object:NSProcessInfo.processInfo]; + } } @end From 5acf5defd189263e5cc535d5aa9836f583275152 Mon Sep 17 00:00:00 2001 From: Andrew McKnight Date: Tue, 13 Dec 2022 12:46:58 -0900 Subject: [PATCH 19/70] fix tvos build --- Sources/Sentry/SentryMetricProfiler.h | 4 ++++ Sources/Sentry/SentryMetricProfiler.mm | 8 ++++++-- Tests/SentryTests/Helper/SentryProfiler+SwiftTest.h | 4 ++++ 3 files changed, 14 insertions(+), 2 deletions(-) diff --git a/Sources/Sentry/SentryMetricProfiler.h b/Sources/Sentry/SentryMetricProfiler.h index 1891b2db7fc..4688b22f245 100644 --- a/Sources/Sentry/SentryMetricProfiler.h +++ b/Sources/Sentry/SentryMetricProfiler.h @@ -1,6 +1,8 @@ #import "SentryDefines.h" #import +#if SENTRY_TARGET_PROFILING_SUPPORTED + @class SentryNSProcessInfoWrapper; @class SentryNSTimerWrapper; @class SentrySystemWrapper; @@ -38,3 +40,5 @@ SENTRY_EXTERN NSString *const kSentryMetricProfilerSerializationUnitPercentage; @end NS_ASSUME_NONNULL_END + +#endif // SENTRY_TARGET_PROFILING_SUPPORTED diff --git a/Sources/Sentry/SentryMetricProfiler.mm b/Sources/Sentry/SentryMetricProfiler.mm index ab5e531c2ac..a98b0a8209f 100644 --- a/Sources/Sentry/SentryMetricProfiler.mm +++ b/Sources/Sentry/SentryMetricProfiler.mm @@ -8,6 +8,8 @@ #import "SentrySystemWrapper.h" #import "SentryTime.h" +#if SENTRY_TARGET_PROFILING_SUPPORTED + static const NSTimeInterval kSentryMetricProfilerTimeseriesInterval = 0.1; // 10 Hz NSString *const kSentryMetricProfilerSerializationKeyMemoryFootprint = @"memory-footprint"; @@ -81,7 +83,7 @@ - (void)dealloc [self stop]; } -#pragma mark - Public +# pragma mark - Public - (void)start { @@ -131,7 +133,7 @@ - (void)stop return dict; } -#pragma mark - Private +# pragma mark - Private - (void)registerSampler { @@ -227,3 +229,5 @@ - (void)recordCPUPercentagePerCore } @end + +#endif // SENTRY_TARGET_PROFILING_SUPPORTED diff --git a/Tests/SentryTests/Helper/SentryProfiler+SwiftTest.h b/Tests/SentryTests/Helper/SentryProfiler+SwiftTest.h index b6b8b5efaa9..fb020ae01d6 100644 --- a/Tests/SentryTests/Helper/SentryProfiler+SwiftTest.h +++ b/Tests/SentryTests/Helper/SentryProfiler+SwiftTest.h @@ -3,6 +3,8 @@ #include "SentryProfiler.h" +#if SENTRY_TARGET_PROFILING_SUPPORTED + @interface SentryProfiler () @@ -22,3 +24,5 @@ SentryProfiler () + (void)useTimerWrapper:(SentryNSTimerWrapper *)timerWrapper NS_SWIFT_NAME(useTimerWrapper(_:)); @end + +#endif // SENTRY_TARGET_PROFILING_SUPPORTED From 9bad80a1d00e4cd95db40883da057d8eefc0f270 Mon Sep 17 00:00:00 2001 From: Andrew McKnight Date: Tue, 13 Dec 2022 14:03:32 -0900 Subject: [PATCH 20/70] fix linker errors --- Sentry.xcodeproj/project.pbxproj | 16 ++++----- Sources/Sentry/SentryMetricProfiler.mm | 34 +++++++++++++------ Sources/Sentry/SentryProfiler.mm | 28 ++++----------- .../{ => include}/SentryMetricProfiler.h | 19 +++++++++-- .../SentryNSProcessInfoWrapper.h | 0 .../{ => include}/SentryNSTimerWrapper+Test.h | 0 .../{ => include}/SentryNSTimerWrapper.h | 0 .../{ => include}/SentrySystemWrapper.h | 0 .../TestSentryNSProcessInfoWrapper.swift | 1 + .../Profiling/SentryProfilerSwiftTests.swift | 14 +++++--- 10 files changed, 63 insertions(+), 49 deletions(-) rename Sources/Sentry/{ => include}/SentryMetricProfiler.h (81%) rename Sources/Sentry/{ => include}/SentryNSProcessInfoWrapper.h (100%) rename Sources/Sentry/{ => include}/SentryNSTimerWrapper+Test.h (100%) rename Sources/Sentry/{ => include}/SentryNSTimerWrapper.h (100%) rename Sources/Sentry/{ => include}/SentrySystemWrapper.h (100%) diff --git a/Sentry.xcodeproj/project.pbxproj b/Sentry.xcodeproj/project.pbxproj index 51c9aed7d8b..088da03cf44 100644 --- a/Sentry.xcodeproj/project.pbxproj +++ b/Sentry.xcodeproj/project.pbxproj @@ -614,9 +614,9 @@ 844EDCE52947DC3100C86F34 /* SentryNSTimerWrapper.h in Headers */ = {isa = PBXBuildFile; fileRef = 844EDCE32947DC3100C86F34 /* SentryNSTimerWrapper.h */; }; 844EDCE62947DC3100C86F34 /* SentryNSTimerWrapper.m in Sources */ = {isa = PBXBuildFile; fileRef = 844EDCE42947DC3100C86F34 /* SentryNSTimerWrapper.m */; }; 844EDCE82947DCD700C86F34 /* TestSentryNSTimerWrapper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 844EDCE72947DCD700C86F34 /* TestSentryNSTimerWrapper.swift */; }; + 844EDD6C2949387000C86F34 /* SentryMetricProfiler.h in Headers */ = {isa = PBXBuildFile; fileRef = 844EDD6B2949387000C86F34 /* SentryMetricProfiler.h */; }; 8453421228BE855D00C22EEC /* SentrySampleDecision.m in Sources */ = {isa = PBXBuildFile; fileRef = 8453421128BE855D00C22EEC /* SentrySampleDecision.m */; }; 8453421628BE8A9500C22EEC /* SentrySpanStatus.m in Sources */ = {isa = PBXBuildFile; fileRef = 8453421528BE8A9500C22EEC /* SentrySpanStatus.m */; }; - 8454CF8C293EAF9A006AC140 /* SentryMetricProfiler.h in Headers */ = {isa = PBXBuildFile; fileRef = 8454CF8A293EAF9A006AC140 /* SentryMetricProfiler.h */; }; 8454CF8D293EAF9A006AC140 /* SentryMetricProfiler.mm in Sources */ = {isa = PBXBuildFile; fileRef = 8454CF8B293EAF9A006AC140 /* SentryMetricProfiler.mm */; }; 84A888FD28D9B11700C51DFD /* SentryProfiler+Test.h in Headers */ = {isa = PBXBuildFile; fileRef = 84A888FC28D9B11700C51DFD /* SentryProfiler+Test.h */; }; 84A8891C28DBD28900C51DFD /* SentryDevice.h in Headers */ = {isa = PBXBuildFile; fileRef = 84A8891A28DBD28900C51DFD /* SentryDevice.h */; }; @@ -1433,20 +1433,20 @@ 844DA81D28246DAE00E6B62E /* develop-docs */ = {isa = PBXFileReference; lastKnownFileType = folder; path = "develop-docs"; sourceTree = ""; }; 844DA81E28246DB900E6B62E /* fastlane */ = {isa = PBXFileReference; lastKnownFileType = folder; path = fastlane; sourceTree = ""; }; 844DA81F28246DE300E6B62E /* scripts */ = {isa = PBXFileReference; lastKnownFileType = folder; path = scripts; sourceTree = ""; }; - 844EDC6D294143B900C86F34 /* SentryNSProcessInfoWrapper.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SentryNSProcessInfoWrapper.h; sourceTree = ""; }; + 844EDC6D294143B900C86F34 /* SentryNSProcessInfoWrapper.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = SentryNSProcessInfoWrapper.h; path = include/SentryNSProcessInfoWrapper.h; sourceTree = ""; }; 844EDC6E294143B900C86F34 /* SentryNSProcessInfoWrapper.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = SentryNSProcessInfoWrapper.mm; sourceTree = ""; }; 844EDC712941442200C86F34 /* TestSentryNSProcessInfoWrapper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TestSentryNSProcessInfoWrapper.swift; sourceTree = ""; }; - 844EDC74294144DB00C86F34 /* SentrySystemWrapper.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SentrySystemWrapper.h; sourceTree = ""; }; + 844EDC74294144DB00C86F34 /* SentrySystemWrapper.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = SentrySystemWrapper.h; path = include/SentrySystemWrapper.h; sourceTree = ""; }; 844EDC75294144DB00C86F34 /* SentrySystemWrapper.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = SentrySystemWrapper.mm; sourceTree = ""; }; 844EDC7829415AB300C86F34 /* TestSentrySystemWrapper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TestSentrySystemWrapper.swift; sourceTree = ""; }; 844EDC7B2942843400C86F34 /* SentryProfiler+SwiftTest.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = "SentryProfiler+SwiftTest.h"; path = "Tests/SentryTests/Helper/SentryProfiler+SwiftTest.h"; sourceTree = SOURCE_ROOT; }; - 844EDCE32947DC3100C86F34 /* SentryNSTimerWrapper.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SentryNSTimerWrapper.h; sourceTree = ""; }; + 844EDCE32947DC3100C86F34 /* SentryNSTimerWrapper.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = SentryNSTimerWrapper.h; path = include/SentryNSTimerWrapper.h; sourceTree = ""; }; 844EDCE42947DC3100C86F34 /* SentryNSTimerWrapper.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SentryNSTimerWrapper.m; sourceTree = ""; }; 844EDCE72947DCD700C86F34 /* TestSentryNSTimerWrapper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TestSentryNSTimerWrapper.swift; sourceTree = ""; }; - 844EDCE92947E78B00C86F34 /* SentryNSTimerWrapper+Test.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "SentryNSTimerWrapper+Test.h"; sourceTree = ""; }; + 844EDCE92947E78B00C86F34 /* SentryNSTimerWrapper+Test.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = "SentryNSTimerWrapper+Test.h"; path = "include/SentryNSTimerWrapper+Test.h"; sourceTree = ""; }; + 844EDD6B2949387000C86F34 /* SentryMetricProfiler.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = SentryMetricProfiler.h; path = Sources/Sentry/include/SentryMetricProfiler.h; sourceTree = SOURCE_ROOT; }; 8453421128BE855D00C22EEC /* SentrySampleDecision.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SentrySampleDecision.m; sourceTree = ""; }; 8453421528BE8A9500C22EEC /* SentrySpanStatus.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SentrySpanStatus.m; sourceTree = ""; }; - 8454CF8A293EAF9A006AC140 /* SentryMetricProfiler.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = SentryMetricProfiler.h; path = Sources/Sentry/SentryMetricProfiler.h; sourceTree = SOURCE_ROOT; }; 8454CF8B293EAF9A006AC140 /* SentryMetricProfiler.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; name = SentryMetricProfiler.mm; path = Sources/Sentry/SentryMetricProfiler.mm; sourceTree = SOURCE_ROOT; }; 84A888FC28D9B11700C51DFD /* SentryProfiler+Test.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = "SentryProfiler+Test.h"; path = "Sources/Sentry/include/SentryProfiler+Test.h"; sourceTree = SOURCE_ROOT; }; 84A8891A28DBD28900C51DFD /* SentryDevice.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = SentryDevice.h; path = include/SentryDevice.h; sourceTree = ""; }; @@ -2878,7 +2878,7 @@ 03F84D1B27DD414C008FE43F /* SentryMachLogging.hpp */, 03F84D2C27DD4191008FE43F /* SentryMachLogging.cpp */, 03F84D1127DD414C008FE43F /* SentryProfiler.h */, - 8454CF8A293EAF9A006AC140 /* SentryMetricProfiler.h */, + 844EDD6B2949387000C86F34 /* SentryMetricProfiler.h */, 8454CF8B293EAF9A006AC140 /* SentryMetricProfiler.mm */, 84A888FC28D9B11700C51DFD /* SentryProfiler+Test.h */, 844EDC7B2942843400C86F34 /* SentryProfiler+SwiftTest.h */, @@ -3180,7 +3180,6 @@ 63FE711F20DA4C1000CDBAE8 /* SentryCrashObjC.h in Headers */, 7BC3936825B1AB3E004F03D3 /* SentryLevelMapper.h in Headers */, 8E4E7C6E25DAAAFE006AB9E2 /* SentrySpan.h in Headers */, - 8454CF8C293EAF9A006AC140 /* SentryMetricProfiler.h in Headers */, D8ACE3CE2762187D00F5A213 /* SentryNSDataTracker.h in Headers */, 03F84D2427DD414C008FE43F /* SentryCompiler.h in Headers */, 631E6D331EBC679C00712345 /* SentryQueueableRequestManager.h in Headers */, @@ -3338,6 +3337,7 @@ 639FCF9C1EBC7F9500778193 /* SentryThread.h in Headers */, 63FE716B20DA4C1100CDBAE8 /* SentryCrashJSONCodecObjC.h in Headers */, 63AA76A51EB9CBC200D153DE /* SentryDsn.h in Headers */, + 844EDD6C2949387000C86F34 /* SentryMetricProfiler.h in Headers */, 636085131ED47BE600E8599E /* SentryFileManager.h in Headers */, 7BD729962463E83300EA3610 /* SentryDateUtil.h in Headers */, 63FE707B20DA4C1000CDBAE8 /* Container+SentryDeepSearch.h in Headers */, diff --git a/Sources/Sentry/SentryMetricProfiler.mm b/Sources/Sentry/SentryMetricProfiler.mm index a98b0a8209f..59e7b8cdc13 100644 --- a/Sources/Sentry/SentryMetricProfiler.mm +++ b/Sources/Sentry/SentryMetricProfiler.mm @@ -1,22 +1,24 @@ #import "SentryMetricProfiler.h" -#import "SentryDependencyContainer.h" -#import "SentryLog.h" -#import "SentryMachLogging.hpp" -#import "SentryNSNotificationCenterWrapper.h" -#import "SentryNSProcessInfoWrapper.h" -#import "SentryNSTimerWrapper.h" -#import "SentrySystemWrapper.h" -#import "SentryTime.h" + +NSString *const kSentryMetricProfilerSerializationKeyPowerState = @"is-low-power-mode"; +NSString *const kSentryMetricProfilerSerializationKeyThermalState = @"thermal-state"; +NSString *const kSentryMetricProfilerSerializationKeyCPUUsageFormat = @"cpu-usage-%d"; #if SENTRY_TARGET_PROFILING_SUPPORTED +# import "SentryDependencyContainer.h" +# import "SentryLog.h" +# import "SentryMachLogging.hpp" +# import "SentryNSNotificationCenterWrapper.h" +# import "SentryNSProcessInfoWrapper.h" +# import "SentryNSTimerWrapper.h" +# import "SentrySystemWrapper.h" +# import "SentryTime.h" + static const NSTimeInterval kSentryMetricProfilerTimeseriesInterval = 0.1; // 10 Hz NSString *const kSentryMetricProfilerSerializationKeyMemoryFootprint = @"memory-footprint"; NSString *const kSentryMetricProfilerSerializationKeyMemoryPressure = @"memory-pressure"; -NSString *const kSentryMetricProfilerSerializationKeyPowerState = @"is-low-power-mode"; -NSString *const kSentryMetricProfilerSerializationKeyThermalState = @"thermal-state"; -NSString *const kSentryMetricProfilerSerializationKeyCPUUsageFormat = @"cpu-usage-%d"; NSString *const kSentryMetricProfilerSerializationUnitBytes = @"bytes"; NSString *const kSentryMetricProfilerSerializationUnitBoolean = @"bool"; @@ -230,4 +232,14 @@ - (void)recordCPUPercentagePerCore @end +#else + +// if we don't have this implementation, we wind up with a linker error: "Undefined symbol: +// _OBJC_CLASS_$_SentryMetricProfiler" referenced from SentryProfiler. Even though both are +// completely covered by #if SENTRY_TARGET_PROFILING_SUPPORTED. Not sure what's going on there +// (armcknight 13 Dec 2022) + +@implementation SentryMetricProfiler +@end + #endif // SENTRY_TARGET_PROFILING_SUPPORTED diff --git a/Sources/Sentry/SentryProfiler.mm b/Sources/Sentry/SentryProfiler.mm index d6b8c09440a..bf98d41722d 100644 --- a/Sources/Sentry/SentryProfiler.mm +++ b/Sources/Sentry/SentryProfiler.mm @@ -189,14 +189,11 @@ @implementation SentryProfiler { + (void)initialize { -# if SENTRY_TARGET_PROFILING_SUPPORTED if (self == [SentryProfiler class]) { _gProfilersPerSpanID = [NSMutableDictionary dictionary]; } -# endif // SENTRY_TARGET_PROFILING_SUPPORTED } -# if SENTRY_TARGET_PROFILING_SUPPORTED - (instancetype)init { if (!(self = [super init])) { @@ -210,26 +207,22 @@ - (instancetype)init _transactions = [NSMutableArray array]; return self; } -# endif # pragma mark - Public + (void)startForSpanID:(SentrySpanId *)spanID hub:(SentryHub *)hub { -# if SENTRY_TARGET_PROFILING_SUPPORTED NSTimeInterval timeoutInterval = 30; -# if defined(TEST) || defined(TESTCI) +# if defined(TEST) || defined(TESTCI) timeoutInterval = 1; -# endif - [self startForSpanID:spanID hub:hub timeoutInterval:timeoutInterval]; # endif + [self startForSpanID:spanID hub:hub timeoutInterval:timeoutInterval]; } + (void)startForSpanID:(SentrySpanId *)spanID hub:(SentryHub *)hub timeoutInterval:(NSTimeInterval)timeoutInterval { -# if SENTRY_TARGET_PROFILING_SUPPORTED std::lock_guard l(_gProfilerLock); if (_gCurrentProfiler == nil) { @@ -238,9 +231,9 @@ + (void)startForSpanID:(SentrySpanId *)spanID SENTRY_LOG_WARN(@"Profiler was not initialized, will not proceed."); return; } -# if SENTRY_HAS_UIKIT +# if SENTRY_HAS_UIKIT [SentryFramesTracker.sharedInstance resetProfilingTimestamps]; -# endif // SENTRY_HAS_UIKIT +# endif // SENTRY_HAS_UIKIT [_gCurrentProfiler start]; _gCurrentProfiler->_timeoutTimer = [NSTimer scheduledTimerWithTimeInterval:timeoutInterval @@ -248,12 +241,12 @@ + (void)startForSpanID:(SentrySpanId *)spanID selector:@selector(timeoutAbort) userInfo:nil repeats:NO]; -# if SENTRY_HAS_UIKIT +# if SENTRY_HAS_UIKIT [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(backgroundAbort) name:UIApplicationWillResignActiveNotification object:nil]; -# endif // SENTRY_HAS_UIKIT +# endif // SENTRY_HAS_UIKIT _gCurrentProfiler->_hub = hub; } @@ -261,12 +254,10 @@ + (void)startForSpanID:(SentrySpanId *)spanID @"Tracking span with ID %@ with profiler %@", spanID.sentrySpanIdString, _gCurrentProfiler); [_gCurrentProfiler->_spansInFlight addObject:spanID]; _gProfilersPerSpanID[spanID] = _gCurrentProfiler; -# endif // SENTRY_TARGET_PROFILING_SUPPORTED } + (void)stopProfilingSpan:(id)span { -# if SENTRY_TARGET_PROFILING_SUPPORTED std::lock_guard l(_gProfilerLock); if (_gCurrentProfiler == nil) { @@ -280,12 +271,10 @@ + (void)stopProfilingSpan:(id)span _gCurrentProfiler, span.spanId.sentrySpanIdString); [self stopProfilerForReason:SentryProfilerTruncationReasonNormal]; } -# endif // SENTRY_TARGET_PROFILING_SUPPORTED } + (void)dropTransaction:(SentryTransaction *)transaction { -# if SENTRY_TARGET_PROFILING_SUPPORTED std::lock_guard l(_gProfilerLock); const auto spanID = transaction.trace.spanId; @@ -296,12 +285,10 @@ + (void)dropTransaction:(SentryTransaction *)transaction } [self captureEnvelopeIfFinished:profiler spanID:spanID]; -# endif // SENTRY_TARGET_PROFILING_SUPPORTED } + (void)linkTransaction:(SentryTransaction *)transaction { -# if SENTRY_TARGET_PROFILING_SUPPORTED std::lock_guard l(_gProfilerLock); const auto spanID = transaction.trace.spanId; @@ -316,15 +303,12 @@ + (void)linkTransaction:(SentryTransaction *)transaction [profiler addTransaction:transaction]; [self captureEnvelopeIfFinished:profiler spanID:spanID]; -# endif // SENTRY_TARGET_PROFILING_SUPPORTED } + (BOOL)isRunning { -# if SENTRY_TARGET_PROFILING_SUPPORTED std::lock_guard l(_gProfilerLock); return [_gCurrentProfiler isRunning]; -# endif // SENTRY_TARGET_PROFILING_SUPPORTED } # pragma mark - Testing diff --git a/Sources/Sentry/SentryMetricProfiler.h b/Sources/Sentry/include/SentryMetricProfiler.h similarity index 81% rename from Sources/Sentry/SentryMetricProfiler.h rename to Sources/Sentry/include/SentryMetricProfiler.h index 4688b22f245..4c789f7017b 100644 --- a/Sources/Sentry/SentryMetricProfiler.h +++ b/Sources/Sentry/include/SentryMetricProfiler.h @@ -1,6 +1,12 @@ #import "SentryDefines.h" #import +NS_ASSUME_NONNULL_BEGIN +SENTRY_EXTERN NSString *const kSentryMetricProfilerSerializationKeyPowerState; +SENTRY_EXTERN NSString *const kSentryMetricProfilerSerializationKeyThermalState; +SENTRY_EXTERN NSString *const kSentryMetricProfilerSerializationKeyCPUUsageFormat; +NS_ASSUME_NONNULL_END + #if SENTRY_TARGET_PROFILING_SUPPORTED @class SentryNSProcessInfoWrapper; @@ -11,9 +17,6 @@ NS_ASSUME_NONNULL_BEGIN SENTRY_EXTERN NSString *const kSentryMetricProfilerSerializationKeyMemoryFootprint; SENTRY_EXTERN NSString *const kSentryMetricProfilerSerializationKeyMemoryPressure; -SENTRY_EXTERN NSString *const kSentryMetricProfilerSerializationKeyPowerState; -SENTRY_EXTERN NSString *const kSentryMetricProfilerSerializationKeyThermalState; -SENTRY_EXTERN NSString *const kSentryMetricProfilerSerializationKeyCPUUsageFormat; SENTRY_EXTERN NSString *const kSentryMetricProfilerSerializationUnitBytes; SENTRY_EXTERN NSString *const kSentryMetricProfilerSerializationUnitBoolean; @@ -41,4 +44,14 @@ SENTRY_EXTERN NSString *const kSentryMetricProfilerSerializationUnitPercentage; NS_ASSUME_NONNULL_END +#else + +// if we don't have this declaration, we wind up with a linker error: "Undefined symbol: +// _OBJC_CLASS_$_SentryMetricProfiler" referenced from SentryProfiler. Even though both are +// completely covered by #if SENTRY_TARGET_PROFILING_SUPPORTED. Not sure what's going on there +// (armcknight 13 Dec 2022) + +@interface SentryMetricProfiler : NSObject +@end + #endif // SENTRY_TARGET_PROFILING_SUPPORTED diff --git a/Sources/Sentry/SentryNSProcessInfoWrapper.h b/Sources/Sentry/include/SentryNSProcessInfoWrapper.h similarity index 100% rename from Sources/Sentry/SentryNSProcessInfoWrapper.h rename to Sources/Sentry/include/SentryNSProcessInfoWrapper.h diff --git a/Sources/Sentry/SentryNSTimerWrapper+Test.h b/Sources/Sentry/include/SentryNSTimerWrapper+Test.h similarity index 100% rename from Sources/Sentry/SentryNSTimerWrapper+Test.h rename to Sources/Sentry/include/SentryNSTimerWrapper+Test.h diff --git a/Sources/Sentry/SentryNSTimerWrapper.h b/Sources/Sentry/include/SentryNSTimerWrapper.h similarity index 100% rename from Sources/Sentry/SentryNSTimerWrapper.h rename to Sources/Sentry/include/SentryNSTimerWrapper.h diff --git a/Sources/Sentry/SentrySystemWrapper.h b/Sources/Sentry/include/SentrySystemWrapper.h similarity index 100% rename from Sources/Sentry/SentrySystemWrapper.h rename to Sources/Sentry/include/SentrySystemWrapper.h diff --git a/Tests/SentryTests/Helper/TestSentryNSProcessInfoWrapper.swift b/Tests/SentryTests/Helper/TestSentryNSProcessInfoWrapper.swift index 81e24ec4cac..ac1aa95d90c 100644 --- a/Tests/SentryTests/Helper/TestSentryNSProcessInfoWrapper.swift +++ b/Tests/SentryTests/Helper/TestSentryNSProcessInfoWrapper.swift @@ -16,6 +16,7 @@ class TestSentryNSProcessInfoWrapper: SentryNSProcessInfoWrapper { overrides.isLowPowerModeEnabled ?? super.isLowPowerModeEnabled } + @available(iOS 9.0, macOS 12.0, *) func sendPowerStateChangeNotification() { SentryDependencyContainer.sharedInstance().notificationCenterWrapper.postNotificationName(NSNotification.Name.NSProcessInfoPowerStateDidChange, object: ProcessInfo.processInfo) } diff --git a/Tests/SentryTests/Profiling/SentryProfilerSwiftTests.swift b/Tests/SentryTests/Profiling/SentryProfilerSwiftTests.swift index 1bdb3d618aa..63ff99fdaad 100644 --- a/Tests/SentryTests/Profiling/SentryProfilerSwiftTests.swift +++ b/Tests/SentryTests/Profiling/SentryProfilerSwiftTests.swift @@ -70,7 +70,9 @@ class SentryProfilerSwiftTests: XCTestCase { [true, false].forEach { fixture.processInfoWrapper.overrides.isLowPowerModeEnabled = $0 - fixture.processInfoWrapper.sendPowerStateChangeNotification() + if #available(macOS 12.0, *) { + fixture.processInfoWrapper.sendPowerStateChangeNotification() + } } [ProcessInfo.ThermalState.critical, .serious, .fair, .nominal].forEach { @@ -305,12 +307,14 @@ private extension SentryProfilerSwiftTests { throw TestError.unexpectedMeasurementsDeserializationType } - guard let powerStateEntry = measurements[kSentryMetricProfilerSerializationKeyPowerState] as? [String: Any], let powerState = powerStateEntry["values"] as? [[String: Any]] else { - throw TestError.noPowerStateEvents + if #available(macOS 12.0, *) { + guard let powerStateEntry = measurements[kSentryMetricProfilerSerializationKeyPowerState] as? [String: Any], let powerState = powerStateEntry["values"] as? [[String: Any]] else { + throw TestError.noPowerStateEvents + } + + XCTAssertEqual(powerState.count, powerStateNotifications) } - XCTAssertEqual(powerState.count, powerStateNotifications) - guard let thermalStateEntry = measurements[kSentryMetricProfilerSerializationKeyThermalState] as? [String: Any], let thermalState = thermalStateEntry["values"] as? [[String: Any]] else { throw TestError.noThermalStateEvents } From 039e934d72c1e77f195dc93b0c608dd9a3349ba7 Mon Sep 17 00:00:00 2001 From: Andrew McKnight Date: Tue, 13 Dec 2022 14:27:02 -0900 Subject: [PATCH 21/70] again --- Sources/Sentry/SentryMetricProfiler.mm | 17 +++------------- Sources/Sentry/include/SentryMetricProfiler.h | 20 ++++--------------- 2 files changed, 7 insertions(+), 30 deletions(-) diff --git a/Sources/Sentry/SentryMetricProfiler.mm b/Sources/Sentry/SentryMetricProfiler.mm index 59e7b8cdc13..443c0198a61 100644 --- a/Sources/Sentry/SentryMetricProfiler.mm +++ b/Sources/Sentry/SentryMetricProfiler.mm @@ -1,9 +1,5 @@ #import "SentryMetricProfiler.h" -NSString *const kSentryMetricProfilerSerializationKeyPowerState = @"is-low-power-mode"; -NSString *const kSentryMetricProfilerSerializationKeyThermalState = @"thermal-state"; -NSString *const kSentryMetricProfilerSerializationKeyCPUUsageFormat = @"cpu-usage-%d"; - #if SENTRY_TARGET_PROFILING_SUPPORTED # import "SentryDependencyContainer.h" @@ -19,6 +15,9 @@ NSString *const kSentryMetricProfilerSerializationKeyMemoryFootprint = @"memory-footprint"; NSString *const kSentryMetricProfilerSerializationKeyMemoryPressure = @"memory-pressure"; +NSString *const kSentryMetricProfilerSerializationKeyPowerState = @"is-low-power-mode"; +NSString *const kSentryMetricProfilerSerializationKeyThermalState = @"thermal-state"; +NSString *const kSentryMetricProfilerSerializationKeyCPUUsageFormat = @"cpu-usage-%d"; NSString *const kSentryMetricProfilerSerializationUnitBytes = @"bytes"; NSString *const kSentryMetricProfilerSerializationUnitBoolean = @"bool"; @@ -232,14 +231,4 @@ - (void)recordCPUPercentagePerCore @end -#else - -// if we don't have this implementation, we wind up with a linker error: "Undefined symbol: -// _OBJC_CLASS_$_SentryMetricProfiler" referenced from SentryProfiler. Even though both are -// completely covered by #if SENTRY_TARGET_PROFILING_SUPPORTED. Not sure what's going on there -// (armcknight 13 Dec 2022) - -@implementation SentryMetricProfiler -@end - #endif // SENTRY_TARGET_PROFILING_SUPPORTED diff --git a/Sources/Sentry/include/SentryMetricProfiler.h b/Sources/Sentry/include/SentryMetricProfiler.h index 4c789f7017b..9a667d1083e 100644 --- a/Sources/Sentry/include/SentryMetricProfiler.h +++ b/Sources/Sentry/include/SentryMetricProfiler.h @@ -1,12 +1,7 @@ #import "SentryDefines.h" +#import "SentryProfilingConditionals.h" #import -NS_ASSUME_NONNULL_BEGIN -SENTRY_EXTERN NSString *const kSentryMetricProfilerSerializationKeyPowerState; -SENTRY_EXTERN NSString *const kSentryMetricProfilerSerializationKeyThermalState; -SENTRY_EXTERN NSString *const kSentryMetricProfilerSerializationKeyCPUUsageFormat; -NS_ASSUME_NONNULL_END - #if SENTRY_TARGET_PROFILING_SUPPORTED @class SentryNSProcessInfoWrapper; @@ -17,6 +12,9 @@ NS_ASSUME_NONNULL_BEGIN SENTRY_EXTERN NSString *const kSentryMetricProfilerSerializationKeyMemoryFootprint; SENTRY_EXTERN NSString *const kSentryMetricProfilerSerializationKeyMemoryPressure; +SENTRY_EXTERN NSString *const kSentryMetricProfilerSerializationKeyPowerState; +SENTRY_EXTERN NSString *const kSentryMetricProfilerSerializationKeyThermalState; +SENTRY_EXTERN NSString *const kSentryMetricProfilerSerializationKeyCPUUsageFormat; SENTRY_EXTERN NSString *const kSentryMetricProfilerSerializationUnitBytes; SENTRY_EXTERN NSString *const kSentryMetricProfilerSerializationUnitBoolean; @@ -44,14 +42,4 @@ SENTRY_EXTERN NSString *const kSentryMetricProfilerSerializationUnitPercentage; NS_ASSUME_NONNULL_END -#else - -// if we don't have this declaration, we wind up with a linker error: "Undefined symbol: -// _OBJC_CLASS_$_SentryMetricProfiler" referenced from SentryProfiler. Even though both are -// completely covered by #if SENTRY_TARGET_PROFILING_SUPPORTED. Not sure what's going on there -// (armcknight 13 Dec 2022) - -@interface SentryMetricProfiler : NSObject -@end - #endif // SENTRY_TARGET_PROFILING_SUPPORTED From 3c2653e3315a383010e8f5733e94eac65d9cc91f Mon Sep 17 00:00:00 2001 From: Andrew McKnight Date: Tue, 13 Dec 2022 14:57:44 -0900 Subject: [PATCH 22/70] try to fix possible timing issue in CI --- .../Profiling/SentryProfilerSwiftTests.swift | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/Tests/SentryTests/Profiling/SentryProfilerSwiftTests.swift b/Tests/SentryTests/Profiling/SentryProfilerSwiftTests.swift index 63ff99fdaad..68975b46c0a 100644 --- a/Tests/SentryTests/Profiling/SentryProfilerSwiftTests.swift +++ b/Tests/SentryTests/Profiling/SentryProfilerSwiftTests.swift @@ -80,11 +80,17 @@ class SentryProfilerSwiftTests: XCTestCase { fixture.processInfoWrapper.sendThermalStateChangeNotification() } - self.fixture.timerWrapper.fire() - self.fixture.timerWrapper.fire() + for _ in 0..<2 { + let cpuExp = expectation(description: "first set of cpu measurements are gathered") + DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) { + self.fixture.timerWrapper.fire() + cpuExp.fulfill() + } + waitForExpectations(timeout: 1) + } let exp = expectation(description: "Receives profile payload") - DispatchQueue.main.asyncAfter(deadline: .now() + 2) { + DispatchQueue.main.asyncAfter(deadline: .now() + 1) { span.finish() do { From 58bf05f36ee9b8c8fabd0c3fc765263026116ef9 Mon Sep 17 00:00:00 2001 From: Andrew McKnight Date: Tue, 13 Dec 2022 16:59:22 -0900 Subject: [PATCH 23/70] add debug log --- Sources/Sentry/SentryMetricProfiler.mm | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Sources/Sentry/SentryMetricProfiler.mm b/Sources/Sentry/SentryMetricProfiler.mm index 443c0198a61..17c9a4f80ff 100644 --- a/Sources/Sentry/SentryMetricProfiler.mm +++ b/Sources/Sentry/SentryMetricProfiler.mm @@ -217,6 +217,8 @@ - (void)recordCPUPercentagePerCore return; } + SENTRY_LOG_DEBUG(@"Reporting CPU usages: %@", result); + [result enumerateObjectsUsingBlock:^(NSNumber *_Nonnull usage, NSUInteger core, BOOL *_Nonnull stop) { [_cpuUsage[@(core)] addObject:[self metricEntryForValue:usage]]; }]; } From 3c0da5e783f34a085cb2b9249b68140da76ddb72 Mon Sep 17 00:00:00 2001 From: Andrew McKnight Date: Tue, 13 Dec 2022 17:59:31 -0900 Subject: [PATCH 24/70] more logging --- Sources/Sentry/SentryProfiler.mm | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/Sources/Sentry/SentryProfiler.mm b/Sources/Sentry/SentryProfiler.mm index bf98d41722d..8d351ece71c 100644 --- a/Sources/Sentry/SentryProfiler.mm +++ b/Sources/Sentry/SentryProfiler.mm @@ -686,6 +686,12 @@ - (void)captureEnvelope const auto envelope = [[SentryEnvelope alloc] initWithHeader:envelopeHeader singleItem:item]; SENTRY_LOG_DEBUG(@"Capturing profile envelope."); + SENTRY_LOG_DEBUG(@"Payload: %@", + [[NSString alloc] + initWithData:[NSJSONSerialization dataWithJSONObject:profile + options:NSJSONWritingPrettyPrinted + error:nil] + encoding:NSUTF8StringEncoding]); [_hub captureEnvelope:envelope]; } From 2492abec43e4afb9ee9e3176f860fc2a86a44139 Mon Sep 17 00:00:00 2001 From: Andrew McKnight Date: Tue, 13 Dec 2022 18:21:59 -0900 Subject: [PATCH 25/70] format log --- Sources/Sentry/SentryProfiler.mm | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/Sources/Sentry/SentryProfiler.mm b/Sources/Sentry/SentryProfiler.mm index 8d351ece71c..bf29090d41b 100644 --- a/Sources/Sentry/SentryProfiler.mm +++ b/Sources/Sentry/SentryProfiler.mm @@ -687,11 +687,10 @@ - (void)captureEnvelope SENTRY_LOG_DEBUG(@"Capturing profile envelope."); SENTRY_LOG_DEBUG(@"Payload: %@", - [[NSString alloc] - initWithData:[NSJSONSerialization dataWithJSONObject:profile - options:NSJSONWritingPrettyPrinted - error:nil] - encoding:NSUTF8StringEncoding]); + [[NSString alloc] initWithData:[NSJSONSerialization dataWithJSONObject:profile + options:0 + error:nil] + encoding:NSUTF8StringEncoding]); [_hub captureEnvelope:envelope]; } From c11991a6e46ae984622ad40ce0716e1132e82ff4 Mon Sep 17 00:00:00 2001 From: Andrew McKnight Date: Tue, 13 Dec 2022 18:22:47 -0900 Subject: [PATCH 26/70] only print metrics --- Sources/Sentry/SentryProfiler.mm | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/Sources/Sentry/SentryProfiler.mm b/Sources/Sentry/SentryProfiler.mm index bf29090d41b..0b7acbac3f0 100644 --- a/Sources/Sentry/SentryProfiler.mm +++ b/Sources/Sentry/SentryProfiler.mm @@ -686,11 +686,12 @@ - (void)captureEnvelope const auto envelope = [[SentryEnvelope alloc] initWithHeader:envelopeHeader singleItem:item]; SENTRY_LOG_DEBUG(@"Capturing profile envelope."); - SENTRY_LOG_DEBUG(@"Payload: %@", - [[NSString alloc] initWithData:[NSJSONSerialization dataWithJSONObject:profile - options:0 - error:nil] - encoding:NSUTF8StringEncoding]); + SENTRY_LOG_DEBUG(@"Metrics Payload: %@", + [[NSString alloc] + initWithData:[NSJSONSerialization dataWithJSONObject:profile[@"measurements"] + options:0 + error:nil] + encoding:NSUTF8StringEncoding]); [_hub captureEnvelope:envelope]; } From 447504811f3f3e36eda1e4212e9384611edf29f7 Mon Sep 17 00:00:00 2001 From: Andrew McKnight Date: Wed, 14 Dec 2022 09:28:26 -0900 Subject: [PATCH 27/70] only add slow frame renders if info exists --- Sources/Sentry/SentryProfiler.mm | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/Sources/Sentry/SentryProfiler.mm b/Sources/Sentry/SentryProfiler.mm index 0b7acbac3f0..e142a1133ed 100644 --- a/Sources/Sentry/SentryProfiler.mm +++ b/Sources/Sentry/SentryProfiler.mm @@ -602,8 +602,10 @@ - (void)captureEnvelope @"value" : @(frameDuration), }]; }]; - metrics[@"slow_frame_renders"] = - @{ @"unit" : @"nanoseconds", @"values" : relativeFrameTimestampsNs }; + if (relativeFrameTimestampsNs.count > 0) { + metrics[@"slow_frame_renders"] = + @{ @"unit" : @"nanoseconds", @"values" : relativeFrameTimestampsNs }; + } relativeFrameTimestampsNs = [NSMutableArray array]; [_frameInfo.frameRateTimestamps enumerateObjectsUsingBlock:^( @@ -619,7 +621,10 @@ - (void)captureEnvelope @"value" : refreshRate, }]; }]; - metrics[@"screen_frame_rates"] = @{ @"unit" : @"hz", @"values" : relativeFrameTimestampsNs }; + if (relativeFrameTimestampsNs.count > 0) { + metrics[@"screen_frame_rates"] = + @{ @"unit" : @"hz", @"values" : relativeFrameTimestampsNs }; + } # endif // SENTRY_HAS_UIKIT // populate info from all transactions that occurred while profiler was running From 7b6ccf3ac8225bca4a1a93c7010751a96b219d0c Mon Sep 17 00:00:00 2001 From: Andrew McKnight Date: Wed, 14 Dec 2022 09:35:43 -0900 Subject: [PATCH 28/70] logging --- Sources/Sentry/SentryMetricProfiler.mm | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/Sources/Sentry/SentryMetricProfiler.mm b/Sources/Sentry/SentryMetricProfiler.mm index 17c9a4f80ff..51ee6af2f7c 100644 --- a/Sources/Sentry/SentryMetricProfiler.mm +++ b/Sources/Sentry/SentryMetricProfiler.mm @@ -61,6 +61,7 @@ - (instancetype)initWithProfileStartTime:(uint64_t)profileStartTime NSMutableArray *> *> dictionary]; const auto processorCount = processInfoWrapper.processorCount; + SENTRY_LOG_DEBUG(@"Preparing %d arrays for CPU core usage readings", processorCount); for (NSUInteger core = 0; core < processorCount; core++) { _cpuUsage[@(core)] = [NSMutableArray *> array]; } @@ -124,6 +125,7 @@ - (void)stop [_cpuUsage enumerateKeysAndObjectsUsingBlock:^(NSNumber *_Nonnull core, NSMutableArray *> *_Nonnull readings, BOOL *_Nonnull stop) { + SENTRY_LOG_DEBUG(@"Serializing CPU usages for core %@: %@", core, readings); if (readings.count > 0) { dict[[NSString stringWithFormat:kSentryMetricProfilerSerializationKeyCPUUsageFormat, core.intValue]] @@ -219,8 +221,11 @@ - (void)recordCPUPercentagePerCore SENTRY_LOG_DEBUG(@"Reporting CPU usages: %@", result); - [result enumerateObjectsUsingBlock:^(NSNumber *_Nonnull usage, NSUInteger core, - BOOL *_Nonnull stop) { [_cpuUsage[@(core)] addObject:[self metricEntryForValue:usage]]; }]; + [result enumerateObjectsUsingBlock:^( + NSNumber *_Nonnull usage, NSUInteger core, BOOL *_Nonnull stop) { + SENTRY_LOG_DEBUG(@"Adding cpu usage %@ for core %d", usage, core); + [_cpuUsage[@(core)] addObject:[self metricEntryForValue:usage]]; + }]; } - (NSDictionary *)metricEntryForValue:(NSNumber *)value From e3f4b10981fa833b486e77c66561203f87c80bf3 Mon Sep 17 00:00:00 2001 From: Andrew McKnight Date: Wed, 14 Dec 2022 09:44:42 -0900 Subject: [PATCH 29/70] fixup! logging --- Sources/Sentry/SentryMetricProfiler.mm | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Sources/Sentry/SentryMetricProfiler.mm b/Sources/Sentry/SentryMetricProfiler.mm index 51ee6af2f7c..b7f4c87426a 100644 --- a/Sources/Sentry/SentryMetricProfiler.mm +++ b/Sources/Sentry/SentryMetricProfiler.mm @@ -61,7 +61,8 @@ - (instancetype)initWithProfileStartTime:(uint64_t)profileStartTime NSMutableArray *> *> dictionary]; const auto processorCount = processInfoWrapper.processorCount; - SENTRY_LOG_DEBUG(@"Preparing %d arrays for CPU core usage readings", processorCount); + SENTRY_LOG_DEBUG( + @"Preparing %lu arrays for CPU core usage readings", (long unsigned)processorCount); for (NSUInteger core = 0; core < processorCount; core++) { _cpuUsage[@(core)] = [NSMutableArray *> array]; } @@ -223,7 +224,7 @@ - (void)recordCPUPercentagePerCore [result enumerateObjectsUsingBlock:^( NSNumber *_Nonnull usage, NSUInteger core, BOOL *_Nonnull stop) { - SENTRY_LOG_DEBUG(@"Adding cpu usage %@ for core %d", usage, core); + SENTRY_LOG_DEBUG(@"Adding cpu usage %@ for core %lu", usage, (long unsigned)core); [_cpuUsage[@(core)] addObject:[self metricEntryForValue:usage]]; }]; } From 621d3404176639cda10d122a6df50fe02b223c6f Mon Sep 17 00:00:00 2001 From: Andrew McKnight Date: Wed, 14 Dec 2022 10:01:29 -0900 Subject: [PATCH 30/70] logging --- Sources/Sentry/SentryMetricProfiler.mm | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Sources/Sentry/SentryMetricProfiler.mm b/Sources/Sentry/SentryMetricProfiler.mm index b7f4c87426a..9466a554245 100644 --- a/Sources/Sentry/SentryMetricProfiler.mm +++ b/Sources/Sentry/SentryMetricProfiler.mm @@ -134,6 +134,8 @@ - (void)stop } }]; + SENTRY_LOG_DEBUG(@"Returning serialized metrics: %@", dict); + return dict; } From 34a4b94d429b21a17c8759699090726c046f3abc Mon Sep 17 00:00:00 2001 From: Andrew McKnight Date: Wed, 14 Dec 2022 10:41:06 -0900 Subject: [PATCH 31/70] more logging --- Tests/SentryTests/Profiling/SentryProfilerSwiftTests.swift | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Tests/SentryTests/Profiling/SentryProfilerSwiftTests.swift b/Tests/SentryTests/Profiling/SentryProfilerSwiftTests.swift index 68975b46c0a..208a6a7e099 100644 --- a/Tests/SentryTests/Profiling/SentryProfilerSwiftTests.swift +++ b/Tests/SentryTests/Profiling/SentryProfilerSwiftTests.swift @@ -329,7 +329,9 @@ private extension SentryProfilerSwiftTests { XCTAssertEqual(thermalState.count, thermalStateNotifications + 1) for (i, expectedUsage) in expectedCPUUsages.enumerated() { - guard let cpuUsage = measurements[NSString(format: kSentryMetricProfilerSerializationKeyCPUUsageFormat as NSString, i) as String] as? [String: Any] else { + let key = NSString(format: kSentryMetricProfilerSerializationKeyCPUUsageFormat as NSString, i) as String + guard let cpuUsage = measurements[key] as? [String: Any] else { + print("no key \(key) in measurements payload: \(measurements)") throw TestError.noCPUUsageEvents } guard let values = cpuUsage["values"] as? [[String: Any]] else { From 6cf38cc9ef21ac933bc277da224938e197211551 Mon Sep 17 00:00:00 2001 From: Andrew McKnight Date: Wed, 14 Dec 2022 11:17:41 -0900 Subject: [PATCH 32/70] override processor count --- .../SentryTests/Helper/TestSentryNSProcessInfoWrapper.swift | 5 +++++ Tests/SentryTests/Profiling/SentryProfilerSwiftTests.swift | 1 + 2 files changed, 6 insertions(+) diff --git a/Tests/SentryTests/Helper/TestSentryNSProcessInfoWrapper.swift b/Tests/SentryTests/Helper/TestSentryNSProcessInfoWrapper.swift index ac1aa95d90c..ec2df68b04a 100644 --- a/Tests/SentryTests/Helper/TestSentryNSProcessInfoWrapper.swift +++ b/Tests/SentryTests/Helper/TestSentryNSProcessInfoWrapper.swift @@ -4,6 +4,7 @@ class TestSentryNSProcessInfoWrapper: SentryNSProcessInfoWrapper { struct Override { var isLowPowerModeEnabled: Bool? var thermalState: ProcessInfo.ThermalState? + var processorCount: UInt } var overrides = Override() @@ -16,6 +17,10 @@ class TestSentryNSProcessInfoWrapper: SentryNSProcessInfoWrapper { overrides.isLowPowerModeEnabled ?? super.isLowPowerModeEnabled } + override var processorCount: UInt { + overrides.processorCount ?? super.processorCount + } + @available(iOS 9.0, macOS 12.0, *) func sendPowerStateChangeNotification() { SentryDependencyContainer.sharedInstance().notificationCenterWrapper.postNotificationName(NSNotification.Name.NSProcessInfoPowerStateDidChange, object: ProcessInfo.processInfo) diff --git a/Tests/SentryTests/Profiling/SentryProfilerSwiftTests.swift b/Tests/SentryTests/Profiling/SentryProfilerSwiftTests.swift index 208a6a7e099..f18138b0dcf 100644 --- a/Tests/SentryTests/Profiling/SentryProfilerSwiftTests.swift +++ b/Tests/SentryTests/Profiling/SentryProfilerSwiftTests.swift @@ -61,6 +61,7 @@ class SentryProfilerSwiftTests: XCTestCase { let cpuUsages = [12.4, 63.5, 1.4, 4.6] fixture.systemWrapper.overrides.cpuUsagePerCore = cpuUsages.map { NSNumber(value: $0) } + fixture.processInfoWrapper.processorCount = cpuUsages.count let memoryFootprint: SentryRAMBytes = 123_455 fixture.systemWrapper.overrides.memoryFootprintBytes = memoryFootprint From cf54864de53aacd36bdd5892cedca0f8c7337c3c Mon Sep 17 00:00:00 2001 From: Andrew McKnight Date: Wed, 14 Dec 2022 11:19:37 -0900 Subject: [PATCH 33/70] remove some verbose debug logs for testing --- Sources/Sentry/SentryMetricProfiler.mm | 12 ++---------- Sources/Sentry/SentryProfiler.mm | 6 ------ .../Profiling/SentryProfilerSwiftTests.swift | 1 - 3 files changed, 2 insertions(+), 17 deletions(-) diff --git a/Sources/Sentry/SentryMetricProfiler.mm b/Sources/Sentry/SentryMetricProfiler.mm index 9466a554245..2124c1e902a 100644 --- a/Sources/Sentry/SentryMetricProfiler.mm +++ b/Sources/Sentry/SentryMetricProfiler.mm @@ -126,7 +126,6 @@ - (void)stop [_cpuUsage enumerateKeysAndObjectsUsingBlock:^(NSNumber *_Nonnull core, NSMutableArray *> *_Nonnull readings, BOOL *_Nonnull stop) { - SENTRY_LOG_DEBUG(@"Serializing CPU usages for core %@: %@", core, readings); if (readings.count > 0) { dict[[NSString stringWithFormat:kSentryMetricProfilerSerializationKeyCPUUsageFormat, core.intValue]] @@ -134,8 +133,6 @@ - (void)stop } }]; - SENTRY_LOG_DEBUG(@"Returning serialized metrics: %@", dict); - return dict; } @@ -222,13 +219,8 @@ - (void)recordCPUPercentagePerCore return; } - SENTRY_LOG_DEBUG(@"Reporting CPU usages: %@", result); - - [result enumerateObjectsUsingBlock:^( - NSNumber *_Nonnull usage, NSUInteger core, BOOL *_Nonnull stop) { - SENTRY_LOG_DEBUG(@"Adding cpu usage %@ for core %lu", usage, (long unsigned)core); - [_cpuUsage[@(core)] addObject:[self metricEntryForValue:usage]]; - }]; + [result enumerateObjectsUsingBlock:^(NSNumber *_Nonnull usage, NSUInteger core, + BOOL *_Nonnull stop) { [_cpuUsage[@(core)] addObject:[self metricEntryForValue:usage]]; }]; } - (NSDictionary *)metricEntryForValue:(NSNumber *)value diff --git a/Sources/Sentry/SentryProfiler.mm b/Sources/Sentry/SentryProfiler.mm index e142a1133ed..0acfb5d853f 100644 --- a/Sources/Sentry/SentryProfiler.mm +++ b/Sources/Sentry/SentryProfiler.mm @@ -691,12 +691,6 @@ - (void)captureEnvelope const auto envelope = [[SentryEnvelope alloc] initWithHeader:envelopeHeader singleItem:item]; SENTRY_LOG_DEBUG(@"Capturing profile envelope."); - SENTRY_LOG_DEBUG(@"Metrics Payload: %@", - [[NSString alloc] - initWithData:[NSJSONSerialization dataWithJSONObject:profile[@"measurements"] - options:0 - error:nil] - encoding:NSUTF8StringEncoding]); [_hub captureEnvelope:envelope]; } diff --git a/Tests/SentryTests/Profiling/SentryProfilerSwiftTests.swift b/Tests/SentryTests/Profiling/SentryProfilerSwiftTests.swift index f18138b0dcf..8ef7a466214 100644 --- a/Tests/SentryTests/Profiling/SentryProfilerSwiftTests.swift +++ b/Tests/SentryTests/Profiling/SentryProfilerSwiftTests.swift @@ -332,7 +332,6 @@ private extension SentryProfilerSwiftTests { for (i, expectedUsage) in expectedCPUUsages.enumerated() { let key = NSString(format: kSentryMetricProfilerSerializationKeyCPUUsageFormat as NSString, i) as String guard let cpuUsage = measurements[key] as? [String: Any] else { - print("no key \(key) in measurements payload: \(measurements)") throw TestError.noCPUUsageEvents } guard let values = cpuUsage["values"] as? [[String: Any]] else { From 8c1a6fc9a2edc2f9d80e9fe727547587294d8d8f Mon Sep 17 00:00:00 2001 From: Andrew McKnight Date: Wed, 14 Dec 2022 11:23:52 -0900 Subject: [PATCH 34/70] fix build --- Tests/SentryTests/Helper/TestSentryNSProcessInfoWrapper.swift | 2 +- Tests/SentryTests/Profiling/SentryProfilerSwiftTests.swift | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Tests/SentryTests/Helper/TestSentryNSProcessInfoWrapper.swift b/Tests/SentryTests/Helper/TestSentryNSProcessInfoWrapper.swift index ec2df68b04a..a7edad4809e 100644 --- a/Tests/SentryTests/Helper/TestSentryNSProcessInfoWrapper.swift +++ b/Tests/SentryTests/Helper/TestSentryNSProcessInfoWrapper.swift @@ -4,7 +4,7 @@ class TestSentryNSProcessInfoWrapper: SentryNSProcessInfoWrapper { struct Override { var isLowPowerModeEnabled: Bool? var thermalState: ProcessInfo.ThermalState? - var processorCount: UInt + var processorCount: UInt? } var overrides = Override() diff --git a/Tests/SentryTests/Profiling/SentryProfilerSwiftTests.swift b/Tests/SentryTests/Profiling/SentryProfilerSwiftTests.swift index 8ef7a466214..e0003ff991d 100644 --- a/Tests/SentryTests/Profiling/SentryProfilerSwiftTests.swift +++ b/Tests/SentryTests/Profiling/SentryProfilerSwiftTests.swift @@ -61,7 +61,7 @@ class SentryProfilerSwiftTests: XCTestCase { let cpuUsages = [12.4, 63.5, 1.4, 4.6] fixture.systemWrapper.overrides.cpuUsagePerCore = cpuUsages.map { NSNumber(value: $0) } - fixture.processInfoWrapper.processorCount = cpuUsages.count + fixture.processInfoWrapper.overrides.processorCount = UInt(cpuUsages.count) let memoryFootprint: SentryRAMBytes = 123_455 fixture.systemWrapper.overrides.memoryFootprintBytes = memoryFootprint From 65044c1977929b2846f4fcb7c1832f8811ef9ab7 Mon Sep 17 00:00:00 2001 From: Andrew McKnight Date: Thu, 15 Dec 2022 17:02:11 -0900 Subject: [PATCH 35/70] add dispatch source wrapper to mock memory pressure notifications --- Sentry.xcodeproj/project.pbxproj | 12 +++++ Sources/Sentry/SentryDispatchSourceWrapper.m | 48 +++++++++++++++++ Sources/Sentry/SentryProfiler.mm | 2 + Sources/Sentry/SentrySystemWrapper.mm | 52 ++++++++++++++----- .../include/SentryDispatchSourceWrapper.h | 26 ++++++++++ .../include/SentrySystemWrapper+Tests.h | 8 +++ Sources/Sentry/include/SentrySystemWrapper.h | 9 ++++ .../Helper/TestSentrySystemWrapper.swift | 4 ++ .../TestSentryDispatchSourceWrapper.swift | 28 ++++++++++ .../Profiling/SentryProfilerSwiftTests.swift | 25 +++++++-- .../SentryTests/SentryTests-Bridging-Header.h | 3 +- 11 files changed, 200 insertions(+), 17 deletions(-) create mode 100644 Sources/Sentry/SentryDispatchSourceWrapper.m create mode 100644 Sources/Sentry/include/SentryDispatchSourceWrapper.h create mode 100644 Sources/Sentry/include/SentrySystemWrapper+Tests.h create mode 100644 Tests/SentryTests/Networking/TestSentryDispatchSourceWrapper.swift diff --git a/Sentry.xcodeproj/project.pbxproj b/Sentry.xcodeproj/project.pbxproj index 03935329645..0ecfa4308e4 100644 --- a/Sentry.xcodeproj/project.pbxproj +++ b/Sentry.xcodeproj/project.pbxproj @@ -601,6 +601,8 @@ 7DC83100239826280043DD9A /* SentryIntegrationProtocol.h in Headers */ = {isa = PBXBuildFile; fileRef = 7DC830FF239826280043DD9A /* SentryIntegrationProtocol.h */; settings = {ATTRIBUTES = (Public, ); }; }; 7DC8310A2398283C0043DD9A /* SentryCrashIntegration.h in Headers */ = {isa = PBXBuildFile; fileRef = 7DC831082398283C0043DD9A /* SentryCrashIntegration.h */; }; 7DC8310C2398283C0043DD9A /* SentryCrashIntegration.m in Sources */ = {isa = PBXBuildFile; fileRef = 7DC831092398283C0043DD9A /* SentryCrashIntegration.m */; }; + 840748E5294BFE980077FC0A /* TestSentryDispatchSourceWrapper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 840748E4294BFE980077FC0A /* TestSentryDispatchSourceWrapper.swift */; }; + 840748E9294BFF9B0077FC0A /* SentryDispatchSourceWrapper.m in Sources */ = {isa = PBXBuildFile; fileRef = 840748E7294BFEBD0077FC0A /* SentryDispatchSourceWrapper.m */; }; 8419C0C428C1889D001C8259 /* SentryProfilerSwiftTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8419C0C328C1889D001C8259 /* SentryProfilerSwiftTests.swift */; }; 844EDC6F294143B900C86F34 /* SentryNSProcessInfoWrapper.h in Headers */ = {isa = PBXBuildFile; fileRef = 844EDC6D294143B900C86F34 /* SentryNSProcessInfoWrapper.h */; }; 844EDC70294143B900C86F34 /* SentryNSProcessInfoWrapper.mm in Sources */ = {isa = PBXBuildFile; fileRef = 844EDC6E294143B900C86F34 /* SentryNSProcessInfoWrapper.mm */; }; @@ -1415,6 +1417,10 @@ 7DC830FF239826280043DD9A /* SentryIntegrationProtocol.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = SentryIntegrationProtocol.h; path = Public/SentryIntegrationProtocol.h; sourceTree = ""; }; 7DC831082398283C0043DD9A /* SentryCrashIntegration.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = SentryCrashIntegration.h; path = include/SentryCrashIntegration.h; sourceTree = ""; }; 7DC831092398283C0043DD9A /* SentryCrashIntegration.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SentryCrashIntegration.m; sourceTree = ""; }; + 840748E4294BFE980077FC0A /* TestSentryDispatchSourceWrapper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TestSentryDispatchSourceWrapper.swift; sourceTree = ""; }; + 840748E6294BFEBD0077FC0A /* SentryDispatchSourceWrapper.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = SentryDispatchSourceWrapper.h; path = include/SentryDispatchSourceWrapper.h; sourceTree = ""; }; + 840748E7294BFEBD0077FC0A /* SentryDispatchSourceWrapper.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SentryDispatchSourceWrapper.m; sourceTree = ""; }; + 840748EA294C078B0077FC0A /* SentrySystemWrapper+Tests.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = "SentrySystemWrapper+Tests.h"; path = "include/SentrySystemWrapper+Tests.h"; sourceTree = ""; }; 8419C0C328C1889D001C8259 /* SentryProfilerSwiftTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SentryProfilerSwiftTests.swift; sourceTree = ""; }; 844A34C3282B278500C6D1DF /* .github */ = {isa = PBXFileReference; lastKnownFileType = folder; path = .github; sourceTree = ""; }; 844A3563282B3C9F00C6D1DF /* .sauce */ = {isa = PBXFileReference; lastKnownFileType = folder; path = .sauce; sourceTree = ""; }; @@ -1701,6 +1707,8 @@ 638DC99F1EBC6B6400A66E41 /* SentryRequestOperation.m */, 7BDB03B6251364F800BAE198 /* SentryDispatchQueueWrapper.h */, 7BDB03BA2513652900BAE198 /* SentryDispatchQueueWrapper.m */, + 840748E6294BFEBD0077FC0A /* SentryDispatchSourceWrapper.h */, + 840748E7294BFEBD0077FC0A /* SentryDispatchSourceWrapper.m */, 0AAE202028ED9BCC00D0CD80 /* SentryReachability.h */, 0AAE201D28ED9B9400D0CD80 /* SentryReachability.m */, ); @@ -1977,6 +1985,7 @@ 844EDC6D294143B900C86F34 /* SentryNSProcessInfoWrapper.h */, 844EDC6E294143B900C86F34 /* SentryNSProcessInfoWrapper.mm */, 844EDC74294144DB00C86F34 /* SentrySystemWrapper.h */, + 840748EA294C078B0077FC0A /* SentrySystemWrapper+Tests.h */, 844EDC75294144DB00C86F34 /* SentrySystemWrapper.mm */, 844EDCE32947DC3100C86F34 /* SentryNSTimerWrapper.h */, 844EDCE92947E78B00C86F34 /* SentryNSTimerWrapper+Test.h */, @@ -2535,6 +2544,7 @@ 7BBD18A1244EE2FD00427C76 /* TestResponseFactory.swift */, 639FCF921EBC746F00778193 /* SentryDsnTests.m */, 7BDB03BE25136A7D00BAE198 /* TestSentryDispatchQueueWrapper.swift */, + 840748E4294BFE980077FC0A /* TestSentryDispatchSourceWrapper.swift */, 7B4E23B5251A07BD00060D68 /* SentryDispatchQueueWrapperTests.swift */, 8ED2D27E26A6581C00CA8329 /* NSURLProtocolSwizzle.h */, 8ED2D27F26A6581C00CA8329 /* NSURLProtocolSwizzle.m */, @@ -3580,6 +3590,7 @@ 7B3398652459C15200BD9C96 /* SentryEnvelopeRateLimit.m in Sources */, 0A2D8D9628997845008720F6 /* NSLocale+Sentry.m in Sources */, A2475E1F25FB648B007D9080 /* fishhook.c in Sources */, + 840748E9294BFF9B0077FC0A /* SentryDispatchSourceWrapper.m in Sources */, 7B0DC730288698F70039995F /* NSMutableDictionary+Sentry.m in Sources */, 7BD4BD4527EB29F50071F4FF /* SentryClientReport.m in Sources */, 631E6D341EBC679C00712345 /* SentryQueueableRequestManager.m in Sources */, @@ -3927,6 +3938,7 @@ A811D867248E2770008A41EA /* SentrySystemEventBreadcrumbsTest.swift in Sources */, 7B7725D8292F5DC20015BBF9 /* SentryCrashInstallationTests.m in Sources */, 7B82D54924E2A2D400EE670F /* SentryIdTests.swift in Sources */, + 840748E5294BFE980077FC0A /* TestSentryDispatchSourceWrapper.swift in Sources */, 7BD47B4E268F0B470076A663 /* ClearTestState.swift in Sources */, 844EDC7A29415AE800C86F34 /* TestSentrySystemWrapper.swift in Sources */, 7B6D98ED24C703F8005502FA /* Async.swift in Sources */, diff --git a/Sources/Sentry/SentryDispatchSourceWrapper.m b/Sources/Sentry/SentryDispatchSourceWrapper.m new file mode 100644 index 00000000000..4f9c80ea985 --- /dev/null +++ b/Sources/Sentry/SentryDispatchSourceWrapper.m @@ -0,0 +1,48 @@ +#import "SentryDispatchSourceWrapper.h" + +NS_ASSUME_NONNULL_BEGIN + +@implementation SentryDispatchSourceWrapper { + dispatch_source_t _source; +} + +- (instancetype)initWithDispatchSource:(dispatch_source_t)source +{ + if (self = [super init]) { + _source = source; + } + return self; +} + +- (void)resumeWithHandler:(dispatch_block_t)handler +{ + dispatch_source_set_event_handler(_source, handler); + dispatch_resume(_source); +} + +- (uintptr_t)getData +{ + return dispatch_source_get_data(_source); +} + +- (void)invalidate +{ + dispatch_source_cancel(_source); +} + +@end + +@implementation SentryDispatchSourceFactory + +- (SentryDispatchSourceWrapper *)dispatchSourceWithType:(dispatch_source_type_t)type + handle:(uintptr_t)handle + mask:(uintptr_t)mask + queue:(dispatch_queue_t _Nullable)sourceQueue; +{ + dispatch_source_t source = dispatch_source_create(type, handle, mask, sourceQueue); + return [[SentryDispatchSourceWrapper alloc] initWithDispatchSource:source]; +} + +@end + +NS_ASSUME_NONNULL_END diff --git a/Sources/Sentry/SentryProfiler.mm b/Sources/Sentry/SentryProfiler.mm index 0acfb5d853f..7c2c01fab5a 100644 --- a/Sources/Sentry/SentryProfiler.mm +++ b/Sources/Sentry/SentryProfiler.mm @@ -10,6 +10,7 @@ # import "SentryDefines.h" # import "SentryDependencyContainer.h" # import "SentryDevice.h" +# import "SentryDispatchSourceWrapper.h" # import "SentryEnvelope.h" # import "SentryEnvelopeItemType.h" # import "SentryFramesTracker.h" @@ -154,6 +155,7 @@ SentryNSProcessInfoWrapper *_gCurrentProcessInfoWrapper; SentrySystemWrapper *_gCurrentSystemWrapper; SentryNSTimerWrapper *_gCurrentTimerWrapper; +SentryDispatchSourceWrapper *_gCurrentDispatchSourceWrapper; NSString * profilerTruncationReasonName(SentryProfilerTruncationReason reason) diff --git a/Sources/Sentry/SentrySystemWrapper.mm b/Sources/Sentry/SentrySystemWrapper.mm index 7dcb592f5bd..bc604a96d0f 100644 --- a/Sources/Sentry/SentrySystemWrapper.mm +++ b/Sources/Sentry/SentrySystemWrapper.mm @@ -1,11 +1,30 @@ #import "SentrySystemWrapper.h" +#import "SentryDispatchSourceWrapper.h" #import "SentryError.h" #import "SentryMachLogging.hpp" #import +const NSUInteger kSentryMemoryPressureLevelNormal = DISPATCH_MEMORYPRESSURE_NORMAL; +const NSUInteger kSentryMemoryPressureLevelWarn = DISPATCH_MEMORYPRESSURE_WARN; +const NSUInteger kSentryMemoryPressureLevelCritical = DISPATCH_MEMORYPRESSURE_CRITICAL; + @implementation SentrySystemWrapper { - dispatch_source_t _memoryWarningSource; dispatch_queue_t _memoryWarningQueue; + SentryDispatchSourceFactory *_dispatchSourceFactory; + SentryDispatchSourceWrapper *_dispatchSourceWrapper; +} + +- (instancetype)initWithDispatchSourceFactory:(SentryDispatchSourceFactory *)dispatchSourceFactory +{ + if (self = [super init]) { + _dispatchSourceFactory = dispatchSourceFactory; + + const auto queueAttributes + = dispatch_queue_attr_make_with_qos_class(DISPATCH_QUEUE_SERIAL, QOS_CLASS_UTILITY, 0); + _memoryWarningQueue + = dispatch_queue_create("io.sentry.queue.memory-warnings", queueAttributes); + } + return self; } - (SentryRAMBytes)memoryFootprintBytes:(NSError *__autoreleasing _Nullable *)error @@ -65,27 +84,34 @@ - (SentryRAMBytes)memoryFootprintBytes:(NSError *__autoreleasing _Nullable *)err - (void)registerMemoryPressureNotifications:(SentryMemoryPressureNotification)handler { - const auto queueAttributes - = dispatch_queue_attr_make_with_qos_class(DISPATCH_QUEUE_SERIAL, QOS_CLASS_UTILITY, 0); - _memoryWarningQueue = dispatch_queue_create("io.sentry.queue.memory-warnings", queueAttributes); - _memoryWarningSource = dispatch_source_create(DISPATCH_SOURCE_TYPE_MEMORYPRESSURE, 0, - DISPATCH_MEMORYPRESSURE_NORMAL | DISPATCH_MEMORYPRESSURE_WARN - | DISPATCH_MEMORYPRESSURE_CRITICAL, - _memoryWarningQueue); + const auto type = DISPATCH_SOURCE_TYPE_MEMORYPRESSURE; + const auto mask = DISPATCH_MEMORYPRESSURE_NORMAL | DISPATCH_MEMORYPRESSURE_WARN + | DISPATCH_MEMORYPRESSURE_CRITICAL; + _dispatchSourceWrapper = [_dispatchSourceFactory dispatchSourceWithType:type + handle:0 + mask:mask + queue:_memoryWarningQueue]; + __weak auto weakSelf = self; - dispatch_source_set_event_handler(_memoryWarningSource, ^{ + [_dispatchSourceWrapper resumeWithHandler:^{ const auto strongSelf = weakSelf; if (!strongSelf) { return; } - handler(dispatch_source_get_data(strongSelf->_memoryWarningSource)); - }); - dispatch_resume(_memoryWarningSource); + handler([strongSelf->_dispatchSourceWrapper getData]); + }]; } - (void)deregisterMemoryPressureNotifications { - dispatch_source_cancel(_memoryWarningSource); + [_dispatchSourceWrapper invalidate]; +} + +#pragma mark - Testing + +- (SentryDispatchSourceWrapper *)dispatchSourceWrapper +{ + return _dispatchSourceWrapper; } @end diff --git a/Sources/Sentry/include/SentryDispatchSourceWrapper.h b/Sources/Sentry/include/SentryDispatchSourceWrapper.h new file mode 100644 index 00000000000..f212c2f5652 --- /dev/null +++ b/Sources/Sentry/include/SentryDispatchSourceWrapper.h @@ -0,0 +1,26 @@ +#import + +NS_ASSUME_NONNULL_BEGIN + +@interface SentryDispatchSourceWrapper : NSObject + +- (instancetype)initWithDispatchSource:(dispatch_source_t)source; + +- (void)resumeWithHandler:(dispatch_block_t)handler; + +- (uintptr_t)getData; + +- (void)invalidate; + +@end + +@interface SentryDispatchSourceFactory : NSObject + +- (SentryDispatchSourceWrapper *)dispatchSourceWithType:(dispatch_source_type_t)type + handle:(uintptr_t)handle + mask:(uintptr_t)mask + queue:(dispatch_queue_t _Nullable)sourceQueue; + +@end + +NS_ASSUME_NONNULL_END diff --git a/Sources/Sentry/include/SentrySystemWrapper+Tests.h b/Sources/Sentry/include/SentrySystemWrapper+Tests.h new file mode 100644 index 00000000000..28fc8f8ef21 --- /dev/null +++ b/Sources/Sentry/include/SentrySystemWrapper+Tests.h @@ -0,0 +1,8 @@ +#import "SentrySystemWrapper.h" + +@interface +SentrySystemWrapper () + +- (SentryDispatchSourceWrapper *)dispatchSourceWrapper; + +@end diff --git a/Sources/Sentry/include/SentrySystemWrapper.h b/Sources/Sentry/include/SentrySystemWrapper.h index 3f48a4bcbe6..43c0960e62c 100644 --- a/Sources/Sentry/include/SentrySystemWrapper.h +++ b/Sources/Sentry/include/SentrySystemWrapper.h @@ -1,5 +1,12 @@ +#import "SentryDefines.h" #import +@class SentryDispatchSourceFactory; + +SENTRY_EXTERN const NSUInteger kSentryMemoryPressureLevelNormal; +SENTRY_EXTERN const NSUInteger kSentryMemoryPressureLevelWarn; +SENTRY_EXTERN const NSUInteger kSentryMemoryPressureLevelCritical; + NS_ASSUME_NONNULL_BEGIN typedef void (^SentryMemoryPressureNotification)(uintptr_t); @@ -16,6 +23,8 @@ typedef mach_vm_size_t SentryRAMBytes; */ @interface SentrySystemWrapper : NSObject +- (instancetype)initWithDispatchSourceFactory:(SentryDispatchSourceFactory *)dispatchSourceFactory; + - (SentryRAMBytes)memoryFootprintBytes:(NSError **)error; /** diff --git a/Tests/SentryTests/Helper/TestSentrySystemWrapper.swift b/Tests/SentryTests/Helper/TestSentrySystemWrapper.swift index 5e515b79329..f207c7eddc6 100644 --- a/Tests/SentryTests/Helper/TestSentrySystemWrapper.swift +++ b/Tests/SentryTests/Helper/TestSentrySystemWrapper.swift @@ -11,6 +11,10 @@ class TestSentrySystemWrapper: SentrySystemWrapper { var overrides = Override() + override init() { + super.init(dispatchSourceFactory: TestSentryDispatchSourceFactory()) + } + override func memoryFootprintBytes(_ error: NSErrorPointer) -> SentryRAMBytes { if let errorOverride = overrides.memoryFootprintError { error?.pointee = errorOverride diff --git a/Tests/SentryTests/Networking/TestSentryDispatchSourceWrapper.swift b/Tests/SentryTests/Networking/TestSentryDispatchSourceWrapper.swift new file mode 100644 index 00000000000..e844775e6f2 --- /dev/null +++ b/Tests/SentryTests/Networking/TestSentryDispatchSourceWrapper.swift @@ -0,0 +1,28 @@ +import Foundation + +class TestSentryDispatchSourceFactory: SentryDispatchSourceFactory { + override func dispatchSource(withType type: __dispatch_source_type_t, handle: UInt, mask: UInt, queue sourceQueue: DispatchQueue?) -> SentryDispatchSourceWrapper { + return TestSentryDispatchSourceWrapper() + } +} + +class TestSentryDispatchSourceWrapper: SentryDispatchSourceWrapper { + var data: UInt! + var handler: (() -> Void)? + + override func getData() -> UInt { + return data + } + + override func resume(handler: @escaping () -> Void) { + self.handler = handler + } + + override func invalidate() { + // no-op + } + + func fire() { + handler?() + } +} diff --git a/Tests/SentryTests/Profiling/SentryProfilerSwiftTests.swift b/Tests/SentryTests/Profiling/SentryProfilerSwiftTests.swift index e0003ff991d..171389159a8 100644 --- a/Tests/SentryTests/Profiling/SentryProfilerSwiftTests.swift +++ b/Tests/SentryTests/Profiling/SentryProfilerSwiftTests.swift @@ -59,16 +59,19 @@ class SentryProfilerSwiftTests: XCTestCase { SentryProfiler.useProcessInfoWrapper(fixture.processInfoWrapper) SentryProfiler.useTimerWrapper(fixture.timerWrapper) + // mock cpu usage let cpuUsages = [12.4, 63.5, 1.4, 4.6] fixture.systemWrapper.overrides.cpuUsagePerCore = cpuUsages.map { NSNumber(value: $0) } fixture.processInfoWrapper.overrides.processorCount = UInt(cpuUsages.count) + // mock memory footprint let memoryFootprint: SentryRAMBytes = 123_455 fixture.systemWrapper.overrides.memoryFootprintBytes = memoryFootprint let span = fixture.newTransaction() forceProfilerSample() + // mock low power mode [true, false].forEach { fixture.processInfoWrapper.overrides.isLowPowerModeEnabled = $0 if #available(macOS 12.0, *) { @@ -76,13 +79,22 @@ class SentryProfilerSwiftTests: XCTestCase { } } + // mock memory pressure notifications + [kSentryMemoryPressureLevelWarn, kSentryMemoryPressureLevelCritical, kSentryMemoryPressureLevelNormal].forEach { + let dispatchSource = fixture.systemWrapper.dispatchSourceWrapper() as! TestSentryDispatchSourceWrapper + dispatchSource.data = $0 + dispatchSource.fire() + } + + // mock thermal state [ProcessInfo.ThermalState.critical, .serious, .fair, .nominal].forEach { fixture.processInfoWrapper.overrides.thermalState = $0 fixture.processInfoWrapper.sendThermalStateChangeNotification() } + // gather mock cpu usages and memory footprints for _ in 0..<2 { - let cpuExp = expectation(description: "first set of cpu measurements are gathered") + let cpuExp = expectation(description: "first set of timeseries measurements are gathered") DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) { self.fixture.timerWrapper.fire() cpuExp.fulfill() @@ -90,12 +102,13 @@ class SentryProfilerSwiftTests: XCTestCase { waitForExpectations(timeout: 1) } + // finish profile let exp = expectation(description: "Receives profile payload") DispatchQueue.main.asyncAfter(deadline: .now() + 1) { span.finish() do { - try self.assertMetricsPayload(thermalStateNotifications: 4, powerStateNotifications: 2, expectedCPUUsages: cpuUsages, cpuReadings: 2) + try self.assertMetricsPayload(thermalStateNotifications: 4, powerStateNotifications: 2, expectedCPUUsages: cpuUsages, cpuReadings: 2, memoryPressureNotifications: 3) exp.fulfill() } catch { XCTFail("Encountered error: \(error)") @@ -258,6 +271,7 @@ private extension SentryProfilerSwiftTests { case malformedMetricValueEntry case noCPUUsageEvents case noCPUUsageReported + case noMemoryPressureNotifications } func getProfileData() throws -> Data { @@ -305,7 +319,7 @@ private extension SentryProfilerSwiftTests { self.assertValidProfileData(data: profileData, transactionEnvironment: transactionEnvironment, numberOfTransactions: numberOfTransactions, shouldTimeout: shouldTimeOut) } - func assertMetricsPayload(thermalStateNotifications: Int, powerStateNotifications: Int, expectedCPUUsages: [Double], cpuReadings: Int) throws { + func assertMetricsPayload(thermalStateNotifications: Int, powerStateNotifications: Int, expectedCPUUsages: [Double], cpuReadings: Int, memoryPressureNotifications: Int) throws { let profileData = try self.getProfileData() guard let profile = try JSONSerialization.jsonObject(with: profileData) as? [String: Any] else { throw TestError.unexpectedProfileDeserializationType @@ -329,6 +343,11 @@ private extension SentryProfilerSwiftTests { // one initial reading per API spec, then all the actual notifications sent XCTAssertEqual(thermalState.count, thermalStateNotifications + 1) + guard let memoryPressureEntry = measurements[kSentryMetricProfilerSerializationKeyMemoryPressure] as? [String: Any], let memoryPressure = memoryPressureEntry["values"] as? [[String: Any]] else { + throw TestError.noMemoryPressureNotifications + } + XCTAssertEqual(memoryPressure.count, memoryPressureNotifications) + for (i, expectedUsage) in expectedCPUUsages.enumerated() { let key = NSString(format: kSentryMetricProfilerSerializationKeyCPUUsageFormat as NSString, i) as String guard let cpuUsage = measurements[key] as? [String: Any] else { diff --git a/Tests/SentryTests/SentryTests-Bridging-Header.h b/Tests/SentryTests/SentryTests-Bridging-Header.h index 3c7e4976b72..2afd263b7be 100644 --- a/Tests/SentryTests/SentryTests-Bridging-Header.h +++ b/Tests/SentryTests/SentryTests-Bridging-Header.h @@ -66,6 +66,7 @@ #import "SentryDiscardReasonMapper.h" #import "SentryDiscardedEvent.h" #import "SentryDispatchQueueWrapper.h" +#import "SentryDispatchSourceWrapper.h" #import "SentryDisplayLinkWrapper.h" #import "SentryDsn.h" #import "SentryEnvelope+Private.h" @@ -145,7 +146,7 @@ #import "SentrySwizzleWrapper.h" #import "SentrySysctl.h" #import "SentrySystemEventBreadcrumbs.h" -#import "SentrySystemWrapper.h" +#import "SentrySystemWrapper+Tests.h" #import "SentryTestIntegration.h" #import "SentryTestObjCRuntimeWrapper.h" #import "SentryThread.h" From b40d64a02766298ae5333f9642e7a134aa2ab88c Mon Sep 17 00:00:00 2001 From: Andrew McKnight Date: Thu, 15 Dec 2022 17:22:19 -0900 Subject: [PATCH 36/70] extract method to start metric profiler; init missing timer wrapper --- Sources/Sentry/SentryProfiler.mm | 32 ++++++++++++++++++++------------ 1 file changed, 20 insertions(+), 12 deletions(-) diff --git a/Sources/Sentry/SentryProfiler.mm b/Sources/Sentry/SentryProfiler.mm index 7c2c01fab5a..6f1708402ee 100644 --- a/Sources/Sentry/SentryProfiler.mm +++ b/Sources/Sentry/SentryProfiler.mm @@ -387,6 +387,25 @@ + (void)stopProfilerForReason:(SentryProfilerTruncationReason)reason _gCurrentProfiler = nil; } +- (void)startMetricProfiler +{ + if (_gCurrentSystemWrapper == nil) { + _gCurrentSystemWrapper = [[SentrySystemWrapper alloc] init]; + } + if (_gCurrentProcessInfoWrapper == nil) { + _gCurrentProcessInfoWrapper = [[SentryNSProcessInfoWrapper alloc] init]; + } + if (_gCurrentTimerWrapper == nil) { + _gCurrentTimerWrapper = [[SentryNSTimerWrapper alloc] init]; + } + _metricProfiler = + [[SentryMetricProfiler alloc] initWithProfileStartTime:_startTimestamp + processInfoWrapper:_gCurrentProcessInfoWrapper + systemWrapper:_gCurrentSystemWrapper + timerWrapper:_gCurrentTimerWrapper]; + [_metricProfiler start]; +} + - (void)start { // Disable profiling when running with TSAN because it produces a TSAN false @@ -478,18 +497,7 @@ - (void)start kSentryProfilerFrequencyHz); _profiler->startSampling(); - if (_gCurrentSystemWrapper == nil) { - _gCurrentSystemWrapper = [[SentrySystemWrapper alloc] init]; - } - if (_gCurrentProcessInfoWrapper == nil) { - _gCurrentProcessInfoWrapper = [[SentryNSProcessInfoWrapper alloc] init]; - } - _metricProfiler = - [[SentryMetricProfiler alloc] initWithProfileStartTime:_startTimestamp - processInfoWrapper:_gCurrentProcessInfoWrapper - systemWrapper:_gCurrentSystemWrapper - timerWrapper:_gCurrentTimerWrapper]; - [_metricProfiler start]; + [self startMetricProfiler]; } } From feb20550165212a6b2af231cb60bf0d95613c25c Mon Sep 17 00:00:00 2001 From: Andrew McKnight Date: Thu, 22 Dec 2022 15:24:02 -0900 Subject: [PATCH 37/70] store string values and put measurements under profile --- Sources/Sentry/SentryMetricProfiler.mm | 33 +++++++++++++------------- Sources/Sentry/SentryProfiler.mm | 2 +- 2 files changed, 18 insertions(+), 17 deletions(-) diff --git a/Sources/Sentry/SentryMetricProfiler.mm b/Sources/Sentry/SentryMetricProfiler.mm index 2124c1e902a..33b809a4970 100644 --- a/Sources/Sentry/SentryMetricProfiler.mm +++ b/Sources/Sentry/SentryMetricProfiler.mm @@ -27,7 +27,7 @@ namespace { NSDictionary * -serializedValues(NSArray *> *values, NSString *unit) +serializedValues(NSArray *> *values, NSString *unit) { return @ { @"unit" : unit, @"values" : values }; } @@ -41,13 +41,13 @@ @implementation SentryMetricProfiler { SentryNSTimerWrapper *_timerWrapper; /// arrays of readings keyed on NSNumbers representing the core number for the set of readings - NSMutableDictionary *> *> + NSMutableDictionary *> *> *_cpuUsage; - NSMutableArray *> *_memoryFootprint; - NSMutableArray *> *_thermalState; - NSMutableArray *> *_powerLevelState; - NSMutableArray *> *_memoryPressureState; + NSMutableArray *> *_memoryFootprint; + NSMutableArray *> *_thermalState; + NSMutableArray *> *_powerLevelState; + NSMutableArray *> *_memoryPressureState; uint64_t _profileStartTime; } @@ -58,23 +58,23 @@ - (instancetype)initWithProfileStartTime:(uint64_t)profileStartTime { if (self = [super init]) { _cpuUsage = [NSMutableDictionary *> *> + NSMutableArray *> *> dictionary]; const auto processorCount = processInfoWrapper.processorCount; SENTRY_LOG_DEBUG( @"Preparing %lu arrays for CPU core usage readings", (long unsigned)processorCount); for (NSUInteger core = 0; core < processorCount; core++) { - _cpuUsage[@(core)] = [NSMutableArray *> array]; + _cpuUsage[@(core)] = [NSMutableArray *> array]; } _systemWrapper = systemWrapper; _processInfoWrapper = processInfoWrapper; _timerWrapper = timerWrapper; - _memoryFootprint = [NSMutableArray *> array]; - _thermalState = [NSMutableArray *> array]; - _powerLevelState = [NSMutableArray *> array]; - _memoryPressureState = [NSMutableArray *> array]; + _memoryFootprint = [NSMutableArray *> array]; + _thermalState = [NSMutableArray *> array]; + _powerLevelState = [NSMutableArray *> array]; + _memoryPressureState = [NSMutableArray *> array]; _profileStartTime = profileStartTime; } @@ -124,7 +124,7 @@ - (void)stop } [_cpuUsage enumerateKeysAndObjectsUsingBlock:^(NSNumber *_Nonnull core, - NSMutableArray *> *_Nonnull readings, + NSMutableArray *> *_Nonnull readings, BOOL *_Nonnull stop) { if (readings.count > 0) { dict[[NSString stringWithFormat:kSentryMetricProfilerSerializationKeyCPUUsageFormat, @@ -223,11 +223,12 @@ - (void)recordCPUPercentagePerCore BOOL *_Nonnull stop) { [_cpuUsage[@(core)] addObject:[self metricEntryForValue:usage]]; }]; } -- (NSDictionary *)metricEntryForValue:(NSNumber *)value +- (NSDictionary *)metricEntryForValue:(NSNumber *)value { return @{ - @"value" : value, - @"elapsed_since_start_ns" : @(getDurationNs(_profileStartTime, getAbsoluteTime())) + @"value" : [value stringValue], + @"elapsed_since_start_ns" : + [@(getDurationNs(_profileStartTime, getAbsoluteTime())) stringValue] }; } diff --git a/Sources/Sentry/SentryProfiler.mm b/Sources/Sentry/SentryProfiler.mm index 6f1708402ee..d9ff9f49f0f 100644 --- a/Sources/Sentry/SentryProfiler.mm +++ b/Sources/Sentry/SentryProfiler.mm @@ -588,7 +588,7 @@ - (void)captureEnvelope profile[@"timestamp"] = [[SentryCurrentDate date] sentry_toIso8601String]; profile[@"release"] = _hub.getClient.options.releaseName; - profile[@"measurements"] = metrics; + profile[@"profile"][@"measurements"] = metrics; # if SENTRY_HAS_UIKIT auto relativeFrameTimestampsNs = [NSMutableArray array]; From 8e58476824ae4438573b05616a9c15bc5173f689 Mon Sep 17 00:00:00 2001 From: Andrew McKnight Date: Wed, 28 Dec 2022 13:55:18 -0900 Subject: [PATCH 38/70] rename invalidate->cancel --- Sources/Sentry/SentryDispatchSourceWrapper.m | 2 +- Sources/Sentry/SentrySystemWrapper.mm | 2 +- Sources/Sentry/include/SentryDispatchSourceWrapper.h | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Sources/Sentry/SentryDispatchSourceWrapper.m b/Sources/Sentry/SentryDispatchSourceWrapper.m index 4f9c80ea985..a525c1473d8 100644 --- a/Sources/Sentry/SentryDispatchSourceWrapper.m +++ b/Sources/Sentry/SentryDispatchSourceWrapper.m @@ -25,7 +25,7 @@ - (uintptr_t)getData return dispatch_source_get_data(_source); } -- (void)invalidate +- (void)cancel { dispatch_source_cancel(_source); } diff --git a/Sources/Sentry/SentrySystemWrapper.mm b/Sources/Sentry/SentrySystemWrapper.mm index bc604a96d0f..df18e20a2e4 100644 --- a/Sources/Sentry/SentrySystemWrapper.mm +++ b/Sources/Sentry/SentrySystemWrapper.mm @@ -104,7 +104,7 @@ - (void)registerMemoryPressureNotifications:(SentryMemoryPressureNotification)ha - (void)deregisterMemoryPressureNotifications { - [_dispatchSourceWrapper invalidate]; + [_dispatchSourceWrapper cancel]; } #pragma mark - Testing diff --git a/Sources/Sentry/include/SentryDispatchSourceWrapper.h b/Sources/Sentry/include/SentryDispatchSourceWrapper.h index f212c2f5652..8c445db67eb 100644 --- a/Sources/Sentry/include/SentryDispatchSourceWrapper.h +++ b/Sources/Sentry/include/SentryDispatchSourceWrapper.h @@ -10,7 +10,7 @@ NS_ASSUME_NONNULL_BEGIN - (uintptr_t)getData; -- (void)invalidate; +- (void)cancel; @end From 6212b2a97261b4c4805461a807eeed3f8603aec9 Mon Sep 17 00:00:00 2001 From: Andrew McKnight Date: Wed, 28 Dec 2022 13:57:08 -0900 Subject: [PATCH 39/70] add comment for metric sampling rate --- Sources/Sentry/SentryMetricProfiler.mm | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/Sources/Sentry/SentryMetricProfiler.mm b/Sources/Sentry/SentryMetricProfiler.mm index 33b809a4970..555486acce4 100644 --- a/Sources/Sentry/SentryMetricProfiler.mm +++ b/Sources/Sentry/SentryMetricProfiler.mm @@ -11,7 +11,12 @@ # import "SentrySystemWrapper.h" # import "SentryTime.h" -static const NSTimeInterval kSentryMetricProfilerTimeseriesInterval = 0.1; // 10 Hz +/** + * Currently set to 10 Hz as we don't anticipate much utility out of a higher resolution when + * sampling CPU usage and memory footprint, and we want to minimize the overhead of making the + * necessary system calls to gather that information. + */ +static const NSTimeInterval kSentryMetricProfilerTimeseriesInterval = 0.1; NSString *const kSentryMetricProfilerSerializationKeyMemoryFootprint = @"memory-footprint"; NSString *const kSentryMetricProfilerSerializationKeyMemoryPressure = @"memory-pressure"; From b9990afdfe75272d98895415f55c8d824134c3ce Mon Sep 17 00:00:00 2001 From: Andrew McKnight Date: Wed, 28 Dec 2022 14:14:20 -0900 Subject: [PATCH 40/70] fixup! rename invalidate->cancel --- .../Networking/TestSentryDispatchSourceWrapper.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Tests/SentryTests/Networking/TestSentryDispatchSourceWrapper.swift b/Tests/SentryTests/Networking/TestSentryDispatchSourceWrapper.swift index e844775e6f2..6b1a460acfc 100644 --- a/Tests/SentryTests/Networking/TestSentryDispatchSourceWrapper.swift +++ b/Tests/SentryTests/Networking/TestSentryDispatchSourceWrapper.swift @@ -18,7 +18,7 @@ class TestSentryDispatchSourceWrapper: SentryDispatchSourceWrapper { self.handler = handler } - override func invalidate() { + override func cancel() { // no-op } From ca88f044756ad7eec9d53ee8df77d0da850c4be1 Mon Sep 17 00:00:00 2001 From: Andrew McKnight Date: Wed, 28 Dec 2022 14:14:30 -0900 Subject: [PATCH 41/70] remove unused ivar --- Sources/Sentry/SentryProfiler.mm | 1 - 1 file changed, 1 deletion(-) diff --git a/Sources/Sentry/SentryProfiler.mm b/Sources/Sentry/SentryProfiler.mm index d9ff9f49f0f..b93a0b62bed 100644 --- a/Sources/Sentry/SentryProfiler.mm +++ b/Sources/Sentry/SentryProfiler.mm @@ -155,7 +155,6 @@ SentryNSProcessInfoWrapper *_gCurrentProcessInfoWrapper; SentrySystemWrapper *_gCurrentSystemWrapper; SentryNSTimerWrapper *_gCurrentTimerWrapper; -SentryDispatchSourceWrapper *_gCurrentDispatchSourceWrapper; NSString * profilerTruncationReasonName(SentryProfilerTruncationReason reason) From fe63d024c43854288e801bd03072f9b7b15776df Mon Sep 17 00:00:00 2001 From: Andrew McKnight Date: Wed, 28 Dec 2022 14:28:47 -0900 Subject: [PATCH 42/70] move test header extensions to test target --- Sentry.xcodeproj/project.pbxproj | 76 +++++++++---------- .../SentryTests}/SentryNSTimerWrapper+Test.h | 0 .../SentryTests}/SentrySystemWrapper+Tests.h | 0 3 files changed, 38 insertions(+), 38 deletions(-) rename {Sources/Sentry/include => Tests/SentryTests}/SentryNSTimerWrapper+Test.h (100%) rename {Sources/Sentry/include => Tests/SentryTests}/SentrySystemWrapper+Tests.h (100%) diff --git a/Sentry.xcodeproj/project.pbxproj b/Sentry.xcodeproj/project.pbxproj index 0ecfa4308e4..6182eb5d711 100644 --- a/Sentry.xcodeproj/project.pbxproj +++ b/Sentry.xcodeproj/project.pbxproj @@ -1420,7 +1420,7 @@ 840748E4294BFE980077FC0A /* TestSentryDispatchSourceWrapper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TestSentryDispatchSourceWrapper.swift; sourceTree = ""; }; 840748E6294BFEBD0077FC0A /* SentryDispatchSourceWrapper.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = SentryDispatchSourceWrapper.h; path = include/SentryDispatchSourceWrapper.h; sourceTree = ""; }; 840748E7294BFEBD0077FC0A /* SentryDispatchSourceWrapper.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SentryDispatchSourceWrapper.m; sourceTree = ""; }; - 840748EA294C078B0077FC0A /* SentrySystemWrapper+Tests.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = "SentrySystemWrapper+Tests.h"; path = "include/SentrySystemWrapper+Tests.h"; sourceTree = ""; }; + 840748EA294C078B0077FC0A /* SentrySystemWrapper+Tests.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "SentrySystemWrapper+Tests.h"; sourceTree = ""; }; 8419C0C328C1889D001C8259 /* SentryProfilerSwiftTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SentryProfilerSwiftTests.swift; sourceTree = ""; }; 844A34C3282B278500C6D1DF /* .github */ = {isa = PBXFileReference; lastKnownFileType = folder; path = .github; sourceTree = ""; }; 844A3563282B3C9F00C6D1DF /* .sauce */ = {isa = PBXFileReference; lastKnownFileType = folder; path = .sauce; sourceTree = ""; }; @@ -1455,7 +1455,7 @@ 844EDCE32947DC3100C86F34 /* SentryNSTimerWrapper.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = SentryNSTimerWrapper.h; path = include/SentryNSTimerWrapper.h; sourceTree = ""; }; 844EDCE42947DC3100C86F34 /* SentryNSTimerWrapper.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SentryNSTimerWrapper.m; sourceTree = ""; }; 844EDCE72947DCD700C86F34 /* TestSentryNSTimerWrapper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TestSentryNSTimerWrapper.swift; sourceTree = ""; }; - 844EDCE92947E78B00C86F34 /* SentryNSTimerWrapper+Test.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = "SentryNSTimerWrapper+Test.h"; path = "include/SentryNSTimerWrapper+Test.h"; sourceTree = ""; }; + 844EDCE92947E78B00C86F34 /* SentryNSTimerWrapper+Test.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "SentryNSTimerWrapper+Test.h"; sourceTree = ""; }; 844EDD6B2949387000C86F34 /* SentryMetricProfiler.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = SentryMetricProfiler.h; path = Sources/Sentry/include/SentryMetricProfiler.h; sourceTree = SOURCE_ROOT; }; 8453421128BE855D00C22EEC /* SentrySampleDecision.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SentrySampleDecision.m; sourceTree = ""; }; 8453421528BE8A9500C22EEC /* SentrySpanStatus.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SentrySpanStatus.m; sourceTree = ""; }; @@ -1985,10 +1985,8 @@ 844EDC6D294143B900C86F34 /* SentryNSProcessInfoWrapper.h */, 844EDC6E294143B900C86F34 /* SentryNSProcessInfoWrapper.mm */, 844EDC74294144DB00C86F34 /* SentrySystemWrapper.h */, - 840748EA294C078B0077FC0A /* SentrySystemWrapper+Tests.h */, 844EDC75294144DB00C86F34 /* SentrySystemWrapper.mm */, 844EDCE32947DC3100C86F34 /* SentryNSTimerWrapper.h */, - 844EDCE92947E78B00C86F34 /* SentryNSTimerWrapper+Test.h */, 844EDCE42947DC3100C86F34 /* SentryNSTimerWrapper.m */, ); name = Helper; @@ -2021,50 +2019,52 @@ 63AA75931EB8AEDB00D153DE /* SentryTests */ = { isa = PBXGroup; children = ( - 035E73C627D5661A005EEB11 /* Profiling */, - 7B6438AD26A710E6000D0F65 /* Categories */, - 7B6C5ED4264E62B60010D138 /* Transaction */, - 7B42602C26302DE500B36EDD /* Performance */, - 8E4A038125F76A4900000D77 /* Dynamic */, - 7BF536D224BEF240004FA6A2 /* TestUtils */, - 7B3D0474249A3D5800E106B6 /* Protocol */, - 7B944FA924697E9700A10721 /* Integrations */, - 7BD7299B24654CD500EA3610 /* Helper */, - 7BBD18AF24517E5D00427C76 /* Networking */, - 63FE71D220DA66C500CDBAE8 /* SentryCrash */, - 7B944FAC2469B41600A10721 /* State */, - D81FDF0F280E9FEC0045E0E4 /* Tools */, - 63AA75941EB8AEDB00D153DE /* Info.plist */, - 639889D21EDF06C100EA7442 /* SentryTests-Bridging-Header.h */, - 63AA75951EB8AEDB00D153DE /* SentryTests.m */, + 7B3878E92490D90400EBDEA2 /* SentryClient+TestInit.h */, + 844EDCE92947E78B00C86F34 /* SentryNSTimerWrapper+Test.h */, + 7B569DFE2590EEF600B653FC /* SentryScope+Equality.h */, + 7B569E052590F04700B653FC /* SentryScope+Properties.h */, 7B9421C4260CA393001F9349 /* SentrySDK+Tests.h */, - 7BD47B4C268F0B080076A663 /* ClearTestState.swift */, - 7BA8409F24A1EC6E00B718AA /* SentrySDKTests.swift */, - 630436151EC0AD3100C4D3FA /* SentryNSDataCompressionTests.m */, - 630C01931EC3402C00C52CEF /* SentryKSCrashReportConverterTests.m */, + 840748EA294C078B0077FC0A /* SentrySystemWrapper+Tests.h */, + 639889D21EDF06C100EA7442 /* SentryTests-Bridging-Header.h */, + 15360CF22433C59500112302 /* SentryInstallationTests.m */, 63B819131EC352A7002FDF4C /* SentryInterfacesTests.m */, + 630C01931EC3402C00C52CEF /* SentryKSCrashReportConverterTests.m */, + 630436151EC0AD3100C4D3FA /* SentryNSDataCompressionTests.m */, 63EED6C22237989300E02400 /* SentryOptionsTest.m */, - 632331F52404FFA8008D91D6 /* SentryScopeTests.m */, - 7B569DFE2590EEF600B653FC /* SentryScope+Equality.h */, 7B569DFF2590EEF600B653FC /* SentryScope+Equality.m */, - 7B569E052590F04700B653FC /* SentryScope+Properties.h */, - 7B6CC50124EE5A42001816D7 /* SentryHubTests.swift */, - 15360CF22433C59500112302 /* SentryInstallationTests.m */, + 632331F52404FFA8008D91D6 /* SentryScopeTests.m */, + 7B0002312477F0520035FEF1 /* SentrySessionTests.m */, + 63AA75951EB8AEDB00D153DE /* SentryTests.m */, + 63AA75941EB8AEDB00D153DE /* Info.plist */, + 7BD47B4C268F0B080076A663 /* ClearTestState.swift */, + 7B6D1262265F7CC600C9BE4B /* PrivateSentrySDKOnlyTests.swift */, + 7B4260332630315C00B36EDD /* SampleError.swift */, 7BAF3DB4243C743E008A5414 /* SentryClientTests.swift */, - 7B3878E92490D90400EBDEA2 /* SentryClient+TestInit.h */, - 7BAF3DD6243DD4A1008A5414 /* TestConstants.swift */, + A8AFFCD12907DA7600967CD7 /* SentryHttpStatusCodeRangeTests.swift */, + 7B6CC50124EE5A42001816D7 /* SentryHubTests.swift */, 15D0AC872459EE4D006541C2 /* SentryNSURLRequestTests.swift */, - 7B0002312477F0520035FEF1 /* SentrySessionTests.m */, - 7B0002332477F52D0035FEF1 /* SentrySessionTests.swift */, - 7B944FAF2469B46000A10721 /* TestClient.swift */, 7B0A54552523178700A71716 /* SentryScopeSwiftTests.swift */, + D8918B212849FA6D00701F9A /* SentrySDKIntegrationTestsBase.swift */, + 7BA8409F24A1EC6E00B718AA /* SentrySDKTests.swift */, + 7B0002332477F52D0035FEF1 /* SentrySessionTests.swift */, 8E70B10025CB8695002B3155 /* SentrySpanIdTests.swift */, - 7B4260332630315C00B36EDD /* SampleError.swift */, - 7B6D1262265F7CC600C9BE4B /* PrivateSentrySDKOnlyTests.swift */, 8ED3D305264DFE700049393B /* SwiftDescriptorTests.swift */, - D8918B212849FA6D00701F9A /* SentrySDKIntegrationTestsBase.swift */, + 7B944FAF2469B46000A10721 /* TestClient.swift */, + 7BAF3DD6243DD4A1008A5414 /* TestConstants.swift */, 0A1B497228E597DD00D7BFA3 /* TestLogOutput.swift */, - A8AFFCD12907DA7600967CD7 /* SentryHttpStatusCodeRangeTests.swift */, + 7B6438AD26A710E6000D0F65 /* Categories */, + 8E4A038125F76A4900000D77 /* Dynamic */, + 7BD7299B24654CD500EA3610 /* Helper */, + 7B944FA924697E9700A10721 /* Integrations */, + 7BBD18AF24517E5D00427C76 /* Networking */, + 7B42602C26302DE500B36EDD /* Performance */, + 035E73C627D5661A005EEB11 /* Profiling */, + 7B3D0474249A3D5800E106B6 /* Protocol */, + 63FE71D220DA66C500CDBAE8 /* SentryCrash */, + 7B944FAC2469B41600A10721 /* State */, + 7BF536D224BEF240004FA6A2 /* TestUtils */, + D81FDF0F280E9FEC0045E0E4 /* Tools */, + 7B6C5ED4264E62B60010D138 /* Transaction */, ); path = SentryTests; sourceTree = ""; diff --git a/Sources/Sentry/include/SentryNSTimerWrapper+Test.h b/Tests/SentryTests/SentryNSTimerWrapper+Test.h similarity index 100% rename from Sources/Sentry/include/SentryNSTimerWrapper+Test.h rename to Tests/SentryTests/SentryNSTimerWrapper+Test.h diff --git a/Sources/Sentry/include/SentrySystemWrapper+Tests.h b/Tests/SentryTests/SentrySystemWrapper+Tests.h similarity index 100% rename from Sources/Sentry/include/SentrySystemWrapper+Tests.h rename to Tests/SentryTests/SentrySystemWrapper+Tests.h From e3ccdb19ee1447c797cffc2640972ae070a2f1e4 Mon Sep 17 00:00:00 2001 From: Andrew McKnight Date: Wed, 28 Dec 2022 14:29:09 -0900 Subject: [PATCH 43/70] set values back to doubles instead of strings; fix test after moving measurements structure --- Sources/Sentry/SentryMetricProfiler.mm | 20 +++++++++---------- .../Profiling/SentryProfilerSwiftTests.swift | 2 +- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/Sources/Sentry/SentryMetricProfiler.mm b/Sources/Sentry/SentryMetricProfiler.mm index 555486acce4..b39a3f6228b 100644 --- a/Sources/Sentry/SentryMetricProfiler.mm +++ b/Sources/Sentry/SentryMetricProfiler.mm @@ -49,10 +49,10 @@ @implementation SentryMetricProfiler { NSMutableDictionary *> *> *_cpuUsage; - NSMutableArray *> *_memoryFootprint; - NSMutableArray *> *_thermalState; - NSMutableArray *> *_powerLevelState; - NSMutableArray *> *_memoryPressureState; + NSMutableArray *> *_memoryFootprint; + NSMutableArray *> *_thermalState; + NSMutableArray *> *_powerLevelState; + NSMutableArray *> *_memoryPressureState; uint64_t _profileStartTime; } @@ -76,10 +76,10 @@ - (instancetype)initWithProfileStartTime:(uint64_t)profileStartTime _processInfoWrapper = processInfoWrapper; _timerWrapper = timerWrapper; - _memoryFootprint = [NSMutableArray *> array]; - _thermalState = [NSMutableArray *> array]; - _powerLevelState = [NSMutableArray *> array]; - _memoryPressureState = [NSMutableArray *> array]; + _memoryFootprint = [NSMutableArray *> array]; + _thermalState = [NSMutableArray *> array]; + _powerLevelState = [NSMutableArray *> array]; + _memoryPressureState = [NSMutableArray *> array]; _profileStartTime = profileStartTime; } @@ -228,10 +228,10 @@ - (void)recordCPUPercentagePerCore BOOL *_Nonnull stop) { [_cpuUsage[@(core)] addObject:[self metricEntryForValue:usage]]; }]; } -- (NSDictionary *)metricEntryForValue:(NSNumber *)value +- (NSDictionary *)metricEntryForValue:(NSNumber *)value { return @{ - @"value" : [value stringValue], + @"value" : value, @"elapsed_since_start_ns" : [@(getDurationNs(_profileStartTime, getAbsoluteTime())) stringValue] }; diff --git a/Tests/SentryTests/Profiling/SentryProfilerSwiftTests.swift b/Tests/SentryTests/Profiling/SentryProfilerSwiftTests.swift index 171389159a8..09ca8d34ac3 100644 --- a/Tests/SentryTests/Profiling/SentryProfilerSwiftTests.swift +++ b/Tests/SentryTests/Profiling/SentryProfilerSwiftTests.swift @@ -324,7 +324,7 @@ private extension SentryProfilerSwiftTests { guard let profile = try JSONSerialization.jsonObject(with: profileData) as? [String: Any] else { throw TestError.unexpectedProfileDeserializationType } - guard let measurements = profile["measurements"] as? [String: Any] else { + guard let measurements = (profile["profile"] as? [String: Any])?["measurements"] as? [String: Any] else { throw TestError.unexpectedMeasurementsDeserializationType } From 13134a936e618968912586df8bb2b8b84ab7a35d Mon Sep 17 00:00:00 2001 From: Andrew McKnight Date: Wed, 28 Dec 2022 14:45:23 -0900 Subject: [PATCH 44/70] synchronize reads and writes to metric profiler data structures --- Sources/Sentry/SentryMetricProfiler.mm | 33 +++++++++++++++++++------- 1 file changed, 24 insertions(+), 9 deletions(-) diff --git a/Sources/Sentry/SentryMetricProfiler.mm b/Sources/Sentry/SentryMetricProfiler.mm index b39a3f6228b..17aaf827eb7 100644 --- a/Sources/Sentry/SentryMetricProfiler.mm +++ b/Sources/Sentry/SentryMetricProfiler.mm @@ -109,7 +109,10 @@ - (void)stop - (NSMutableDictionary *)serialize { - const auto dict = [NSMutableDictionary dictionary]; + NSMutableDictionary *dict; + @synchronized(self) { + dict = [NSMutableDictionary dictionary]; + } if (_memoryFootprint.count > 0) { dict[kSentryMetricProfilerSerializationKeyMemoryFootprint] @@ -168,8 +171,10 @@ - (void)registerMemoryPressureWarningHandler if (!strongSelf) { return; } - [strongSelf->_memoryPressureState - addObject:[strongSelf metricEntryForValue:@(memoryPressureState)]]; + @synchronized(strongSelf) { + [strongSelf->_memoryPressureState + addObject:[strongSelf metricEntryForValue:@(memoryPressureState)]]; + } }]; } @@ -190,14 +195,18 @@ - (void)registerStateChangeNotifications - (void)recordThermalState { - [_thermalState addObject:[self metricEntryForValue:@(_processInfoWrapper.thermalState)]]; + @synchronized(self) { + [_thermalState addObject:[self metricEntryForValue:@(_processInfoWrapper.thermalState)]]; + } } - (void)recordPowerLevelState { if (@available(macOS 12.0, *)) { - [_powerLevelState - addObject:[self metricEntryForValue:@(_processInfoWrapper.isLowPowerModeEnabled)]]; + @synchronized(self) { + [_powerLevelState + addObject:[self metricEntryForValue:@(_processInfoWrapper.isLowPowerModeEnabled)]]; + } } } @@ -211,7 +220,9 @@ - (void)recordMemoryFootprint return; } - [_memoryFootprint addObject:[self metricEntryForValue:@(footprintBytes)]]; + @synchronized(self) { + [_memoryFootprint addObject:[self metricEntryForValue:@(footprintBytes)]]; + } } - (void)recordCPUPercentagePerCore @@ -224,8 +235,12 @@ - (void)recordCPUPercentagePerCore return; } - [result enumerateObjectsUsingBlock:^(NSNumber *_Nonnull usage, NSUInteger core, - BOOL *_Nonnull stop) { [_cpuUsage[@(core)] addObject:[self metricEntryForValue:usage]]; }]; + @synchronized(self) { + [result enumerateObjectsUsingBlock:^( + NSNumber *_Nonnull usage, NSUInteger core, BOOL *_Nonnull stop) { + [_cpuUsage[@(core)] addObject:[self metricEntryForValue:usage]]; + }]; + } } - (NSDictionary *)metricEntryForValue:(NSNumber *)value From d71398a90c60ec837c52b340d8a69d332d257a5b Mon Sep 17 00:00:00 2001 From: Andrew McKnight Date: Wed, 4 Jan 2023 11:16:39 -0500 Subject: [PATCH 45/70] convert kabob case to snake case --- Sources/Sentry/SentryMetricProfiler.mm | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/Sources/Sentry/SentryMetricProfiler.mm b/Sources/Sentry/SentryMetricProfiler.mm index 17aaf827eb7..9725c5b4287 100644 --- a/Sources/Sentry/SentryMetricProfiler.mm +++ b/Sources/Sentry/SentryMetricProfiler.mm @@ -18,16 +18,16 @@ */ static const NSTimeInterval kSentryMetricProfilerTimeseriesInterval = 0.1; -NSString *const kSentryMetricProfilerSerializationKeyMemoryFootprint = @"memory-footprint"; -NSString *const kSentryMetricProfilerSerializationKeyMemoryPressure = @"memory-pressure"; -NSString *const kSentryMetricProfilerSerializationKeyPowerState = @"is-low-power-mode"; -NSString *const kSentryMetricProfilerSerializationKeyThermalState = @"thermal-state"; -NSString *const kSentryMetricProfilerSerializationKeyCPUUsageFormat = @"cpu-usage-%d"; +NSString *const kSentryMetricProfilerSerializationKeyMemoryFootprint = @"memory_footprint"; +NSString *const kSentryMetricProfilerSerializationKeyMemoryPressure = @"memory_pressure"; +NSString *const kSentryMetricProfilerSerializationKeyPowerState = @"is_low_power_mode"; +NSString *const kSentryMetricProfilerSerializationKeyThermalState = @"thermal_state"; +NSString *const kSentryMetricProfilerSerializationKeyCPUUsageFormat = @"cpu_usage_%d"; NSString *const kSentryMetricProfilerSerializationUnitBytes = @"bytes"; NSString *const kSentryMetricProfilerSerializationUnitBoolean = @"bool"; -NSString *const kSentryMetricProfilerSerializationUnitMemoryPressureEnum = @"memory-pressure-enum"; -NSString *const kSentryMetricProfilerSerializationUnitThermalStateEnum = @"thermal-state-enum"; +NSString *const kSentryMetricProfilerSerializationUnitMemoryPressureEnum = @"memory_pressure_enum"; +NSString *const kSentryMetricProfilerSerializationUnitThermalStateEnum = @"thermal_state_enum"; NSString *const kSentryMetricProfilerSerializationUnitPercentage = @"percent"; namespace { From bd6165c25e2ac7facb9c69e654940e03b6f41ca3 Mon Sep 17 00:00:00 2001 From: Andrew McKnight Date: Wed, 4 Jan 2023 11:22:29 -0500 Subject: [PATCH 46/70] pass low power mode enabled as float value instead of bool --- Sources/Sentry/SentryMetricProfiler.mm | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Sources/Sentry/SentryMetricProfiler.mm b/Sources/Sentry/SentryMetricProfiler.mm index 9725c5b4287..ea19d8bd6a5 100644 --- a/Sources/Sentry/SentryMetricProfiler.mm +++ b/Sources/Sentry/SentryMetricProfiler.mm @@ -205,7 +205,9 @@ - (void)recordPowerLevelState if (@available(macOS 12.0, *)) { @synchronized(self) { [_powerLevelState - addObject:[self metricEntryForValue:@(_processInfoWrapper.isLowPowerModeEnabled)]]; + addObject:[self metricEntryForValue:@(_processInfoWrapper.isLowPowerModeEnabled + ? 1.f + : 0.f)]]; } } } From 827dd5a3b7d0c01fbb937a5e90482aa6df4daec6 Mon Sep 17 00:00:00 2001 From: Andrew McKnight Date: Wed, 4 Jan 2023 11:22:39 -0500 Subject: [PATCH 47/70] make measurements sibling to profile --- Sources/Sentry/SentryProfiler.mm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/Sentry/SentryProfiler.mm b/Sources/Sentry/SentryProfiler.mm index b93a0b62bed..e80f9b30f49 100644 --- a/Sources/Sentry/SentryProfiler.mm +++ b/Sources/Sentry/SentryProfiler.mm @@ -587,7 +587,7 @@ - (void)captureEnvelope profile[@"timestamp"] = [[SentryCurrentDate date] sentry_toIso8601String]; profile[@"release"] = _hub.getClient.options.releaseName; - profile[@"profile"][@"measurements"] = metrics; + profile[@"measurements"] = metrics; # if SENTRY_HAS_UIKIT auto relativeFrameTimestampsNs = [NSMutableArray array]; From acc122db0216f56c20a9bb793a51270f557e61cf Mon Sep 17 00:00:00 2001 From: Andrew McKnight Date: Wed, 4 Jan 2023 11:50:18 -0500 Subject: [PATCH 48/70] split slow/frozen frames; use singular unit names --- Sources/Sentry/SentryFramesTracker.m | 21 +++-- Sources/Sentry/SentryMetricProfiler.mm | 2 +- Sources/Sentry/SentryProfiler.mm | 83 ++++++++++--------- Sources/Sentry/SentryScreenFrames.m | 9 +- .../include/HybridPublic/SentryScreenFrames.h | 13 ++- 5 files changed, 73 insertions(+), 55 deletions(-) diff --git a/Sources/Sentry/SentryFramesTracker.m b/Sources/Sentry/SentryFramesTracker.m index 4fae7a71600..1c8da9e248b 100644 --- a/Sources/Sentry/SentryFramesTracker.m +++ b/Sources/Sentry/SentryFramesTracker.m @@ -30,7 +30,8 @@ @property (nonatomic, strong, readonly) SentryDisplayLinkWrapper *displayLinkWrapper; @property (nonatomic, assign) CFTimeInterval previousFrameTimestamp; # if SENTRY_TARGET_PROFILING_SUPPORTED -@property (nonatomic, readwrite) SentryMutableFrameInfoTimeSeries *frameTimestamps; +@property (nonatomic, readwrite) SentryMutableFrameInfoTimeSeries *frozenFrameTimestamps; +@property (nonatomic, readwrite) SentryMutableFrameInfoTimeSeries *slowFrameTimestamps; @property (nonatomic, readwrite) SentryMutableFrameInfoTimeSeries *frameRateTimestamps; # endif // SENTRY_TARGET_PROFILING_SUPPORTED @@ -91,7 +92,8 @@ - (void)resetFrames # if SENTRY_TARGET_PROFILING_SUPPORTED - (void)resetProfilingTimestamps { - self.frameTimestamps = [SentryMutableFrameInfoTimeSeries array]; + self.frozenFrameTimestamps = [SentryMutableFrameInfoTimeSeries array]; + self.slowFrameTimestamps = [SentryMutableFrameInfoTimeSeries array]; self.frameRateTimestamps = [SentryMutableFrameInfoTimeSeries array]; } # endif // SENTRY_TARGET_PROFILING_SUPPORTED @@ -157,12 +159,16 @@ - (void)displayLinkCallback if (frameDuration > slowFrameThreshold && frameDuration <= SentryFrozenFrameThreshold) { atomic_fetch_add_explicit(&_slowFrames, 1, SentryFramesMemoryOrder); # if SENTRY_TARGET_PROFILING_SUPPORTED - [self recordTimestampStart:@(self.previousFrameTimestamp) end:@(thisFrameTimestamp)]; + [self recordTimestampStart:@(self.previousFrameTimestamp) + end:@(thisFrameTimestamp) + array:self.slowFrameTimestamps]; # endif // SENTRY_TARGET_PROFILING_SUPPORTED } else if (frameDuration > SentryFrozenFrameThreshold) { atomic_fetch_add_explicit(&_frozenFrames, 1, SentryFramesMemoryOrder); # if SENTRY_TARGET_PROFILING_SUPPORTED - [self recordTimestampStart:@(self.previousFrameTimestamp) end:@(thisFrameTimestamp)]; + [self recordTimestampStart:@(self.previousFrameTimestamp) + end:@(thisFrameTimestamp) + array:self.frozenFrameTimestamps]; # endif // SENTRY_TARGET_PROFILING_SUPPORTED } @@ -171,14 +177,14 @@ - (void)displayLinkCallback } # if SENTRY_TARGET_PROFILING_SUPPORTED -- (void)recordTimestampStart:(NSNumber *)start end:(NSNumber *)end +- (void)recordTimestampStart:(NSNumber *)start end:(NSNumber *)end array:(NSMutableArray *)array { BOOL shouldRecord = [SentryProfiler isRunning]; # if defined(TEST) || defined(TESTCI) shouldRecord = YES; # endif if (shouldRecord) { - [self.frameTimestamps addObject:@{ @"start_timestamp" : start, @"end_timestamp" : end }]; + [array addObject:@{ @"start_timestamp" : start, @"end_timestamp" : end }]; } } # endif // SENTRY_TARGET_PROFILING_SUPPORTED @@ -193,7 +199,8 @@ - (SentryScreenFrames *)currentFrames return [[SentryScreenFrames alloc] initWithTotal:total frozen:frozen slow:slow - frameTimestamps:self.frameTimestamps + slowFrameTimestamps:self.slowFrameTimestamps + frozenFrameTimestamps:self.frozenFrameTimestamps frameRateTimestamps:self.frameRateTimestamps]; # else return [[SentryScreenFrames alloc] initWithTotal:total frozen:frozen slow:slow]; diff --git a/Sources/Sentry/SentryMetricProfiler.mm b/Sources/Sentry/SentryMetricProfiler.mm index ea19d8bd6a5..3b96a30b5c6 100644 --- a/Sources/Sentry/SentryMetricProfiler.mm +++ b/Sources/Sentry/SentryMetricProfiler.mm @@ -24,7 +24,7 @@ NSString *const kSentryMetricProfilerSerializationKeyThermalState = @"thermal_state"; NSString *const kSentryMetricProfilerSerializationKeyCPUUsageFormat = @"cpu_usage_%d"; -NSString *const kSentryMetricProfilerSerializationUnitBytes = @"bytes"; +NSString *const kSentryMetricProfilerSerializationUnitBytes = @"byte"; NSString *const kSentryMetricProfilerSerializationUnitBoolean = @"bool"; NSString *const kSentryMetricProfilerSerializationUnitMemoryPressureEnum = @"memory_pressure_enum"; NSString *const kSentryMetricProfilerSerializationUnitThermalStateEnum = @"thermal_state_enum"; diff --git a/Sources/Sentry/SentryProfiler.mm b/Sources/Sentry/SentryProfiler.mm index e80f9b30f49..d62673dc3c9 100644 --- a/Sources/Sentry/SentryProfiler.mm +++ b/Sources/Sentry/SentryProfiler.mm @@ -169,6 +169,33 @@ } } +NSArray * +processFrameRenderInfo(SentryFrameInfoTimeSeries *frameInfo, uint64_t start, uint64_t duration) +{ + auto relativeFrameTimestampsNs = [NSMutableArray array]; + [frameInfo enumerateObjectsUsingBlock:^( + NSDictionary *_Nonnull obj, NSUInteger idx, BOOL *_Nonnull stop) { + const auto begin = (uint64_t)(obj[@"start_timestamp"].doubleValue * 1e9); + if (begin < start) { + return; + } + const auto end = (uint64_t)(obj[@"end_timestamp"].doubleValue * 1e9); + const auto relativeEnd = getDurationNs(start, end); + if (relativeEnd > duration) { + SENTRY_LOG_DEBUG(@"The last slow/frozen frame extended past the end of the profile, " + @"will not report it."); + return; + } + const auto relativeStart = getDurationNs(start, begin); + const auto frameDuration = relativeEnd - relativeStart; + [relativeFrameTimestampsNs addObject:@{ + @"elapsed_since_start_ns" : @(relativeStart), + @"value" : @(frameDuration), + }]; + }]; + return relativeFrameTimestampsNs; +} + @implementation SentryProfiler { NSMutableDictionary *_profile; uint64_t _startTimestamp; @@ -590,49 +617,23 @@ - (void)captureEnvelope profile[@"measurements"] = metrics; # if SENTRY_HAS_UIKIT - auto relativeFrameTimestampsNs = [NSMutableArray array]; - [_frameInfo.frameTimestamps enumerateObjectsUsingBlock:^( - NSDictionary *_Nonnull obj, NSUInteger idx, BOOL *_Nonnull stop) { - const auto begin = (uint64_t)(obj[@"start_timestamp"].doubleValue * 1e9); - if (begin < _startTimestamp) { - return; - } - const auto end = (uint64_t)(obj[@"end_timestamp"].doubleValue * 1e9); - const auto relativeEnd = getDurationNs(_startTimestamp, end); - if (relativeEnd > profileDuration) { - SENTRY_LOG_DEBUG(@"The last slow/frozen frame extended past the end of the profile, " - @"will not report it."); - return; - } - const auto relativeStart = getDurationNs(_startTimestamp, begin); - const auto frameDuration = relativeEnd - relativeStart; - [relativeFrameTimestampsNs addObject:@{ - @"elapsed_since_start_ns" : @(relativeStart), - @"value" : @(frameDuration), - }]; - }]; - if (relativeFrameTimestampsNs.count > 0) { - metrics[@"slow_frame_renders"] = - @{ @"unit" : @"nanoseconds", @"values" : relativeFrameTimestampsNs }; + const auto slowTimestamps + = processFrameRenderInfo(_frameInfo.slowFrameTimestamps, _startTimestamp, profileDuration); + if (slowTimestamps.count > 0) { + metrics[@"slow_frame_renders"] = @{ @"unit" : @"nanosecond", @"values" : slowTimestamps }; } - relativeFrameTimestampsNs = [NSMutableArray array]; - [_frameInfo.frameRateTimestamps enumerateObjectsUsingBlock:^( - NSDictionary *_Nonnull obj, NSUInteger idx, BOOL *_Nonnull stop) { - const auto timestamp = (uint64_t)(obj[@"timestamp"].doubleValue * 1e9); - const auto refreshRate = obj[@"frame_rate"]; - uint64_t relativeTimestamp = 0; - if (timestamp >= _startTimestamp) { - relativeTimestamp = getDurationNs(_startTimestamp, timestamp); - } - [relativeFrameTimestampsNs addObject:@{ - @"elapsed_since_start_ns" : @(relativeTimestamp), - @"value" : refreshRate, - }]; - }]; - if (relativeFrameTimestampsNs.count > 0) { - metrics[@"screen_frame_rates"] = - @{ @"unit" : @"hz", @"values" : relativeFrameTimestampsNs }; + const auto frozenTimestamps + = processFrameRenderInfo(_frameInfo.slowFrameTimestamps, _startTimestamp, profileDuration); + if (frozenTimestamps.count > 0) { + metrics[@"frozen_frame_renders"] = + @{ @"unit" : @"nanosecond", @"values" : frozenTimestamps }; + } + + const auto frameRates + = processFrameRenderInfo(_frameInfo.frameRateTimestamps, _startTimestamp, profileDuration); + if (frameRates.count > 0) { + metrics[@"screen_frame_rates"] = @{ @"unit" : @"hz", @"values" : frameRates }; } # endif // SENTRY_HAS_UIKIT diff --git a/Sources/Sentry/SentryScreenFrames.m b/Sources/Sentry/SentryScreenFrames.m index 37e594ecc0d..c019a06af98 100644 --- a/Sources/Sentry/SentryScreenFrames.m +++ b/Sources/Sentry/SentryScreenFrames.m @@ -10,7 +10,8 @@ - (instancetype)initWithTotal:(NSUInteger)total frozen:(NSUInteger)frozen slow:( return [self initWithTotal:total frozen:frozen slow:slow - frameTimestamps:@[] + slowFrameTimestamps:@[] + frozenFrameTimestamps:@[] frameRateTimestamps:@[]]; # else if (self = [super init]) { @@ -27,14 +28,16 @@ - (instancetype)initWithTotal:(NSUInteger)total frozen:(NSUInteger)frozen slow:( - (instancetype)initWithTotal:(NSUInteger)total frozen:(NSUInteger)frozen slow:(NSUInteger)slow - frameTimestamps:(SentryFrameInfoTimeSeries *)frameTimestamps + slowFrameTimestamps:(SentryFrameInfoTimeSeries *)slowFrameTimestamps + frozenFrameTimestamps:(SentryFrameInfoTimeSeries *)frozenFrameTimestamps frameRateTimestamps:(SentryFrameInfoTimeSeries *)frameRateTimestamps { if (self = [super init]) { _total = total; _slow = slow; _frozen = frozen; - _frameTimestamps = frameTimestamps; + _slowFrameTimestamps = slowFrameTimestamps; + _frozenFrameTimestamps = frozenFrameTimestamps; _frameRateTimestamps = frameRateTimestamps; } diff --git a/Sources/Sentry/include/HybridPublic/SentryScreenFrames.h b/Sources/Sentry/include/HybridPublic/SentryScreenFrames.h index 9c64e51dadc..a0116b76a31 100644 --- a/Sources/Sentry/include/HybridPublic/SentryScreenFrames.h +++ b/Sources/Sentry/include/HybridPublic/SentryScreenFrames.h @@ -18,7 +18,8 @@ SENTRY_NO_INIT - (instancetype)initWithTotal:(NSUInteger)total frozen:(NSUInteger)frozen slow:(NSUInteger)slow - frameTimestamps:(SentryFrameInfoTimeSeries *)frameTimestamps + slowFrameTimestamps:(SentryFrameInfoTimeSeries *)slowFrameTimestamps + frozenFrameTimestamps:(SentryFrameInfoTimeSeries *)frozenFrameTimestamps frameRateTimestamps:(SentryFrameInfoTimeSeries *)frameRateTimestamps; # endif // SENTRY_TARGET_PROFILING_SUPPORTED @@ -28,10 +29,16 @@ SENTRY_NO_INIT # if SENTRY_TARGET_PROFILING_SUPPORTED /** - * Array of dictionaries describing slow/frozen frames' timestamps. Each dictionary start and end + * Array of dictionaries describing slow frames' timestamps. Each dictionary has a start and end * timestamp for every such frame, keyed under @c start_timestamp and @c end_timestamp. */ -@property (nonatomic, copy, readonly) SentryFrameInfoTimeSeries *frameTimestamps; +@property (nonatomic, copy, readonly) SentryFrameInfoTimeSeries *slowFrameTimestamps; + +/** + * Array of dictionaries describing frozen frames' timestamps. Each dictionary has a start and end + * timestamp for every such frame, keyed under @c start_timestamp and @c end_timestamp. + */ +@property (nonatomic, copy, readonly) SentryFrameInfoTimeSeries *frozenFrameTimestamps; /** * Array of dictionaries describing the screen refresh rate at all points in time that it changes, From 2f6c51d0a5431e8d240bbe3673e58e8bd9053456 Mon Sep 17 00:00:00 2001 From: Andrew McKnight Date: Wed, 4 Jan 2023 11:55:00 -0500 Subject: [PATCH 49/70] fixup! make measurements sibling to profile --- Tests/SentryTests/Profiling/SentryProfilerSwiftTests.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Tests/SentryTests/Profiling/SentryProfilerSwiftTests.swift b/Tests/SentryTests/Profiling/SentryProfilerSwiftTests.swift index 09ca8d34ac3..171389159a8 100644 --- a/Tests/SentryTests/Profiling/SentryProfilerSwiftTests.swift +++ b/Tests/SentryTests/Profiling/SentryProfilerSwiftTests.swift @@ -324,7 +324,7 @@ private extension SentryProfilerSwiftTests { guard let profile = try JSONSerialization.jsonObject(with: profileData) as? [String: Any] else { throw TestError.unexpectedProfileDeserializationType } - guard let measurements = (profile["profile"] as? [String: Any])?["measurements"] as? [String: Any] else { + guard let measurements = profile["measurements"] as? [String: Any] else { throw TestError.unexpectedMeasurementsDeserializationType } From 075c08f86789e4970c3894a843b4d630b1974ec4 Mon Sep 17 00:00:00 2001 From: Andrew McKnight Date: Mon, 9 Jan 2023 11:24:13 -0500 Subject: [PATCH 50/70] dont declare abstract fire method --- Sentry.xcodeproj/project.pbxproj | 13 ------------- Sources/Sentry/SentryNSTimerWrapper.m | 7 ------- Sources/Sentry/include/SentryNSTimerWrapper+Test.h | 8 -------- .../Helper/TestSentryNSTimerWrapper.swift | 2 +- Tests/SentryTests/SentryTests-Bridging-Header.h | 2 +- 5 files changed, 2 insertions(+), 30 deletions(-) delete mode 100644 Sources/Sentry/include/SentryNSTimerWrapper+Test.h diff --git a/Sentry.xcodeproj/project.pbxproj b/Sentry.xcodeproj/project.pbxproj index 07a6ca61adb..f3737377fe2 100644 --- a/Sentry.xcodeproj/project.pbxproj +++ b/Sentry.xcodeproj/project.pbxproj @@ -49,10 +49,6 @@ 0A2D8DA9289BC905008720F6 /* SentryViewHierarchy.m in Sources */ = {isa = PBXBuildFile; fileRef = 0A2D8DA7289BC905008720F6 /* SentryViewHierarchy.m */; }; 0A4EDEA928D3461B00FA67CB /* SentryPerformanceTracker+Private.h in Headers */ = {isa = PBXBuildFile; fileRef = 0A4EDEA828D3461B00FA67CB /* SentryPerformanceTracker+Private.h */; }; 0A5370A128A3EC2400B2DCDE /* SentryViewHierarchyTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A5370A028A3EC2400B2DCDE /* SentryViewHierarchyTests.swift */; }; - 0A54C3B0294B3F2100318F31 /* SentryNSTimerWrapper.m in Sources */ = {isa = PBXBuildFile; fileRef = 0A54C3AF294B3F2100318F31 /* SentryNSTimerWrapper.m */; }; - 0A54C3B2294B3F4D00318F31 /* SentryNSTimerWrapper.h in Headers */ = {isa = PBXBuildFile; fileRef = 0A54C3B1294B3F4D00318F31 /* SentryNSTimerWrapper.h */; }; - 0A54C3B6294B3FCD00318F31 /* SentryNSTimerWrapper+Test.h in Headers */ = {isa = PBXBuildFile; fileRef = 0A54C3B5294B3FCD00318F31 /* SentryNSTimerWrapper+Test.h */; }; - 0A54C3B7294B403F00318F31 /* TestSentryNSTimerWrapper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A54C3B3294B3FA000318F31 /* TestSentryNSTimerWrapper.swift */; }; 0A56DA5F28ABA01B00C400D5 /* SentryTransactionContext+Private.h in Headers */ = {isa = PBXBuildFile; fileRef = 0A56DA5E28ABA01B00C400D5 /* SentryTransactionContext+Private.h */; }; 0A6EEADD28A657970076B469 /* UIViewRecursiveDescriptionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A6EEADC28A657970076B469 /* UIViewRecursiveDescriptionTests.swift */; }; 0A80E433291017C300095219 /* SentryWatchdogTerminationScopeObserver.m in Sources */ = {isa = PBXBuildFile; fileRef = 0A80E432291017C300095219 /* SentryWatchdogTerminationScopeObserver.m */; }; @@ -842,10 +838,6 @@ 0A2D8DA7289BC905008720F6 /* SentryViewHierarchy.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SentryViewHierarchy.m; sourceTree = ""; }; 0A4EDEA828D3461B00FA67CB /* SentryPerformanceTracker+Private.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = "SentryPerformanceTracker+Private.h"; path = "include/SentryPerformanceTracker+Private.h"; sourceTree = ""; }; 0A5370A028A3EC2400B2DCDE /* SentryViewHierarchyTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SentryViewHierarchyTests.swift; sourceTree = ""; }; - 0A54C3AF294B3F2100318F31 /* SentryNSTimerWrapper.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SentryNSTimerWrapper.m; sourceTree = ""; }; - 0A54C3B1294B3F4D00318F31 /* SentryNSTimerWrapper.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = SentryNSTimerWrapper.h; path = include/SentryNSTimerWrapper.h; sourceTree = ""; }; - 0A54C3B3294B3FA000318F31 /* TestSentryNSTimerWrapper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TestSentryNSTimerWrapper.swift; sourceTree = ""; }; - 0A54C3B5294B3FCD00318F31 /* SentryNSTimerWrapper+Test.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = "SentryNSTimerWrapper+Test.h"; path = "include/SentryNSTimerWrapper+Test.h"; sourceTree = ""; }; 0A56DA5E28ABA01B00C400D5 /* SentryTransactionContext+Private.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = "SentryTransactionContext+Private.h"; path = "include/SentryTransactionContext+Private.h"; sourceTree = ""; }; 0A6EEADC28A657970076B469 /* UIViewRecursiveDescriptionTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIViewRecursiveDescriptionTests.swift; sourceTree = ""; }; 0A80E432291017C300095219 /* SentryWatchdogTerminationScopeObserver.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SentryWatchdogTerminationScopeObserver.m; sourceTree = ""; }; @@ -2025,7 +2017,6 @@ 844EDC74294144DB00C86F34 /* SentrySystemWrapper.h */, 844EDC75294144DB00C86F34 /* SentrySystemWrapper.mm */, 844EDCE32947DC3100C86F34 /* SentryNSTimerWrapper.h */, - 0A54C3B5294B3FCD00318F31 /* SentryNSTimerWrapper+Test.h */, 844EDCE42947DC3100C86F34 /* SentryNSTimerWrapper.m */, ); name = Helper; @@ -3236,7 +3227,6 @@ 63B818F91EC34639002FDF4C /* SentryDebugMeta.h in Headers */, 6360850D1ED2AFE100E8599E /* SentryBreadcrumb.h in Headers */, 7BAF3DD92440AEC8008A5414 /* SentryRequestManager.h in Headers */, - 0A54C3B2294B3F4D00318F31 /* SentryNSTimerWrapper.h in Headers */, 7BE3C77B2446111500A38442 /* SentryRateLimitParser.h in Headers */, 84A888FD28D9B11700C51DFD /* SentryProfiler+Test.h in Headers */, 7D0637032382B34300B30749 /* SentryScope.h in Headers */, @@ -3293,7 +3283,6 @@ 7B8713AE26415ADF006D6004 /* SentryAppStartTrackingIntegration.h in Headers */, 7B7D873224864BB900D2ECFF /* SentryCrashMachineContextWrapper.h in Headers */, 861265F92404EC1500C4AFDE /* NSArray+SentrySanitize.h in Headers */, - 0A54C3B6294B3FCD00318F31 /* SentryNSTimerWrapper+Test.h in Headers */, 63FE712320DA4C1000CDBAE8 /* SentryCrashID.h in Headers */, 7DC27EC523997EB7006998B5 /* SentryAutoBreadcrumbTrackingIntegration.h in Headers */, 63FE707F20DA4C1000CDBAE8 /* SentryCrashVarArgs.h in Headers */, @@ -3749,7 +3738,6 @@ 63FE70D520DA4C1000CDBAE8 /* SentryCrashMonitor_NSException.m in Sources */, 0AAE201E28ED9B9400D0CD80 /* SentryReachability.m in Sources */, 7B0A54282521C22C00A71716 /* SentryFrameRemover.m in Sources */, - 0A54C3B0294B3F2100318F31 /* SentryNSTimerWrapper.m in Sources */, 7BC63F0A28081288009D9E37 /* SentrySwizzleWrapper.m in Sources */, 7B6C5EDC264E8DA80010D138 /* SentryFramesTrackingIntegration.m in Sources */, 63295AF71EF3C7DB002D4490 /* NSDictionary+SentrySanitize.m in Sources */, @@ -4053,7 +4041,6 @@ 8E70B0FD25CB72BE002B3155 /* SentrySpanTests.swift in Sources */, 7BBD188F2448469A00427C76 /* HttpDateFormatter.swift in Sources */, 63FE720C20DA66EC00CDBAE8 /* SentryCrashMonitor_Tests.m in Sources */, - 0A54C3B7294B403F00318F31 /* TestSentryNSTimerWrapper.swift in Sources */, D855B3EA27D652C700BCED76 /* TestCoreDataStack.swift in Sources */, 63FE721820DA66EC00CDBAE8 /* TestThread.m in Sources */, 7B4D308A26FC616B00C94DE9 /* SentryHttpTransportTests.swift in Sources */, diff --git a/Sources/Sentry/SentryNSTimerWrapper.m b/Sources/Sentry/SentryNSTimerWrapper.m index d1e1202e325..5e6af70ad1b 100644 --- a/Sources/Sentry/SentryNSTimerWrapper.m +++ b/Sources/Sentry/SentryNSTimerWrapper.m @@ -9,11 +9,4 @@ - (NSTimer *)scheduledTimerWithTimeInterval:(NSTimeInterval)interval return [NSTimer scheduledTimerWithTimeInterval:interval repeats:repeats block:block]; } -#pragma mark - Testing - -- (void)fire -{ - // no-op -} - @end diff --git a/Sources/Sentry/include/SentryNSTimerWrapper+Test.h b/Sources/Sentry/include/SentryNSTimerWrapper+Test.h deleted file mode 100644 index 245668340d7..00000000000 --- a/Sources/Sentry/include/SentryNSTimerWrapper+Test.h +++ /dev/null @@ -1,8 +0,0 @@ -#import "SentryNSTimerWrapper.h" - -@interface -SentryNSTimerWrapper () - -- (void)fire; - -@end diff --git a/Tests/SentryTests/Helper/TestSentryNSTimerWrapper.swift b/Tests/SentryTests/Helper/TestSentryNSTimerWrapper.swift index dc40dd77863..3ea72a25edb 100644 --- a/Tests/SentryTests/Helper/TestSentryNSTimerWrapper.swift +++ b/Tests/SentryTests/Helper/TestSentryNSTimerWrapper.swift @@ -25,7 +25,7 @@ class TestSentryNSTimerWrapper: SentryNSTimerWrapper { return timer } - override func fire() { + func fire() { overrides.block?(overrides.timer) } } diff --git a/Tests/SentryTests/SentryTests-Bridging-Header.h b/Tests/SentryTests/SentryTests-Bridging-Header.h index 136d961f88e..4918370193c 100644 --- a/Tests/SentryTests/SentryTests-Bridging-Header.h +++ b/Tests/SentryTests/SentryTests-Bridging-Header.h @@ -107,7 +107,7 @@ #import "SentryNSError.h" #import "SentryNSNotificationCenterWrapper.h" #import "SentryNSProcessInfoWrapper.h" -#import "SentryNSTimerWrapper+Test.h" +#import "SentryNSTimerWrapper.h" #import "SentryNSURLRequest.h" #import "SentryNSURLRequestBuilder.h" #import "SentryNSURLSessionTaskSearch.h" From 071e57d3dbc5343189c922a6494630eb3b38b4af Mon Sep 17 00:00:00 2001 From: Andrew McKnight Date: Mon, 9 Jan 2023 11:44:22 -0500 Subject: [PATCH 51/70] fix macos build --- Sources/Sentry/SentryProfiler.mm | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Sources/Sentry/SentryProfiler.mm b/Sources/Sentry/SentryProfiler.mm index d62673dc3c9..95f4455b41d 100644 --- a/Sources/Sentry/SentryProfiler.mm +++ b/Sources/Sentry/SentryProfiler.mm @@ -169,6 +169,7 @@ } } +# if SENTRY_HAS_UIKIT NSArray * processFrameRenderInfo(SentryFrameInfoTimeSeries *frameInfo, uint64_t start, uint64_t duration) { @@ -195,6 +196,7 @@ }]; return relativeFrameTimestampsNs; } +# endif // SENTRY_HAS_UIKIT @implementation SentryProfiler { NSMutableDictionary *_profile; From 80e5a399c41c8f26f4d73f6c7839eb7910135083 Mon Sep 17 00:00:00 2001 From: Andrew McKnight Date: Mon, 9 Jan 2023 11:56:17 -0500 Subject: [PATCH 52/70] wrap bare nanosecond conversions in function --- Sources/Sentry/SentryProfiler.mm | 12 ++++++------ Sources/Sentry/SentryTime.mm | 9 +++++++++ Sources/Sentry/include/SentryTime.h | 6 ++++++ 3 files changed, 21 insertions(+), 6 deletions(-) diff --git a/Sources/Sentry/SentryProfiler.mm b/Sources/Sentry/SentryProfiler.mm index 95f4455b41d..a07179b97e7 100644 --- a/Sources/Sentry/SentryProfiler.mm +++ b/Sources/Sentry/SentryProfiler.mm @@ -176,11 +176,11 @@ auto relativeFrameTimestampsNs = [NSMutableArray array]; [frameInfo enumerateObjectsUsingBlock:^( NSDictionary *_Nonnull obj, NSUInteger idx, BOOL *_Nonnull stop) { - const auto begin = (uint64_t)(obj[@"start_timestamp"].doubleValue * 1e9); + const auto begin = timeIntervalToNanoseconds(obj[@"start_timestamp"].doubleValue); if (begin < start) { return; } - const auto end = (uint64_t)(obj[@"end_timestamp"].doubleValue * 1e9); + const auto end = timeIntervalToNanoseconds(obj[@"end_timestamp"].doubleValue); const auto relativeEnd = getDurationNs(start, end); if (relativeEnd > duration) { SENTRY_LOG_DEBUG(@"The last slow/frozen frame extended past the end of the profile, " @@ -654,15 +654,15 @@ - (void)captureEnvelope [NSString stringWithFormat:@"%llu", [transaction.startTimestamp compare:_startDate] == NSOrderedAscending ? 0 - : (unsigned long long)( - [transaction.startTimestamp timeIntervalSinceDate:_startDate] * 1e9)]; + : timeIntervalToNanoseconds( + [transaction.startTimestamp timeIntervalSinceDate:_startDate])]; NSString *relativeEnd; if ([transaction.timestamp compare:_endDate] == NSOrderedDescending) { relativeEnd = [NSString stringWithFormat:@"%llu", profileDuration]; } else { - const auto profileStartToTransactionEnd_ns = - [transaction.timestamp timeIntervalSinceDate:_startDate] * 1e9; + const auto profileStartToTransactionEnd_ns = timeIntervalToNanoseconds( + [transaction.timestamp timeIntervalSinceDate:_startDate]); if (profileStartToTransactionEnd_ns < 0) { SENTRY_LOG_DEBUG(@"Transaction %@ ended before the profiler started, won't " @"associate it with this profile.", diff --git a/Sources/Sentry/SentryTime.mm b/Sources/Sentry/SentryTime.mm index 6df4fde6396..16c534b6356 100644 --- a/Sources/Sentry/SentryTime.mm +++ b/Sources/Sentry/SentryTime.mm @@ -6,6 +6,15 @@ #import "SentryMachLogging.hpp" +uint64_t +timeIntervalToNanoseconds(double seconds) +{ + NSCAssert(seconds >= 0, @"Seconds must be a positive value"); + NSCAssert(seconds <= UINT64_MAX / 1e9, + @"Value of seconds is too great; will overflow if casted to a uint64_t"); + return (uint64_t)(seconds * 1e9); +} + uint64_t getAbsoluteTime(void) { diff --git a/Sources/Sentry/include/SentryTime.h b/Sources/Sentry/include/SentryTime.h index 2f5860793f8..f21e7dcb328 100644 --- a/Sources/Sentry/include/SentryTime.h +++ b/Sources/Sentry/include/SentryTime.h @@ -4,6 +4,12 @@ SENTRY_EXTERN_C_BEGIN +/** + * Given a fractional amount of seconds in a @c double from a Cocoa API like @c -[NSDate @c + * timeIntervalSinceDate:], return an integer representing the amount of nanoseconds. + */ +uint64_t timeIntervalToNanoseconds(double seconds); + /** * Returns the absolute timestamp, which has no defined reference point or unit * as it is platform dependent. From 74e2375adcb686e3b6fb5d2e5a1d889365a1c042 Mon Sep 17 00:00:00 2001 From: Andrew McKnight Date: Mon, 9 Jan 2023 20:01:13 -0500 Subject: [PATCH 53/70] use the correct routine to process frame rates, which is different from the logic for frame renders --- Sources/Sentry/SentryProfiler.mm | 38 +++++++++++++++++++++++++------- 1 file changed, 30 insertions(+), 8 deletions(-) diff --git a/Sources/Sentry/SentryProfiler.mm b/Sources/Sentry/SentryProfiler.mm index a07179b97e7..ea7c1f74d21 100644 --- a/Sources/Sentry/SentryProfiler.mm +++ b/Sources/Sentry/SentryProfiler.mm @@ -171,9 +171,9 @@ # if SENTRY_HAS_UIKIT NSArray * -processFrameRenderInfo(SentryFrameInfoTimeSeries *frameInfo, uint64_t start, uint64_t duration) +processFrameRenders(SentryFrameInfoTimeSeries *frameInfo, uint64_t start, uint64_t duration) { - auto relativeFrameTimestampsNs = [NSMutableArray array]; + auto relativeFrameInfo = [NSMutableArray array]; [frameInfo enumerateObjectsUsingBlock:^( NSDictionary *_Nonnull obj, NSUInteger idx, BOOL *_Nonnull stop) { const auto begin = timeIntervalToNanoseconds(obj[@"start_timestamp"].doubleValue); @@ -189,12 +189,35 @@ } const auto relativeStart = getDurationNs(start, begin); const auto frameDuration = relativeEnd - relativeStart; - [relativeFrameTimestampsNs addObject:@{ + [relativeFrameInfo addObject:@{ @"elapsed_since_start_ns" : @(relativeStart), @"value" : @(frameDuration), }]; }]; - return relativeFrameTimestampsNs; + return relativeFrameInfo; +} + +NSArray * +processFrameRates(SentryFrameInfoTimeSeries *frameRates, uint64_t start) +{ + if (frameRates.count == 0) { + return nil; + } + auto relativeFrameRates = [NSMutableArray array]; + [frameRates enumerateObjectsUsingBlock:^( + NSDictionary *_Nonnull obj, NSUInteger idx, BOOL *_Nonnull stop) { + const auto timestamp = (uint64_t)(obj[@"timestamp"].doubleValue * 1e9); + const auto refreshRate = obj[@"frame_rate"]; + uint64_t relativeTimestamp = 0; + if (timestamp >= start) { + relativeTimestamp = getDurationNs(start, timestamp); + } + [relativeFrameRates addObject:@{ + @"elapsed_since_start_ns" : @(relativeTimestamp), + @"value" : refreshRate, + }]; + }]; + return relativeFrameRates; } # endif // SENTRY_HAS_UIKIT @@ -620,20 +643,19 @@ - (void)captureEnvelope # if SENTRY_HAS_UIKIT const auto slowTimestamps - = processFrameRenderInfo(_frameInfo.slowFrameTimestamps, _startTimestamp, profileDuration); + = processFrameRenders(_frameInfo.slowFrameTimestamps, _startTimestamp, profileDuration); if (slowTimestamps.count > 0) { metrics[@"slow_frame_renders"] = @{ @"unit" : @"nanosecond", @"values" : slowTimestamps }; } const auto frozenTimestamps - = processFrameRenderInfo(_frameInfo.slowFrameTimestamps, _startTimestamp, profileDuration); + = processFrameRenders(_frameInfo.frozenFrameTimestamps, _startTimestamp, profileDuration); if (frozenTimestamps.count > 0) { metrics[@"frozen_frame_renders"] = @{ @"unit" : @"nanosecond", @"values" : frozenTimestamps }; } - const auto frameRates - = processFrameRenderInfo(_frameInfo.frameRateTimestamps, _startTimestamp, profileDuration); + const auto frameRates = processFrameRates(_frameInfo.frameRateTimestamps, _startTimestamp); if (frameRates.count > 0) { metrics[@"screen_frame_rates"] = @{ @"unit" : @"hz", @"values" : frameRates }; } From 360b2a9ba1492fb32413a430b6da818f759fbc42 Mon Sep 17 00:00:00 2001 From: Andrew McKnight Date: Tue, 10 Jan 2023 17:38:22 -0500 Subject: [PATCH 54/70] remove all metrics but cpu usage and memory footprint --- Sentry.xcodeproj/project.pbxproj | 12 --- Sources/Sentry/SentryDispatchSourceWrapper.m | 48 ----------- Sources/Sentry/SentryMetricProfiler.mm | 86 ------------------- Sources/Sentry/SentryNSProcessInfoWrapper.mm | 55 ------------ Sources/Sentry/SentryProfiler.mm | 1 - Sources/Sentry/SentrySystemWrapper.mm | 57 +----------- .../include/SentryDispatchSourceWrapper.h | 26 ------ Sources/Sentry/include/SentryMetricProfiler.h | 6 -- .../include/SentryNSProcessInfoWrapper.h | 6 -- Sources/Sentry/include/SentrySystemWrapper.h | 11 --- .../TestSentryNSProcessInfoWrapper.swift | 19 ---- .../Helper/TestSentrySystemWrapper.swift | 4 - .../TestSentryDispatchSourceWrapper.swift | 28 ------ .../Profiling/SentryProfilerSwiftTests.swift | 48 +---------- Tests/SentryTests/SentrySystemWrapper+Tests.h | 8 -- .../SentryTests/SentryTests-Bridging-Header.h | 3 +- 16 files changed, 4 insertions(+), 414 deletions(-) delete mode 100644 Sources/Sentry/SentryDispatchSourceWrapper.m delete mode 100644 Sources/Sentry/include/SentryDispatchSourceWrapper.h delete mode 100644 Tests/SentryTests/Networking/TestSentryDispatchSourceWrapper.swift delete mode 100644 Tests/SentryTests/SentrySystemWrapper+Tests.h diff --git a/Sentry.xcodeproj/project.pbxproj b/Sentry.xcodeproj/project.pbxproj index f3737377fe2..1a53b5868a1 100644 --- a/Sentry.xcodeproj/project.pbxproj +++ b/Sentry.xcodeproj/project.pbxproj @@ -605,8 +605,6 @@ 7DC83100239826280043DD9A /* SentryIntegrationProtocol.h in Headers */ = {isa = PBXBuildFile; fileRef = 7DC830FF239826280043DD9A /* SentryIntegrationProtocol.h */; settings = {ATTRIBUTES = (Public, ); }; }; 7DC8310A2398283C0043DD9A /* SentryCrashIntegration.h in Headers */ = {isa = PBXBuildFile; fileRef = 7DC831082398283C0043DD9A /* SentryCrashIntegration.h */; }; 7DC8310C2398283C0043DD9A /* SentryCrashIntegration.m in Sources */ = {isa = PBXBuildFile; fileRef = 7DC831092398283C0043DD9A /* SentryCrashIntegration.m */; }; - 840748E5294BFE980077FC0A /* TestSentryDispatchSourceWrapper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 840748E4294BFE980077FC0A /* TestSentryDispatchSourceWrapper.swift */; }; - 840748E9294BFF9B0077FC0A /* SentryDispatchSourceWrapper.m in Sources */ = {isa = PBXBuildFile; fileRef = 840748E7294BFEBD0077FC0A /* SentryDispatchSourceWrapper.m */; }; 8419C0C428C1889D001C8259 /* SentryProfilerSwiftTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8419C0C328C1889D001C8259 /* SentryProfilerSwiftTests.swift */; }; 844EDC6F294143B900C86F34 /* SentryNSProcessInfoWrapper.h in Headers */ = {isa = PBXBuildFile; fileRef = 844EDC6D294143B900C86F34 /* SentryNSProcessInfoWrapper.h */; }; 844EDC70294143B900C86F34 /* SentryNSProcessInfoWrapper.mm in Sources */ = {isa = PBXBuildFile; fileRef = 844EDC6E294143B900C86F34 /* SentryNSProcessInfoWrapper.mm */; }; @@ -1432,10 +1430,6 @@ 7DC830FF239826280043DD9A /* SentryIntegrationProtocol.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = SentryIntegrationProtocol.h; path = Public/SentryIntegrationProtocol.h; sourceTree = ""; }; 7DC831082398283C0043DD9A /* SentryCrashIntegration.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = SentryCrashIntegration.h; path = include/SentryCrashIntegration.h; sourceTree = ""; }; 7DC831092398283C0043DD9A /* SentryCrashIntegration.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SentryCrashIntegration.m; sourceTree = ""; }; - 840748E4294BFE980077FC0A /* TestSentryDispatchSourceWrapper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TestSentryDispatchSourceWrapper.swift; sourceTree = ""; }; - 840748E6294BFEBD0077FC0A /* SentryDispatchSourceWrapper.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = SentryDispatchSourceWrapper.h; path = include/SentryDispatchSourceWrapper.h; sourceTree = ""; }; - 840748E7294BFEBD0077FC0A /* SentryDispatchSourceWrapper.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SentryDispatchSourceWrapper.m; sourceTree = ""; }; - 840748EA294C078B0077FC0A /* SentrySystemWrapper+Tests.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "SentrySystemWrapper+Tests.h"; sourceTree = ""; }; 8419C0C328C1889D001C8259 /* SentryProfilerSwiftTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SentryProfilerSwiftTests.swift; sourceTree = ""; }; 844A34C3282B278500C6D1DF /* .github */ = {isa = PBXFileReference; lastKnownFileType = folder; path = .github; sourceTree = ""; }; 844A3563282B3C9F00C6D1DF /* .sauce */ = {isa = PBXFileReference; lastKnownFileType = folder; path = .sauce; sourceTree = ""; }; @@ -1732,8 +1726,6 @@ 638DC99F1EBC6B6400A66E41 /* SentryRequestOperation.m */, 7BDB03B6251364F800BAE198 /* SentryDispatchQueueWrapper.h */, 7BDB03BA2513652900BAE198 /* SentryDispatchQueueWrapper.m */, - 840748E6294BFEBD0077FC0A /* SentryDispatchSourceWrapper.h */, - 840748E7294BFEBD0077FC0A /* SentryDispatchSourceWrapper.m */, 0AAE202028ED9BCC00D0CD80 /* SentryReachability.h */, 0AAE201D28ED9B9400D0CD80 /* SentryReachability.m */, ); @@ -2054,7 +2046,6 @@ 7B569DFE2590EEF600B653FC /* SentryScope+Equality.h */, 7B569E052590F04700B653FC /* SentryScope+Properties.h */, 7B9421C4260CA393001F9349 /* SentrySDK+Tests.h */, - 840748EA294C078B0077FC0A /* SentrySystemWrapper+Tests.h */, 639889D21EDF06C100EA7442 /* SentryTests-Bridging-Header.h */, 15360CF22433C59500112302 /* SentryInstallationTests.m */, 63B819131EC352A7002FDF4C /* SentryInterfacesTests.m */, @@ -2575,7 +2566,6 @@ 7BBD18A1244EE2FD00427C76 /* TestResponseFactory.swift */, 639FCF921EBC746F00778193 /* SentryDsnTests.m */, 7BDB03BE25136A7D00BAE198 /* TestSentryDispatchQueueWrapper.swift */, - 840748E4294BFE980077FC0A /* TestSentryDispatchSourceWrapper.swift */, 7B4E23B5251A07BD00060D68 /* SentryDispatchQueueWrapperTests.swift */, 8ED2D27E26A6581C00CA8329 /* NSURLProtocolSwizzle.h */, 8ED2D27F26A6581C00CA8329 /* NSURLProtocolSwizzle.m */, @@ -3631,7 +3621,6 @@ 7B3398652459C15200BD9C96 /* SentryEnvelopeRateLimit.m in Sources */, 0A2D8D9628997845008720F6 /* NSLocale+Sentry.m in Sources */, A2475E1F25FB648B007D9080 /* fishhook.c in Sources */, - 840748E9294BFF9B0077FC0A /* SentryDispatchSourceWrapper.m in Sources */, 7B0DC730288698F70039995F /* NSMutableDictionary+Sentry.m in Sources */, 7BD4BD4527EB29F50071F4FF /* SentryClientReport.m in Sources */, 631E6D341EBC679C00712345 /* SentryQueueableRequestManager.m in Sources */, @@ -3981,7 +3970,6 @@ A811D867248E2770008A41EA /* SentrySystemEventBreadcrumbsTest.swift in Sources */, 7B7725D8292F5DC20015BBF9 /* SentryCrashInstallationTests.m in Sources */, 7B82D54924E2A2D400EE670F /* SentryIdTests.swift in Sources */, - 840748E5294BFE980077FC0A /* TestSentryDispatchSourceWrapper.swift in Sources */, 7BD47B4E268F0B470076A663 /* ClearTestState.swift in Sources */, 844EDC7A29415AE800C86F34 /* TestSentrySystemWrapper.swift in Sources */, 7B87C916295ECFD700510C52 /* SentryMetricKitEventTests.swift in Sources */, diff --git a/Sources/Sentry/SentryDispatchSourceWrapper.m b/Sources/Sentry/SentryDispatchSourceWrapper.m deleted file mode 100644 index a525c1473d8..00000000000 --- a/Sources/Sentry/SentryDispatchSourceWrapper.m +++ /dev/null @@ -1,48 +0,0 @@ -#import "SentryDispatchSourceWrapper.h" - -NS_ASSUME_NONNULL_BEGIN - -@implementation SentryDispatchSourceWrapper { - dispatch_source_t _source; -} - -- (instancetype)initWithDispatchSource:(dispatch_source_t)source -{ - if (self = [super init]) { - _source = source; - } - return self; -} - -- (void)resumeWithHandler:(dispatch_block_t)handler -{ - dispatch_source_set_event_handler(_source, handler); - dispatch_resume(_source); -} - -- (uintptr_t)getData -{ - return dispatch_source_get_data(_source); -} - -- (void)cancel -{ - dispatch_source_cancel(_source); -} - -@end - -@implementation SentryDispatchSourceFactory - -- (SentryDispatchSourceWrapper *)dispatchSourceWithType:(dispatch_source_type_t)type - handle:(uintptr_t)handle - mask:(uintptr_t)mask - queue:(dispatch_queue_t _Nullable)sourceQueue; -{ - dispatch_source_t source = dispatch_source_create(type, handle, mask, sourceQueue); - return [[SentryDispatchSourceWrapper alloc] initWithDispatchSource:source]; -} - -@end - -NS_ASSUME_NONNULL_END diff --git a/Sources/Sentry/SentryMetricProfiler.mm b/Sources/Sentry/SentryMetricProfiler.mm index 3b96a30b5c6..42a4272a095 100644 --- a/Sources/Sentry/SentryMetricProfiler.mm +++ b/Sources/Sentry/SentryMetricProfiler.mm @@ -2,10 +2,7 @@ #if SENTRY_TARGET_PROFILING_SUPPORTED -# import "SentryDependencyContainer.h" # import "SentryLog.h" -# import "SentryMachLogging.hpp" -# import "SentryNSNotificationCenterWrapper.h" # import "SentryNSProcessInfoWrapper.h" # import "SentryNSTimerWrapper.h" # import "SentrySystemWrapper.h" @@ -19,15 +16,9 @@ static const NSTimeInterval kSentryMetricProfilerTimeseriesInterval = 0.1; NSString *const kSentryMetricProfilerSerializationKeyMemoryFootprint = @"memory_footprint"; -NSString *const kSentryMetricProfilerSerializationKeyMemoryPressure = @"memory_pressure"; -NSString *const kSentryMetricProfilerSerializationKeyPowerState = @"is_low_power_mode"; -NSString *const kSentryMetricProfilerSerializationKeyThermalState = @"thermal_state"; NSString *const kSentryMetricProfilerSerializationKeyCPUUsageFormat = @"cpu_usage_%d"; NSString *const kSentryMetricProfilerSerializationUnitBytes = @"byte"; -NSString *const kSentryMetricProfilerSerializationUnitBoolean = @"bool"; -NSString *const kSentryMetricProfilerSerializationUnitMemoryPressureEnum = @"memory_pressure_enum"; -NSString *const kSentryMetricProfilerSerializationUnitThermalStateEnum = @"thermal_state_enum"; NSString *const kSentryMetricProfilerSerializationUnitPercentage = @"percent"; namespace { @@ -50,9 +41,6 @@ @implementation SentryMetricProfiler { *_cpuUsage; NSMutableArray *> *_memoryFootprint; - NSMutableArray *> *_thermalState; - NSMutableArray *> *_powerLevelState; - NSMutableArray *> *_memoryPressureState; uint64_t _profileStartTime; } @@ -77,9 +65,6 @@ - (instancetype)initWithProfileStartTime:(uint64_t)profileStartTime _timerWrapper = timerWrapper; _memoryFootprint = [NSMutableArray *> array]; - _thermalState = [NSMutableArray *> array]; - _powerLevelState = [NSMutableArray *> array]; - _memoryPressureState = [NSMutableArray *> array]; _profileStartTime = profileStartTime; } @@ -96,15 +81,11 @@ - (void)dealloc - (void)start { [self registerSampler]; - [self registerStateChangeNotifications]; - [self registerMemoryPressureWarningHandler]; } - (void)stop { [_timer invalidate]; - [_systemWrapper deregisterMemoryPressureNotifications]; - [_processInfoWrapper stopMonitoring:self]; } - (NSMutableDictionary *)serialize @@ -118,18 +99,6 @@ - (void)stop dict[kSentryMetricProfilerSerializationKeyMemoryFootprint] = serializedValues(_memoryFootprint, kSentryMetricProfilerSerializationUnitBytes); } - if (_memoryPressureState.count > 0) { - dict[kSentryMetricProfilerSerializationKeyMemoryPressure] = serializedValues( - _memoryPressureState, kSentryMetricProfilerSerializationUnitMemoryPressureEnum); - } - if (_powerLevelState.count > 0) { - dict[kSentryMetricProfilerSerializationKeyPowerState] - = serializedValues(_powerLevelState, kSentryMetricProfilerSerializationUnitBoolean); - } - if (_thermalState.count > 0) { - dict[kSentryMetricProfilerSerializationKeyThermalState] = serializedValues( - _thermalState, kSentryMetricProfilerSerializationUnitThermalStateEnum); - } [_cpuUsage enumerateKeysAndObjectsUsingBlock:^(NSNumber *_Nonnull core, NSMutableArray *> *_Nonnull readings, @@ -157,61 +126,6 @@ - (void)registerSampler }]; } -/** - * This is a more fine-grained API, providing normal/warn/critical levels of memory usage, versus - * using `UIApplicationDidReceiveMemoryWarningNotification` which does not provide any additional - * information ("This notification does not contain a userInfo dictionary." from - * https://developer.apple.com/documentation/uikit/uiapplication/1622920-didreceivememorywarningnotificat). - */ -- (void)registerMemoryPressureWarningHandler -{ - __weak auto weakSelf = self; - [_systemWrapper registerMemoryPressureNotifications:^(uintptr_t memoryPressureState) { - const auto strongSelf = weakSelf; - if (!strongSelf) { - return; - } - @synchronized(strongSelf) { - [strongSelf->_memoryPressureState - addObject:[strongSelf metricEntryForValue:@(memoryPressureState)]]; - } - }]; -} - -- (void)registerStateChangeNotifications -{ - // According to Apple docs: "To receive NSProcessInfoThermalStateDidChangeNotification, you must - // access the thermalState prior to registering for the notification." (from - // https://developer.apple.com/documentation/foundation/nsprocessinfothermalstatedidchangenotification/) - [self recordThermalState]; - - [_processInfoWrapper monitorForThermalStateChanges:self callback:@selector(recordThermalState)]; - - if (@available(macOS 12.0, *)) { - [_processInfoWrapper monitorForPowerStateChanges:self - callback:@selector(recordPowerLevelState)]; - } -} - -- (void)recordThermalState -{ - @synchronized(self) { - [_thermalState addObject:[self metricEntryForValue:@(_processInfoWrapper.thermalState)]]; - } -} - -- (void)recordPowerLevelState -{ - if (@available(macOS 12.0, *)) { - @synchronized(self) { - [_powerLevelState - addObject:[self metricEntryForValue:@(_processInfoWrapper.isLowPowerModeEnabled - ? 1.f - : 0.f)]]; - } - } -} - - (void)recordMemoryFootprint { NSError *error; diff --git a/Sources/Sentry/SentryNSProcessInfoWrapper.mm b/Sources/Sentry/SentryNSProcessInfoWrapper.mm index 886ef924504..ba63cc3bbd5 100644 --- a/Sources/Sentry/SentryNSProcessInfoWrapper.mm +++ b/Sources/Sentry/SentryNSProcessInfoWrapper.mm @@ -1,65 +1,10 @@ #import "SentryNSProcessInfoWrapper.h" -#import "SentryDependencyContainer.h" -#import "SentryNSNotificationCenterWrapper.h" @implementation SentryNSProcessInfoWrapper -- (NSProcessInfoThermalState)thermalState -{ - return NSProcessInfo.processInfo.thermalState; -} - -- (BOOL)isLowPowerModeEnabled -{ - if (@available(macOS 12.0, *)) { - return NSProcessInfo.processInfo.isLowPowerModeEnabled; - } else { - return NO; - } -} - - (NSUInteger)processorCount { return NSProcessInfo.processInfo.processorCount; } -- (void)monitorForPowerStateChanges:(id)observer callback:(SEL)callback -{ - // According to Apple docs: "This notification is posted on the global dispatch queue. The - // object associated with the notification is NSProcessInfo.processInfo." See the declaration - // for NSProcessInfoPowerStateDidChangeNotification in NSProcessInfo.h for more information. - if (@available(macOS 12.0, *)) { - [SentryDependencyContainer.sharedInstance.notificationCenterWrapper - addObserver:observer - selector:callback - name:NSProcessInfoPowerStateDidChangeNotification - object:NSProcessInfo.processInfo]; - } -} - -- (void)monitorForThermalStateChanges:(id)observer callback:(SEL)callback -{ - // According to Apple docs: "This notification is posted on the global dispatch queue. The - // object associated with the notification is NSProcessInfo.processInfo." See the declaration - // for NSProcessInfoThermalStateDidChangeNotification in NSProcessInfo.h for more information. - [SentryDependencyContainer.sharedInstance.notificationCenterWrapper - addObserver:observer - selector:callback - name:NSProcessInfoThermalStateDidChangeNotification - object:NSProcessInfo.processInfo]; -} - -- (void)stopMonitoring:(id)observer -{ - const auto notifier = SentryDependencyContainer.sharedInstance.notificationCenterWrapper; - [notifier removeObserver:observer - name:NSProcessInfoThermalStateDidChangeNotification - object:NSProcessInfo.processInfo]; - if (@available(macOS 12.0, *)) { - [notifier removeObserver:observer - name:NSProcessInfoPowerStateDidChangeNotification - object:NSProcessInfo.processInfo]; - } -} - @end diff --git a/Sources/Sentry/SentryProfiler.mm b/Sources/Sentry/SentryProfiler.mm index ea7c1f74d21..2984117459b 100644 --- a/Sources/Sentry/SentryProfiler.mm +++ b/Sources/Sentry/SentryProfiler.mm @@ -10,7 +10,6 @@ # import "SentryDefines.h" # import "SentryDependencyContainer.h" # import "SentryDevice.h" -# import "SentryDispatchSourceWrapper.h" # import "SentryEnvelope.h" # import "SentryEnvelopeItemType.h" # import "SentryFramesTracker.h" diff --git a/Sources/Sentry/SentrySystemWrapper.mm b/Sources/Sentry/SentrySystemWrapper.mm index df18e20a2e4..6e626700e8e 100644 --- a/Sources/Sentry/SentrySystemWrapper.mm +++ b/Sources/Sentry/SentrySystemWrapper.mm @@ -1,31 +1,8 @@ #import "SentrySystemWrapper.h" -#import "SentryDispatchSourceWrapper.h" #import "SentryError.h" -#import "SentryMachLogging.hpp" #import -const NSUInteger kSentryMemoryPressureLevelNormal = DISPATCH_MEMORYPRESSURE_NORMAL; -const NSUInteger kSentryMemoryPressureLevelWarn = DISPATCH_MEMORYPRESSURE_WARN; -const NSUInteger kSentryMemoryPressureLevelCritical = DISPATCH_MEMORYPRESSURE_CRITICAL; - -@implementation SentrySystemWrapper { - dispatch_queue_t _memoryWarningQueue; - SentryDispatchSourceFactory *_dispatchSourceFactory; - SentryDispatchSourceWrapper *_dispatchSourceWrapper; -} - -- (instancetype)initWithDispatchSourceFactory:(SentryDispatchSourceFactory *)dispatchSourceFactory -{ - if (self = [super init]) { - _dispatchSourceFactory = dispatchSourceFactory; - - const auto queueAttributes - = dispatch_queue_attr_make_with_qos_class(DISPATCH_QUEUE_SERIAL, QOS_CLASS_UTILITY, 0); - _memoryWarningQueue - = dispatch_queue_create("io.sentry.queue.memory-warnings", queueAttributes); - } - return self; -} +@implementation SentrySystemWrapper - (SentryRAMBytes)memoryFootprintBytes:(NSError *__autoreleasing _Nullable *)error { @@ -82,36 +59,4 @@ - (SentryRAMBytes)memoryFootprintBytes:(NSError *__autoreleasing _Nullable *)err return result; } -- (void)registerMemoryPressureNotifications:(SentryMemoryPressureNotification)handler -{ - const auto type = DISPATCH_SOURCE_TYPE_MEMORYPRESSURE; - const auto mask = DISPATCH_MEMORYPRESSURE_NORMAL | DISPATCH_MEMORYPRESSURE_WARN - | DISPATCH_MEMORYPRESSURE_CRITICAL; - _dispatchSourceWrapper = [_dispatchSourceFactory dispatchSourceWithType:type - handle:0 - mask:mask - queue:_memoryWarningQueue]; - - __weak auto weakSelf = self; - [_dispatchSourceWrapper resumeWithHandler:^{ - const auto strongSelf = weakSelf; - if (!strongSelf) { - return; - } - handler([strongSelf->_dispatchSourceWrapper getData]); - }]; -} - -- (void)deregisterMemoryPressureNotifications -{ - [_dispatchSourceWrapper cancel]; -} - -#pragma mark - Testing - -- (SentryDispatchSourceWrapper *)dispatchSourceWrapper -{ - return _dispatchSourceWrapper; -} - @end diff --git a/Sources/Sentry/include/SentryDispatchSourceWrapper.h b/Sources/Sentry/include/SentryDispatchSourceWrapper.h deleted file mode 100644 index 8c445db67eb..00000000000 --- a/Sources/Sentry/include/SentryDispatchSourceWrapper.h +++ /dev/null @@ -1,26 +0,0 @@ -#import - -NS_ASSUME_NONNULL_BEGIN - -@interface SentryDispatchSourceWrapper : NSObject - -- (instancetype)initWithDispatchSource:(dispatch_source_t)source; - -- (void)resumeWithHandler:(dispatch_block_t)handler; - -- (uintptr_t)getData; - -- (void)cancel; - -@end - -@interface SentryDispatchSourceFactory : NSObject - -- (SentryDispatchSourceWrapper *)dispatchSourceWithType:(dispatch_source_type_t)type - handle:(uintptr_t)handle - mask:(uintptr_t)mask - queue:(dispatch_queue_t _Nullable)sourceQueue; - -@end - -NS_ASSUME_NONNULL_END diff --git a/Sources/Sentry/include/SentryMetricProfiler.h b/Sources/Sentry/include/SentryMetricProfiler.h index 9a667d1083e..a67895c22fe 100644 --- a/Sources/Sentry/include/SentryMetricProfiler.h +++ b/Sources/Sentry/include/SentryMetricProfiler.h @@ -11,15 +11,9 @@ NS_ASSUME_NONNULL_BEGIN SENTRY_EXTERN NSString *const kSentryMetricProfilerSerializationKeyMemoryFootprint; -SENTRY_EXTERN NSString *const kSentryMetricProfilerSerializationKeyMemoryPressure; -SENTRY_EXTERN NSString *const kSentryMetricProfilerSerializationKeyPowerState; -SENTRY_EXTERN NSString *const kSentryMetricProfilerSerializationKeyThermalState; SENTRY_EXTERN NSString *const kSentryMetricProfilerSerializationKeyCPUUsageFormat; SENTRY_EXTERN NSString *const kSentryMetricProfilerSerializationUnitBytes; -SENTRY_EXTERN NSString *const kSentryMetricProfilerSerializationUnitBoolean; -SENTRY_EXTERN NSString *const kSentryMetricProfilerSerializationUnitMemoryPressureEnum; -SENTRY_EXTERN NSString *const kSentryMetricProfilerSerializationUnitThermalStateEnum; SENTRY_EXTERN NSString *const kSentryMetricProfilerSerializationUnitPercentage; /** diff --git a/Sources/Sentry/include/SentryNSProcessInfoWrapper.h b/Sources/Sentry/include/SentryNSProcessInfoWrapper.h index c7f3391ac0e..12539afe3d0 100644 --- a/Sources/Sentry/include/SentryNSProcessInfoWrapper.h +++ b/Sources/Sentry/include/SentryNSProcessInfoWrapper.h @@ -4,14 +4,8 @@ NS_ASSUME_NONNULL_BEGIN @interface SentryNSProcessInfoWrapper : NSObject -@property (readonly) NSProcessInfoThermalState thermalState; -@property (readonly, getter=isLowPowerModeEnabled) BOOL lowPowerModeEnabled; @property (readonly) NSUInteger processorCount; -- (void)monitorForPowerStateChanges:(id)observer callback:(SEL)callback; -- (void)monitorForThermalStateChanges:(id)observer callback:(SEL)callback; -- (void)stopMonitoring:(id)observer; - @end NS_ASSUME_NONNULL_END diff --git a/Sources/Sentry/include/SentrySystemWrapper.h b/Sources/Sentry/include/SentrySystemWrapper.h index 43c0960e62c..03ecc7d52f0 100644 --- a/Sources/Sentry/include/SentrySystemWrapper.h +++ b/Sources/Sentry/include/SentrySystemWrapper.h @@ -1,12 +1,6 @@ #import "SentryDefines.h" #import -@class SentryDispatchSourceFactory; - -SENTRY_EXTERN const NSUInteger kSentryMemoryPressureLevelNormal; -SENTRY_EXTERN const NSUInteger kSentryMemoryPressureLevelWarn; -SENTRY_EXTERN const NSUInteger kSentryMemoryPressureLevelCritical; - NS_ASSUME_NONNULL_BEGIN typedef void (^SentryMemoryPressureNotification)(uintptr_t); @@ -23,8 +17,6 @@ typedef mach_vm_size_t SentryRAMBytes; */ @interface SentrySystemWrapper : NSObject -- (instancetype)initWithDispatchSourceFactory:(SentryDispatchSourceFactory *)dispatchSourceFactory; - - (SentryRAMBytes)memoryFootprintBytes:(NSError **)error; /** @@ -34,9 +26,6 @@ typedef mach_vm_size_t SentryRAMBytes; */ - (nullable NSArray *)cpuUsagePerCore:(NSError **)error; -- (void)registerMemoryPressureNotifications:(SentryMemoryPressureNotification)handler; -- (void)deregisterMemoryPressureNotifications; - @end NS_ASSUME_NONNULL_END diff --git a/Tests/SentryTests/Helper/TestSentryNSProcessInfoWrapper.swift b/Tests/SentryTests/Helper/TestSentryNSProcessInfoWrapper.swift index a7edad4809e..310f4afe7d2 100644 --- a/Tests/SentryTests/Helper/TestSentryNSProcessInfoWrapper.swift +++ b/Tests/SentryTests/Helper/TestSentryNSProcessInfoWrapper.swift @@ -2,31 +2,12 @@ import Sentry class TestSentryNSProcessInfoWrapper: SentryNSProcessInfoWrapper { struct Override { - var isLowPowerModeEnabled: Bool? - var thermalState: ProcessInfo.ThermalState? var processorCount: UInt? } var overrides = Override() - override var thermalState: ProcessInfo.ThermalState { - overrides.thermalState ?? super.thermalState - } - - override var isLowPowerModeEnabled: Bool { - overrides.isLowPowerModeEnabled ?? super.isLowPowerModeEnabled - } - override var processorCount: UInt { overrides.processorCount ?? super.processorCount } - - @available(iOS 9.0, macOS 12.0, *) - func sendPowerStateChangeNotification() { - SentryDependencyContainer.sharedInstance().notificationCenterWrapper.postNotificationName(NSNotification.Name.NSProcessInfoPowerStateDidChange, object: ProcessInfo.processInfo) - } - - func sendThermalStateChangeNotification() { - SentryDependencyContainer.sharedInstance().notificationCenterWrapper.postNotificationName(ProcessInfo.thermalStateDidChangeNotification, object: ProcessInfo.processInfo) - } } diff --git a/Tests/SentryTests/Helper/TestSentrySystemWrapper.swift b/Tests/SentryTests/Helper/TestSentrySystemWrapper.swift index f207c7eddc6..5e515b79329 100644 --- a/Tests/SentryTests/Helper/TestSentrySystemWrapper.swift +++ b/Tests/SentryTests/Helper/TestSentrySystemWrapper.swift @@ -11,10 +11,6 @@ class TestSentrySystemWrapper: SentrySystemWrapper { var overrides = Override() - override init() { - super.init(dispatchSourceFactory: TestSentryDispatchSourceFactory()) - } - override func memoryFootprintBytes(_ error: NSErrorPointer) -> SentryRAMBytes { if let errorOverride = overrides.memoryFootprintError { error?.pointee = errorOverride diff --git a/Tests/SentryTests/Networking/TestSentryDispatchSourceWrapper.swift b/Tests/SentryTests/Networking/TestSentryDispatchSourceWrapper.swift deleted file mode 100644 index 6b1a460acfc..00000000000 --- a/Tests/SentryTests/Networking/TestSentryDispatchSourceWrapper.swift +++ /dev/null @@ -1,28 +0,0 @@ -import Foundation - -class TestSentryDispatchSourceFactory: SentryDispatchSourceFactory { - override func dispatchSource(withType type: __dispatch_source_type_t, handle: UInt, mask: UInt, queue sourceQueue: DispatchQueue?) -> SentryDispatchSourceWrapper { - return TestSentryDispatchSourceWrapper() - } -} - -class TestSentryDispatchSourceWrapper: SentryDispatchSourceWrapper { - var data: UInt! - var handler: (() -> Void)? - - override func getData() -> UInt { - return data - } - - override func resume(handler: @escaping () -> Void) { - self.handler = handler - } - - override func cancel() { - // no-op - } - - func fire() { - handler?() - } -} diff --git a/Tests/SentryTests/Profiling/SentryProfilerSwiftTests.swift b/Tests/SentryTests/Profiling/SentryProfilerSwiftTests.swift index 171389159a8..633b8453e81 100644 --- a/Tests/SentryTests/Profiling/SentryProfilerSwiftTests.swift +++ b/Tests/SentryTests/Profiling/SentryProfilerSwiftTests.swift @@ -71,27 +71,6 @@ class SentryProfilerSwiftTests: XCTestCase { let span = fixture.newTransaction() forceProfilerSample() - // mock low power mode - [true, false].forEach { - fixture.processInfoWrapper.overrides.isLowPowerModeEnabled = $0 - if #available(macOS 12.0, *) { - fixture.processInfoWrapper.sendPowerStateChangeNotification() - } - } - - // mock memory pressure notifications - [kSentryMemoryPressureLevelWarn, kSentryMemoryPressureLevelCritical, kSentryMemoryPressureLevelNormal].forEach { - let dispatchSource = fixture.systemWrapper.dispatchSourceWrapper() as! TestSentryDispatchSourceWrapper - dispatchSource.data = $0 - dispatchSource.fire() - } - - // mock thermal state - [ProcessInfo.ThermalState.critical, .serious, .fair, .nominal].forEach { - fixture.processInfoWrapper.overrides.thermalState = $0 - fixture.processInfoWrapper.sendThermalStateChangeNotification() - } - // gather mock cpu usages and memory footprints for _ in 0..<2 { let cpuExp = expectation(description: "first set of timeseries measurements are gathered") @@ -108,7 +87,7 @@ class SentryProfilerSwiftTests: XCTestCase { span.finish() do { - try self.assertMetricsPayload(thermalStateNotifications: 4, powerStateNotifications: 2, expectedCPUUsages: cpuUsages, cpuReadings: 2, memoryPressureNotifications: 3) + try self.assertMetricsPayload(expectedCPUUsages: cpuUsages, cpuReadings: 2) exp.fulfill() } catch { XCTFail("Encountered error: \(error)") @@ -266,12 +245,9 @@ private extension SentryProfilerSwiftTests { case unexpectedMeasurementsDeserializationType case noEnvelopeCaptured case noProfileEnvelopeItem - case noPowerStateEvents - case noThermalStateEvents case malformedMetricValueEntry case noCPUUsageEvents case noCPUUsageReported - case noMemoryPressureNotifications } func getProfileData() throws -> Data { @@ -319,7 +295,7 @@ private extension SentryProfilerSwiftTests { self.assertValidProfileData(data: profileData, transactionEnvironment: transactionEnvironment, numberOfTransactions: numberOfTransactions, shouldTimeout: shouldTimeOut) } - func assertMetricsPayload(thermalStateNotifications: Int, powerStateNotifications: Int, expectedCPUUsages: [Double], cpuReadings: Int, memoryPressureNotifications: Int) throws { + func assertMetricsPayload(expectedCPUUsages: [Double], cpuReadings: Int) throws { let profileData = try self.getProfileData() guard let profile = try JSONSerialization.jsonObject(with: profileData) as? [String: Any] else { throw TestError.unexpectedProfileDeserializationType @@ -328,26 +304,6 @@ private extension SentryProfilerSwiftTests { throw TestError.unexpectedMeasurementsDeserializationType } - if #available(macOS 12.0, *) { - guard let powerStateEntry = measurements[kSentryMetricProfilerSerializationKeyPowerState] as? [String: Any], let powerState = powerStateEntry["values"] as? [[String: Any]] else { - throw TestError.noPowerStateEvents - } - - XCTAssertEqual(powerState.count, powerStateNotifications) - } - - guard let thermalStateEntry = measurements[kSentryMetricProfilerSerializationKeyThermalState] as? [String: Any], let thermalState = thermalStateEntry["values"] as? [[String: Any]] else { - throw TestError.noThermalStateEvents - } - - // one initial reading per API spec, then all the actual notifications sent - XCTAssertEqual(thermalState.count, thermalStateNotifications + 1) - - guard let memoryPressureEntry = measurements[kSentryMetricProfilerSerializationKeyMemoryPressure] as? [String: Any], let memoryPressure = memoryPressureEntry["values"] as? [[String: Any]] else { - throw TestError.noMemoryPressureNotifications - } - XCTAssertEqual(memoryPressure.count, memoryPressureNotifications) - for (i, expectedUsage) in expectedCPUUsages.enumerated() { let key = NSString(format: kSentryMetricProfilerSerializationKeyCPUUsageFormat as NSString, i) as String guard let cpuUsage = measurements[key] as? [String: Any] else { diff --git a/Tests/SentryTests/SentrySystemWrapper+Tests.h b/Tests/SentryTests/SentrySystemWrapper+Tests.h deleted file mode 100644 index 28fc8f8ef21..00000000000 --- a/Tests/SentryTests/SentrySystemWrapper+Tests.h +++ /dev/null @@ -1,8 +0,0 @@ -#import "SentrySystemWrapper.h" - -@interface -SentrySystemWrapper () - -- (SentryDispatchSourceWrapper *)dispatchSourceWrapper; - -@end diff --git a/Tests/SentryTests/SentryTests-Bridging-Header.h b/Tests/SentryTests/SentryTests-Bridging-Header.h index 4918370193c..f68060c2858 100644 --- a/Tests/SentryTests/SentryTests-Bridging-Header.h +++ b/Tests/SentryTests/SentryTests-Bridging-Header.h @@ -66,7 +66,6 @@ #import "SentryDiscardReasonMapper.h" #import "SentryDiscardedEvent.h" #import "SentryDispatchQueueWrapper.h" -#import "SentryDispatchSourceWrapper.h" #import "SentryDisplayLinkWrapper.h" #import "SentryDsn.h" #import "SentryEnvelope+Private.h" @@ -147,7 +146,7 @@ #import "SentrySwizzleWrapper.h" #import "SentrySysctl.h" #import "SentrySystemEventBreadcrumbs.h" -#import "SentrySystemWrapper+Tests.h" +#import "SentrySystemWrapper.h" #import "SentryTestIntegration.h" #import "SentryTestObjCRuntimeWrapper.h" #import "SentryThread.h" From 2b727d39a1a7ef758bc8f61a010ea6641c722750 Mon Sep 17 00:00:00 2001 From: Andrew McKnight Date: Tue, 10 Jan 2023 17:38:40 -0500 Subject: [PATCH 55/70] fix test by removing async dispatch --- Tests/SentryTests/Profiling/SentryProfilerSwiftTests.swift | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/Tests/SentryTests/Profiling/SentryProfilerSwiftTests.swift b/Tests/SentryTests/Profiling/SentryProfilerSwiftTests.swift index 633b8453e81..8cc65d7ca4e 100644 --- a/Tests/SentryTests/Profiling/SentryProfilerSwiftTests.swift +++ b/Tests/SentryTests/Profiling/SentryProfilerSwiftTests.swift @@ -73,12 +73,7 @@ class SentryProfilerSwiftTests: XCTestCase { // gather mock cpu usages and memory footprints for _ in 0..<2 { - let cpuExp = expectation(description: "first set of timeseries measurements are gathered") - DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) { - self.fixture.timerWrapper.fire() - cpuExp.fulfill() - } - waitForExpectations(timeout: 1) + self.fixture.timerWrapper.fire() } // finish profile From 6b690bc1dc053041a6ff4be85f120237f5f1d74c Mon Sep 17 00:00:00 2001 From: Andrew McKnight Date: Thu, 12 Jan 2023 13:16:52 -0500 Subject: [PATCH 56/70] add test case for new error function; reenable another test case for errors --- .../xcshareddata/xcschemes/Sentry.xcscheme | 3 --- Tests/SentryTests/Protocol/SentryNSErrorTests.swift | 13 ++++++++++++- 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/Sentry.xcodeproj/xcshareddata/xcschemes/Sentry.xcscheme b/Sentry.xcodeproj/xcshareddata/xcschemes/Sentry.xcscheme index 4cae1785284..16a451aa92b 100644 --- a/Sentry.xcodeproj/xcshareddata/xcschemes/Sentry.xcscheme +++ b/Sentry.xcodeproj/xcshareddata/xcschemes/Sentry.xcscheme @@ -67,9 +67,6 @@ - - diff --git a/Tests/SentryTests/Protocol/SentryNSErrorTests.swift b/Tests/SentryTests/Protocol/SentryNSErrorTests.swift index 81953b16f96..95757c8544c 100644 --- a/Tests/SentryTests/Protocol/SentryNSErrorTests.swift +++ b/Tests/SentryTests/Protocol/SentryNSErrorTests.swift @@ -27,7 +27,7 @@ class SentryNSErrorTests: XCTestCase { XCTAssertEqual(actualUnderlyingError.domain, inputUnderlyingError.domain) } - func testSerializeWithUnderlyingNSException_disabled() { + func testSerializeWithUnderlyingNSException() { let inputExceptionName = NSExceptionName.decimalNumberDivideByZeroException let inputExceptionReason = "test exception reason" let inputUnderlyingException = NSException(name: inputExceptionName, reason: inputExceptionReason, userInfo: ["some userinfo key": "some userinfo value"]) @@ -42,4 +42,15 @@ class SentryNSErrorTests: XCTestCase { XCTAssertEqual(actualDescription, "\(inputDescription) (\(inputExceptionReason))") } + func testWithKernelError() { + let inputKernelErrorCode = KERN_NOT_RECEIVER + let inputDescription = "some test kernel error" + let actualError = NSErrorFromSentryErrorWithKernelError(SentryError.unknownError, inputDescription, inputKernelErrorCode) + + guard let actualDescription = actualError?.localizedDescription else { + XCTFail("Expected a localizedDescription in the resulting error") + return + } + XCTAssertEqual(actualDescription, "\(inputDescription) (The task in question does not hold receive rights for the port argument.)") + } } From 16b2ba3d370d637513ba995ba6ef4d290f96048b Mon Sep 17 00:00:00 2001 From: Andrew McKnight Date: Thu, 12 Jan 2023 13:20:33 -0500 Subject: [PATCH 57/70] test errors gathering cpu and memory usages --- Tests/SentryTests/Profiling/SentryProfilerSwiftTests.swift | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/Tests/SentryTests/Profiling/SentryProfilerSwiftTests.swift b/Tests/SentryTests/Profiling/SentryProfilerSwiftTests.swift index 8cc65d7ca4e..d10661c9744 100644 --- a/Tests/SentryTests/Profiling/SentryProfilerSwiftTests.swift +++ b/Tests/SentryTests/Profiling/SentryProfilerSwiftTests.swift @@ -76,6 +76,11 @@ class SentryProfilerSwiftTests: XCTestCase { self.fixture.timerWrapper.fire() } + // mock errors gathering cpu usage and memory footprint to ensure they don't add more information to the payload + fixture.systemWrapper.overrides.cpuUsageError = NSError(domain: "test-error", code: 0) + fixture.systemWrapper.overrides.memoryFootprintError = NSError(domain: "test-error", code: 1) + self.fixture.timerWrapper.fire() + // finish profile let exp = expectation(description: "Receives profile payload") DispatchQueue.main.asyncAfter(deadline: .now() + 1) { From 94e8c3bddbec7e022757cad98ac17b0eb1373d92 Mon Sep 17 00:00:00 2001 From: Andrew McKnight Date: Thu, 12 Jan 2023 13:36:54 -0500 Subject: [PATCH 58/70] add missing asserts for memory footprint; generalize logic to perform assertions on different metric values --- .../Profiling/SentryProfilerSwiftTests.swift | 35 +++++++++++-------- 1 file changed, 20 insertions(+), 15 deletions(-) diff --git a/Tests/SentryTests/Profiling/SentryProfilerSwiftTests.swift b/Tests/SentryTests/Profiling/SentryProfilerSwiftTests.swift index d10661c9744..71fb43b0258 100644 --- a/Tests/SentryTests/Profiling/SentryProfilerSwiftTests.swift +++ b/Tests/SentryTests/Profiling/SentryProfilerSwiftTests.swift @@ -87,7 +87,7 @@ class SentryProfilerSwiftTests: XCTestCase { span.finish() do { - try self.assertMetricsPayload(expectedCPUUsages: cpuUsages, cpuReadings: 2) + try self.assertMetricsPayload(expectedCPUUsages: cpuUsages, usageReadings: 2, expectedMemoryFootprint: memoryFootprint) exp.fulfill() } catch { XCTFail("Encountered error: \(error)") @@ -246,8 +246,8 @@ private extension SentryProfilerSwiftTests { case noEnvelopeCaptured case noProfileEnvelopeItem case malformedMetricValueEntry - case noCPUUsageEvents - case noCPUUsageReported + case noMetricsReported + case noMetricValuesFound } func getProfileData() throws -> Data { @@ -295,7 +295,7 @@ private extension SentryProfilerSwiftTests { self.assertValidProfileData(data: profileData, transactionEnvironment: transactionEnvironment, numberOfTransactions: numberOfTransactions, shouldTimeout: shouldTimeOut) } - func assertMetricsPayload(expectedCPUUsages: [Double], cpuReadings: Int) throws { + func assertMetricsPayload(expectedCPUUsages: [Double], usageReadings: Int, expectedMemoryFootprint: SentryRAMBytes) throws { let profileData = try self.getProfileData() guard let profile = try JSONSerialization.jsonObject(with: profileData) as? [String: Any] else { throw TestError.unexpectedProfileDeserializationType @@ -306,19 +306,24 @@ private extension SentryProfilerSwiftTests { for (i, expectedUsage) in expectedCPUUsages.enumerated() { let key = NSString(format: kSentryMetricProfilerSerializationKeyCPUUsageFormat as NSString, i) as String - guard let cpuUsage = measurements[key] as? [String: Any] else { - throw TestError.noCPUUsageEvents - } - guard let values = cpuUsage["values"] as? [[String: Any]] else { - throw TestError.malformedMetricValueEntry - } - XCTAssertEqual(values.count, cpuReadings) - guard let firstReport = values[0]["value"] as? Double else { - throw TestError.noCPUUsageReported - } + try assertMetricValue(measurements: measurements, key: key, numberOfReadings: usageReadings, expectedValue: expectedUsage) + } - XCTAssertEqual(firstReport, expectedUsage) + try assertMetricValue(measurements: measurements, key: kSentryMetricProfilerSerializationKeyMemoryFootprint, numberOfReadings: usageReadings, expectedValue: expectedMemoryFootprint) + } + + func assertMetricValue(measurements: [String: Any], key: String, numberOfReadings: Int, expectedValue: T) throws { + guard let metricContainer = measurements[key] as? [String: Any] else { + throw TestError.noMetricsReported + } + XCTAssertEqual(metricContainer.count, numberOfReadings) + guard let memoryFootprint = metricContainer["values"] as? [[String: Any]] else { + throw TestError.malformedMetricValueEntry + } + guard let memoryFootprintValue = memoryFootprint[0]["value"] as? T else { + throw TestError.noMetricValuesFound } + XCTAssertEqual(memoryFootprintValue, expectedValue) } func assertValidProfileData(data: Data, transactionEnvironment: String = kSentryDefaultEnvironment, numberOfTransactions: Int = 1, shouldTimeout: Bool = false) { From c940b1ac366a9eb8dc043dc7e0fe0833e8dce55d Mon Sep 17 00:00:00 2001 From: Andrew McKnight Date: Thu, 12 Jan 2023 14:32:16 -0500 Subject: [PATCH 59/70] add slow/frozen frames to test case --- Sources/Sentry/SentryProfiler.mm | 12 ++++-- Sources/Sentry/include/SentryProfiler.h | 8 +++- .../Profiling/SentryProfilerSwiftTests.swift | 39 ++++++++++++++----- 3 files changed, 45 insertions(+), 14 deletions(-) diff --git a/Sources/Sentry/SentryProfiler.mm b/Sources/Sentry/SentryProfiler.mm index 2984117459b..fd64f0d5a1a 100644 --- a/Sources/Sentry/SentryProfiler.mm +++ b/Sources/Sentry/SentryProfiler.mm @@ -45,6 +45,10 @@ const int kSentryProfilerFrequencyHz = 101; NSString *const kTestStringConst = @"test"; +NSString *const kSentryProfilerSerializationKeySlowFrameRenders = @"slow_frame_renders"; +NSString *const kSentryProfilerSerializationKeyFrozenFrameRenders = @"frozen_frame_renders"; +NSString *const kSentryProfilerSerializationKeyFrameRates = @"screen_frame_rates"; + using namespace sentry::profiling; NSString * @@ -644,19 +648,21 @@ - (void)captureEnvelope const auto slowTimestamps = processFrameRenders(_frameInfo.slowFrameTimestamps, _startTimestamp, profileDuration); if (slowTimestamps.count > 0) { - metrics[@"slow_frame_renders"] = @{ @"unit" : @"nanosecond", @"values" : slowTimestamps }; + metrics[kSentryProfilerSerializationKeySlowFrameRenders] = + @{ @"unit" : @"nanosecond", @"values" : slowTimestamps }; } const auto frozenTimestamps = processFrameRenders(_frameInfo.frozenFrameTimestamps, _startTimestamp, profileDuration); if (frozenTimestamps.count > 0) { - metrics[@"frozen_frame_renders"] = + metrics[kSentryProfilerSerializationKeyFrozenFrameRenders] = @{ @"unit" : @"nanosecond", @"values" : frozenTimestamps }; } const auto frameRates = processFrameRates(_frameInfo.frameRateTimestamps, _startTimestamp); if (frameRates.count > 0) { - metrics[@"screen_frame_rates"] = @{ @"unit" : @"hz", @"values" : frameRates }; + metrics[kSentryProfilerSerializationKeyFrameRates] = + @{ @"unit" : @"hz", @"values" : frameRates }; } # endif // SENTRY_HAS_UIKIT diff --git a/Sources/Sentry/include/SentryProfiler.h b/Sources/Sentry/include/SentryProfiler.h index 08006b7f1ce..97cc71bc3c6 100644 --- a/Sources/Sentry/include/SentryProfiler.h +++ b/Sources/Sentry/include/SentryProfiler.h @@ -25,8 +25,12 @@ typedef NS_ENUM(NSUInteger, SentryProfilerTruncationReason) { NS_ASSUME_NONNULL_BEGIN -FOUNDATION_EXPORT const int kSentryProfilerFrequencyHz; -FOUNDATION_EXPORT NSString *const kTestStringConst; +SENTRY_EXTERN const int kSentryProfilerFrequencyHz; +SENTRY_EXTERN NSString *const kTestStringConst; + +SENTRY_EXTERN NSString *const kSentryProfilerSerializationKeySlowFrameRenders; +SENTRY_EXTERN NSString *const kSentryProfilerSerializationKeyFrozenFrameRenders; +SENTRY_EXTERN NSString *const kSentryProfilerSerializationKeyFrameRates; SENTRY_EXTERN_C_BEGIN diff --git a/Tests/SentryTests/Profiling/SentryProfilerSwiftTests.swift b/Tests/SentryTests/Profiling/SentryProfilerSwiftTests.swift index 71fb43b0258..14ece9103ce 100644 --- a/Tests/SentryTests/Profiling/SentryProfilerSwiftTests.swift +++ b/Tests/SentryTests/Profiling/SentryProfilerSwiftTests.swift @@ -23,10 +23,14 @@ class SentryProfilerSwiftTests: XCTestCase { let message = "some message" let transactionName = "Some Transaction" let transactionOperation = "Some Operation" + lazy var systemWrapper = TestSentrySystemWrapper() lazy var processInfoWrapper = TestSentryNSProcessInfoWrapper() lazy var timerWrapper = TestSentryNSTimerWrapper() + lazy var displayLinkWrapper = TestDisplayLinkWrapper() + lazy var frameTracker = SentryFramesTracker(displayLinkWrapper: displayLinkWrapper) + func newTransaction() -> Span { hub.startTransaction(name: transactionName, operation: transactionOperation) } @@ -76,6 +80,16 @@ class SentryProfilerSwiftTests: XCTestCase { self.fixture.timerWrapper.fire() } + // gather mock GPU frame render timestamps + fixture.frameTracker.start() + fixture.displayLinkWrapper.call() + fixture.displayLinkWrapper.slowFrame() + fixture.displayLinkWrapper.normalFrame() + fixture.displayLinkWrapper.almostFrozenFrame() + fixture.displayLinkWrapper.normalFrame() + fixture.displayLinkWrapper.frozenFrame() + fixture.frameTracker.stop() + // mock errors gathering cpu usage and memory footprint to ensure they don't add more information to the payload fixture.systemWrapper.overrides.cpuUsageError = NSError(domain: "test-error", code: 0) fixture.systemWrapper.overrides.memoryFootprintError = NSError(domain: "test-error", code: 1) @@ -87,7 +101,7 @@ class SentryProfilerSwiftTests: XCTestCase { span.finish() do { - try self.assertMetricsPayload(expectedCPUUsages: cpuUsages, usageReadings: 2, expectedMemoryFootprint: memoryFootprint) + try self.assertMetricsPayload(expectedCPUUsages: cpuUsages, usageReadings: 2, expectedMemoryFootprint: memoryFootprint, expectedSlowFrameCount: 2, expectedFrozenFrameCount: 1, expectedFrameRateCount: 1) exp.fulfill() } catch { XCTFail("Encountered error: \(error)") @@ -295,7 +309,7 @@ private extension SentryProfilerSwiftTests { self.assertValidProfileData(data: profileData, transactionEnvironment: transactionEnvironment, numberOfTransactions: numberOfTransactions, shouldTimeout: shouldTimeOut) } - func assertMetricsPayload(expectedCPUUsages: [Double], usageReadings: Int, expectedMemoryFootprint: SentryRAMBytes) throws { + func assertMetricsPayload(expectedCPUUsages: [Double], usageReadings: Int, expectedMemoryFootprint: SentryRAMBytes, expectedSlowFrameCount: Int, expectedFrozenFrameCount: Int, expectedFrameRateCount: Int) throws { let profileData = try self.getProfileData() guard let profile = try JSONSerialization.jsonObject(with: profileData) as? [String: Any] else { throw TestError.unexpectedProfileDeserializationType @@ -310,20 +324,27 @@ private extension SentryProfilerSwiftTests { } try assertMetricValue(measurements: measurements, key: kSentryMetricProfilerSerializationKeyMemoryFootprint, numberOfReadings: usageReadings, expectedValue: expectedMemoryFootprint) + + let dummyValue: UInt64? = nil + try assertMetricValue(measurements: measurements, key: kSentryProfilerSerializationKeySlowFrameRenders, numberOfReadings: expectedSlowFrameCount, expectedValue: dummyValue) + try assertMetricValue(measurements: measurements, key: kSentryProfilerSerializationKeyFrozenFrameRenders, numberOfReadings: expectedFrozenFrameCount, expectedValue: dummyValue) + try assertMetricValue(measurements: measurements, key: kSentryProfilerSerializationKeyFrameRates, numberOfReadings: expectedFrameRateCount, expectedValue: dummyValue) } - func assertMetricValue(measurements: [String: Any], key: String, numberOfReadings: Int, expectedValue: T) throws { + func assertMetricValue(measurements: [String: Any], key: String, numberOfReadings: Int, expectedValue: T?) throws { guard let metricContainer = measurements[key] as? [String: Any] else { throw TestError.noMetricsReported } XCTAssertEqual(metricContainer.count, numberOfReadings) - guard let memoryFootprint = metricContainer["values"] as? [[String: Any]] else { - throw TestError.malformedMetricValueEntry - } - guard let memoryFootprintValue = memoryFootprint[0]["value"] as? T else { - throw TestError.noMetricValuesFound + if let expectedValue { + guard let memoryFootprint = metricContainer["values"] as? [[String: Any]] else { + throw TestError.malformedMetricValueEntry + } + guard let memoryFootprintValue = memoryFootprint[0]["value"] as? T else { + throw TestError.noMetricValuesFound + } + XCTAssertEqual(memoryFootprintValue, expectedValue) } - XCTAssertEqual(memoryFootprintValue, expectedValue) } func assertValidProfileData(data: Data, transactionEnvironment: String = kSentryDefaultEnvironment, numberOfTransactions: Int = 1, shouldTimeout: Bool = false) { From 1cfed1c669a5ce90aa4736c526a5b3b8016c8f99 Mon Sep 17 00:00:00 2001 From: Andrew McKnight Date: Thu, 12 Jan 2023 14:32:48 -0500 Subject: [PATCH 60/70] fix typo; simplify lazy property init --- .../FramesTracking/SentryFramesTrackerTests.swift | 8 +++----- .../SentryFramesTrackingIntegrationTests.swift | 2 +- .../FramesTracking/TestDisplayLinkWrapper.swift | 2 +- Tests/SentryTests/Performance/SentryTracerTests.swift | 4 ++-- Tests/SentryTests/PrivateSentrySDKOnlyTests.swift | 2 +- 5 files changed, 8 insertions(+), 10 deletions(-) diff --git a/Tests/SentryTests/Integrations/Performance/FramesTracking/SentryFramesTrackerTests.swift b/Tests/SentryTests/Integrations/Performance/FramesTracking/SentryFramesTrackerTests.swift index 98a44962196..8a0a509eaf4 100644 --- a/Tests/SentryTests/Integrations/Performance/FramesTracking/SentryFramesTrackerTests.swift +++ b/Tests/SentryTests/Integrations/Performance/FramesTracking/SentryFramesTrackerTests.swift @@ -5,17 +5,15 @@ class SentryFramesTrackerTests: XCTestCase { private class Fixture { - var displayLinkWrapper: TestDiplayLinkWrapper + var displayLinkWrapper: TestDisplayLinkWrapper var queue: DispatchQueue init() { - displayLinkWrapper = TestDiplayLinkWrapper() + displayLinkWrapper = TestDisplayLinkWrapper() queue = DispatchQueue(label: "SentryFramesTrackerTests", qos: .background, attributes: [.concurrent]) } - lazy var sut: SentryFramesTracker = { - return SentryFramesTracker(displayLinkWrapper: displayLinkWrapper) - }() + lazy var sut: SentryFramesTracker = SentryFramesTracker(displayLinkWrapper: displayLinkWrapper) } private var fixture: Fixture! diff --git a/Tests/SentryTests/Integrations/Performance/FramesTracking/SentryFramesTrackingIntegrationTests.swift b/Tests/SentryTests/Integrations/Performance/FramesTracking/SentryFramesTrackingIntegrationTests.swift index 59b6eead8e3..5d560ccd54a 100644 --- a/Tests/SentryTests/Integrations/Performance/FramesTracking/SentryFramesTrackingIntegrationTests.swift +++ b/Tests/SentryTests/Integrations/Performance/FramesTracking/SentryFramesTrackingIntegrationTests.swift @@ -5,7 +5,7 @@ class SentryFramesTrackingIntegrationTests: XCTestCase { private class Fixture { let options = Options() - let displayLink = TestDiplayLinkWrapper() + let displayLink = TestDisplayLinkWrapper() init() { options.dsn = TestConstants.dsnAsString(username: "SentryFramesTrackingIntegrationTests") diff --git a/Tests/SentryTests/Integrations/Performance/FramesTracking/TestDisplayLinkWrapper.swift b/Tests/SentryTests/Integrations/Performance/FramesTracking/TestDisplayLinkWrapper.swift index a2808407509..987d0d1d816 100644 --- a/Tests/SentryTests/Integrations/Performance/FramesTracking/TestDisplayLinkWrapper.swift +++ b/Tests/SentryTests/Integrations/Performance/FramesTracking/TestDisplayLinkWrapper.swift @@ -1,7 +1,7 @@ import Foundation #if os(iOS) || os(tvOS) || targetEnvironment(macCatalyst) -class TestDiplayLinkWrapper: SentryDisplayLinkWrapper { +class TestDisplayLinkWrapper: SentryDisplayLinkWrapper { var target: AnyObject! var selector: Selector! diff --git a/Tests/SentryTests/Performance/SentryTracerTests.swift b/Tests/SentryTests/Performance/SentryTracerTests.swift index f91bb1fc07c..f89a1e1673e 100644 --- a/Tests/SentryTests/Performance/SentryTracerTests.swift +++ b/Tests/SentryTests/Performance/SentryTracerTests.swift @@ -38,7 +38,7 @@ class SentryTracerTests: XCTestCase { let idleTimeout: TimeInterval = 1.0 #if os(iOS) || os(tvOS) || targetEnvironment(macCatalyst) - var displayLinkWrapper: TestDiplayLinkWrapper + var displayLinkWrapper: TestDisplayLinkWrapper #endif init() { @@ -58,7 +58,7 @@ class SentryTracerTests: XCTestCase { CurrentDate.setCurrentDateProvider(currentDateProvider) #if os(iOS) || os(tvOS) || targetEnvironment(macCatalyst) - displayLinkWrapper = TestDiplayLinkWrapper() + displayLinkWrapper = TestDisplayLinkWrapper() SentryFramesTracker.sharedInstance().setDisplayLinkWrapper(displayLinkWrapper) SentryFramesTracker.sharedInstance().start() diff --git a/Tests/SentryTests/PrivateSentrySDKOnlyTests.swift b/Tests/SentryTests/PrivateSentrySDKOnlyTests.swift index e4e72e7be7a..bf1afd7dd7a 100644 --- a/Tests/SentryTests/PrivateSentrySDKOnlyTests.swift +++ b/Tests/SentryTests/PrivateSentrySDKOnlyTests.swift @@ -127,7 +127,7 @@ class PrivateSentrySDKOnlyTests: XCTestCase { func testGetFrames() { let tracker = SentryFramesTracker.sharedInstance() - let displayLink = TestDiplayLinkWrapper() + let displayLink = TestDisplayLinkWrapper() tracker.setDisplayLinkWrapper(displayLink) tracker.start() From 5443b84bc5229d0fe08c20e6d814645e3d9eed1e Mon Sep 17 00:00:00 2001 From: Andrew McKnight Date: Thu, 12 Jan 2023 15:24:37 -0500 Subject: [PATCH 61/70] fix older swift builds --- Tests/SentryTests/Profiling/SentryProfilerSwiftTests.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Tests/SentryTests/Profiling/SentryProfilerSwiftTests.swift b/Tests/SentryTests/Profiling/SentryProfilerSwiftTests.swift index 14ece9103ce..157bd3a1bca 100644 --- a/Tests/SentryTests/Profiling/SentryProfilerSwiftTests.swift +++ b/Tests/SentryTests/Profiling/SentryProfilerSwiftTests.swift @@ -336,7 +336,7 @@ private extension SentryProfilerSwiftTests { throw TestError.noMetricsReported } XCTAssertEqual(metricContainer.count, numberOfReadings) - if let expectedValue { + if let expectedValue = expectedValue { guard let memoryFootprint = metricContainer["values"] as? [[String: Any]] else { throw TestError.malformedMetricValueEntry } From acb3cfdc919bf32649eb73a6c0371631901cb4c2 Mon Sep 17 00:00:00 2001 From: Andrew McKnight Date: Thu, 12 Jan 2023 19:51:13 -0500 Subject: [PATCH 62/70] test amount of GPU frame render timestamps recorded for profiling metrics, using mock in test --- Sources/Sentry/SentryProfiler.mm | 47 ++++++++++++++----- .../Helper/SentryProfiler+SwiftTest.h | 2 + .../Profiling/SentryProfilerSwiftTests.swift | 21 +++++---- 3 files changed, 47 insertions(+), 23 deletions(-) diff --git a/Sources/Sentry/SentryProfiler.mm b/Sources/Sentry/SentryProfiler.mm index fd64f0d5a1a..129b8b68175 100644 --- a/Sources/Sentry/SentryProfiler.mm +++ b/Sources/Sentry/SentryProfiler.mm @@ -158,6 +158,7 @@ SentryNSProcessInfoWrapper *_gCurrentProcessInfoWrapper; SentrySystemWrapper *_gCurrentSystemWrapper; SentryNSTimerWrapper *_gCurrentTimerWrapper; +SentryFramesTracker *_gCurrentFramesTracker; NSString * profilerTruncationReasonName(SentryProfilerTruncationReason reason) @@ -174,28 +175,42 @@ # if SENTRY_HAS_UIKIT NSArray * -processFrameRenders(SentryFrameInfoTimeSeries *frameInfo, uint64_t start, uint64_t duration) +processFrameRenders( + SentryFrameInfoTimeSeries *frameInfo, uint64_t profileStart, uint64_t profileDuration) { auto relativeFrameInfo = [NSMutableArray array]; [frameInfo enumerateObjectsUsingBlock:^( NSDictionary *_Nonnull obj, NSUInteger idx, BOOL *_Nonnull stop) { - const auto begin = timeIntervalToNanoseconds(obj[@"start_timestamp"].doubleValue); - if (begin < start) { + const auto frameRenderStart + = timeIntervalToNanoseconds(obj[@"start_timestamp"].doubleValue); + +# if defined(TEST) || defined(TESTCI) + // we don't currently validate the timestamps in tests, and the mock doesn't provide + // realistic ones, so they'd fail the checks below. just write them directly into the data + // structure so we can count *how many* were recorded + [relativeFrameInfo addObject:@{ + @"elapsed_since_start_ns" : @(frameRenderStart), + @"value" : @(frameRenderStart), + }]; + return; +# else // if not testing, ie, development or production + if (frameRenderStart < profileStart) { return; } - const auto end = timeIntervalToNanoseconds(obj[@"end_timestamp"].doubleValue); - const auto relativeEnd = getDurationNs(start, end); - if (relativeEnd > duration) { + const auto frameRenderEnd = timeIntervalToNanoseconds(obj[@"end_timestamp"].doubleValue); + const auto frameRenderEndRelativeToProfileStart = getDurationNs(profileStart, frameRenderEnd); + if (frameRenderEndRelativeToProfileStart > profileDuration) { SENTRY_LOG_DEBUG(@"The last slow/frozen frame extended past the end of the profile, " @"will not report it."); return; } - const auto relativeStart = getDurationNs(start, begin); - const auto frameDuration = relativeEnd - relativeStart; + const auto frameRenderStartRelativeToProfileStartNs = getDurationNs(profileStart, frameRenderStart); + const auto frameRenderDurationNs = frameRenderEndRelativeToProfileStart - frameRenderStartRelativeToProfileStartNs; [relativeFrameInfo addObject:@{ - @"elapsed_since_start_ns" : @(relativeStart), - @"value" : @(frameDuration), + @"elapsed_since_start_ns" : @(frameRenderStartRelativeToProfileStartNs), + @"value" : @(frameRenderDurationNs), }]; +# endif // defined(TEST) || defined(TESTCI) }]; return relativeFrameInfo; } @@ -288,7 +303,7 @@ + (void)startForSpanID:(SentrySpanId *)spanID return; } # if SENTRY_HAS_UIKIT - [SentryFramesTracker.sharedInstance resetProfilingTimestamps]; + [_gCurrentFramesTracker resetProfilingTimestamps]; # endif // SENTRY_HAS_UIKIT [_gCurrentProfiler start]; _gCurrentProfiler->_timeoutTimer = @@ -387,6 +402,12 @@ + (void)useTimerWrapper:(SentryNSTimerWrapper *)timerWrapper _gCurrentTimerWrapper = timerWrapper; } ++ (void)useFramesTracker:(SentryFramesTracker *)framesTracker +{ + std::lock_guard l(_gProfilerLock); + _gCurrentFramesTracker = framesTracker; +} + # pragma mark - Private + (void)captureEnvelopeIfFinished:(SentryProfiler *)profiler spanID:(SentrySpanId *)spanID @@ -435,8 +456,8 @@ + (void)stopProfilerForReason:(SentryProfilerTruncationReason)reason [_gCurrentProfiler stop]; _gCurrentProfiler->_truncationReason = reason; # if SENTRY_HAS_UIKIT - _gCurrentProfiler->_frameInfo = SentryFramesTracker.sharedInstance.currentFrames; - [SentryFramesTracker.sharedInstance resetProfilingTimestamps]; + _gCurrentProfiler->_frameInfo = _gCurrentFramesTracker.currentFrames; + [_gCurrentFramesTracker resetProfilingTimestamps]; # endif // SENTRY_HAS_UIKIT _gCurrentProfiler = nil; } diff --git a/Tests/SentryTests/Helper/SentryProfiler+SwiftTest.h b/Tests/SentryTests/Helper/SentryProfiler+SwiftTest.h index fb020ae01d6..a3ad6fa9b49 100644 --- a/Tests/SentryTests/Helper/SentryProfiler+SwiftTest.h +++ b/Tests/SentryTests/Helper/SentryProfiler+SwiftTest.h @@ -23,6 +23,8 @@ SentryProfiler () + (void)useTimerWrapper:(SentryNSTimerWrapper *)timerWrapper NS_SWIFT_NAME(useTimerWrapper(_:)); ++ (void)useFramesTracker:(SentryFramesTracker *)framesTracker NS_SWIFT_NAME(useFramesTracker(_:)); + @end #endif // SENTRY_TARGET_PROFILING_SUPPORTED diff --git a/Tests/SentryTests/Profiling/SentryProfilerSwiftTests.swift b/Tests/SentryTests/Profiling/SentryProfilerSwiftTests.swift index 157bd3a1bca..d65d19ab335 100644 --- a/Tests/SentryTests/Profiling/SentryProfilerSwiftTests.swift +++ b/Tests/SentryTests/Profiling/SentryProfilerSwiftTests.swift @@ -29,7 +29,7 @@ class SentryProfilerSwiftTests: XCTestCase { lazy var timerWrapper = TestSentryNSTimerWrapper() lazy var displayLinkWrapper = TestDisplayLinkWrapper() - lazy var frameTracker = SentryFramesTracker(displayLinkWrapper: displayLinkWrapper) + lazy var framesTracker = SentryFramesTracker(displayLinkWrapper: displayLinkWrapper) func newTransaction() -> Span { hub.startTransaction(name: transactionName, operation: transactionOperation) @@ -62,6 +62,7 @@ class SentryProfilerSwiftTests: XCTestCase { SentryProfiler.useSystemWrapper(fixture.systemWrapper) SentryProfiler.useProcessInfoWrapper(fixture.processInfoWrapper) SentryProfiler.useTimerWrapper(fixture.timerWrapper) + SentryProfiler.useFramesTracker(fixture.framesTracker) // mock cpu usage let cpuUsages = [12.4, 63.5, 1.4, 4.6] @@ -81,14 +82,14 @@ class SentryProfilerSwiftTests: XCTestCase { } // gather mock GPU frame render timestamps - fixture.frameTracker.start() - fixture.displayLinkWrapper.call() + fixture.framesTracker.start() + fixture.displayLinkWrapper.call() // call once directly to capture previous frame timestamp for comparison with later ones fixture.displayLinkWrapper.slowFrame() fixture.displayLinkWrapper.normalFrame() fixture.displayLinkWrapper.almostFrozenFrame() fixture.displayLinkWrapper.normalFrame() fixture.displayLinkWrapper.frozenFrame() - fixture.frameTracker.stop() + fixture.framesTracker.stop() // mock errors gathering cpu usage and memory footprint to ensure they don't add more information to the payload fixture.systemWrapper.overrides.cpuUsageError = NSError(domain: "test-error", code: 0) @@ -335,15 +336,15 @@ private extension SentryProfilerSwiftTests { guard let metricContainer = measurements[key] as? [String: Any] else { throw TestError.noMetricsReported } - XCTAssertEqual(metricContainer.count, numberOfReadings) + guard let values = metricContainer["values"] as? [[String: Any]] else { + throw TestError.malformedMetricValueEntry + } + XCTAssertEqual(values.count, numberOfReadings, "Wrong number of values under \(key)") if let expectedValue = expectedValue { - guard let memoryFootprint = metricContainer["values"] as? [[String: Any]] else { - throw TestError.malformedMetricValueEntry - } - guard let memoryFootprintValue = memoryFootprint[0]["value"] as? T else { + guard let memoryFootprintValue = values[0]["value"] as? T else { throw TestError.noMetricValuesFound } - XCTAssertEqual(memoryFootprintValue, expectedValue) + XCTAssertEqual(memoryFootprintValue, expectedValue, "Wrong value for \(key)") } } From 6b4a1699c25d3ab4fc99ca8c138838b662bee602 Mon Sep 17 00:00:00 2001 From: Andrew McKnight Date: Thu, 12 Jan 2023 20:04:55 -0500 Subject: [PATCH 63/70] fix macos build --- Sources/Sentry/SentryProfiler.mm | 4 ++++ Tests/SentryTests/Helper/SentryProfiler+SwiftTest.h | 2 ++ 2 files changed, 6 insertions(+) diff --git a/Sources/Sentry/SentryProfiler.mm b/Sources/Sentry/SentryProfiler.mm index 129b8b68175..df2ce0b6692 100644 --- a/Sources/Sentry/SentryProfiler.mm +++ b/Sources/Sentry/SentryProfiler.mm @@ -158,7 +158,9 @@ SentryNSProcessInfoWrapper *_gCurrentProcessInfoWrapper; SentrySystemWrapper *_gCurrentSystemWrapper; SentryNSTimerWrapper *_gCurrentTimerWrapper; +# if SENTRY_HAS_UIKIT SentryFramesTracker *_gCurrentFramesTracker; +# endif // SENTRY_HAS_UIKIT NSString * profilerTruncationReasonName(SentryProfilerTruncationReason reason) @@ -402,11 +404,13 @@ + (void)useTimerWrapper:(SentryNSTimerWrapper *)timerWrapper _gCurrentTimerWrapper = timerWrapper; } +# if SENTRY_HAS_UIKIT + (void)useFramesTracker:(SentryFramesTracker *)framesTracker { std::lock_guard l(_gProfilerLock); _gCurrentFramesTracker = framesTracker; } +# endif // SENTRY_HAS_UIKIT # pragma mark - Private diff --git a/Tests/SentryTests/Helper/SentryProfiler+SwiftTest.h b/Tests/SentryTests/Helper/SentryProfiler+SwiftTest.h index a3ad6fa9b49..35d8aae8b25 100644 --- a/Tests/SentryTests/Helper/SentryProfiler+SwiftTest.h +++ b/Tests/SentryTests/Helper/SentryProfiler+SwiftTest.h @@ -23,7 +23,9 @@ SentryProfiler () + (void)useTimerWrapper:(SentryNSTimerWrapper *)timerWrapper NS_SWIFT_NAME(useTimerWrapper(_:)); +# if SENTRY_HAS_UIKIT + (void)useFramesTracker:(SentryFramesTracker *)framesTracker NS_SWIFT_NAME(useFramesTracker(_:)); +# endif // SENTRY_HAS_UIKIT @end From 52a1b3883f2815193f69312c02d2784659c2f633 Mon Sep 17 00:00:00 2001 From: Andrew McKnight Date: Thu, 12 Jan 2023 20:11:48 -0500 Subject: [PATCH 64/70] fixup! fix macos build --- .../Profiling/SentryProfilerSwiftTests.swift | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/Tests/SentryTests/Profiling/SentryProfilerSwiftTests.swift b/Tests/SentryTests/Profiling/SentryProfilerSwiftTests.swift index d65d19ab335..1fd7fb29465 100644 --- a/Tests/SentryTests/Profiling/SentryProfilerSwiftTests.swift +++ b/Tests/SentryTests/Profiling/SentryProfilerSwiftTests.swift @@ -28,8 +28,10 @@ class SentryProfilerSwiftTests: XCTestCase { lazy var processInfoWrapper = TestSentryNSProcessInfoWrapper() lazy var timerWrapper = TestSentryNSTimerWrapper() +#if !os(macOS) lazy var displayLinkWrapper = TestDisplayLinkWrapper() lazy var framesTracker = SentryFramesTracker(displayLinkWrapper: displayLinkWrapper) +#endif func newTransaction() -> Span { hub.startTransaction(name: transactionName, operation: transactionOperation) @@ -49,7 +51,7 @@ class SentryProfilerSwiftTests: XCTestCase { super.tearDown() clearTestState() SentryTracer.resetAppStartMeasurementRead() -#if os(iOS) || os(tvOS) || targetEnvironment(macCatalyst) +#if !os(macOS) SentryFramesTracker.sharedInstance().resetFrames() SentryFramesTracker.sharedInstance().stop() #endif @@ -62,7 +64,9 @@ class SentryProfilerSwiftTests: XCTestCase { SentryProfiler.useSystemWrapper(fixture.systemWrapper) SentryProfiler.useProcessInfoWrapper(fixture.processInfoWrapper) SentryProfiler.useTimerWrapper(fixture.timerWrapper) +#if !os(macOS) SentryProfiler.useFramesTracker(fixture.framesTracker) +#endif // mock cpu usage let cpuUsages = [12.4, 63.5, 1.4, 4.6] @@ -81,6 +85,7 @@ class SentryProfilerSwiftTests: XCTestCase { self.fixture.timerWrapper.fire() } +#if !os(macOS) // gather mock GPU frame render timestamps fixture.framesTracker.start() fixture.displayLinkWrapper.call() // call once directly to capture previous frame timestamp for comparison with later ones @@ -90,6 +95,7 @@ class SentryProfilerSwiftTests: XCTestCase { fixture.displayLinkWrapper.normalFrame() fixture.displayLinkWrapper.frozenFrame() fixture.framesTracker.stop() +#endif // mock errors gathering cpu usage and memory footprint to ensure they don't add more information to the payload fixture.systemWrapper.overrides.cpuUsageError = NSError(domain: "test-error", code: 0) @@ -326,10 +332,12 @@ private extension SentryProfilerSwiftTests { try assertMetricValue(measurements: measurements, key: kSentryMetricProfilerSerializationKeyMemoryFootprint, numberOfReadings: usageReadings, expectedValue: expectedMemoryFootprint) +#if !os(macOS) let dummyValue: UInt64? = nil try assertMetricValue(measurements: measurements, key: kSentryProfilerSerializationKeySlowFrameRenders, numberOfReadings: expectedSlowFrameCount, expectedValue: dummyValue) try assertMetricValue(measurements: measurements, key: kSentryProfilerSerializationKeyFrozenFrameRenders, numberOfReadings: expectedFrozenFrameCount, expectedValue: dummyValue) try assertMetricValue(measurements: measurements, key: kSentryProfilerSerializationKeyFrameRates, numberOfReadings: expectedFrameRateCount, expectedValue: dummyValue) +#endif } func assertMetricValue(measurements: [String: Any], key: String, numberOfReadings: Int, expectedValue: T?) throws { From 48f2b5a7e33ae5c163494c7c4e3efa1b26f75c99 Mon Sep 17 00:00:00 2001 From: Andrew McKnight Date: Fri, 13 Jan 2023 11:47:50 -0500 Subject: [PATCH 65/70] add system and process info wrapper tests --- Sentry.xcodeproj/project.pbxproj | 8 ++++++ .../SentryNSProcessInfoWrapperTests.swift | 12 +++++++++ .../Helper/SentrySystemWrapperTests.swift | 26 +++++++++++++++++++ 3 files changed, 46 insertions(+) create mode 100644 Tests/SentryTests/Helper/SentryNSProcessInfoWrapperTests.swift create mode 100644 Tests/SentryTests/Helper/SentrySystemWrapperTests.swift diff --git a/Sentry.xcodeproj/project.pbxproj b/Sentry.xcodeproj/project.pbxproj index 1a53b5868a1..fd7b9487d83 100644 --- a/Sentry.xcodeproj/project.pbxproj +++ b/Sentry.xcodeproj/project.pbxproj @@ -619,6 +619,8 @@ 8453421228BE855D00C22EEC /* SentrySampleDecision.m in Sources */ = {isa = PBXBuildFile; fileRef = 8453421128BE855D00C22EEC /* SentrySampleDecision.m */; }; 8453421628BE8A9500C22EEC /* SentrySpanStatus.m in Sources */ = {isa = PBXBuildFile; fileRef = 8453421528BE8A9500C22EEC /* SentrySpanStatus.m */; }; 8454CF8D293EAF9A006AC140 /* SentryMetricProfiler.mm in Sources */ = {isa = PBXBuildFile; fileRef = 8454CF8B293EAF9A006AC140 /* SentryMetricProfiler.mm */; }; + 849472812971C107002603DE /* SentrySystemWrapperTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 849472802971C107002603DE /* SentrySystemWrapperTests.swift */; }; + 849472832971C2CD002603DE /* SentryNSProcessInfoWrapperTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 849472822971C2CD002603DE /* SentryNSProcessInfoWrapperTests.swift */; }; 84A888FD28D9B11700C51DFD /* SentryProfiler+Test.h in Headers */ = {isa = PBXBuildFile; fileRef = 84A888FC28D9B11700C51DFD /* SentryProfiler+Test.h */; }; 84A8891C28DBD28900C51DFD /* SentryDevice.h in Headers */ = {isa = PBXBuildFile; fileRef = 84A8891A28DBD28900C51DFD /* SentryDevice.h */; }; 84A8891D28DBD28900C51DFD /* SentryDevice.mm in Sources */ = {isa = PBXBuildFile; fileRef = 84A8891B28DBD28900C51DFD /* SentryDevice.mm */; }; @@ -1469,6 +1471,8 @@ 8453421128BE855D00C22EEC /* SentrySampleDecision.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SentrySampleDecision.m; sourceTree = ""; }; 8453421528BE8A9500C22EEC /* SentrySpanStatus.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SentrySpanStatus.m; sourceTree = ""; }; 8454CF8B293EAF9A006AC140 /* SentryMetricProfiler.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; name = SentryMetricProfiler.mm; path = Sources/Sentry/SentryMetricProfiler.mm; sourceTree = SOURCE_ROOT; }; + 849472802971C107002603DE /* SentrySystemWrapperTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SentrySystemWrapperTests.swift; sourceTree = ""; }; + 849472822971C2CD002603DE /* SentryNSProcessInfoWrapperTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SentryNSProcessInfoWrapperTests.swift; sourceTree = ""; }; 84A888FC28D9B11700C51DFD /* SentryProfiler+Test.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = "SentryProfiler+Test.h"; path = "Sources/Sentry/include/SentryProfiler+Test.h"; sourceTree = SOURCE_ROOT; }; 84A8891A28DBD28900C51DFD /* SentryDevice.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = SentryDevice.h; path = include/SentryDevice.h; sourceTree = ""; }; 84A8891B28DBD28900C51DFD /* SentryDevice.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = SentryDevice.mm; sourceTree = ""; }; @@ -2629,6 +2633,8 @@ 844EDC712941442200C86F34 /* TestSentryNSProcessInfoWrapper.swift */, 844EDC7829415AB300C86F34 /* TestSentrySystemWrapper.swift */, 844EDCE72947DCD700C86F34 /* TestSentryNSTimerWrapper.swift */, + 849472802971C107002603DE /* SentrySystemWrapperTests.swift */, + 849472822971C2CD002603DE /* SentryNSProcessInfoWrapperTests.swift */, ); path = Helper; sourceTree = ""; @@ -3878,6 +3884,7 @@ 632331F62404FFA8008D91D6 /* SentryScopeTests.m in Sources */, D808FB88281AB33C009A2A33 /* SentryUIEventTrackerTests.swift in Sources */, 0A283E79291A67E000EF4126 /* SentryUIDeviceWrapperTests.swift in Sources */, + 849472812971C107002603DE /* SentrySystemWrapperTests.swift in Sources */, 63FE720D20DA66EC00CDBAE8 /* NSError+SimpleConstructor_Tests.m in Sources */, 69BEE6F72620729E006DF9DF /* UrlSessionDelegateSpy.swift in Sources */, 035E73C827D56757005EEB11 /* SentryBacktraceTests.mm in Sources */, @@ -3943,6 +3950,7 @@ 7BBD188D2448453600427C76 /* SentryHttpDateParserTests.swift in Sources */, 7B72D23A28D074BC0014798A /* TestExtensions.swift in Sources */, 7BBD18BB24530D2600427C76 /* SentryFileManagerTests.swift in Sources */, + 849472832971C2CD002603DE /* SentryNSProcessInfoWrapperTests.swift in Sources */, 63FE722020DA66EC00CDBAE8 /* SentryCrashObjC_Tests.m in Sources */, 7B58816727FC5D790098B121 /* SentryDiscardReasonMapperTests.swift in Sources */, 63FE720320DA66EC00CDBAE8 /* SentryCrashCPU_Tests.m in Sources */, diff --git a/Tests/SentryTests/Helper/SentryNSProcessInfoWrapperTests.swift b/Tests/SentryTests/Helper/SentryNSProcessInfoWrapperTests.swift new file mode 100644 index 00000000000..e7e1fbd6a9b --- /dev/null +++ b/Tests/SentryTests/Helper/SentryNSProcessInfoWrapperTests.swift @@ -0,0 +1,12 @@ +import XCTest + +class SentryNSProcessInfoWrapperTests: XCTestCase { + struct Fixture { + lazy var processInfoWrapper = SentryNSProcessInfoWrapper() + } + lazy var fixture = Fixture() + + func testProcessorCount() { + XCTAssert((0...UInt.max).contains(fixture.processInfoWrapper.processorCount)) + } +} diff --git a/Tests/SentryTests/Helper/SentrySystemWrapperTests.swift b/Tests/SentryTests/Helper/SentrySystemWrapperTests.swift new file mode 100644 index 00000000000..2607ecba33d --- /dev/null +++ b/Tests/SentryTests/Helper/SentrySystemWrapperTests.swift @@ -0,0 +1,26 @@ +import XCTest + +class SentrySystemWrapperTests: XCTestCase { + struct Fixture { + lazy var systemWrapper = SentrySystemWrapper() + } + lazy var fixture = Fixture() + + func testCPUUsageReportsData() throws { + XCTAssertNoThrow({ + let cpuUsages = try self.fixture.systemWrapper.cpuUsagePerCore() + XCTAssertGreaterThan(cpuUsages.count, 0) + let range = 0.0 ... 100.0 + cpuUsages.forEach { + XCTAssert(range.contains($0.doubleValue)) + } + }) + } + + func testMemoryFootprint() { + let error: NSErrorPointer = nil + let memoryFootprint = fixture.systemWrapper.memoryFootprintBytes(error) + XCTAssertNil(error?.pointee) + XCTAssert((0...UINT64_MAX).contains(memoryFootprint)) + } +} From 08c0979e17481f740be9b184c9c55579ca9c5dc3 Mon Sep 17 00:00:00 2001 From: Andrew McKnight Date: Fri, 13 Jan 2023 11:57:06 -0500 Subject: [PATCH 66/70] add nstimer wrapper tests --- Sentry.xcodeproj/project.pbxproj | 4 +++ .../Helper/SentryNSTimerWrapperTest.swift | 30 +++++++++++++++++++ 2 files changed, 34 insertions(+) create mode 100644 Tests/SentryTests/Helper/SentryNSTimerWrapperTest.swift diff --git a/Sentry.xcodeproj/project.pbxproj b/Sentry.xcodeproj/project.pbxproj index fd7b9487d83..6bd3baffc6d 100644 --- a/Sentry.xcodeproj/project.pbxproj +++ b/Sentry.xcodeproj/project.pbxproj @@ -621,6 +621,7 @@ 8454CF8D293EAF9A006AC140 /* SentryMetricProfiler.mm in Sources */ = {isa = PBXBuildFile; fileRef = 8454CF8B293EAF9A006AC140 /* SentryMetricProfiler.mm */; }; 849472812971C107002603DE /* SentrySystemWrapperTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 849472802971C107002603DE /* SentrySystemWrapperTests.swift */; }; 849472832971C2CD002603DE /* SentryNSProcessInfoWrapperTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 849472822971C2CD002603DE /* SentryNSProcessInfoWrapperTests.swift */; }; + 849472852971C41A002603DE /* SentryNSTimerWrapperTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 849472842971C41A002603DE /* SentryNSTimerWrapperTest.swift */; }; 84A888FD28D9B11700C51DFD /* SentryProfiler+Test.h in Headers */ = {isa = PBXBuildFile; fileRef = 84A888FC28D9B11700C51DFD /* SentryProfiler+Test.h */; }; 84A8891C28DBD28900C51DFD /* SentryDevice.h in Headers */ = {isa = PBXBuildFile; fileRef = 84A8891A28DBD28900C51DFD /* SentryDevice.h */; }; 84A8891D28DBD28900C51DFD /* SentryDevice.mm in Sources */ = {isa = PBXBuildFile; fileRef = 84A8891B28DBD28900C51DFD /* SentryDevice.mm */; }; @@ -1473,6 +1474,7 @@ 8454CF8B293EAF9A006AC140 /* SentryMetricProfiler.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; name = SentryMetricProfiler.mm; path = Sources/Sentry/SentryMetricProfiler.mm; sourceTree = SOURCE_ROOT; }; 849472802971C107002603DE /* SentrySystemWrapperTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SentrySystemWrapperTests.swift; sourceTree = ""; }; 849472822971C2CD002603DE /* SentryNSProcessInfoWrapperTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SentryNSProcessInfoWrapperTests.swift; sourceTree = ""; }; + 849472842971C41A002603DE /* SentryNSTimerWrapperTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SentryNSTimerWrapperTest.swift; sourceTree = ""; }; 84A888FC28D9B11700C51DFD /* SentryProfiler+Test.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = "SentryProfiler+Test.h"; path = "Sources/Sentry/include/SentryProfiler+Test.h"; sourceTree = SOURCE_ROOT; }; 84A8891A28DBD28900C51DFD /* SentryDevice.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = SentryDevice.h; path = include/SentryDevice.h; sourceTree = ""; }; 84A8891B28DBD28900C51DFD /* SentryDevice.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = SentryDevice.mm; sourceTree = ""; }; @@ -2635,6 +2637,7 @@ 844EDCE72947DCD700C86F34 /* TestSentryNSTimerWrapper.swift */, 849472802971C107002603DE /* SentrySystemWrapperTests.swift */, 849472822971C2CD002603DE /* SentryNSProcessInfoWrapperTests.swift */, + 849472842971C41A002603DE /* SentryNSTimerWrapperTest.swift */, ); path = Helper; sourceTree = ""; @@ -3884,6 +3887,7 @@ 632331F62404FFA8008D91D6 /* SentryScopeTests.m in Sources */, D808FB88281AB33C009A2A33 /* SentryUIEventTrackerTests.swift in Sources */, 0A283E79291A67E000EF4126 /* SentryUIDeviceWrapperTests.swift in Sources */, + 849472852971C41A002603DE /* SentryNSTimerWrapperTest.swift in Sources */, 849472812971C107002603DE /* SentrySystemWrapperTests.swift in Sources */, 63FE720D20DA66EC00CDBAE8 /* NSError+SimpleConstructor_Tests.m in Sources */, 69BEE6F72620729E006DF9DF /* UrlSessionDelegateSpy.swift in Sources */, diff --git a/Tests/SentryTests/Helper/SentryNSTimerWrapperTest.swift b/Tests/SentryTests/Helper/SentryNSTimerWrapperTest.swift new file mode 100644 index 00000000000..bece4f12f4f --- /dev/null +++ b/Tests/SentryTests/Helper/SentryNSTimerWrapperTest.swift @@ -0,0 +1,30 @@ +import XCTest + +class SentryNSTimerWrapperTests: XCTestCase { + struct Fixture { + lazy var timerWrapper = SentryNSTimerWrapper() + } + lazy var fixture = Fixture() + + func testNonrepeatingTimer() { + let exp = expectation(description: "timer fires exactly once") + fixture.timerWrapper.scheduledTimer(withTimeInterval: 0.1, repeats: false) { _ in + exp.fulfill() + } + waitForExpectations(timeout: 1) + } + + func testRepeatingTimer() { + var count = 0 + let exp = expectation(description: "timer fires multiple times") + exp.expectedFulfillmentCount = 2 + fixture.timerWrapper.scheduledTimer(withTimeInterval: 0.1, repeats: true) { + if count == exp.expectedFulfillmentCount { + $0.invalidate() + } + count += 1 + exp.fulfill() + } + waitForExpectations(timeout: 1) + } +} From cf11d7fd9af8c708944faae5e6badf4fec3b3bfa Mon Sep 17 00:00:00 2001 From: Andrew McKnight Date: Fri, 13 Jan 2023 16:52:32 -0500 Subject: [PATCH 67/70] avoid possible negative interval between transaction end and profile start --- Sources/Sentry/SentryProfiler.mm | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/Sources/Sentry/SentryProfiler.mm b/Sources/Sentry/SentryProfiler.mm index df2ce0b6692..3e8917d9592 100644 --- a/Sources/Sentry/SentryProfiler.mm +++ b/Sources/Sentry/SentryProfiler.mm @@ -713,14 +713,16 @@ - (void)captureEnvelope if ([transaction.timestamp compare:_endDate] == NSOrderedDescending) { relativeEnd = [NSString stringWithFormat:@"%llu", profileDuration]; } else { - const auto profileStartToTransactionEnd_ns = timeIntervalToNanoseconds( - [transaction.timestamp timeIntervalSinceDate:_startDate]); - if (profileStartToTransactionEnd_ns < 0) { + const auto profileStartToTransactionEndInterval = + [transaction.timestamp timeIntervalSinceDate:_startDate]; + if (profileStartToTransactionEndInterval < 0) { SENTRY_LOG_DEBUG(@"Transaction %@ ended before the profiler started, won't " @"associate it with this profile.", transaction.trace.traceId.sentryIdString); continue; } else { + const auto profileStartToTransactionEnd_ns + = timeIntervalToNanoseconds(profileStartToTransactionEndInterval); relativeEnd = [NSString stringWithFormat:@"%llu", (unsigned long long)profileStartToTransactionEnd_ns]; } From eea61536781d8cb203feccc7be020c6a1b5ba46c Mon Sep 17 00:00:00 2001 From: Andrew McKnight Date: Wed, 18 Jan 2023 09:47:55 -0500 Subject: [PATCH 68/70] fix changelog --- CHANGELOG.md | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 72a9481042e..711f4acc7e4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,10 +1,15 @@ # Changelog -## 8.0.0 +## Unreleased ### Features - Gather profiling timeseries metrics for CPU usage and memory footprint, and thermal and memory pressure events (#2493) + +## 8.0.0 + +### Features + This version adds a dependency on Swift. This version adds a dependency on Swift. We renamed the default branch from `master` to `main`. We are going to keep the `master` branch for backwards compatibility for package managers pointing to the `master` branch. From adef44cea22fe6471781e0c6ef3c351ba9810c80 Mon Sep 17 00:00:00 2001 From: Andrew McKnight Date: Wed, 18 Jan 2023 09:54:36 -0500 Subject: [PATCH 69/70] fixup! dont declare abstract fire method --- Tests/SentryTests/SentryNSTimerWrapper+Test.h | 2 -- 1 file changed, 2 deletions(-) diff --git a/Tests/SentryTests/SentryNSTimerWrapper+Test.h b/Tests/SentryTests/SentryNSTimerWrapper+Test.h index 245668340d7..ba40732d01d 100644 --- a/Tests/SentryTests/SentryNSTimerWrapper+Test.h +++ b/Tests/SentryTests/SentryNSTimerWrapper+Test.h @@ -3,6 +3,4 @@ @interface SentryNSTimerWrapper () -- (void)fire; - @end From bccfd3c72721c3c8f09bca8a96c35ff537f1c6c2 Mon Sep 17 00:00:00 2001 From: Andrew McKnight Date: Wed, 18 Jan 2023 10:01:14 -0500 Subject: [PATCH 70/70] fix broken nstimer test --- Tests/SentryTests/Helper/SentryNSTimerWrapperTest.swift | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Tests/SentryTests/Helper/SentryNSTimerWrapperTest.swift b/Tests/SentryTests/Helper/SentryNSTimerWrapperTest.swift index bece4f12f4f..3ba112b7277 100644 --- a/Tests/SentryTests/Helper/SentryNSTimerWrapperTest.swift +++ b/Tests/SentryTests/Helper/SentryNSTimerWrapperTest.swift @@ -19,8 +19,9 @@ class SentryNSTimerWrapperTests: XCTestCase { let exp = expectation(description: "timer fires multiple times") exp.expectedFulfillmentCount = 2 fixture.timerWrapper.scheduledTimer(withTimeInterval: 0.1, repeats: true) { - if count == exp.expectedFulfillmentCount { + guard count < exp.expectedFulfillmentCount else { $0.invalidate() + return } count += 1 exp.fulfill()