diff --git a/Sentry.xcodeproj/project.pbxproj b/Sentry.xcodeproj/project.pbxproj index dab18d0dd8a..b2aed0f8914 100644 --- a/Sentry.xcodeproj/project.pbxproj +++ b/Sentry.xcodeproj/project.pbxproj @@ -601,6 +601,9 @@ 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 */; }; + 84281C432A578E5600EE88F2 /* SentryProfilerState.mm in Sources */ = {isa = PBXBuildFile; fileRef = 84281C422A578E5600EE88F2 /* SentryProfilerState.mm */; }; + 84281C462A57905700EE88F2 /* SentrySample.h in Headers */ = {isa = PBXBuildFile; fileRef = 84281C442A57905700EE88F2 /* SentrySample.h */; }; + 84281C472A57905700EE88F2 /* SentrySample.m in Sources */ = {isa = PBXBuildFile; fileRef = 84281C452A57905700EE88F2 /* SentrySample.m */; }; 8431EE5B29ADB8EA00D8DC56 /* SentryTimeTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 8431EE5A29ADB8EA00D8DC56 /* SentryTimeTests.m */; }; 8431EFD129B27B1100D8DC56 /* Sentry.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 63AA759B1EB8AEF500D153DE /* Sentry.framework */; settings = {ATTRIBUTES = (Required, ); }; }; 8431EFD329B27B1100D8DC56 /* Resources in Resources */ = {isa = PBXBuildFile; fileRef = 630C01951EC341D600C52CEF /* Resources */; }; @@ -1521,6 +1524,9 @@ 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 = ""; }; 8419C0C328C1889D001C8259 /* SentryProfilerSwiftTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SentryProfilerSwiftTests.swift; sourceTree = ""; }; + 84281C422A578E5600EE88F2 /* SentryProfilerState.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = SentryProfilerState.mm; sourceTree = ""; }; + 84281C442A57905700EE88F2 /* SentrySample.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = SentrySample.h; path = ../include/SentrySample.h; sourceTree = ""; }; + 84281C452A57905700EE88F2 /* SentrySample.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SentrySample.m; sourceTree = ""; }; 8431EE5A29ADB8EA00D8DC56 /* SentryTimeTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SentryTimeTests.m; sourceTree = ""; }; 8431EFD929B27B1100D8DC56 /* SentryProfilerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = SentryProfilerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 8431EFDA29B27B1200D8DC56 /* SentryTests copy-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; name = "SentryTests copy-Info.plist"; path = "/Users/andrewmcknight/Code/organization/getsentry/repos/public/sentry-cocoa/SentryTests copy-Info.plist"; sourceTree = ""; }; @@ -1556,7 +1562,6 @@ 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 /* SentryNSTimerFactory.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = SentryNSTimerFactory.h; path = include/SentryNSTimerFactory.h; sourceTree = ""; }; 844EDCE42947DC3100C86F34 /* SentryNSTimerFactory.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SentryNSTimerFactory.m; sourceTree = ""; }; 844EDCE72947DCD700C86F34 /* TestSentryNSTimerFactory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TestSentryNSTimerFactory.swift; sourceTree = ""; }; @@ -3086,37 +3091,40 @@ 8405A517279906EF001B38A1 /* Profiling */ = { isa = PBXGroup; children = ( - 84354E0F29BF944900CDBB8B /* SentryProfileTimeseries.h */, - 84354E1029BF944900CDBB8B /* SentryProfileTimeseries.mm */, 03F84D1327DD414C008FE43F /* SentryAsyncSafeLogging.h */, - 03F84D1227DD414C008FE43F /* SentryBacktrace.hpp */, 03F84D3127DD4191008FE43F /* SentryBacktrace.cpp */, + 03F84D1227DD414C008FE43F /* SentryBacktrace.hpp */, 03F84D1827DD414C008FE43F /* SentryCompiler.h */, 03F84D1C27DD414C008FE43F /* SentryCPU.h */, - 03F84D1B27DD414C008FE43F /* SentryMachLogging.hpp */, 03F84D2C27DD4191008FE43F /* SentryMachLogging.cpp */, - 03F84D1127DD414C008FE43F /* SentryProfiler.h */, + 03F84D1B27DD414C008FE43F /* SentryMachLogging.hpp */, 844EDD6B2949387000C86F34 /* SentryMetricProfiler.h */, 8454CF8B293EAF9A006AC140 /* SentryMetricProfiler.mm */, - 0354A22A2A134D9C003C3A04 /* SentryProfilerState.h */, - 84A888FC28D9B11700C51DFD /* SentryProfiler+Test.h */, + 03F84D1127DD414C008FE43F /* SentryProfiler.h */, 03F84D2B27DD4191008FE43F /* SentryProfiler.mm */, + 84A888FC28D9B11700C51DFD /* SentryProfiler+Test.h */, + 0354A22A2A134D9C003C3A04 /* SentryProfilerState.h */, + 84281C422A578E5600EE88F2 /* SentryProfilerState.mm */, + 0356A56E288B4612008BF593 /* SentryProfilesSampler.h */, + 0356A56F288B4612008BF593 /* SentryProfilesSampler.m */, + 84354E0F29BF944900CDBB8B /* SentryProfileTimeseries.h */, + 84354E1029BF944900CDBB8B /* SentryProfileTimeseries.mm */, 03BCC38D27E2A377003232C7 /* SentryProfilingConditionals.h */, 03F84D2927DD416B008FE43F /* SentryProfilingLogging.hpp */, 03F84D2F27DD4191008FE43F /* SentryProfilingLogging.mm */, - 03F84D1527DD414C008FE43F /* SentrySamplingProfiler.hpp */, + 84281C442A57905700EE88F2 /* SentrySample.h */, + 84281C452A57905700EE88F2 /* SentrySample.m */, 03F84D3027DD4191008FE43F /* SentrySamplingProfiler.cpp */, - 03F84D1727DD414C008FE43F /* SentryThreadHandle.hpp */, - 03F84D2E27DD4191008FE43F /* SentryThreadHandle.cpp */, - 03F84D1A27DD414C008FE43F /* SentryThreadMetadataCache.hpp */, - 03F84D2D27DD4191008FE43F /* SentryThreadMetadataCache.cpp */, + 03F84D1527DD414C008FE43F /* SentrySamplingProfiler.hpp */, 03F84D1427DD414C008FE43F /* SentryStackBounds.hpp */, 03F84D1627DD414C008FE43F /* SentryStackFrame.hpp */, + 03F84D2E27DD4191008FE43F /* SentryThreadHandle.cpp */, + 03F84D1727DD414C008FE43F /* SentryThreadHandle.hpp */, + 03F84D2D27DD4191008FE43F /* SentryThreadMetadataCache.cpp */, + 03F84D1A27DD414C008FE43F /* SentryThreadMetadataCache.hpp */, 03F84D1927DD414C008FE43F /* SentryThreadState.hpp */, 03BCC38927E1BF49003232C7 /* SentryTime.h */, 03BCC38B27E1C01A003232C7 /* SentryTime.mm */, - 0356A56E288B4612008BF593 /* SentryProfilesSampler.h */, - 0356A56F288B4612008BF593 /* SentryProfilesSampler.m */, ); path = Profiling; sourceTree = ""; @@ -3564,6 +3572,7 @@ D8603DD8284F894C000E1227 /* SentryBaggage.h in Headers */, 03F84D2127DD414C008FE43F /* SentrySamplingProfiler.hpp in Headers */, 84AC61D229F7541E009EEF61 /* SentryDispatchSourceWrapper.h in Headers */, + 84281C462A57905700EE88F2 /* SentrySample.h in Headers */, 63FE712B20DA4C1100CDBAE8 /* SentryCrashStackCursor.h in Headers */, D8C67E9C28000E24007E326E /* SentryScreenshot.h in Headers */, 7BA61CBF247CEA8100C130A8 /* SentryFormatter.h in Headers */, @@ -4091,6 +4100,7 @@ 7BB65501253DC1B500887E87 /* SentryUserFeedback.m in Sources */, 7D5C441A237C2E1F00DAB0A3 /* SentrySDK.m in Sources */, 7D65260E237F649E00113EA2 /* SentryScope.m in Sources */, + 84281C472A57905700EE88F2 /* SentrySample.m in Sources */, 84AC61D329F7541E009EEF61 /* SentryDispatchSourceWrapper.m in Sources */, 63FE712D20DA4C1100CDBAE8 /* SentryCrashJSONCodecObjC.m in Sources */, 7BBD18932449BEDD00427C76 /* SentryDefaultRateLimits.m in Sources */, @@ -4131,6 +4141,7 @@ 7BC9A20428F4166D001E7C4C /* SentryMeasurementValue.m in Sources */, D859696B27BECD8F0036A46E /* SentryCoreDataTrackingIntegration.m in Sources */, 7BD86EC7264A641D005439DB /* SentrySysctl.m in Sources */, + 84281C432A578E5600EE88F2 /* SentryProfilerState.mm in Sources */, D859697327BECDD20036A46E /* SentryCoreDataSwizzling.m in Sources */, 639889BD1EDED18400EA7442 /* SentrySwizzle.m in Sources */, D8ABB0BC29264275005D1E24 /* Sentry.swift in Sources */, diff --git a/Sources/Sentry/Profiling/SentryProfilerState.mm b/Sources/Sentry/Profiling/SentryProfilerState.mm new file mode 100644 index 00000000000..10b47e40a64 --- /dev/null +++ b/Sources/Sentry/Profiling/SentryProfilerState.mm @@ -0,0 +1,184 @@ +#import "SentryProfilerState.h" +#if SENTRY_TARGET_PROFILING_SUPPORTED +# import "SentryBacktrace.hpp" +# import "SentryFormatter.h" +# import "SentryProfileTimeseries.h" +# import "SentrySample.h" +# import + +# if defined(DEBUG) +# include +# endif + +using namespace sentry::profiling; + +NSString * +parseBacktraceSymbolsFunctionName(const char *symbol) +{ + static NSRegularExpression *regex = nil; + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + regex = [NSRegularExpression + regularExpressionWithPattern:@"\\d+\\s+\\S+\\s+0[xX][0-9a-fA-F]+\\s+(.+)\\s+\\+\\s+\\d+" + options:0 + error:nil]; + }); + const auto symbolNSStr = [NSString stringWithUTF8String:symbol]; + const auto match = [regex firstMatchInString:symbolNSStr + options:0 + range:NSMakeRange(0, [symbolNSStr length])]; + if (match == nil) { + return symbolNSStr; + } + return [symbolNSStr substringWithRange:[match rangeAtIndex:1]]; +} + +@implementation SentryProfilerMutableState + +- (instancetype)init +{ + if (self = [super init]) { + _samples = [NSMutableArray array]; + _stacks = [NSMutableArray *> array]; + _frames = [NSMutableArray *> array]; + _threadMetadata = [NSMutableDictionary dictionary]; + _queueMetadata = [NSMutableDictionary dictionary]; + _frameIndexLookup = [NSMutableDictionary dictionary]; + _stackIndexLookup = [NSMutableDictionary dictionary]; + } + return self; +} + +@end + +@implementation SentryProfilerState { + SentryProfilerMutableState *_mutableState; + std::mutex _lock; +} + +- (instancetype)init +{ + if (self = [super init]) { + _mutableState = [[SentryProfilerMutableState alloc] init]; + } + return self; +} + +- (void)mutate:(void (^)(SentryProfilerMutableState *))block +{ + NSParameterAssert(block); + std::lock_guard l(_lock); + block(_mutableState); +} + +- (void)appendBacktrace:(const Backtrace &)backtrace +{ + [self mutate:^(SentryProfilerMutableState *state) { + const auto threadID = sentry_stringForUInt64(backtrace.threadMetadata.threadID); + + NSString *queueAddress = nil; + if (backtrace.queueMetadata.address != 0) { + queueAddress = sentry_formatHexAddressUInt64(backtrace.queueMetadata.address); + } + NSMutableDictionary *metadata = state.threadMetadata[threadID]; + if (metadata == nil) { + metadata = [NSMutableDictionary dictionary]; + state.threadMetadata[threadID] = metadata; + } + if (!backtrace.threadMetadata.name.empty() && metadata[@"name"] == nil) { + metadata[@"name"] = + [NSString stringWithUTF8String:backtrace.threadMetadata.name.c_str()]; + } + if (backtrace.threadMetadata.priority != -1 && metadata[@"priority"] == nil) { + metadata[@"priority"] = @(backtrace.threadMetadata.priority); + } + if (queueAddress != nil && state.queueMetadata[queueAddress] == nil + && backtrace.queueMetadata.label != nullptr) { + NSString *const labelNSStr = + [NSString stringWithUTF8String:backtrace.queueMetadata.label->c_str()]; + // -[NSString stringWithUTF8String:] can return `nil` for malformed string data + if (labelNSStr != nil) { + state.queueMetadata[queueAddress] = @ { @"label" : labelNSStr }; + } + } +# if defined(DEBUG) + const auto symbols + = backtrace_symbols(reinterpret_cast(backtrace.addresses.data()), + static_cast(backtrace.addresses.size())); +# endif + + const auto stack = [NSMutableArray array]; + for (std::vector::size_type backtraceAddressIdx = 0; + backtraceAddressIdx < backtrace.addresses.size(); backtraceAddressIdx++) { + const auto instructionAddress + = sentry_formatHexAddressUInt64(backtrace.addresses[backtraceAddressIdx]); + + const auto frameIndex = state.frameIndexLookup[instructionAddress]; + if (frameIndex == nil) { + const auto frame = [NSMutableDictionary dictionary]; + frame[@"instruction_addr"] = instructionAddress; +# if defined(DEBUG) + frame[@"function"] + = parseBacktraceSymbolsFunctionName(symbols[backtraceAddressIdx]); +# endif + const auto newFrameIndex = @(state.frames.count); + [stack addObject:newFrameIndex]; + state.frameIndexLookup[instructionAddress] = newFrameIndex; + [state.frames addObject:frame]; + } else { + [stack addObject:frameIndex]; + } + } + + const auto sample = [[SentrySample alloc] init]; + sample.absoluteTimestamp = backtrace.absoluteTimestamp; + sample.threadID = backtrace.threadMetadata.threadID; + if (queueAddress != nil) { + sample.queueAddress = queueAddress; + } + + const auto stackKey = [stack componentsJoinedByString:@"|"]; + const auto stackIndex = state.stackIndexLookup[stackKey]; + if (stackIndex) { + sample.stackIndex = stackIndex; + } else { + const auto nextStackIndex = @(state.stacks.count); + sample.stackIndex = nextStackIndex; + state.stackIndexLookup[stackKey] = nextStackIndex; + [state.stacks addObject:stack]; + } + + [state.samples addObject:sample]; + }]; +} + +- (NSDictionary *)copyProfilingData +{ + std::lock_guard l(_lock); + + NSMutableArray *const samples = [_mutableState.samples copy]; + NSMutableArray *> *const stacks = [_mutableState.stacks copy]; + NSMutableArray *> *const frames = [_mutableState.frames copy]; + NSMutableDictionary *const queueMetadata = + [_mutableState.queueMetadata copy]; + + // thread metadata contains a mutable substructure, so it's not enough to perform a copy of + // the top-level dictionary, we need to go deeper to copy the mutable subdictionaries + const auto threadMetadata = [NSMutableDictionary dictionary]; + [_mutableState.threadMetadata enumerateKeysAndObjectsUsingBlock:^(NSString *_Nonnull key, + NSDictionary *_Nonnull obj, BOOL *_Nonnull stop) { threadMetadata[key] = [obj copy]; }]; + + return @{ + @"profile" : @ { + @"samples" : samples, + @"stacks" : stacks, + @"frames" : frames, + @"thread_metadata" : threadMetadata, + @"queue_metadata" : queueMetadata + } + }; +} + +@end + +#endif // SENTRY_TARGET_PROFILING_SUPPORTED diff --git a/Sources/Sentry/Profiling/SentrySample.m b/Sources/Sentry/Profiling/SentrySample.m new file mode 100644 index 00000000000..58dd48b575d --- /dev/null +++ b/Sources/Sentry/Profiling/SentrySample.m @@ -0,0 +1,4 @@ +#import "SentrySample.h" + +@implementation SentrySample +@end diff --git a/Sources/Sentry/SentryProfileTimeseries.mm b/Sources/Sentry/SentryProfileTimeseries.mm index bd1d01c83bc..b79024a4a22 100644 --- a/Sources/Sentry/SentryProfileTimeseries.mm +++ b/Sources/Sentry/SentryProfileTimeseries.mm @@ -5,6 +5,7 @@ # import "SentryEvent+Private.h" # import "SentryInternalDefines.h" # import "SentryLog.h" +# import "SentrySample.h" # import "SentryTransaction.h" /** @@ -83,7 +84,4 @@ return [samples objectsAtIndexes:indices]; } -@implementation SentrySample -@end - #endif // SENTRY_TARGET_PROFILING_SUPPORTED diff --git a/Sources/Sentry/SentryProfiler.mm b/Sources/Sentry/SentryProfiler.mm index abd8cdecb6b..29cda4d439e 100644 --- a/Sources/Sentry/SentryProfiler.mm +++ b/Sources/Sentry/SentryProfiler.mm @@ -1,9 +1,7 @@ #import "SentryProfiler+Test.h" -#import "SentryProfilerState.h" #if SENTRY_TARGET_PROFILING_SUPPORTED # import "NSDate+SentryExtras.h" -# import "SentryBacktrace.hpp" # import "SentryClient+Private.h" # import "SentryCurrentDate.h" # import "SentryDebugImageProvider.h" @@ -26,6 +24,8 @@ # import "SentryNSProcessInfoWrapper.h" # import "SentryNSTimerFactory.h" # import "SentryProfileTimeseries.h" +# import "SentryProfilerState.h" +# import "SentrySample.h" # import "SentrySamplingProfiler.hpp" # import "SentryScope+Private.h" # import "SentryScreenFrames.h" @@ -38,10 +38,6 @@ # import "SentryTransaction.h" # import "SentryTransactionContext+Private.h" -# if defined(DEBUG) -# include -# endif - # import # import @@ -58,27 +54,6 @@ using namespace sentry::profiling; -NSString * -parseBacktraceSymbolsFunctionName(const char *symbol) -{ - static NSRegularExpression *regex = nil; - static dispatch_once_t onceToken; - dispatch_once(&onceToken, ^{ - regex = [NSRegularExpression - regularExpressionWithPattern:@"\\d+\\s+\\S+\\s+0[xX][0-9a-fA-F]+\\s+(.+)\\s+\\+\\s+\\d+" - options:0 - error:nil]; - }); - const auto symbolNSStr = [NSString stringWithUTF8String:symbol]; - const auto match = [regex firstMatchInString:symbolNSStr - options:0 - range:NSMakeRange(0, [symbolNSStr length])]; - if (match == nil) { - return symbolNSStr; - } - return [symbolNSStr substringWithRange:[match rangeAtIndex:1]]; -} - std::mutex _gProfilerLock; SentryProfiler *_Nullable _gCurrentProfiler; @@ -292,156 +267,8 @@ return payload; } -@implementation SentryProfilingMutableState - -- (instancetype)init -{ - if (self = [super init]) { - _samples = [NSMutableArray array]; - _stacks = [NSMutableArray *> array]; - _frames = [NSMutableArray *> array]; - _threadMetadata = [NSMutableDictionary dictionary]; - _queueMetadata = [NSMutableDictionary dictionary]; - _frameIndexLookup = [NSMutableDictionary dictionary]; - _stackIndexLookup = [NSMutableDictionary dictionary]; - } - return self; -} - -@end - -@implementation SentryProfilingState { - SentryProfilingMutableState *_mutableState; - std::mutex _lock; -} - -- (instancetype)init -{ - if (self = [super init]) { - _mutableState = [[SentryProfilingMutableState alloc] init]; - } - return self; -} - -- (void)mutate:(void (^)(SentryProfilingMutableState *))block -{ - NSParameterAssert(block); - std::lock_guard l(_lock); - block(_mutableState); -} - -- (void)appendBacktrace:(const Backtrace &)backtrace -{ - [self mutate:^(SentryProfilingMutableState *state) { - const auto threadID = sentry_stringForUInt64(backtrace.threadMetadata.threadID); - - NSString *queueAddress = nil; - if (backtrace.queueMetadata.address != 0) { - queueAddress = sentry_formatHexAddressUInt64(backtrace.queueMetadata.address); - } - NSMutableDictionary *metadata = state.threadMetadata[threadID]; - if (metadata == nil) { - metadata = [NSMutableDictionary dictionary]; - state.threadMetadata[threadID] = metadata; - } - if (!backtrace.threadMetadata.name.empty() && metadata[@"name"] == nil) { - metadata[@"name"] = - [NSString stringWithUTF8String:backtrace.threadMetadata.name.c_str()]; - } - if (backtrace.threadMetadata.priority != -1 && metadata[@"priority"] == nil) { - metadata[@"priority"] = @(backtrace.threadMetadata.priority); - } - if (queueAddress != nil && state.queueMetadata[queueAddress] == nil - && backtrace.queueMetadata.label != nullptr) { - NSString *const labelNSStr = - [NSString stringWithUTF8String:backtrace.queueMetadata.label->c_str()]; - // -[NSString stringWithUTF8String:] can return `nil` for malformed string data - if (labelNSStr != nil) { - state.queueMetadata[queueAddress] = @ { @"label" : labelNSStr }; - } - } -# if defined(DEBUG) - const auto symbols - = backtrace_symbols(reinterpret_cast(backtrace.addresses.data()), - static_cast(backtrace.addresses.size())); -# endif - - const auto stack = [NSMutableArray array]; - for (std::vector::size_type backtraceAddressIdx = 0; - backtraceAddressIdx < backtrace.addresses.size(); backtraceAddressIdx++) { - const auto instructionAddress - = sentry_formatHexAddressUInt64(backtrace.addresses[backtraceAddressIdx]); - - const auto frameIndex = state.frameIndexLookup[instructionAddress]; - if (frameIndex == nil) { - const auto frame = [NSMutableDictionary dictionary]; - frame[@"instruction_addr"] = instructionAddress; -# if defined(DEBUG) - frame[@"function"] - = parseBacktraceSymbolsFunctionName(symbols[backtraceAddressIdx]); -# endif - const auto newFrameIndex = @(state.frames.count); - [stack addObject:newFrameIndex]; - state.frameIndexLookup[instructionAddress] = newFrameIndex; - [state.frames addObject:frame]; - } else { - [stack addObject:frameIndex]; - } - } - - const auto sample = [[SentrySample alloc] init]; - sample.absoluteTimestamp = backtrace.absoluteTimestamp; - sample.threadID = backtrace.threadMetadata.threadID; - if (queueAddress != nil) { - sample.queueAddress = queueAddress; - } - - const auto stackKey = [stack componentsJoinedByString:@"|"]; - const auto stackIndex = state.stackIndexLookup[stackKey]; - if (stackIndex) { - sample.stackIndex = stackIndex; - } else { - const auto nextStackIndex = @(state.stacks.count); - sample.stackIndex = nextStackIndex; - state.stackIndexLookup[stackKey] = nextStackIndex; - [state.stacks addObject:stack]; - } - - [state.samples addObject:sample]; - }]; -} - -- (NSDictionary *)copyProfilingData -{ - std::lock_guard l(_lock); - - NSMutableArray *const samples = [_mutableState.samples copy]; - NSMutableArray *> *const stacks = [_mutableState.stacks copy]; - NSMutableArray *> *const frames = [_mutableState.frames copy]; - NSMutableDictionary *const queueMetadata = - [_mutableState.queueMetadata copy]; - - // thread metadata contains a mutable substructure, so it's not enough to perform a copy of - // the top-level dictionary, we need to go deeper to copy the mutable subdictionaries - const auto threadMetadata = [NSMutableDictionary dictionary]; - [_mutableState.threadMetadata enumerateKeysAndObjectsUsingBlock:^(NSString *_Nonnull key, - NSDictionary *_Nonnull obj, BOOL *_Nonnull stop) { threadMetadata[key] = [obj copy]; }]; - - return @{ - @"profile" : @ { - @"samples" : samples, - @"stacks" : stacks, - @"frames" : frames, - @"thread_metadata" : threadMetadata, - @"queue_metadata" : queueMetadata - } - }; -} - -@end - @implementation SentryProfiler { - SentryProfilingState *_state; + SentryProfilerState *_state; std::shared_ptr _profiler; SentryMetricProfiler *_metricProfiler; SentryDebugImageProvider *_debugImageProvider; @@ -642,7 +469,7 @@ - (void)start SENTRY_LOG_DEBUG(@"Starting profiler."); - SentryProfilingState *const state = [[SentryProfilingState alloc] init]; + SentryProfilerState *const state = [[SentryProfilerState alloc] init]; _state = state; _profiler = std::make_shared( [state](auto &backtrace) { diff --git a/Sources/Sentry/include/SentryProfileTimeseries.h b/Sources/Sentry/include/SentryProfileTimeseries.h index a8d2203df5f..342cfce0a94 100644 --- a/Sources/Sentry/include/SentryProfileTimeseries.h +++ b/Sources/Sentry/include/SentryProfileTimeseries.h @@ -4,20 +4,12 @@ # import "SentryDefines.h" # import -# import +@class SentrySample; @class SentryTransaction; NS_ASSUME_NONNULL_BEGIN -/** A storage class to hold the data associated with a single profiler sample. */ -@interface SentrySample : NSObject -@property (nonatomic, assign) uint64_t absoluteTimestamp; -@property (nonatomic, strong) NSNumber *stackIndex; -@property (nonatomic, assign) uint64_t threadID; -@property (nullable, nonatomic, copy) NSString *queueAddress; -@end - NSArray *_Nullable slicedProfileSamples( NSArray *samples, SentryTransaction *transaction); diff --git a/Sources/Sentry/include/SentryProfiler.h b/Sources/Sentry/include/SentryProfiler.h index 2fa99f8331c..a7e9c01eb4f 100644 --- a/Sources/Sentry/include/SentryProfiler.h +++ b/Sources/Sentry/include/SentryProfiler.h @@ -28,19 +28,6 @@ SENTRY_EXTERN NSString *const kSentryProfilerSerializationKeyFrameRates; SENTRY_EXTERN_C_BEGIN -/** - * Parses a symbol that is returned from @c backtrace_symbols() which encodes information - * like the frame index, image name, function name, and offset in a single string. - * @discussion For the input: - * @code - * 2 UIKitCore 0x00000001850d97ac -[UIFieldEditor _fullContentInsetsFromFonts] + 160 - * @endcode - * This function would return: - * @code -[UIFieldEditor _fullContentInsetsFromFonts] @endcode - * @note If the format does not match the expected format, this returns the input string. - */ -NSString *parseBacktraceSymbolsFunctionName(const char *symbol); - NSString *profilerTruncationReasonName(SentryProfilerTruncationReason reason); SENTRY_EXTERN_C_END diff --git a/Sources/Sentry/include/SentryProfilerState.h b/Sources/Sentry/include/SentryProfilerState.h index 8ba1a27e6f5..d7c12972d33 100644 --- a/Sources/Sentry/include/SentryProfilerState.h +++ b/Sources/Sentry/include/SentryProfilerState.h @@ -6,9 +6,22 @@ NS_ASSUME_NONNULL_BEGIN +/** + * Parses a symbol that is returned from @c backtrace_symbols() which encodes information + * like the frame index, image name, function name, and offset in a single string. + * @discussion For the input: + * @code + * 2 UIKitCore 0x00000001850d97ac -[UIFieldEditor _fullContentInsetsFromFonts] + 160 + * @endcode + * This function would return: + * @code -[UIFieldEditor _fullContentInsetsFromFonts] @endcode + * @note If the format does not match the expected format, this returns the input string. + */ +NSString *parseBacktraceSymbolsFunctionName(const char *symbol); + @class SentrySample; -@interface SentryProfilingMutableState : NSObject +@interface SentryProfilerMutableState : NSObject @property (nonatomic, strong, readonly) NSMutableArray *samples; @property (nonatomic, strong, readonly) NSMutableArray *> *stacks; @property (nonatomic, strong, readonly) NSMutableArray *> *frames; @@ -57,9 +70,9 @@ NS_ASSUME_NONNULL_BEGIN NSMutableDictionary *stackIndexLookup; @end -@interface SentryProfilingState : NSObject +@interface SentryProfilerState : NSObject // All functions are safe to call from multiple threads concurrently -- (void)mutate:(void (^)(SentryProfilingMutableState *))block; +- (void)mutate:(void (^)(SentryProfilerMutableState *))block; - (void)appendBacktrace:(const sentry::profiling::Backtrace &)backtrace; - (NSDictionary *)copyProfilingData; @end diff --git a/Sources/Sentry/include/SentrySample.h b/Sources/Sentry/include/SentrySample.h new file mode 100644 index 00000000000..4c0852c388f --- /dev/null +++ b/Sources/Sentry/include/SentrySample.h @@ -0,0 +1,13 @@ +#import + +NS_ASSUME_NONNULL_BEGIN + +/** A storage class to hold the data associated with a single profiler sample. */ +@interface SentrySample : NSObject +@property (nonatomic, assign) uint64_t absoluteTimestamp; +@property (nonatomic, strong) NSNumber *stackIndex; +@property (nonatomic, assign) uint64_t threadID; +@property (nullable, nonatomic, copy) NSString *queueAddress; +@end + +NS_ASSUME_NONNULL_END diff --git a/Tests/SentryProfilerTests/SentryProfilerTests.mm b/Tests/SentryProfilerTests/SentryProfilerTests.mm index 2cc4005e3d5..1d898ed4f0f 100644 --- a/Tests/SentryProfilerTests/SentryProfilerTests.mm +++ b/Tests/SentryProfilerTests/SentryProfilerTests.mm @@ -62,7 +62,7 @@ - (void)testProfilerCanBeInitializedOffMainThread - (void)testProfilerMutationDuringSlicing { - SentryProfilingState *state = [[SentryProfilingState alloc] init]; + SentryProfilerState *state = [[SentryProfilerState alloc] init]; // generate a large timeseries of simulated data const auto threads = 5; @@ -119,7 +119,7 @@ - (void)testProfilerMutationDuringSlicing sliceExpectation.expectedFulfillmentCount = operations; void (^sliceBlock)(void) = ^(void) { - [state mutate:^(SentryProfilingMutableState *mutableState) { + [state mutate:^(SentryProfilerMutableState *mutableState) { __unused const auto slice = slicedProfileSamples(mutableState.samples, transaction); [sliceExpectation fulfill]; }]; @@ -148,7 +148,7 @@ - (void)testProfilerMutationDuringSlicing void (^mutateBlock)(void) = ^(void) { [state mutate:^( - __unused SentryProfilingMutableState *mutableState) { [mutateExpectation fulfill]; }]; + __unused SentryProfilerMutableState *mutableState) { [mutateExpectation fulfill]; }]; }; const auto sliceOperations = [[NSOperationQueue alloc] init]; // concurrent queue @@ -177,7 +177,7 @@ - (void)testProfilerMutationDuringSlicing */ - (void)testProfilerMutationDuringSerialization { - SentryProfilingState *state = [[SentryProfilingState alloc] init]; + SentryProfilerState *state = [[SentryProfilerState alloc] init]; // initialize the data structures with some simulated data { ThreadMetadata threadMetadata; @@ -289,7 +289,7 @@ - (void)testProfilerMutationDuringSerialization - (void)testProfilerPayload { - SentryProfilingState *state = [[SentryProfilingState alloc] init]; + SentryProfilerState *state = [[SentryProfilerState alloc] init]; // record an initial backtrace @@ -337,7 +337,7 @@ - (void)testProfilerPayload [state appendBacktrace:backtrace2]; - [state mutate:^(SentryProfilingMutableState *mutableState) { + [state mutate:^(SentryProfilerMutableState *mutableState) { XCTAssertEqual(mutableState.frames.count, 5UL); const auto expectedOrdered = @[ @"0x0000000000000123", @"0x0000000000000456", @"0x0000000000000789",