From ee3f02eee71adf7c703e8f79f16dc0d7eca34dbd Mon Sep 17 00:00:00 2001 From: Andrew McKnight Date: Mon, 10 Jul 2023 21:05:37 -0800 Subject: [PATCH] ref: profile sample mocking (#3133) --- Sentry.xcodeproj/project.pbxproj | 31 ++++- SentryTestUtils/SentryProfilerMocks.h | 18 +++ SentryTestUtils/SentryProfilerMocks.mm | 28 ++++ .../SentryProfilerMocksSwiftCompatible.h | 28 ++++ .../SentryProfilerMocksSwiftCompatible.mm | 37 +++++ Sources/Sentry/SentryProfiler.mm | 18 ++- ...ofiler+Test.h => SentryProfiler+Private.h} | 11 +- .../include/SentryProfilerState+ObjCpp.h | 20 +++ Sources/Sentry/include/SentryProfilerState.h | 7 +- .../SentryProfilerSwiftTests.swift | 35 +++-- .../SentryProfilerTests.mm | 131 +++--------------- Tests/SentryTests/SentryProfiler+Test.h | 17 +++ .../SentryTests/SentryTests-Bridging-Header.h | 5 +- .../Transaction/SentryTracerObjCTests.m | 6 - 14 files changed, 251 insertions(+), 141 deletions(-) create mode 100644 SentryTestUtils/SentryProfilerMocks.h create mode 100644 SentryTestUtils/SentryProfilerMocks.mm create mode 100644 SentryTestUtils/SentryProfilerMocksSwiftCompatible.h create mode 100644 SentryTestUtils/SentryProfilerMocksSwiftCompatible.mm rename Sources/Sentry/include/{SentryProfiler+Test.h => SentryProfiler+Private.h} (77%) create mode 100644 Sources/Sentry/include/SentryProfilerState+ObjCpp.h create mode 100644 Tests/SentryTests/SentryProfiler+Test.h diff --git a/Sentry.xcodeproj/project.pbxproj b/Sentry.xcodeproj/project.pbxproj index b2aed0f8914..0df123996ad 100644 --- a/Sentry.xcodeproj/project.pbxproj +++ b/Sentry.xcodeproj/project.pbxproj @@ -604,6 +604,8 @@ 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 */; }; + 84281C622A579D0700EE88F2 /* SentryProfilerMocksSwiftCompatible.mm in Sources */ = {isa = PBXBuildFile; fileRef = 84281C4D2A579A0C00EE88F2 /* SentryProfilerMocksSwiftCompatible.mm */; }; + 84281C632A579D0700EE88F2 /* SentryProfilerMocks.mm in Sources */ = {isa = PBXBuildFile; fileRef = 84281C492A57933600EE88F2 /* SentryProfilerMocks.mm */; }; 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 */; }; @@ -639,7 +641,7 @@ 8454CF8D293EAF9A006AC140 /* SentryMetricProfiler.mm in Sources */ = {isa = PBXBuildFile; fileRef = 8454CF8B293EAF9A006AC140 /* SentryMetricProfiler.mm */; }; 849AC40029E0C1FF00889C16 /* SentryFormatterTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 849AC3FF29E0C1FF00889C16 /* SentryFormatterTests.swift */; }; 84A5D75B29D5170700388BFA /* TimeInterval+Sentry.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84A5D75A29D5170700388BFA /* TimeInterval+Sentry.swift */; }; - 84A888FD28D9B11700C51DFD /* SentryProfiler+Test.h in Headers */ = {isa = PBXBuildFile; fileRef = 84A888FC28D9B11700C51DFD /* SentryProfiler+Test.h */; }; + 84A888FD28D9B11700C51DFD /* SentryProfiler+Private.h in Headers */ = {isa = PBXBuildFile; fileRef = 84A888FC28D9B11700C51DFD /* SentryProfiler+Private.h */; }; 84A8891C28DBD28900C51DFD /* SentryDevice.h in Headers */ = {isa = PBXBuildFile; fileRef = 84A8891A28DBD28900C51DFD /* SentryDevice.h */; }; 84A8891D28DBD28900C51DFD /* SentryDevice.mm in Sources */ = {isa = PBXBuildFile; fileRef = 84A8891B28DBD28900C51DFD /* SentryDevice.mm */; }; 84A8892128DBD8D600C51DFD /* SentryDeviceTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 84A8892028DBD8D600C51DFD /* SentryDeviceTests.mm */; }; @@ -1527,6 +1529,12 @@ 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 = ""; }; + 84281C482A57933600EE88F2 /* SentryProfilerMocks.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SentryProfilerMocks.h; sourceTree = ""; }; + 84281C492A57933600EE88F2 /* SentryProfilerMocks.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = SentryProfilerMocks.mm; sourceTree = ""; }; + 84281C4C2A579A0C00EE88F2 /* SentryProfilerMocksSwiftCompatible.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SentryProfilerMocksSwiftCompatible.h; sourceTree = ""; }; + 84281C4D2A579A0C00EE88F2 /* SentryProfilerMocksSwiftCompatible.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = SentryProfilerMocksSwiftCompatible.mm; sourceTree = ""; }; + 84281C642A57D36100EE88F2 /* SentryProfilerState+ObjCpp.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = "SentryProfilerState+ObjCpp.h"; path = "../include/SentryProfilerState+ObjCpp.h"; sourceTree = ""; }; + 84281C652A58A16500EE88F2 /* SentryProfiler+Test.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "SentryProfiler+Test.h"; 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 = ""; }; @@ -1574,7 +1582,7 @@ 849472842971C41A002603DE /* SentryNSTimerFactoryTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SentryNSTimerFactoryTest.swift; sourceTree = ""; }; 849AC3FF29E0C1FF00889C16 /* SentryFormatterTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SentryFormatterTests.swift; sourceTree = ""; }; 84A5D75A29D5170700388BFA /* TimeInterval+Sentry.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "TimeInterval+Sentry.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; }; + 84A888FC28D9B11700C51DFD /* SentryProfiler+Private.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = "SentryProfiler+Private.h"; path = "Sources/Sentry/include/SentryProfiler+Private.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 = ""; }; 84A8892028DBD8D600C51DFD /* SentryDeviceTests.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = SentryDeviceTests.mm; sourceTree = ""; }; @@ -2017,6 +2025,7 @@ 84C47B2B2A09239100DAEB8A /* .codecov.yml */, 844DA80628246D5000E6B62E /* .craft.yml */, 844DA80A28246D5000E6B62E /* .swiftlint.yml */, + 84281C552A579C2B00EE88F2 /* SentryTestUtilsObjc */, 6304360C1EC05CEF00C4D3FA /* Frameworks */, 6327C5D41EB8A783004E799B /* Products */, 63AA756E1EB8AEDB00D153DE /* Sources */, @@ -2215,6 +2224,7 @@ isa = PBXGroup; children = ( 7B3878E92490D90400EBDEA2 /* SentryClient+TestInit.h */, + 84281C652A58A16500EE88F2 /* SentryProfiler+Test.h */, 7B569DFE2590EEF600B653FC /* SentryScope+Equality.h */, 7B569E052590F04700B653FC /* SentryScope+Properties.h */, 7B9421C4260CA393001F9349 /* SentrySDK+Tests.h */, @@ -3103,7 +3113,9 @@ 03F84D1127DD414C008FE43F /* SentryProfiler.h */, 03F84D2B27DD4191008FE43F /* SentryProfiler.mm */, 84A888FC28D9B11700C51DFD /* SentryProfiler+Test.h */, + 84A888FC28D9B11700C51DFD /* SentryProfiler+Private.h */, 0354A22A2A134D9C003C3A04 /* SentryProfilerState.h */, + 84281C642A57D36100EE88F2 /* SentryProfilerState+ObjCpp.h */, 84281C422A578E5600EE88F2 /* SentryProfilerState.mm */, 0356A56E288B4612008BF593 /* SentryProfilesSampler.h */, 0356A56F288B4612008BF593 /* SentryProfilesSampler.m */, @@ -3129,6 +3141,13 @@ path = Profiling; sourceTree = ""; }; + 84281C552A579C2B00EE88F2 /* SentryTestUtilsObjc */ = { + isa = PBXGroup; + children = ( + ); + path = SentryTestUtilsObjc; + sourceTree = ""; + }; 8431EFDB29B27B3D00D8DC56 /* SentryProfilerTests */ = { isa = PBXGroup; children = ( @@ -3161,6 +3180,10 @@ children = ( 7BD47B4C268F0B080076A663 /* ClearTestState.swift */, 84AC61D829F7643B009EEF61 /* TestDispatchFactory.swift */, + 84281C482A57933600EE88F2 /* SentryProfilerMocks.h */, + 84281C492A57933600EE88F2 /* SentryProfilerMocks.mm */, + 84281C4C2A579A0C00EE88F2 /* SentryProfilerMocksSwiftCompatible.h */, + 84281C4D2A579A0C00EE88F2 /* SentryProfilerMocksSwiftCompatible.mm */, 84AC61DA29F7654A009EEF61 /* TestDispatchSourceWrapper.swift */, 84A5D75A29D5170700388BFA /* TimeInterval+Sentry.swift */, 7B30B68126527C55006B2752 /* TestDisplayLinkWrapper.swift */, @@ -3469,7 +3492,7 @@ 7BAF3DD92440AEC8008A5414 /* SentryRequestManager.h in Headers */, 627E7589299F6FE40085504D /* SentryInternalDefines.h in Headers */, 7BE3C77B2446111500A38442 /* SentryRateLimitParser.h in Headers */, - 84A888FD28D9B11700C51DFD /* SentryProfiler+Test.h in Headers */, + 84A888FD28D9B11700C51DFD /* SentryProfiler+Private.h in Headers */, 7D0637032382B34300B30749 /* SentryScope.h in Headers */, 03F84D2727DD414C008FE43F /* SentryMachLogging.hpp in Headers */, 0356A570288B4612008BF593 /* SentryProfilesSampler.h in Headers */, @@ -4442,6 +4465,7 @@ 84AC61D929F7643B009EEF61 /* TestDispatchFactory.swift in Sources */, 8431F01929B2852D00D8DC56 /* Invocation.swift in Sources */, 84B7FA4629B2935F00AD93B1 /* ClearTestState.swift in Sources */, + 84281C622A579D0700EE88F2 /* SentryProfilerMocksSwiftCompatible.mm in Sources */, 8431F01529B2851500D8DC56 /* TestSentryNSTimerFactory.swift in Sources */, 84B7FA3C29B2876F00AD93B1 /* TestConstants.swift in Sources */, 8431F01A29B2852D00D8DC56 /* Dynamic.swift in Sources */, @@ -4453,6 +4477,7 @@ 84B7FA4529B2926900AD93B1 /* TestDisplayLinkWrapper.swift in Sources */, 84AC61DB29F7654A009EEF61 /* TestDispatchSourceWrapper.swift in Sources */, 8431F01729B2851500D8DC56 /* TestSentrySystemWrapper.swift in Sources */, + 84281C632A579D0700EE88F2 /* SentryProfilerMocks.mm in Sources */, 84B7FA4129B28CD200AD93B1 /* TestSentryDispatchQueueWrapper.swift in Sources */, 84B7FA3E29B28ADD00AD93B1 /* TestClient.swift in Sources */, ); diff --git a/SentryTestUtils/SentryProfilerMocks.h b/SentryTestUtils/SentryProfilerMocks.h new file mode 100644 index 00000000000..b5ff0f9cf69 --- /dev/null +++ b/SentryTestUtils/SentryProfilerMocks.h @@ -0,0 +1,18 @@ +#import "SentryProfilingConditionals.h" +#import + +#if SENTRY_TARGET_PROFILING_SUPPORTED + +# import "SentryBacktrace.hpp" + +using namespace sentry::profiling; + +NS_ASSUME_NONNULL_BEGIN + +Backtrace mockBacktrace(thread::TIDType threadID, const int threadPriority, + const char *_Nullable threadName, std::uint64_t queueAddress, std::string queueLabel, + std::vector addresses); + +NS_ASSUME_NONNULL_END + +#endif // SENTRY_TARGET_PROFILING_SUPPORTED diff --git a/SentryTestUtils/SentryProfilerMocks.mm b/SentryTestUtils/SentryProfilerMocks.mm new file mode 100644 index 00000000000..b7c662980d1 --- /dev/null +++ b/SentryTestUtils/SentryProfilerMocks.mm @@ -0,0 +1,28 @@ +#import "SentryProfilerMocks.h" + +#if SENTRY_TARGET_PROFILING_SUPPORTED + +Backtrace +mockBacktrace(thread::TIDType threadID, const int threadPriority, const char *threadName, + std::uint64_t queueAddress, std::string queueLabel, std::vector addresses) +{ + ThreadMetadata threadMetadata; + if (threadName != nullptr) { + threadMetadata.name = threadName; + } + threadMetadata.threadID = threadID; + threadMetadata.priority = threadPriority; + + QueueMetadata queueMetadata; + queueMetadata.address = queueAddress; + queueMetadata.label = std::make_shared(queueLabel); + + Backtrace backtrace; + backtrace.threadMetadata = threadMetadata; + backtrace.queueMetadata = queueMetadata; + backtrace.addresses = std::vector(addresses); + + return backtrace; +} + +#endif // SENTRY_TARGET_PROFILING_SUPPORTED diff --git a/SentryTestUtils/SentryProfilerMocksSwiftCompatible.h b/SentryTestUtils/SentryProfilerMocksSwiftCompatible.h new file mode 100644 index 00000000000..bc038ac73d2 --- /dev/null +++ b/SentryTestUtils/SentryProfilerMocksSwiftCompatible.h @@ -0,0 +1,28 @@ +#import "SentryProfilingConditionals.h" +#import + +#if SENTRY_TARGET_PROFILING_SUPPORTED + +@class SentryProfilerState; + +NS_ASSUME_NONNULL_BEGIN + +/** + * This delivers a wrapper around the C++ function to create a mock backtrace for incorporation into + * profiler state that can be called from Swift tests. + */ +@interface SentryProfilerMocksSwiftCompatible : NSObject + ++ (void)appendMockBacktraceToState:(SentryProfilerState *)state + threadID:(uint64_t)threadID + threadPriority:(const int)threadPriority + threadName:(nullable NSString *)threadName + queueAddress:(uint64_t)queueAddress + queueLabel:(NSString *)queueLabel + addresses:(NSArray *)addresses; + +@end + +NS_ASSUME_NONNULL_END + +#endif // SENTRY_TARGET_PROFILING_SUPPORTED diff --git a/SentryTestUtils/SentryProfilerMocksSwiftCompatible.mm b/SentryTestUtils/SentryProfilerMocksSwiftCompatible.mm new file mode 100644 index 00000000000..8a7687349e3 --- /dev/null +++ b/SentryTestUtils/SentryProfilerMocksSwiftCompatible.mm @@ -0,0 +1,37 @@ +#import "SentryProfilerMocksSwiftCompatible.h" + +#if SENTRY_TARGET_PROFILING_SUPPORTED + +# import "SentryCurrentDate.h" +# import "SentryProfilerMocks.h" +# import "SentryProfilerState+ObjCpp.h" +# include + +using namespace std; + +@implementation SentryProfilerMocksSwiftCompatible + ++ (void)appendMockBacktraceToState:(SentryProfilerState *)state + threadID:(uint64_t)threadID + threadPriority:(const int)threadPriority + threadName:(nullable NSString *)threadName + queueAddress:(uint64_t)queueAddress + queueLabel:(NSString *)queueLabel + addresses:(NSArray *)addresses +{ + auto backtraceAddresses = std::vector(); + + for (NSNumber *address in addresses) { + backtraceAddresses.push_back(address.unsignedLongLongValue); + } + + auto backtrace = mockBacktrace(threadID, threadPriority, + [threadName cStringUsingEncoding:NSUTF8StringEncoding], queueAddress, + [queueLabel cStringUsingEncoding:NSUTF8StringEncoding], backtraceAddresses); + backtrace.absoluteTimestamp = SentryCurrentDate.getCurrentDateProvider.systemTime; + [state appendBacktrace:backtrace]; +} + +@end + +#endif // SENTRY_TARGET_PROFILING_SUPPORTED diff --git a/Sources/Sentry/SentryProfiler.mm b/Sources/Sentry/SentryProfiler.mm index 29cda4d439e..344197b6c47 100644 --- a/Sources/Sentry/SentryProfiler.mm +++ b/Sources/Sentry/SentryProfiler.mm @@ -1,4 +1,4 @@ -#import "SentryProfiler+Test.h" +#import "SentryProfiler+Private.h" #if SENTRY_TARGET_PROFILING_SUPPORTED # import "NSDate+SentryExtras.h" @@ -24,7 +24,7 @@ # import "SentryNSProcessInfoWrapper.h" # import "SentryNSTimerFactory.h" # import "SentryProfileTimeseries.h" -# import "SentryProfilerState.h" +# import "SentryProfilerState+ObjCpp.h" # import "SentrySample.h" # import "SentrySamplingProfiler.hpp" # import "SentryScope+Private.h" @@ -268,7 +268,6 @@ } @implementation SentryProfiler { - SentryProfilerState *_state; std::shared_ptr _profiler; SentryMetricProfiler *_metricProfiler; SentryDebugImageProvider *_debugImageProvider; @@ -375,7 +374,7 @@ + (SentryEnvelopeItem *)createProfilingEnvelopeItemForTransaction:(SentryTransac return nil; } - return serializedProfileData([_gCurrentProfiler->_state copyProfilingData], transaction, + return serializedProfileData([_gCurrentProfiler._state copyProfilingData], transaction, profileID, profilerTruncationReasonName(_gCurrentProfiler->_truncationReason), _gCurrentProfiler -> _hub.scope.environmentString ?: _gCurrentProfiler->_hub.getClient.options.environment, @@ -470,7 +469,7 @@ - (void)start SENTRY_LOG_DEBUG(@"Starting profiler."); SentryProfilerState *const state = [[SentryProfilerState alloc] init]; - _state = state; + self._state = state; _profiler = std::make_shared( [state](auto &backtrace) { // in test, we'll overwrite the sample's timestamp to one mocked by SentryCurrentDate @@ -529,6 +528,15 @@ - (BOOL)isRunning return _profiler->isSampling(); } +# pragma mark - Testing helpers + +# if defined(TEST) || defined(TESTCI) ++ (SentryProfiler *)getCurrentProfiler +{ + return _gCurrentProfiler; +} +# endif // defined(TEST) || defined(TESTCI) + @end #endif diff --git a/Sources/Sentry/include/SentryProfiler+Test.h b/Sources/Sentry/include/SentryProfiler+Private.h similarity index 77% rename from Sources/Sentry/include/SentryProfiler+Test.h rename to Sources/Sentry/include/SentryProfiler+Private.h index a063e41c09b..6f402ca75e4 100644 --- a/Sources/Sentry/include/SentryProfiler+Test.h +++ b/Sources/Sentry/include/SentryProfiler+Private.h @@ -1,4 +1,3 @@ -#include "SentryBacktrace.hpp" #import "SentryProfiler.h" #import "SentryProfilingConditionals.h" @@ -6,6 +5,7 @@ @class SentryDebugMeta; @class SentryId; +@class SentryProfilerState; @class SentrySample; @class SentryTransaction; @@ -16,6 +16,13 @@ NSDictionary *serializedProfileData(NSDictionary NSString *environment, NSString *release, NSDictionary *serializedMetrics, NSArray *debugMeta, SentryHub *hub); +@interface +SentryProfiler () + +@property (strong, nonatomic) SentryProfilerState *_state; + +@end + NS_ASSUME_NONNULL_END -#endif +#endif // SENTRY_TARGET_PROFILING_SUPPORTED diff --git a/Sources/Sentry/include/SentryProfilerState+ObjCpp.h b/Sources/Sentry/include/SentryProfilerState+ObjCpp.h new file mode 100644 index 00000000000..6fadeaee19b --- /dev/null +++ b/Sources/Sentry/include/SentryProfilerState+ObjCpp.h @@ -0,0 +1,20 @@ +#import "SentryProfilingConditionals.h" + +#if SENTRY_TARGET_PROFILING_SUPPORTED + +# import "SentryBacktrace.hpp" +# import "SentryProfilerState.h" + +/* + * This extension defines C++ interface on SentryProfilerState that is not able to be imported into + * a bridging header via SentryProfilerState.h due to C++/Swift interop limitations. + */ + +@interface +SentryProfilerState () + +- (void)appendBacktrace:(const sentry::profiling::Backtrace &)backtrace; + +@end + +#endif // SENTRY_TARGET_PROFILING_SUPPORTED diff --git a/Sources/Sentry/include/SentryProfilerState.h b/Sources/Sentry/include/SentryProfilerState.h index d7c12972d33..9006a3dfff5 100644 --- a/Sources/Sentry/include/SentryProfilerState.h +++ b/Sources/Sentry/include/SentryProfilerState.h @@ -1,7 +1,11 @@ -#import "SentryBacktrace.hpp" #import "SentryProfilingConditionals.h" #import +/* + * This file should not contain any C++ interfaces so it can be used from Swift tests. See + * SentryProfilerState+ObjCpp.h. + */ + #if SENTRY_TARGET_PROFILING_SUPPORTED NS_ASSUME_NONNULL_BEGIN @@ -73,7 +77,6 @@ NSString *parseBacktraceSymbolsFunctionName(const char *symbol); @interface SentryProfilerState : NSObject // All functions are safe to call from multiple threads concurrently - (void)mutate:(void (^)(SentryProfilerMutableState *))block; -- (void)appendBacktrace:(const sentry::profiling::Backtrace &)backtrace; - (NSDictionary *)copyProfilingData; @end diff --git a/Tests/SentryProfilerTests/SentryProfilerSwiftTests.swift b/Tests/SentryProfilerTests/SentryProfilerSwiftTests.swift index 34cbcc9a789..63939e14e57 100644 --- a/Tests/SentryProfilerTests/SentryProfilerSwiftTests.swift +++ b/Tests/SentryProfilerTests/SentryProfilerSwiftTests.swift @@ -228,7 +228,7 @@ class SentryProfilerSwiftTests: XCTestCase { func testMetricProfiler() throws { let span = try fixture.newTransaction() - forceProfilerSample() + addMockSamples() try fixture.gatherMockedMetrics(span: span) self.fixture.currentDateProvider.advanceBy(nanoseconds: 1.toNanoSeconds()) span.finish() @@ -246,7 +246,7 @@ class SentryProfilerSwiftTests: XCTestCase { fixture.currentDateProvider.advanceBy(nanoseconds: 100) } - forceProfilerSample() + addMockSamples() for (i, span) in spans.enumerated() { try fixture.gatherMockedMetrics(span: span) @@ -280,7 +280,7 @@ class SentryProfilerSwiftTests: XCTestCase { func testConcurrentSpansWithTimeout() throws { let spanA = try fixture.newTransaction() fixture.currentDateProvider.advanceBy(nanoseconds: 1.toNanoSeconds()) - forceProfilerSample() + addMockSamples() fixture.currentDateProvider.advanceBy(nanoseconds: 30.toNanoSeconds()) fixture.timeoutTimerFactory.fire() @@ -288,7 +288,7 @@ class SentryProfilerSwiftTests: XCTestCase { let spanB = try fixture.newTransaction() fixture.currentDateProvider.advanceBy(nanoseconds: 0.5.toNanoSeconds()) - forceProfilerSample() + addMockSamples() spanB.finish() try self.assertValidProfileData() @@ -297,6 +297,14 @@ class SentryProfilerSwiftTests: XCTestCase { try self.assertValidProfileData() } + /** + * We received a report of unbounded memory growth from a customer. Was able to reproduce by endlessly starting transactions that go on to timeout, thereby also forcing the profiler to timeout. But, doing it on a nonmain queue meant that the timeout timer in the profiler would never fire. The fix was to schedule that timer on a dispatch to the main queue. + */ + func testRepeatedTransactionTimeoutsFromBgQueue() { + let state = SentryProfilerState() + SentryProfilerMocksSwiftCompatible.appendMockBacktrace(to: state, threadID: 1, threadPriority: 2, threadName: "test", queueAddress: 1, queueLabel: "test", addresses: [0x1, 0x2, 0x3]) + } + func testProfileTimeoutTimer() throws { try performTest(shouldTimeOut: true) } @@ -397,13 +405,13 @@ private extension SentryProfilerSwiftTests { return try XCTUnwrap(envelope.event as? Transaction) } - /// 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" - var concatStr = "" - for _ in 0..<100_000 { - concatStr = concatStr.appending(str) - } + func addMockSamples() { + let state = SentryProfiler.getCurrent()._state + SentryProfilerMocksSwiftCompatible.appendMockBacktrace(to: state, threadID: 1, threadPriority: 2, threadName: "test-thread", queueAddress: 3, queueLabel: "test-queue", addresses: [0x3, 0x4, 0x5]) + fixture.currentDateProvider.advanceBy(nanoseconds: 1) + SentryProfilerMocksSwiftCompatible.appendMockBacktrace(to: state, threadID: 1, threadPriority: 2, threadName: "test-thread", queueAddress: 3, queueLabel: "test-queue", addresses: [0x3, 0x4, 0x5]) + fixture.currentDateProvider.advanceBy(nanoseconds: 1) + SentryProfilerMocksSwiftCompatible.appendMockBacktrace(to: state, threadID: 1, threadPriority: 2, threadName: "test-thread", queueAddress: 3, queueLabel: "test-queue", addresses: [0x3, 0x4, 0x5]) } func performTest(transactionEnvironment: String = kSentryDefaultEnvironment, shouldTimeOut: Bool = false, launchType: SentryAppStartType? = nil, prewarmed: Bool = false) throws { @@ -415,7 +423,8 @@ private extension SentryProfilerSwiftTests { } let span = try fixture.newTransaction(testingAppLaunchSpans: testingAppLaunchSpans) - forceProfilerSample() + + addMockSamples() fixture.currentDateProvider.advance(by: 31) if shouldTimeOut { fixture.timeoutTimerFactory.fire() @@ -639,7 +648,7 @@ private extension SentryProfilerSwiftTests { Dynamic(hub).tracesSampler.random = TestRandom(value: 1.0) let span = try fixture.newTransaction() - forceProfilerSample() + addMockSamples() fixture.currentDateProvider.advance(by: 5) span.finish() diff --git a/Tests/SentryProfilerTests/SentryProfilerTests.mm b/Tests/SentryProfilerTests/SentryProfilerTests.mm index 1d898ed4f0f..bbad9da551b 100644 --- a/Tests/SentryProfilerTests/SentryProfilerTests.mm +++ b/Tests/SentryProfilerTests/SentryProfilerTests.mm @@ -2,8 +2,10 @@ #import "SentryHub+TestInit.h" #import "SentryId.h" #import "SentryProfileTimeseries.h" +#import "SentryProfiler+Private.h" #import "SentryProfiler+Test.h" -#import "SentryProfilerState.h" +#import "SentryProfilerMocks.h" +#import "SentryProfilerState+ObjCpp.h" #import "SentryProfilingConditionals.h" #import "SentryThread.h" #import "SentryTransaction.h" @@ -75,23 +77,16 @@ - (void)testProfilerMutationDuringSlicing const auto queue = thread + threads * 3; uint64_t address = thread + threads * 4; - ThreadMetadata threadMetadata; - threadMetadata.name = [[NSString stringWithFormat:@"testThread-%d", thread] + const auto threadName = [[NSString stringWithFormat:@"testThread-%d", thread] cStringUsingEncoding:NSUTF8StringEncoding]; - threadMetadata.threadID = threadID; - threadMetadata.priority = threadPriority; - - QueueMetadata queueMetadata; - queueMetadata.address = queue; - queueMetadata.label = std::make_shared([[NSString - stringWithFormat:@"testQueue-%d", thread] cStringUsingEncoding:NSUTF8StringEncoding]); - - Backtrace backtrace; - backtrace.threadMetadata = threadMetadata; - backtrace.queueMetadata = queueMetadata; - backtrace.addresses + const auto queueLabel = std::string([[NSString stringWithFormat:@"testQueue-%d", thread] + cStringUsingEncoding:NSUTF8StringEncoding]); + const auto addresses = std::vector({ address + 1, address + 2, address + 3 }); + auto backtrace + = mockBacktrace(threadID, threadPriority, threadName, queue, queueLabel, addresses); + for (auto sample = 0; sample < samplesPerThread; sample++) { backtrace.absoluteTimestamp = sampleIdx; // simulate 1 sample per nanosecond [state appendBacktrace:backtrace]; @@ -125,22 +120,8 @@ - (void)testProfilerMutationDuringSlicing }]; }; - ThreadMetadata threadMetadata; - threadMetadata.name = "testThread"; - threadMetadata.threadID = 12345568910; - threadMetadata.priority = 666; - - QueueMetadata queueMetadata; - queueMetadata.address = 9876543210; - queueMetadata.label = std::make_shared("testQueue"); - - const auto addresses = std::vector({ 777, 888, 789 }); - - Backtrace backtrace; - backtrace.threadMetadata = threadMetadata; - backtrace.queueMetadata = queueMetadata; - backtrace.absoluteTimestamp = 5; - backtrace.addresses = addresses; + const auto backtrace = mockBacktrace(12345568910, 666, "testThread", 9876543210, "testQueue", + std::vector({ 777, 888, 789 })); const auto mutateExpectation = [self expectationWithDescription:@"all mutating operations complete"]; @@ -180,19 +161,9 @@ - (void)testProfilerMutationDuringSerialization SentryProfilerState *state = [[SentryProfilerState alloc] init]; // initialize the data structures with some simulated data { - ThreadMetadata threadMetadata; // leave thread name as nil so it can be overwritten later - threadMetadata.threadID = 1; - threadMetadata.priority = 2; - - QueueMetadata queueMetadata; - queueMetadata.address = 3; - queueMetadata.label = std::make_shared("testQueue-1"); - - Backtrace backtrace; - backtrace.threadMetadata = threadMetadata; - backtrace.queueMetadata = queueMetadata; - backtrace.addresses = std::vector({ 0x4, 0x5, 0x6 }); + auto backtrace = mockBacktrace( + 1, 2, nullptr, 3, "testQueue-1", std::vector({ 0x4, 0x5, 0x6 })); backtrace.absoluteTimestamp = 1; [state appendBacktrace:backtrace]; @@ -222,42 +193,17 @@ - (void)testProfilerMutationDuringSerialization // cause the data structures to be modified again: add new addresses { - ThreadMetadata threadMetadata; - threadMetadata.name = "newThread-2"; - threadMetadata.threadID = 12345568910; - threadMetadata.priority = 666; - - QueueMetadata queueMetadata; - queueMetadata.address = 9876543210; - queueMetadata.label = std::make_shared("newQueue-2"); - - Backtrace backtrace; - backtrace.threadMetadata = threadMetadata; - backtrace.queueMetadata = queueMetadata; - backtrace.absoluteTimestamp = 5; - backtrace.addresses = std::vector({ 0x777, 0x888, 0x999 }); - + const auto backtrace = mockBacktrace(12345568910, 666, "newThread-2", 9876543210, + "newQueue-2", std::vector({ 0x777, 0x888, 0x999 })); [state appendBacktrace:backtrace]; } // cause the data structures to be modified again: overwrite previous thread metadata // subdictionary contents { - ThreadMetadata threadMetadata; - threadMetadata.name = "testThread-1"; - threadMetadata.threadID = 1; - threadMetadata.priority = 2; - - QueueMetadata queueMetadata; - queueMetadata.address = 3; - queueMetadata.label = std::make_shared("testQueue-1"); - - Backtrace backtrace; - backtrace.threadMetadata = threadMetadata; - backtrace.queueMetadata = queueMetadata; + auto backtrace = mockBacktrace( + 1, 2, "testThread-1", 3, "testQueue-1", std::vector({ 0x4, 0x5, 0x6 })); backtrace.absoluteTimestamp = 6; - backtrace.addresses = std::vector({ 0x4, 0x5, 0x6 }); - [state appendBacktrace:backtrace]; } @@ -292,49 +238,16 @@ - (void)testProfilerPayload SentryProfilerState *state = [[SentryProfilerState alloc] init]; // record an initial backtrace - - ThreadMetadata threadMetadata1; - threadMetadata1.name = "testThread"; - threadMetadata1.threadID = 12345568910; - threadMetadata1.priority = 666; - - QueueMetadata queueMetadata1; - queueMetadata1.address = 9876543210; - queueMetadata1.label = std::make_shared("testQueue"); - - const auto addresses1 = std::vector({ 0x123, 0x456, 0x789 }); - - Backtrace backtrace1; - backtrace1.threadMetadata = threadMetadata1; - backtrace1.queueMetadata = queueMetadata1; - backtrace1.absoluteTimestamp = 5; - backtrace1.addresses = addresses1; - + const auto backtrace1 = mockBacktrace(12345568910, 666, "testThread", 9876543210, "testQueue", + std::vector({ 0x123, 0x456, 0x789 })); [state appendBacktrace:backtrace1]; // record a second backtrace with some common addresses to test frame deduplication - - ThreadMetadata threadMetadata2; - threadMetadata2.name = "testThread"; - threadMetadata2.threadID = 12345568910; - threadMetadata2.priority = 666; - - QueueMetadata queueMetadata2; - queueMetadata2.address = 9876543210; - queueMetadata2.label = std::make_shared("testQueue"); - - const auto addresses2 = std::vector({ 0x777, 0x888, 0x789 }); - - Backtrace backtrace2; - backtrace2.threadMetadata = threadMetadata2; - backtrace2.queueMetadata = queueMetadata2; - backtrace2.absoluteTimestamp = 5; - backtrace2.addresses = addresses2; - + const auto backtrace2 = mockBacktrace(12345568910, 666, "testThread", 9876543210, "testQueue", + std::vector({ 0x777, 0x888, 0x789 })); [state appendBacktrace:backtrace2]; // record a third backtrace that's identical to the second to test stack/frame deduplication - [state appendBacktrace:backtrace2]; [state mutate:^(SentryProfilerMutableState *mutableState) { diff --git a/Tests/SentryTests/SentryProfiler+Test.h b/Tests/SentryTests/SentryProfiler+Test.h new file mode 100644 index 00000000000..83a7655ed1b --- /dev/null +++ b/Tests/SentryTests/SentryProfiler+Test.h @@ -0,0 +1,17 @@ +#import "SentryProfiler.h" +#import "SentryProfilingConditionals.h" + +#if SENTRY_TARGET_PROFILING_SUPPORTED + +NS_ASSUME_NONNULL_BEGIN + +@interface +SentryProfiler () + ++ (SentryProfiler *)getCurrentProfiler; + +@end + +NS_ASSUME_NONNULL_END + +#endif // SENTRY_TARGET_PROFILING_SUPPORTED diff --git a/Tests/SentryTests/SentryTests-Bridging-Header.h b/Tests/SentryTests/SentryTests-Bridging-Header.h index fa681edffe0..a8e7460efa0 100644 --- a/Tests/SentryTests/SentryTests-Bridging-Header.h +++ b/Tests/SentryTests/SentryTests-Bridging-Header.h @@ -124,7 +124,10 @@ #import "SentryPerformanceTracker.h" #import "SentryPerformanceTrackingIntegration.h" #import "SentryPredicateDescriptor.h" -#import "SentryProfiler.h" +#import "SentryProfiler+Private.h" +#import "SentryProfiler+Test.h" +#import "SentryProfilerMocksSwiftCompatible.h" +#import "SentryProfilerState.h" #import "SentryQueueableRequestManager.h" #import "SentryRandom.h" #import "SentryRateLimitParser.h" diff --git a/Tests/SentryTests/Transaction/SentryTracerObjCTests.m b/Tests/SentryTests/Transaction/SentryTracerObjCTests.m index 8378315cfb6..1f04fb2a853 100644 --- a/Tests/SentryTests/Transaction/SentryTracerObjCTests.m +++ b/Tests/SentryTests/Transaction/SentryTracerObjCTests.m @@ -76,12 +76,6 @@ - (void)testConcurrentTracerProfiling configuration.waitForChildren = YES; }]]; - // force some samples to be taken by the profiler - NSMutableString *string = [NSMutableString string]; - for (int i = 0; i < 100000; i++) { - [string appendString:@"a"]; - } - XCTestExpectation *exp = [self expectationWithDescription:@"finishes tracers"]; dispatch_after( dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{