From e70a2e1f0fe27b4e5419ff68a79fba90de72457d Mon Sep 17 00:00:00 2001 From: Andrew McKnight Date: Tue, 11 Jun 2024 12:53:35 -0800 Subject: [PATCH] feat(profiling): change how continuous profiling is enabled (#4024) --- .../xcshareddata/xcschemes/iOS-Swift.xcscheme | 2 +- Samples/iOS-Swift/iOS-Swift/AppDelegate.swift | 11 +- Sentry.xcodeproj/project.pbxproj | 4 + .../Sentry/Profiling/SentryLaunchProfiling.m | 8 +- Sources/Sentry/Public/SentryOptions.h | 19 +- Sources/Sentry/SentryOptions.m | 20 +- Sources/Sentry/SentryProfiler.mm | 8 +- Sources/Sentry/SentrySDK.m | 14 +- Sources/Sentry/SentrySampling.m | 2 +- Sources/Sentry/SentrySpan.m | 7 +- Sources/Sentry/SentryTimeToDisplayTracker.m | 4 +- Sources/Sentry/SentryTracer.m | 8 +- Sources/Sentry/SentryTransaction.m | 8 + .../Sentry/include/SentryOptions+Private.h | 2 +- Sources/Sentry/include/SentrySpan+Private.h | 12 ++ .../SentryCrashMonitor_CPPException.cpp | 1 - .../SentryAppLaunchProfilingTests.swift | 196 ++++++++++-------- .../SentryContinuousProfilerTests.swift | 12 +- Tests/SentryTests/SentryOptionsTest.m | 57 +++-- Tests/SentryTests/SentrySDKTests.swift | 41 ++++ .../Transaction/SentrySpanTests.swift | 126 ++++++----- .../Transaction/SentryTransactionTests.swift | 28 +++ 22 files changed, 385 insertions(+), 205 deletions(-) create mode 100644 Sources/Sentry/include/SentrySpan+Private.h diff --git a/Samples/iOS-Swift/iOS-Swift.xcodeproj/xcshareddata/xcschemes/iOS-Swift.xcscheme b/Samples/iOS-Swift/iOS-Swift.xcodeproj/xcshareddata/xcschemes/iOS-Swift.xcscheme index 8c482519066..0d21ce7f36e 100644 --- a/Samples/iOS-Swift/iOS-Swift.xcodeproj/xcshareddata/xcschemes/iOS-Swift.xcscheme +++ b/Samples/iOS-Swift/iOS-Swift.xcodeproj/xcshareddata/xcschemes/iOS-Swift.xcscheme @@ -74,7 +74,7 @@ isEnabled = "NO"> Bool { diff --git a/Sentry.xcodeproj/project.pbxproj b/Sentry.xcodeproj/project.pbxproj index ba2ad75a7f5..f33b4d8996f 100644 --- a/Sentry.xcodeproj/project.pbxproj +++ b/Sentry.xcodeproj/project.pbxproj @@ -686,6 +686,7 @@ 84A305572BC9EF8C00D84283 /* SentryTraceProfiler.h in Headers */ = {isa = PBXBuildFile; fileRef = 84A305552BC9EF8C00D84283 /* SentryTraceProfiler.h */; }; 84A305582BC9EF8C00D84283 /* SentryTraceProfiler.mm in Sources */ = {isa = PBXBuildFile; fileRef = 84A305562BC9EF8C00D84283 /* SentryTraceProfiler.mm */; }; 84A5D75B29D5170700388BFA /* TimeInterval+Sentry.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84A5D75A29D5170700388BFA /* TimeInterval+Sentry.swift */; }; + 84A7890A2C0E9F6400FF0803 /* SentrySpan+Private.h in Headers */ = {isa = PBXBuildFile; fileRef = 84A789092C0E9F5800FF0803 /* SentrySpan+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 */; }; @@ -1742,6 +1743,7 @@ 84A305562BC9EF8C00D84283 /* SentryTraceProfiler.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = SentryTraceProfiler.mm; sourceTree = ""; }; 84A305592BC9FD1600D84283 /* SentryTraceProfiler+Test.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "SentryTraceProfiler+Test.h"; sourceTree = ""; }; 84A5D75A29D5170700388BFA /* TimeInterval+Sentry.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "TimeInterval+Sentry.swift"; sourceTree = ""; }; + 84A789092C0E9F5800FF0803 /* SentrySpan+Private.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = "SentrySpan+Private.h"; path = "include/SentrySpan+Private.h"; sourceTree = ""; }; 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 = ""; }; @@ -3524,6 +3526,7 @@ 7B3B83712833832B0001FDEB /* SentrySpanOperations.h */, 622C08D729E546F4002571D4 /* SentryTraceOrigins.h */, 8E4E7C6C25DAAAFE006AB9E2 /* SentrySpan.h */, + 84A789092C0E9F5800FF0803 /* SentrySpan+Private.h */, 8EC3AE7925CA23B600E7591A /* SentrySpan.m */, 7BE912AA272162AF00E49E62 /* SentryNoOpSpan.h */, 7BE912AC272162D900E49E62 /* SentryNoOpSpan.m */, @@ -4179,6 +4182,7 @@ 7BC852392458830A005A70F0 /* SentryEnvelopeItemType.h in Headers */, 63AA769D1EB9C57A00D153DE /* SentryError.h in Headers */, 63FE714F20DA4C1100CDBAE8 /* SentryCrashNSErrorUtil.h in Headers */, + 84A7890A2C0E9F6400FF0803 /* SentrySpan+Private.h in Headers */, 7BC5B6FA290BCDE500D99477 /* SentryHttpStatusCodeRange+Private.h in Headers */, 7B04A9AF24EAC02C00E710B1 /* SentryRetryAfterHeaderParser.h in Headers */, 9286059529A5096600F96038 /* SentryGeo.h in Headers */, diff --git a/Sources/Sentry/Profiling/SentryLaunchProfiling.m b/Sources/Sentry/Profiling/SentryLaunchProfiling.m index a017b969918..2a8754c37ff 100644 --- a/Sources/Sentry/Profiling/SentryLaunchProfiling.m +++ b/Sources/Sentry/Profiling/SentryLaunchProfiling.m @@ -56,7 +56,7 @@ SentryLaunchProfileConfig sentry_shouldProfileNextLaunch(SentryOptions *options) { - if (options.enableAppLaunchProfiling && options.enableContinuousProfiling) { + if (options.enableAppLaunchProfiling && [options isContinuousProfilingEnabled]) { return (SentryLaunchProfileConfig) { YES, nil, nil }; } @@ -174,14 +174,14 @@ NSMutableDictionary *configDict = [NSMutableDictionary dictionary]; - if (options.enableContinuousProfiling) { + if ([options isContinuousProfilingEnabled]) { configDict[kSentryLaunchProfileConfigKeyContinuousProfiling] = @YES; } else { configDict[kSentryLaunchProfileConfigKeyTracesSampleRate] = config.tracesDecision.sampleRate; + configDict[kSentryLaunchProfileConfigKeyProfilesSampleRate] + = config.profilesDecision.sampleRate; } - configDict[kSentryLaunchProfileConfigKeyProfilesSampleRate] - = config.profilesDecision.sampleRate; writeAppLaunchProfilingConfigFile(configDict); }]; } diff --git a/Sources/Sentry/Public/SentryOptions.h b/Sources/Sentry/Public/SentryOptions.h index aee08355e85..852ffa67c8c 100644 --- a/Sources/Sentry/Public/SentryOptions.h +++ b/Sources/Sentry/Public/SentryOptions.h @@ -456,7 +456,17 @@ NS_SWIFT_NAME(Options) * the SDK sets it to the default of @c 0. * This property is dependent on @c tracesSampleRate -- if @c tracesSampleRate is @c 0 (default), * no profiles will be collected no matter what this property is set to. This property is - * used to undersample profiles *relative to* @c tracesSampleRate + * used to undersample profiles *relative to* @c tracesSampleRate . + * @note Setting this value to @c nil enables an experimental new profiling mode, called continuous + * profiling. This allows you to start and stop a profiler any time with @c SentrySDK.startProfiler + * and @c SentrySDK.stopProfiler, which can run with no time limit, periodically uploading profiling + * data. You can also set @c SentryOptions.enableAppLaunchProfiling to have the profiler start on + * app launch; there is no automatic stop, you must stop it manually at some later time if you + * choose to do so. Sampling rates do not apply to continuous profiles, including those + * automatically started for app launches. If you wish to sample them, you must do so at the + * callsites where you use the API or configure launch profiling. Continuous profiling is not + * automatically started for performance transactions as was the previous version of profiling. + * @warning The new continuous profiling mode is experimental and may still contain bugs. */ @property (nullable, nonatomic, strong) NSNumber *profilesSampleRate; @@ -474,8 +484,11 @@ NS_SWIFT_NAME(Options) /** * If profiling should be enabled or not. * @note Profiling is not supported on watchOS or tvOS. - * @returns @c YES if either a profilesSampleRate > @c 0 and \<= @c 1 or a profilesSampler is set, - * otherwise @c NO. + * @note This only returns whether or not trace-based profiling is enabled. If it is not, then + * continuous profiling is effectively enabled, and calling SentrySDK.startProfiler will + * successfully start a continuous profile. + * @returns @c YES if either @c profilesSampleRate > @c 0 and \<= @c 1 , or @c profilesSampler is + * set, otherwise @c NO. */ @property (nonatomic, assign, readonly) BOOL isProfilingEnabled; diff --git a/Sources/Sentry/SentryOptions.m b/Sources/Sentry/SentryOptions.m index 3a041127807..760409ff49f 100644 --- a/Sources/Sentry/SentryOptions.m +++ b/Sources/Sentry/SentryOptions.m @@ -131,7 +131,6 @@ - (instancetype)init #if SENTRY_TARGET_PROFILING_SUPPORTED _enableProfiling = NO; self.profilesSampleRate = nil; - _enableContinuousProfiling = NO; #endif // SENTRY_TARGET_PROFILING_SUPPORTED self.enableCoreDataTracing = YES; _enableSwizzling = YES; @@ -496,9 +495,6 @@ - (BOOL)validateOptions:(NSDictionary *)options [self setBool:options[NSStringFromSelector(@selector(enableAppLaunchProfiling))] block:^(BOOL value) { self->_enableAppLaunchProfiling = value; }]; - - [self setBool:options[@"enableContinuousProfiling"] - block:^(BOOL value) { self->_enableContinuousProfiling = value; }]; #endif // SENTRY_TARGET_PROFILING_SUPPORTED [self setBool:options[@"sendClientReports"] @@ -650,18 +646,32 @@ - (BOOL)isProfilingEnabled || _profilesSampler != nil || _enableProfiling; } +- (BOOL)isContinuousProfilingEnabled +{ # pragma clang diagnostic push # pragma clang diagnostic ignored "-Wdeprecated-declarations" + // this looks a little weird with the `!self.enableProfiling` but that actually is the + // deprecated way to say "enable trace-based profiling", which necessarily disables continuous + // profiling as they are mutually exclusive modes + return _profilesSampleRate == nil && _profilesSampler == nil && !self.enableProfiling; +# pragma clang diagnostic pop +} + - (void)setEnableProfiling_DEPRECATED_TEST_ONLY:(BOOL)enableProfiling_DEPRECATED_TEST_ONLY { +# pragma clang diagnostic push +# pragma clang diagnostic ignored "-Wdeprecated-declarations" self.enableProfiling = enableProfiling_DEPRECATED_TEST_ONLY; +# pragma clang diagnostic pop } - (BOOL)enableProfiling_DEPRECATED_TEST_ONLY { +# pragma clang diagnostic push +# pragma clang diagnostic ignored "-Wdeprecated-declarations" return self.enableProfiling; -} # pragma clang diagnostic pop +} #endif // SENTRY_TARGET_PROFILING_SUPPORTED /** diff --git a/Sources/Sentry/SentryProfiler.mm b/Sources/Sentry/SentryProfiler.mm index 07c8c46826d..4397968ffc2 100644 --- a/Sources/Sentry/SentryProfiler.mm +++ b/Sources/Sentry/SentryProfiler.mm @@ -40,7 +40,7 @@ sentry_manageTraceProfilerOnStartSDK(SentryOptions *options, SentryHub *hub) { [SentryDependencyContainer.sharedInstance.dispatchQueueWrapper dispatchAsyncWithBlock:^{ - BOOL shouldStopAndTransmitLaunchProfile = !options.enableContinuousProfiling; + BOOL shouldStopAndTransmitLaunchProfile = options.profilesSampleRate != nil; # if SENTRY_HAS_UIKIT if (SentryUIViewControllerPerformanceTracker.shared.enableWaitForFullDisplay) { shouldStopAndTransmitLaunchProfile = NO; @@ -61,6 +61,12 @@ @implementation SentryProfiler { + (void)load { +# if defined(TEST) || defined(TESTCI) + // we want to allow starting a launch profile from here for UI tests, but not unit tests + if (NSProcessInfo.processInfo.environment[@"io.sentry.ui-test.test-name"] == nil) { + return; + } +# endif // defined(TEST) || defined(TESTCI) sentry_startLaunchProfile(); } diff --git a/Sources/Sentry/SentrySDK.m b/Sources/Sentry/SentrySDK.m index 71d6b6d7a15..348e2556c73 100644 --- a/Sources/Sentry/SentrySDK.m +++ b/Sources/Sentry/SentrySDK.m @@ -528,9 +528,10 @@ + (void)crash #if SENTRY_TARGET_PROFILING_SUPPORTED + (void)startProfiler { - if (!SENTRY_ASSERT_RETURN(currentHub.client.options.enableContinuousProfiling, - @"You must set SentryOptions.enableContinuousProfiling to true before starting a " - @"continuous profiler.")) { + if (![currentHub.client.options isContinuousProfilingEnabled]) { + SENTRY_LOG_WARN( + @"You must disable trace profiling by setting SentryOptions.profilesSampleRate to nil " + @"or 0 before using continuous profiling features."); return; } @@ -539,9 +540,10 @@ + (void)startProfiler + (void)stopProfiler { - if (!SENTRY_ASSERT_RETURN(currentHub.client.options.enableContinuousProfiling, - @"You must set SentryOptions.enableContinuousProfiling to true before using continuous " - @"profiling API.")) { + if (![currentHub.client.options isContinuousProfilingEnabled]) { + SENTRY_LOG_WARN( + @"You must disable trace profiling by setting SentryOptions.profilesSampleRate to nil " + @"or 0 before using continuous profiling features."); return; } diff --git a/Sources/Sentry/SentrySampling.m b/Sources/Sentry/SentrySampling.m index 01307e322bd..446dfca5139 100644 --- a/Sources/Sentry/SentrySampling.m +++ b/Sources/Sentry/SentrySampling.m @@ -18,7 +18,7 @@ * returned a valid value, @c nil otherwise. */ NSNumber *_Nullable _sentry_samplerCallbackRate(SentryTracesSamplerCallback _Nullable callback, - SentrySamplingContext *context, NSNumber *defaultSampleRate) + SentrySamplingContext *context, NSNumber *_Nullable defaultSampleRate) { if (callback == nil) { return nil; diff --git a/Sources/Sentry/SentrySpan.m b/Sources/Sentry/SentrySpan.m index b43ec27b058..d231b7d1e80 100644 --- a/Sources/Sentry/SentrySpan.m +++ b/Sources/Sentry/SentrySpan.m @@ -1,4 +1,3 @@ -#import "SentrySpan.h" #import "SentryCrashThread.h" #import "SentryDependencyContainer.h" #import "SentryFrame.h" @@ -8,6 +7,7 @@ #import "SentryNSDictionarySanitize.h" #import "SentryNoOpSpan.h" #import "SentrySampleDecision+Private.h" +#import "SentrySpan+Private.h" #import "SentrySpanContext.h" #import "SentrySpanId.h" #import "SentrySwift.h" @@ -50,7 +50,6 @@ @implementation SentrySpan { #endif // SENTRY_HAS_UIKIT #if SENTRY_TARGET_PROFILING_SUPPORTED - NSString *_Nullable _profileSessionID; BOOL _isContinuousProfiling; #endif // SENTRY_TARGET_PROFILING_SUPPORTED } @@ -99,7 +98,7 @@ - (instancetype)initWithContext:(SentrySpanContext *)context _origin = context.origin; #if SENTRY_TARGET_PROFILING_SUPPORTED - _isContinuousProfiling = SentrySDK.options.enableContinuousProfiling; + _isContinuousProfiling = [SentrySDK.options isContinuousProfilingEnabled]; if (_isContinuousProfiling) { _profileSessionID = SentryContinuousProfiler.currentProfilerID.sentryIdString; if (_profileSessionID == nil) { @@ -376,7 +375,7 @@ - (NSDictionary *)serialize #if SENTRY_TARGET_PROFILING_SUPPORTED if (_profileSessionID != nil) { - mutableDictionary[@"profile_id"] = _profileSessionID; + mutableDictionary[@"profiler_id"] = _profileSessionID; } #endif // SENTRY_TARGET_PROFILING_SUPPORTED diff --git a/Sources/Sentry/SentryTimeToDisplayTracker.m b/Sources/Sentry/SentryTimeToDisplayTracker.m index dbc68ef9936..a78c29feec5 100644 --- a/Sources/Sentry/SentryTimeToDisplayTracker.m +++ b/Sources/Sentry/SentryTimeToDisplayTracker.m @@ -142,7 +142,7 @@ - (void)framesTrackerHasNewFrame:(NSDate *)newFrameDate if (!_waitForFullDisplay) { [SentryDependencyContainer.sharedInstance.framesTracker removeListener:self]; # if SENTRY_TARGET_PROFILING_SUPPORTED - if (!SentrySDK.options.enableContinuousProfiling) { + if (![SentrySDK.options isContinuousProfilingEnabled]) { sentry_stopAndDiscardLaunchProfileTracer(); } # endif // SENTRY_TARGET_PROFILING_SUPPORTED @@ -154,7 +154,7 @@ - (void)framesTrackerHasNewFrame:(NSDate *)newFrameDate self.fullDisplaySpan.timestamp = newFrameDate; [self.fullDisplaySpan finish]; # if SENTRY_TARGET_PROFILING_SUPPORTED - if (!SentrySDK.options.enableContinuousProfiling) { + if (![SentrySDK.options isContinuousProfilingEnabled]) { sentry_stopAndDiscardLaunchProfileTracer(); } # endif // SENTRY_TARGET_PROFILING_SUPPORTED diff --git a/Sources/Sentry/SentryTracer.m b/Sources/Sentry/SentryTracer.m index ca971984c69..e758f08bd56 100644 --- a/Sources/Sentry/SentryTracer.m +++ b/Sources/Sentry/SentryTracer.m @@ -179,9 +179,11 @@ - (instancetype)initWithTransactionContext:(SentryTransactionContext *)transacti #endif // SENTRY_HAS_UIKIT #if SENTRY_TARGET_PROFILING_SUPPORTED - if (!hub.client.options.enableContinuousProfiling - && (_configuration.profilesSamplerDecision.decision == kSentrySampleDecisionYes - || sentry_isTracingAppLaunch)) { + BOOL profileShouldBeSampled + = _configuration.profilesSamplerDecision.decision == kSentrySampleDecisionYes; + BOOL isContinuousProfiling = [hub.client.options isContinuousProfilingEnabled]; + BOOL shouldStartNormalTraceProfile = !isContinuousProfiling && profileShouldBeSampled; + if (sentry_isTracingAppLaunch || shouldStartNormalTraceProfile) { _internalID = [[SentryId alloc] init]; if ((_isProfiling = [SentryTraceProfiler startWithTracer:_internalID])) { SENTRY_LOG_DEBUG(@"Started profiler for trace %@ with internal id %@", diff --git a/Sources/Sentry/SentryTransaction.m b/Sources/Sentry/SentryTransaction.m index ac682149bfe..5aa4459fa3f 100644 --- a/Sources/Sentry/SentryTransaction.m +++ b/Sources/Sentry/SentryTransaction.m @@ -2,6 +2,8 @@ #import "SentryEnvelopeItemType.h" #import "SentryMeasurementValue.h" #import "SentryNSDictionarySanitize.h" +#import "SentryProfilingConditionals.h" +#import "SentrySpan+Private.h" #import "SentrySwift.h" #import "SentryTransactionContext.h" @@ -54,6 +56,12 @@ - (instancetype)initWithTrace:(SentryTracer *)trace children:(NSArray #include #include -#include #define STACKTRACE_BUFFER_LENGTH 30 #define DESCRIPTION_BUFFER_LENGTH 1000 diff --git a/Tests/SentryProfilerTests/SentryAppLaunchProfilingTests.swift b/Tests/SentryProfilerTests/SentryAppLaunchProfilingTests.swift index e6ca9931cb0..2cc70766ed3 100644 --- a/Tests/SentryProfilerTests/SentryAppLaunchProfilingTests.swift +++ b/Tests/SentryProfilerTests/SentryAppLaunchProfilingTests.swift @@ -27,7 +27,7 @@ final class SentryAppLaunchProfilingSwiftTests: XCTestCase { func testLaunchContinuousProfileNotStoppedOnFullyDisplayed() throws { // start a launch profile fixture.options.enableAppLaunchProfiling = true - fixture.options.enableContinuousProfiling = true + fixture.options.profilesSampleRate = nil sentry_configureLaunchProfiling(fixture.options) _sentry_nondeduplicated_startLaunchProfile() XCTAssert(SentryContinuousProfiler.isCurrentlyProfiling()) @@ -54,6 +54,7 @@ final class SentryAppLaunchProfilingSwiftTests: XCTestCase { _sentry_nondeduplicated_startLaunchProfile() XCTAssert(try XCTUnwrap(SentryTraceProfiler.getCurrentProfiler()).isRunning()) + SentrySDK.setStart(fixture.options) let ttd = SentryTimeToDisplayTracker(for: UIViewController(nibName: nil, bundle: nil), waitForFullDisplay: true, dispatchQueueWrapper: fixture.dispatchQueueWrapper) ttd.start(for: try XCTUnwrap(sentry_launchTracer)) ttd.reportInitialDisplay() @@ -66,7 +67,7 @@ final class SentryAppLaunchProfilingSwiftTests: XCTestCase { func testLaunchContinuousProfileNotStoppedOnInitialDisplayWithoutWaitingForFullDisplay() throws { // start a launch profile fixture.options.enableAppLaunchProfiling = true - fixture.options.enableContinuousProfiling = true + fixture.options.profilesSampleRate = nil sentry_configureLaunchProfiling(fixture.options) _sentry_nondeduplicated_startLaunchProfile() XCTAssert(SentryContinuousProfiler.isCurrentlyProfiling()) @@ -92,6 +93,7 @@ final class SentryAppLaunchProfilingSwiftTests: XCTestCase { _sentry_nondeduplicated_startLaunchProfile() XCTAssert(try XCTUnwrap(SentryTraceProfiler.getCurrentProfiler()).isRunning()) + SentrySDK.setStart(fixture.options) let ttd = SentryTimeToDisplayTracker(for: UIViewController(nibName: nil, bundle: nil), waitForFullDisplay: false, dispatchQueueWrapper: fixture.dispatchQueueWrapper) ttd.start(for: try XCTUnwrap(sentry_launchTracer)) ttd.reportInitialDisplay() @@ -139,7 +141,7 @@ final class SentryAppLaunchProfilingSwiftTests: XCTestCase { XCTAssertFalse(appLaunchProfileConfigFileExists()) sentry_manageTraceProfilerOnStartSDK(options, TestHub(client: nil, andScope: nil)) XCTAssert(appLaunchProfileConfigFileExists()) - options.profilesSampleRate = 0 + options.profilesSampleRate = 0.1 // less than the fixture's "random" value of 0.5 sentry_manageTraceProfilerOnStartSDK(options, TestHub(client: nil, andScope: nil)) XCTAssertFalse(appLaunchProfileConfigFileExists()) // ensure we get another config written, to test removal again @@ -155,10 +157,9 @@ final class SentryAppLaunchProfilingSwiftTests: XCTestCase { func testContinuousLaunchProfileConfiguration() throws { let options = Options() options.enableAppLaunchProfiling = true - options.enableContinuousProfiling = true + options.profilesSampleRate = nil - // sample rates are not considered for continuous profiling - options.profilesSampleRate = 0 + // sample rates are not considered for continuous profiling (can't test this with a profilesSampleRate of 0 though, because it must be nil to enable continuous profiling) options.tracesSampleRate = 0 XCTAssertFalse(appLaunchProfileConfigFileExists()) @@ -170,8 +171,31 @@ final class SentryAppLaunchProfilingSwiftTests: XCTestCase { _sentry_nondeduplicated_startLaunchProfile() XCTAssert(SentryContinuousProfiler.isCurrentlyProfiling()) } - - func testTraceProfilerStartsWhenBothSampleRatesAreSet() { + + // test that after configuring trace based app launch profiling, then on + // the next launch, configuring profiling for continuous mode, that the + // configuration file switches from trace-based to continuous-style config + func testSwitchFromTraceBasedToContinuousLaunchProfileConfiguration() throws { + let options = Options() + options.enableAppLaunchProfiling = true + options.profilesSampleRate = 0.567 + options.tracesSampleRate = 0.789 + XCTAssertFalse(appLaunchProfileConfigFileExists()) + sentry_manageTraceProfilerOnStartSDK(options, TestHub(client: nil, andScope: nil)) + XCTAssert(appLaunchProfileConfigFileExists()) + let dict = try XCTUnwrap(appLaunchProfileConfiguration()) + XCTAssertEqual(dict[kSentryLaunchProfileConfigKeyTracesSampleRate], options.tracesSampleRate) + XCTAssertEqual(dict[kSentryLaunchProfileConfigKeyProfilesSampleRate], options.profilesSampleRate) + + options.profilesSampleRate = nil + sentry_manageTraceProfilerOnStartSDK(options, TestHub(client: nil, andScope: nil)) + let newDict = try XCTUnwrap(appLaunchProfileConfiguration()) + XCTAssertEqual(newDict[kSentryLaunchProfileConfigKeyContinuousProfiling], true) + XCTAssertNil(newDict[kSentryLaunchProfileConfigKeyTracesSampleRate]) + XCTAssertNil(newDict[kSentryLaunchProfileConfigKeyProfilesSampleRate]) + } + + func testTraceProfilerStartsWhenBothSampleRatesAreSetAboveZero() { let options = Options() options.enableAppLaunchProfiling = true options.profilesSampleRate = 0.567 @@ -187,95 +211,103 @@ final class SentryAppLaunchProfilingSwiftTests: XCTestCase { * Test how combinations of the following options interact to ultimately decide whether or not to start the profiler on the next app launch.. * - `enableLaunchProfiling` * - `enableTracing` - * - `enableContinuousProfiling` (always profiles regardless of sample rate or trace options) * - `tracesSampleRate` - * - `profilesSampleRate` - * - `profilesSampler` + * - `profilesSampleRate` (set to `nil` to enable continuous profiling, which ignores sample rates) + * - `profilesSampler` (return `nil` to enable continuous profiling, which ignores sample rates) */ func testShouldProfileLaunchBasedOnOptionsCombinations() { - for testCase: (enableAppLaunchProfiling: Bool, enableTracing: Bool, enableContinuousProfiling: Bool, tracesSampleRate: Int, profilesSampleRate: Int, profilesSamplerReturnValue: Int, shouldProfileLaunch: Bool) in [ + for testCase: (enableAppLaunchProfiling: Bool, enableTracing: Bool, tracesSampleRate: Int, profilesSampleRate: Int?, profilesSamplerReturnValue: Int?, shouldProfileLaunch: Bool) in [ // everything false/0 - (enableAppLaunchProfiling: false, enableTracing: false, enableContinuousProfiling: false, tracesSampleRate: 0, profilesSampleRate: 0, profilesSamplerReturnValue: 0, shouldProfileLaunch: false), + (enableAppLaunchProfiling: false, enableTracing: false, tracesSampleRate: 0, profilesSampleRate: 0, profilesSamplerReturnValue: 0, shouldProfileLaunch: false), // change profilesSampleRate to 1 - (enableAppLaunchProfiling: false, enableTracing: false, enableContinuousProfiling: false, tracesSampleRate: 0, profilesSampleRate: 1, profilesSamplerReturnValue: 0, shouldProfileLaunch: false), + (enableAppLaunchProfiling: false, enableTracing: false, tracesSampleRate: 0, profilesSampleRate: 1, profilesSamplerReturnValue: 0, shouldProfileLaunch: false), // change tracesSampleRate to 1 - (enableAppLaunchProfiling: false, enableTracing: false, enableContinuousProfiling: false, tracesSampleRate: 1, profilesSampleRate: 0, profilesSamplerReturnValue: 0, shouldProfileLaunch: false), - (enableAppLaunchProfiling: false, enableTracing: false, enableContinuousProfiling: false, tracesSampleRate: 1, profilesSampleRate: 1, profilesSamplerReturnValue: 0, shouldProfileLaunch: false), - // change enableContinuousProfiling to true - (enableAppLaunchProfiling: false, enableTracing: false, enableContinuousProfiling: true, tracesSampleRate: 0, profilesSampleRate: 0, profilesSamplerReturnValue: 0, shouldProfileLaunch: false), - (enableAppLaunchProfiling: false, enableTracing: false, enableContinuousProfiling: true, tracesSampleRate: 0, profilesSampleRate: 1, profilesSamplerReturnValue: 0, shouldProfileLaunch: false), - (enableAppLaunchProfiling: false, enableTracing: false, enableContinuousProfiling: true, tracesSampleRate: 1, profilesSampleRate: 0, profilesSamplerReturnValue: 0, shouldProfileLaunch: false), - (enableAppLaunchProfiling: false, enableTracing: false, enableContinuousProfiling: true, tracesSampleRate: 1, profilesSampleRate: 1, profilesSamplerReturnValue: 0, shouldProfileLaunch: false), + (enableAppLaunchProfiling: false, enableTracing: false, tracesSampleRate: 1, profilesSampleRate: 0, profilesSamplerReturnValue: 0, shouldProfileLaunch: false), + (enableAppLaunchProfiling: false, enableTracing: false, tracesSampleRate: 1, profilesSampleRate: 1, profilesSamplerReturnValue: 0, shouldProfileLaunch: false), + // enable continuous profiling by setting profilesSampleRate to nil + (enableAppLaunchProfiling: false, enableTracing: false, tracesSampleRate: 0, profilesSampleRate: nil, profilesSamplerReturnValue: 0, shouldProfileLaunch: false), + (enableAppLaunchProfiling: false, enableTracing: false, tracesSampleRate: 1, profilesSampleRate: nil, profilesSamplerReturnValue: 0, shouldProfileLaunch: false), // change enableTracing to true - (enableAppLaunchProfiling: false, enableTracing: true, enableContinuousProfiling: false, tracesSampleRate: 0, profilesSampleRate: 0, profilesSamplerReturnValue: 0, shouldProfileLaunch: false), - (enableAppLaunchProfiling: false, enableTracing: true, enableContinuousProfiling: false, tracesSampleRate: 0, profilesSampleRate: 1, profilesSamplerReturnValue: 0, shouldProfileLaunch: false), - (enableAppLaunchProfiling: false, enableTracing: true, enableContinuousProfiling: false, tracesSampleRate: 1, profilesSampleRate: 0, profilesSamplerReturnValue: 0, shouldProfileLaunch: false), - (enableAppLaunchProfiling: false, enableTracing: true, enableContinuousProfiling: false, tracesSampleRate: 1, profilesSampleRate: 1, profilesSamplerReturnValue: 0, shouldProfileLaunch: false), - (enableAppLaunchProfiling: false, enableTracing: true, enableContinuousProfiling: true, tracesSampleRate: 0, profilesSampleRate: 0, profilesSamplerReturnValue: 0, shouldProfileLaunch: false), - (enableAppLaunchProfiling: false, enableTracing: true, enableContinuousProfiling: true, tracesSampleRate: 0, profilesSampleRate: 1, profilesSamplerReturnValue: 0, shouldProfileLaunch: false), - (enableAppLaunchProfiling: false, enableTracing: true, enableContinuousProfiling: true, tracesSampleRate: 1, profilesSampleRate: 0, profilesSamplerReturnValue: 0, shouldProfileLaunch: false), - (enableAppLaunchProfiling: false, enableTracing: true, enableContinuousProfiling: true, tracesSampleRate: 1, profilesSampleRate: 1, profilesSamplerReturnValue: 0, shouldProfileLaunch: false), + (enableAppLaunchProfiling: false, enableTracing: true, tracesSampleRate: 0, profilesSampleRate: 0, profilesSamplerReturnValue: 0, shouldProfileLaunch: false), + (enableAppLaunchProfiling: false, enableTracing: true, tracesSampleRate: 0, profilesSampleRate: 1, profilesSamplerReturnValue: 0, shouldProfileLaunch: false), + (enableAppLaunchProfiling: false, enableTracing: true, tracesSampleRate: 1, profilesSampleRate: 0, profilesSamplerReturnValue: 0, shouldProfileLaunch: false), + (enableAppLaunchProfiling: false, enableTracing: true, tracesSampleRate: 1, profilesSampleRate: 1, profilesSamplerReturnValue: 0, shouldProfileLaunch: false), + (enableAppLaunchProfiling: false, enableTracing: true, tracesSampleRate: 0, profilesSampleRate: nil, profilesSamplerReturnValue: 0, shouldProfileLaunch: false), + (enableAppLaunchProfiling: false, enableTracing: true, tracesSampleRate: 1, profilesSampleRate: nil, profilesSamplerReturnValue: 0, shouldProfileLaunch: false), // change enableAppLaunchProfiling to true - (enableAppLaunchProfiling: true, enableTracing: false, enableContinuousProfiling: false, tracesSampleRate: 0, profilesSampleRate: 0, profilesSamplerReturnValue: 0, shouldProfileLaunch: false), - (enableAppLaunchProfiling: true, enableTracing: false, enableContinuousProfiling: false, tracesSampleRate: 0, profilesSampleRate: 1, profilesSamplerReturnValue: 0, shouldProfileLaunch: false), - (enableAppLaunchProfiling: true, enableTracing: false, enableContinuousProfiling: false, tracesSampleRate: 1, profilesSampleRate: 0, profilesSamplerReturnValue: 0, shouldProfileLaunch: false), - (enableAppLaunchProfiling: true, enableTracing: false, enableContinuousProfiling: false, tracesSampleRate: 1, profilesSampleRate: 1, profilesSamplerReturnValue: 0, shouldProfileLaunch: false), - (enableAppLaunchProfiling: true, enableTracing: false, enableContinuousProfiling: true, tracesSampleRate: 0, profilesSampleRate: 0, profilesSamplerReturnValue: 0, shouldProfileLaunch: true), - (enableAppLaunchProfiling: true, enableTracing: false, enableContinuousProfiling: true, tracesSampleRate: 0, profilesSampleRate: 1, profilesSamplerReturnValue: 0, shouldProfileLaunch: true), - (enableAppLaunchProfiling: true, enableTracing: false, enableContinuousProfiling: true, tracesSampleRate: 1, profilesSampleRate: 0, profilesSamplerReturnValue: 0, shouldProfileLaunch: true), - (enableAppLaunchProfiling: true, enableTracing: false, enableContinuousProfiling: true, tracesSampleRate: 1, profilesSampleRate: 1, profilesSamplerReturnValue: 0, shouldProfileLaunch: true), - (enableAppLaunchProfiling: true, enableTracing: true, enableContinuousProfiling: false, tracesSampleRate: 0, profilesSampleRate: 0, profilesSamplerReturnValue: 0, shouldProfileLaunch: false), - (enableAppLaunchProfiling: true, enableTracing: true, enableContinuousProfiling: false, tracesSampleRate: 0, profilesSampleRate: 1, profilesSamplerReturnValue: 0, shouldProfileLaunch: false), - (enableAppLaunchProfiling: true, enableTracing: true, enableContinuousProfiling: false, tracesSampleRate: 1, profilesSampleRate: 0, profilesSamplerReturnValue: 0, shouldProfileLaunch: false), - (enableAppLaunchProfiling: true, enableTracing: true, enableContinuousProfiling: false, tracesSampleRate: 1, profilesSampleRate: 1, profilesSamplerReturnValue: 0, shouldProfileLaunch: false), - (enableAppLaunchProfiling: true, enableTracing: true, enableContinuousProfiling: true, tracesSampleRate: 0, profilesSampleRate: 0, profilesSamplerReturnValue: 0, shouldProfileLaunch: true), - (enableAppLaunchProfiling: true, enableTracing: true, enableContinuousProfiling: true, tracesSampleRate: 0, profilesSampleRate: 1, profilesSamplerReturnValue: 0, shouldProfileLaunch: true), - (enableAppLaunchProfiling: true, enableTracing: true, enableContinuousProfiling: true, tracesSampleRate: 1, profilesSampleRate: 0, profilesSamplerReturnValue: 0, shouldProfileLaunch: true), - (enableAppLaunchProfiling: true, enableTracing: true, enableContinuousProfiling: true, tracesSampleRate: 1, profilesSampleRate: 1, profilesSamplerReturnValue: 0, shouldProfileLaunch: true), + (enableAppLaunchProfiling: true, enableTracing: false, tracesSampleRate: 0, profilesSampleRate: 0, profilesSamplerReturnValue: 0, shouldProfileLaunch: false), + (enableAppLaunchProfiling: true, enableTracing: false, tracesSampleRate: 0, profilesSampleRate: 1, profilesSamplerReturnValue: 0, shouldProfileLaunch: false), + (enableAppLaunchProfiling: true, enableTracing: false, tracesSampleRate: 1, profilesSampleRate: 0, profilesSamplerReturnValue: 0, shouldProfileLaunch: false), + (enableAppLaunchProfiling: true, enableTracing: false, tracesSampleRate: 1, profilesSampleRate: 1, profilesSamplerReturnValue: 0, shouldProfileLaunch: false), + (enableAppLaunchProfiling: true, enableTracing: false, tracesSampleRate: 0, profilesSampleRate: nil, profilesSamplerReturnValue: 0, shouldProfileLaunch: false), + (enableAppLaunchProfiling: true, enableTracing: false, tracesSampleRate: 1, profilesSampleRate: nil, profilesSamplerReturnValue: 0, shouldProfileLaunch: false), + (enableAppLaunchProfiling: true, enableTracing: true, tracesSampleRate: 0, profilesSampleRate: 0, profilesSamplerReturnValue: 0, shouldProfileLaunch: false), + (enableAppLaunchProfiling: true, enableTracing: true, tracesSampleRate: 0, profilesSampleRate: 1, profilesSamplerReturnValue: 0, shouldProfileLaunch: false), + (enableAppLaunchProfiling: true, enableTracing: true, tracesSampleRate: 1, profilesSampleRate: 0, profilesSamplerReturnValue: 0, shouldProfileLaunch: false), + (enableAppLaunchProfiling: true, enableTracing: true, tracesSampleRate: 1, profilesSampleRate: 1, profilesSamplerReturnValue: 0, shouldProfileLaunch: false), + (enableAppLaunchProfiling: true, enableTracing: true, tracesSampleRate: 0, profilesSampleRate: nil, profilesSamplerReturnValue: 0, shouldProfileLaunch: false), + (enableAppLaunchProfiling: true, enableTracing: true, tracesSampleRate: 1, profilesSampleRate: nil, profilesSamplerReturnValue: 0, shouldProfileLaunch: false), // change profilesSamplerReturnValue to 1 - (enableAppLaunchProfiling: false, enableTracing: false, enableContinuousProfiling: false, tracesSampleRate: 0, profilesSampleRate: 0, profilesSamplerReturnValue: 1, shouldProfileLaunch: false), - (enableAppLaunchProfiling: false, enableTracing: false, enableContinuousProfiling: false, tracesSampleRate: 0, profilesSampleRate: 1, profilesSamplerReturnValue: 1, shouldProfileLaunch: false), - (enableAppLaunchProfiling: false, enableTracing: false, enableContinuousProfiling: false, tracesSampleRate: 1, profilesSampleRate: 0, profilesSamplerReturnValue: 1, shouldProfileLaunch: false), - (enableAppLaunchProfiling: false, enableTracing: false, enableContinuousProfiling: false, tracesSampleRate: 1, profilesSampleRate: 1, profilesSamplerReturnValue: 1, shouldProfileLaunch: false), - (enableAppLaunchProfiling: false, enableTracing: false, enableContinuousProfiling: true, tracesSampleRate: 0, profilesSampleRate: 0, profilesSamplerReturnValue: 1, shouldProfileLaunch: false), - (enableAppLaunchProfiling: false, enableTracing: false, enableContinuousProfiling: true, tracesSampleRate: 0, profilesSampleRate: 1, profilesSamplerReturnValue: 1, shouldProfileLaunch: false), - (enableAppLaunchProfiling: false, enableTracing: false, enableContinuousProfiling: true, tracesSampleRate: 1, profilesSampleRate: 0, profilesSamplerReturnValue: 1, shouldProfileLaunch: false), - (enableAppLaunchProfiling: false, enableTracing: false, enableContinuousProfiling: true, tracesSampleRate: 1, profilesSampleRate: 1, profilesSamplerReturnValue: 1, shouldProfileLaunch: false), - (enableAppLaunchProfiling: false, enableTracing: true, enableContinuousProfiling: false, tracesSampleRate: 0, profilesSampleRate: 0, profilesSamplerReturnValue: 1, shouldProfileLaunch: false), - (enableAppLaunchProfiling: false, enableTracing: true, enableContinuousProfiling: false, tracesSampleRate: 0, profilesSampleRate: 1, profilesSamplerReturnValue: 1, shouldProfileLaunch: false), - (enableAppLaunchProfiling: false, enableTracing: true, enableContinuousProfiling: false, tracesSampleRate: 1, profilesSampleRate: 0, profilesSamplerReturnValue: 1, shouldProfileLaunch: false), - (enableAppLaunchProfiling: false, enableTracing: true, enableContinuousProfiling: false, tracesSampleRate: 1, profilesSampleRate: 1, profilesSamplerReturnValue: 1, shouldProfileLaunch: false), - (enableAppLaunchProfiling: false, enableTracing: true, enableContinuousProfiling: true, tracesSampleRate: 0, profilesSampleRate: 0, profilesSamplerReturnValue: 1, shouldProfileLaunch: false), - (enableAppLaunchProfiling: false, enableTracing: true, enableContinuousProfiling: true, tracesSampleRate: 0, profilesSampleRate: 1, profilesSamplerReturnValue: 1, shouldProfileLaunch: false), - (enableAppLaunchProfiling: false, enableTracing: true, enableContinuousProfiling: true, tracesSampleRate: 1, profilesSampleRate: 0, profilesSamplerReturnValue: 1, shouldProfileLaunch: false), - (enableAppLaunchProfiling: false, enableTracing: true, enableContinuousProfiling: true, tracesSampleRate: 1, profilesSampleRate: 1, profilesSamplerReturnValue: 1, shouldProfileLaunch: false), - (enableAppLaunchProfiling: true, enableTracing: false, enableContinuousProfiling: false, tracesSampleRate: 0, profilesSampleRate: 0, profilesSamplerReturnValue: 1, shouldProfileLaunch: false), - (enableAppLaunchProfiling: true, enableTracing: false, enableContinuousProfiling: false, tracesSampleRate: 0, profilesSampleRate: 1, profilesSamplerReturnValue: 1, shouldProfileLaunch: false), - (enableAppLaunchProfiling: true, enableTracing: false, enableContinuousProfiling: false, tracesSampleRate: 1, profilesSampleRate: 0, profilesSamplerReturnValue: 1, shouldProfileLaunch: false), - (enableAppLaunchProfiling: true, enableTracing: false, enableContinuousProfiling: false, tracesSampleRate: 1, profilesSampleRate: 1, profilesSamplerReturnValue: 1, shouldProfileLaunch: false), - (enableAppLaunchProfiling: true, enableTracing: false, enableContinuousProfiling: true, tracesSampleRate: 0, profilesSampleRate: 0, profilesSamplerReturnValue: 1, shouldProfileLaunch: true), - (enableAppLaunchProfiling: true, enableTracing: false, enableContinuousProfiling: true, tracesSampleRate: 0, profilesSampleRate: 1, profilesSamplerReturnValue: 1, shouldProfileLaunch: true), - (enableAppLaunchProfiling: true, enableTracing: false, enableContinuousProfiling: true, tracesSampleRate: 1, profilesSampleRate: 0, profilesSamplerReturnValue: 1, shouldProfileLaunch: true), - (enableAppLaunchProfiling: true, enableTracing: false, enableContinuousProfiling: true, tracesSampleRate: 1, profilesSampleRate: 1, profilesSamplerReturnValue: 1, shouldProfileLaunch: true), - (enableAppLaunchProfiling: true, enableTracing: true, enableContinuousProfiling: false, tracesSampleRate: 0, profilesSampleRate: 0, profilesSamplerReturnValue: 1, shouldProfileLaunch: false), - (enableAppLaunchProfiling: true, enableTracing: true, enableContinuousProfiling: false, tracesSampleRate: 0, profilesSampleRate: 1, profilesSamplerReturnValue: 1, shouldProfileLaunch: false), - (enableAppLaunchProfiling: true, enableTracing: true, enableContinuousProfiling: false, tracesSampleRate: 1, profilesSampleRate: 0, profilesSamplerReturnValue: 1, shouldProfileLaunch: true), - (enableAppLaunchProfiling: true, enableTracing: true, enableContinuousProfiling: false, tracesSampleRate: 1, profilesSampleRate: 1, profilesSamplerReturnValue: 1, shouldProfileLaunch: true), - (enableAppLaunchProfiling: true, enableTracing: true, enableContinuousProfiling: true, tracesSampleRate: 0, profilesSampleRate: 0, profilesSamplerReturnValue: 1, shouldProfileLaunch: true), - (enableAppLaunchProfiling: true, enableTracing: true, enableContinuousProfiling: true, tracesSampleRate: 0, profilesSampleRate: 1, profilesSamplerReturnValue: 1, shouldProfileLaunch: true), - (enableAppLaunchProfiling: true, enableTracing: true, enableContinuousProfiling: true, tracesSampleRate: 1, profilesSampleRate: 0, profilesSamplerReturnValue: 1, shouldProfileLaunch: true), - (enableAppLaunchProfiling: true, enableTracing: true, enableContinuousProfiling: true, tracesSampleRate: 1, profilesSampleRate: 1, profilesSamplerReturnValue: 1, shouldProfileLaunch: true) + (enableAppLaunchProfiling: false, enableTracing: false, tracesSampleRate: 0, profilesSampleRate: 0, profilesSamplerReturnValue: 1, shouldProfileLaunch: false), + (enableAppLaunchProfiling: false, enableTracing: false, tracesSampleRate: 0, profilesSampleRate: 1, profilesSamplerReturnValue: 1, shouldProfileLaunch: false), + (enableAppLaunchProfiling: false, enableTracing: false, tracesSampleRate: 1, profilesSampleRate: 0, profilesSamplerReturnValue: 1, shouldProfileLaunch: false), + (enableAppLaunchProfiling: false, enableTracing: false, tracesSampleRate: 1, profilesSampleRate: 1, profilesSamplerReturnValue: 1, shouldProfileLaunch: false), + (enableAppLaunchProfiling: false, enableTracing: false, tracesSampleRate: 0, profilesSampleRate: nil, profilesSamplerReturnValue: 1, shouldProfileLaunch: false), + (enableAppLaunchProfiling: false, enableTracing: false, tracesSampleRate: 1, profilesSampleRate: nil, profilesSamplerReturnValue: 1, shouldProfileLaunch: false), + (enableAppLaunchProfiling: false, enableTracing: true, tracesSampleRate: 0, profilesSampleRate: 0, profilesSamplerReturnValue: 1, shouldProfileLaunch: false), + (enableAppLaunchProfiling: false, enableTracing: true, tracesSampleRate: 0, profilesSampleRate: 1, profilesSamplerReturnValue: 1, shouldProfileLaunch: false), + (enableAppLaunchProfiling: false, enableTracing: true, tracesSampleRate: 1, profilesSampleRate: 0, profilesSamplerReturnValue: 1, shouldProfileLaunch: false), + (enableAppLaunchProfiling: false, enableTracing: true, tracesSampleRate: 1, profilesSampleRate: 1, profilesSamplerReturnValue: 1, shouldProfileLaunch: false), + (enableAppLaunchProfiling: false, enableTracing: true, tracesSampleRate: 0, profilesSampleRate: nil, profilesSamplerReturnValue: 1, shouldProfileLaunch: false), + (enableAppLaunchProfiling: false, enableTracing: true, tracesSampleRate: 1, profilesSampleRate: nil, profilesSamplerReturnValue: 1, shouldProfileLaunch: false), + (enableAppLaunchProfiling: true, enableTracing: false, tracesSampleRate: 0, profilesSampleRate: 0, profilesSamplerReturnValue: 1, shouldProfileLaunch: false), + (enableAppLaunchProfiling: true, enableTracing: false, tracesSampleRate: 0, profilesSampleRate: 1, profilesSamplerReturnValue: 1, shouldProfileLaunch: false), + (enableAppLaunchProfiling: true, enableTracing: false, tracesSampleRate: 1, profilesSampleRate: 0, profilesSamplerReturnValue: 1, shouldProfileLaunch: false), + (enableAppLaunchProfiling: true, enableTracing: false, tracesSampleRate: 1, profilesSampleRate: 1, profilesSamplerReturnValue: 1, shouldProfileLaunch: false), + (enableAppLaunchProfiling: true, enableTracing: false, tracesSampleRate: 0, profilesSampleRate: nil, profilesSamplerReturnValue: 1, shouldProfileLaunch: false), + (enableAppLaunchProfiling: true, enableTracing: false, tracesSampleRate: 1, profilesSampleRate: nil, profilesSamplerReturnValue: 1, shouldProfileLaunch: false), + (enableAppLaunchProfiling: true, enableTracing: true, tracesSampleRate: 0, profilesSampleRate: 0, profilesSamplerReturnValue: 1, shouldProfileLaunch: false), + (enableAppLaunchProfiling: true, enableTracing: true, tracesSampleRate: 0, profilesSampleRate: 1, profilesSamplerReturnValue: 1, shouldProfileLaunch: false), + (enableAppLaunchProfiling: true, enableTracing: true, tracesSampleRate: 1, profilesSampleRate: 0, profilesSamplerReturnValue: 1, shouldProfileLaunch: true), + (enableAppLaunchProfiling: true, enableTracing: true, tracesSampleRate: 1, profilesSampleRate: 1, profilesSamplerReturnValue: 1, shouldProfileLaunch: true), + (enableAppLaunchProfiling: true, enableTracing: true, tracesSampleRate: 0, profilesSampleRate: nil, profilesSamplerReturnValue: 1, shouldProfileLaunch: false), + (enableAppLaunchProfiling: true, enableTracing: true, tracesSampleRate: 1, profilesSampleRate: nil, profilesSamplerReturnValue: 1, shouldProfileLaunch: true), + + // just those cases that had nil profilesSampleRate but nonnil profilesSamplerReturnValue, now with both as nil, which would enable launch profiling with continuous mode + (enableAppLaunchProfiling: false, enableTracing: false, tracesSampleRate: 0, profilesSampleRate: nil, profilesSamplerReturnValue: nil, shouldProfileLaunch: false), + (enableAppLaunchProfiling: false, enableTracing: false, tracesSampleRate: 1, profilesSampleRate: nil, profilesSamplerReturnValue: nil, shouldProfileLaunch: false), + (enableAppLaunchProfiling: false, enableTracing: true, tracesSampleRate: 0, profilesSampleRate: nil, profilesSamplerReturnValue: nil, shouldProfileLaunch: false), + (enableAppLaunchProfiling: false, enableTracing: true, tracesSampleRate: 1, profilesSampleRate: nil, profilesSamplerReturnValue: nil, shouldProfileLaunch: false), + (enableAppLaunchProfiling: true, enableTracing: false, tracesSampleRate: 0, profilesSampleRate: nil, profilesSamplerReturnValue: nil, shouldProfileLaunch: true), + (enableAppLaunchProfiling: true, enableTracing: false, tracesSampleRate: 1, profilesSampleRate: nil, profilesSamplerReturnValue: nil, shouldProfileLaunch: true), + (enableAppLaunchProfiling: true, enableTracing: true, tracesSampleRate: 0, profilesSampleRate: nil, profilesSamplerReturnValue: nil, shouldProfileLaunch: true), + (enableAppLaunchProfiling: true, enableTracing: true, tracesSampleRate: 1, profilesSampleRate: nil, profilesSamplerReturnValue: nil, shouldProfileLaunch: true), + (enableAppLaunchProfiling: false, enableTracing: false, tracesSampleRate: 0, profilesSampleRate: nil, profilesSamplerReturnValue: nil, shouldProfileLaunch: false), + (enableAppLaunchProfiling: false, enableTracing: false, tracesSampleRate: 1, profilesSampleRate: nil, profilesSamplerReturnValue: nil, shouldProfileLaunch: false), + (enableAppLaunchProfiling: false, enableTracing: true, tracesSampleRate: 0, profilesSampleRate: nil, profilesSamplerReturnValue: nil, shouldProfileLaunch: false), + (enableAppLaunchProfiling: false, enableTracing: true, tracesSampleRate: 1, profilesSampleRate: nil, profilesSamplerReturnValue: nil, shouldProfileLaunch: false), + (enableAppLaunchProfiling: true, enableTracing: false, tracesSampleRate: 0, profilesSampleRate: nil, profilesSamplerReturnValue: nil, shouldProfileLaunch: true), + (enableAppLaunchProfiling: true, enableTracing: false, tracesSampleRate: 1, profilesSampleRate: nil, profilesSamplerReturnValue: nil, shouldProfileLaunch: true), + (enableAppLaunchProfiling: true, enableTracing: true, tracesSampleRate: 0, profilesSampleRate: nil, profilesSamplerReturnValue: nil, shouldProfileLaunch: true), + (enableAppLaunchProfiling: true, enableTracing: true, tracesSampleRate: 1, profilesSampleRate: nil, profilesSamplerReturnValue: nil, shouldProfileLaunch: true) ] { let options = Options() options.enableAppLaunchProfiling = testCase.enableAppLaunchProfiling options.enableTracing = testCase.enableTracing - options.enableContinuousProfiling = testCase.enableContinuousProfiling options.tracesSampleRate = NSNumber(value: testCase.tracesSampleRate) - options.profilesSampleRate = NSNumber(value: testCase.profilesSampleRate) - options.profilesSampler = { _ in - NSNumber(value: testCase.profilesSamplerReturnValue) + if let profilesSampleRate = testCase.profilesSampleRate { + options.profilesSampleRate = NSNumber(value: profilesSampleRate) + } else { + options.profilesSampleRate = nil + } + if let profilesSamplerReturnValue = testCase.profilesSamplerReturnValue { + options.profilesSampler = { _ in + NSNumber(value: profilesSamplerReturnValue) + } + } else { + options.profilesSampler = nil } - XCTAssertEqual(sentry_willProfileNextLaunch(options), testCase.shouldProfileLaunch, "Expected \(testCase.shouldProfileLaunch ? "" : "not ")to enable app launch profiling with options: { enableAppLaunchProfiling: \(testCase.enableAppLaunchProfiling), enableTracing: \(testCase.enableTracing), enableContinuousProfiling: \(testCase.enableContinuousProfiling), tracesSampleRate: \(testCase.tracesSampleRate), profilesSampleRate: \(testCase.profilesSampleRate), profilesSamplerReturnValue: \(testCase.profilesSamplerReturnValue) }") + XCTAssertEqual(sentry_willProfileNextLaunch(options), testCase.shouldProfileLaunch, "Expected \(testCase.shouldProfileLaunch ? "" : "not ")to enable app launch profiling with options: { enableAppLaunchProfiling: \(testCase.enableAppLaunchProfiling), enableTracing: \(testCase.enableTracing), tracesSampleRate: \(testCase.tracesSampleRate), profilesSampleRate: \(String(describing: testCase.profilesSampleRate)), profilesSamplerReturnValue: \(String(describing: testCase.profilesSamplerReturnValue)) }") } } } diff --git a/Tests/SentryProfilerTests/SentryContinuousProfilerTests.swift b/Tests/SentryProfilerTests/SentryContinuousProfilerTests.swift index 685dbe7be10..89a1180c40e 100644 --- a/Tests/SentryProfilerTests/SentryContinuousProfilerTests.swift +++ b/Tests/SentryProfilerTests/SentryContinuousProfilerTests.swift @@ -16,7 +16,7 @@ final class SentryContinuousProfilerTests: XCTestCase { override func setUp() { super.setUp() fixture = SentryProfileTestFixture() - fixture.options.enableContinuousProfiling = true + fixture.options.profilesSampleRate = nil } override func tearDown() { @@ -63,6 +63,16 @@ final class SentryContinuousProfilerTests: XCTestCase { try performContinuousProfilingTest(expectedEnvironment: expectedEnvironment) } + func testStartingContinuousProfilerWithSampleRateOne() throws { + fixture.options.profilesSampleRate = 1 + try performContinuousProfilingTest() + } + + func testStartingContinuousProfilerWithZeroSampleRate() throws { + fixture.options.profilesSampleRate = 0 + try performContinuousProfilingTest() + } + #if !os(macOS) // test that receiving a background notification stops the continuous // profiler after it has been started manually diff --git a/Tests/SentryTests/SentryOptionsTest.m b/Tests/SentryTests/SentryOptionsTest.m index c9c2104cea4..6fea3d117db 100644 --- a/Tests/SentryTests/SentryOptionsTest.m +++ b/Tests/SentryTests/SentryOptionsTest.m @@ -577,13 +577,17 @@ - (void)testNSNull_SetsDefaultValue @"enableUIViewControllerTracing" : [NSNull null], @"attachScreenshot" : [NSNull null], @"sessionReplayOptions" : [NSNull null], -#endif +#endif // SENTRY_HAS_UIKIT @"enableAppHangTracking" : [NSNull null], @"appHangTimeoutInterval" : [NSNull null], @"enableNetworkTracking" : [NSNull null], @"enableAutoBreadcrumbTracking" : [NSNull null], @"tracesSampleRate" : [NSNull null], @"tracesSampler" : [NSNull null], +#if SENTRY_TARGET_PROFILING_SUPPORTED + @"profilesSampleRate" : [NSNull null], + @"profilesSampler" : [NSNull null], +#endif // SENTRY_TARGET_PROFILING_SUPPORTED @"inAppIncludes" : [NSNull null], @"inAppExcludes" : [NSNull null], @"urlSessionDelegate" : [NSNull null], @@ -639,7 +643,7 @@ - (void)assertDefaultValues:(SentryOptions *)options XCTAssertEqual(options.attachViewHierarchy, NO); XCTAssertEqual(options.experimental.sessionReplay.errorSampleRate, 0); XCTAssertEqual(options.experimental.sessionReplay.sessionSampleRate, 0); -#endif +#endif // SENTRY_HAS_UIKIT XCTAssertFalse(options.enableTracing); XCTAssertTrue(options.enableAppHangTracking); XCTAssertEqual(options.appHangTimeoutInterval, 2); @@ -661,7 +665,7 @@ - (void)assertDefaultValues:(SentryOptions *)options XCTAssertEqual(NO, options.enableMetricKit); XCTAssertEqual(NO, options.enableMetricKitRawPayload); } -#endif +#endif // SENTRY_HAS_METRIC_KIT NSRegularExpression *regexTrace = options.tracePropagationTargets[0]; XCTAssertTrue([regexTrace.pattern isEqualToString:@".*"]); @@ -684,8 +688,8 @@ - (void)assertDefaultValues:(SentryOptions *)options # pragma clang diagnostic pop XCTAssertNil(options.profilesSampleRate); XCTAssertNil(options.profilesSampler); - XCTAssertFalse(options.enableContinuousProfiling); -#endif + XCTAssert([options isContinuousProfilingEnabled]); +#endif // SENTRY_TARGET_PROFILING_SUPPORTED XCTAssertTrue([options.spotlightUrl isEqualToString:@"http://localhost:8969/stream"]); } @@ -743,7 +747,7 @@ - (void)testInvalidDsnViaEnvironment XCTAssertEqual(options.enabled, YES); setenv("SENTRY_DSN", "", 1); } -#endif +#endif // TARGET_OS_OSX - (void)testMaxAttachmentSize { @@ -839,7 +843,7 @@ - (void)testSessionReplaySettingsDefaults } } -#endif +#endif // SENTRY_HAS_UIKIT #if SENTRY_HAS_METRIC_KIT @@ -856,7 +860,7 @@ - (void)testenableMetricKitRawPayload [self testBooleanField:@"enableMetricKitRawPayload" defaultValue:NO]; } } -#endif +#endif // SENTRY_HAS_METRIC_KIT - (void)testEnableAppHangTracking { @@ -1071,21 +1075,19 @@ - (void)testEnableProfiling [self testBooleanField:@"enableProfiling" defaultValue:NO]; } -- (void)testEnableContinuousProfiling -{ - [self testBooleanField:@"enableContinuousProfiling" defaultValue:NO]; -} - - (void)testProfilesSampleRate { SentryOptions *options = [self getValidOptions:@{ @"profilesSampleRate" : @0.1 }]; XCTAssertEqual(options.profilesSampleRate.doubleValue, 0.1); + XCTAssertFalse([options isContinuousProfilingEnabled]); } - (void)testDefaultProfilesSampleRate { SentryOptions *options = [self getValidOptions:@{}]; - XCTAssertEqual(options.profilesSampleRate.doubleValue, 0); + XCTAssertNil(options.profilesSampleRate); + XCTAssertFalse(options.isProfilingEnabled); + XCTAssert([options isContinuousProfilingEnabled]); } - (void)testProfilesSampleRate_SetToNil @@ -1093,7 +1095,8 @@ - (void)testProfilesSampleRate_SetToNil SentryOptions *options = [[SentryOptions alloc] init]; options.profilesSampleRate = nil; XCTAssertNil(options.profilesSampleRate); - XCTAssertEqual(options.profilesSampleRate.doubleValue, 0); + XCTAssertFalse(options.isProfilingEnabled); + XCTAssert([options isContinuousProfilingEnabled]); } - (void)testProfilesSampleRateLowerBound @@ -1110,6 +1113,8 @@ - (void)testProfilesSampleRateLowerBound NSNumber *tooLow = @-0.01; options.profilesSampleRate = tooLow; XCTAssertEqual(options.profilesSampleRate.doubleValue, 0); + + XCTAssertFalse([options isContinuousProfilingEnabled]); } - (void)testProfilesSampleRateUpperBound @@ -1126,13 +1131,16 @@ - (void)testProfilesSampleRateUpperBound NSNumber *tooLow = @1.01; options.profilesSampleRate = tooLow; XCTAssertEqual(options.profilesSampleRate.doubleValue, 0); + + XCTAssertFalse([options isContinuousProfilingEnabled]); } - (void)testIsProfilingEnabled_NothingSet_IsDisabled { SentryOptions *options = [[SentryOptions alloc] init]; XCTAssertFalse(options.isProfilingEnabled); - XCTAssertFalse(options.enableContinuousProfiling); + XCTAssertNil(options.profilesSampleRate); + XCTAssert([options isContinuousProfilingEnabled]); } - (void)testIsProfilingEnabled_ProfilesSampleRateSetToZero_IsDisabled @@ -1140,7 +1148,8 @@ - (void)testIsProfilingEnabled_ProfilesSampleRateSetToZero_IsDisabled SentryOptions *options = [[SentryOptions alloc] init]; options.profilesSampleRate = @0.00; XCTAssertFalse(options.isProfilingEnabled); - XCTAssertFalse(options.enableContinuousProfiling); + XCTAssertNotNil(options.profilesSampleRate); + XCTAssertFalse([options isContinuousProfilingEnabled]); } - (void)testIsProfilingEnabled_ProfilesSampleRateSet_IsEnabled @@ -1148,7 +1157,8 @@ - (void)testIsProfilingEnabled_ProfilesSampleRateSet_IsEnabled SentryOptions *options = [[SentryOptions alloc] init]; options.profilesSampleRate = @0.01; XCTAssertTrue(options.isProfilingEnabled); - XCTAssertFalse(options.enableContinuousProfiling); + XCTAssertNotNil(options.profilesSampleRate); + XCTAssertFalse([options isContinuousProfilingEnabled]); } - (void)testIsProfilingEnabled_ProfilesSamplerSet_IsEnabled @@ -1159,7 +1169,8 @@ - (void)testIsProfilingEnabled_ProfilesSamplerSet_IsEnabled return @0.0; }; XCTAssertTrue(options.isProfilingEnabled); - XCTAssertFalse(options.enableContinuousProfiling); + XCTAssertNil(options.profilesSampleRate); + XCTAssertFalse([options isContinuousProfilingEnabled]); } - (void)testIsProfilingEnabled_EnableProfilingSet_IsEnabled @@ -1170,7 +1181,8 @@ - (void)testIsProfilingEnabled_EnableProfilingSet_IsEnabled options.enableProfiling = YES; # pragma clang diagnostic pop XCTAssertTrue(options.isProfilingEnabled); - XCTAssertFalse(options.enableContinuousProfiling); + XCTAssertNil(options.profilesSampleRate); + XCTAssertFalse([options isContinuousProfilingEnabled]); } - (void)testProfilesSampler @@ -1184,19 +1196,22 @@ - (void)testProfilesSampler SentrySamplingContext *context = [[SentrySamplingContext alloc] init]; XCTAssertEqual(options.profilesSampler(context), @1.0); - XCTAssertFalse(options.enableContinuousProfiling); + XCTAssertNil(options.profilesSampleRate); + XCTAssertFalse([options isContinuousProfilingEnabled]); } - (void)testDefaultProfilesSampler { SentryOptions *options = [self getValidOptions:@{}]; XCTAssertNil(options.profilesSampler); + XCTAssert([options isContinuousProfilingEnabled]); } - (void)testGarbageProfilesSampler_ReturnsNil { SentryOptions *options = [self getValidOptions:@{ @"profilesSampler" : @"fault" }]; XCTAssertNil(options.profilesSampler); + XCTAssert([options isContinuousProfilingEnabled]); } #endif // SENTRY_TARGET_PROFILING_SUPPORTED diff --git a/Tests/SentryTests/SentrySDKTests.swift b/Tests/SentryTests/SentrySDKTests.swift index d9f5a6a68c1..4226eab2738 100644 --- a/Tests/SentryTests/SentrySDKTests.swift +++ b/Tests/SentryTests/SentrySDKTests.swift @@ -415,6 +415,47 @@ class SentrySDKTests: XCTestCase { XCTAssert(transaction === newSpan) } +#if os(iOS) || os(macOS) || targetEnvironment(macCatalyst) + + func testStartingContinuousProfilerWithSampleRateZero() throws { + givenSdkWithHub() + + // nil is the default value for profilesSampleRate, so we don't have to explicitly set it on the fixture + XCTAssertNil(fixture.options.profilesSampleRate) + XCTAssertFalse(SentryContinuousProfiler.isCurrentlyProfiling()) + SentrySDK.startProfiler() + XCTAssert(SentryContinuousProfiler.isCurrentlyProfiling()) + } + + func testStartingContinuousProfilerWithSampleRateNil() throws { + givenSdkWithHub() + + fixture.options.profilesSampleRate = nil + XCTAssertFalse(SentryContinuousProfiler.isCurrentlyProfiling()) + SentrySDK.startProfiler() + XCTAssert(SentryContinuousProfiler.isCurrentlyProfiling()) + } + + func testNotStartingContinuousProfilerWithSampleRateBlock() throws { + givenSdkWithHub() + + fixture.options.profilesSampler = { _ in 0 } + XCTAssertFalse(SentryContinuousProfiler.isCurrentlyProfiling()) + SentrySDK.startProfiler() + XCTAssertFalse(SentryContinuousProfiler.isCurrentlyProfiling()) + } + + func testNotStartingContinuousProfilerWithSampleRateNonZero() throws { + givenSdkWithHub() + + fixture.options.profilesSampleRate = 1 + XCTAssertFalse(SentryContinuousProfiler.isCurrentlyProfiling()) + SentrySDK.startProfiler() + XCTAssertFalse(SentryContinuousProfiler.isCurrentlyProfiling()) + } + +#endif // os(iOS) || os(macOS) || targetEnvironment(macCatalyst) + func testInstallIntegrations() throws { let options = Options() options.dsn = "mine" diff --git a/Tests/SentryTests/Transaction/SentrySpanTests.swift b/Tests/SentryTests/Transaction/SentrySpanTests.swift index 7c20d41618d..64d477c7007 100644 --- a/Tests/SentryTests/Transaction/SentrySpanTests.swift +++ b/Tests/SentryTests/Transaction/SentrySpanTests.swift @@ -6,7 +6,7 @@ import XCTest class SentrySpanTests: XCTestCase { private var logOutput: TestLogOutput! private var fixture: Fixture! - + private class Fixture { let someTransaction = "Some Transaction" let someOperation = "Some Operation" @@ -21,7 +21,7 @@ class SentrySpanTests: XCTestCase { #else let tracer = SentryTracer(context: SpanContext(operation: "TEST")) #endif - + init() { options = Options() options.tracesSampleRate = 1 @@ -52,11 +52,11 @@ class SentrySpanTests: XCTestCase { override func setUp() { super.setUp() - + logOutput = TestLogOutput() SentryLog.configure(true, diagnosticLevel: SentryLevel.debug) SentryLog.setLogOutput(logOutput) - + fixture = Fixture() SentryDependencyContainer.sharedInstance().dateProvider = fixture.currentDateProvider } @@ -69,7 +69,6 @@ class SentrySpanTests: XCTestCase { #if os(iOS) || os(macOS) || targetEnvironment(macCatalyst) func testSpanDoesNotIncludeTraceProfilerID() throws { fixture.options.profilesSampleRate = 1 - fixture.options.enableContinuousProfiling = false SentrySDK.setStart(fixture.options) let span = fixture.getSut() let continuousProfileObservations = fixture.notificationCenter.addObserverInvocations.invocations.filter { @@ -84,7 +83,7 @@ class SentrySpanTests: XCTestCase { } func testSpanDoesNotSubscribeToNotificationsIfAlreadyCapturedContinuousProfileID() { - fixture.options.enableContinuousProfiling = true + fixture.options.profilesSampleRate = nil SentryContinuousProfiler.start() SentrySDK.setStart(fixture.options) let _ = fixture.getSut() @@ -96,7 +95,6 @@ class SentrySpanTests: XCTestCase { func testSpanDoesNotSubscribeToNotificationsIfContinuousProfilingDisabled() { fixture.options.profilesSampleRate = 1 - fixture.options.enableContinuousProfiling = false SentrySDK.setStart(fixture.options) let _ = fixture.getSut() let continuousProfileObservations = fixture.notificationCenter.addObserverInvocations.invocations.filter { @@ -106,7 +104,7 @@ class SentrySpanTests: XCTestCase { } func testSpanDoesSubscribeToNotificationsIfNotAlreadyCapturedContinuousProfileID() { - fixture.options.enableContinuousProfiling = true + fixture.options.profilesSampleRate = nil SentrySDK.setStart(fixture.options) let _ = fixture.getSut() let continuousProfileObservations = fixture.notificationCenter.addObserverInvocations.invocations.filter { @@ -114,7 +112,7 @@ class SentrySpanTests: XCTestCase { } XCTAssertEqual(continuousProfileObservations.count, 1) } - + /// Test a span that starts before and ends before a continuous profile, includes profile id /// /// ``` @@ -122,7 +120,7 @@ class SentrySpanTests: XCTestCase { /// +----profile----+ /// ``` func test_spanStart_profileStart_spanEnd_profileEnd_spanIncludesProfileID() throws { - fixture.options.enableContinuousProfiling = true + fixture.options.profilesSampleRate = nil SentrySDK.setStart(fixture.options) let span = fixture.getSut() XCTAssertEqual(fixture.notificationCenter.addObserverInvocations.invocations.filter { @@ -131,12 +129,12 @@ class SentrySpanTests: XCTestCase { SentryContinuousProfiler.start() let profileId = try XCTUnwrap(SentryContinuousProfiler.profiler()?.profilerId.sentryIdString) span.finish() - + let serialized = span.serialize() - - XCTAssertEqual(try XCTUnwrap(serialized["profile_id"] as? String), profileId) + + XCTAssertEqual(try XCTUnwrap(serialized["profiler_id"] as? String), profileId) } - + /// Test a span that starts before and ends after a continuous profile, includes profile id /// /// ``` @@ -144,19 +142,19 @@ class SentrySpanTests: XCTestCase { /// +----profile----+ /// ``` func test_spanStart_profileStart_profileEnd_spanEnd_spanIncludesProfileID() throws { - fixture.options.enableContinuousProfiling = true + fixture.options.profilesSampleRate = nil SentrySDK.setStart(fixture.options) let span = fixture.getSut() SentryContinuousProfiler.start() let profileId = try XCTUnwrap(SentryContinuousProfiler.profiler()?.profilerId.sentryIdString) SentryContinuousProfiler.stop() span.finish() - + let serialized = span.serialize() - - XCTAssertEqual(try XCTUnwrap(serialized["profile_id"] as? String), profileId) + + XCTAssertEqual(try XCTUnwrap(serialized["profiler_id"] as? String), profileId) } - + /// Test a span that starts after and ends after a continuous profile, includes profile id /// /// ``` @@ -164,19 +162,19 @@ class SentrySpanTests: XCTestCase { /// +-------span-------+ /// ``` func test_profileStart_spanStart_profileEnd_spanEnd_spanIncludesProfileID() throws { - fixture.options.enableContinuousProfiling = true + fixture.options.profilesSampleRate = nil SentrySDK.setStart(fixture.options) SentryContinuousProfiler.start() let profileId = try XCTUnwrap(SentryContinuousProfiler.profiler()?.profilerId.sentryIdString) let span = fixture.getSut() SentryContinuousProfiler.stop() span.finish() - + let serialized = span.serialize() - - XCTAssertEqual(try XCTUnwrap(serialized["profile_id"] as? String), profileId) + + XCTAssertEqual(try XCTUnwrap(serialized["profiler_id"] as? String), profileId) } - + /// Test a span that starts after and ends before a continuous profile, includes profile id /// /// ``` @@ -184,17 +182,17 @@ class SentrySpanTests: XCTestCase { /// +-------span-------+ /// ``` func test_profileStart_spanStart_spanEnd_profileEnd_spanIncludesProfileID() throws { - fixture.options.enableContinuousProfiling = true + fixture.options.profilesSampleRate = nil SentrySDK.setStart(fixture.options) SentryContinuousProfiler.start() let profileId = try XCTUnwrap(SentryContinuousProfiler.profiler()?.profilerId.sentryIdString) let span = fixture.getSut() span.finish() - + let serialized = span.serialize() - XCTAssertEqual(try XCTUnwrap(serialized["profile_id"] as? String), profileId) + XCTAssertEqual(try XCTUnwrap(serialized["profiler_id"] as? String), profileId) } - + /// Test a span that spans multiple profiles, which both should have the same profile ID, and that /// the span also contains that profile ID. /// @@ -203,7 +201,7 @@ class SentrySpanTests: XCTestCase { /// +--profile1--+ +--profile2--+ /// ``` func test_spanStart_profileStart_profileEnd_profileStart_profileEnd_spanEnd_spanIncludesSameProfileID() throws { - fixture.options.enableContinuousProfiling = true + fixture.options.profilesSampleRate = nil SentrySDK.setStart(fixture.options) let span = fixture.getSut() SentryContinuousProfiler.start() @@ -214,11 +212,11 @@ class SentrySpanTests: XCTestCase { SentryContinuousProfiler.stop() XCTAssertEqual(profileId1, profileId2) span.finish() - + let serialized = span.serialize() - XCTAssertEqual(try XCTUnwrap(serialized["profile_id"] as? String), profileId1) + XCTAssertEqual(try XCTUnwrap(serialized["profiler_id"] as? String), profileId1) } - + /// Test a span that starts and ends before a profile starts, does not include profile id /// /// ``` @@ -226,18 +224,18 @@ class SentrySpanTests: XCTestCase { /// +----profile----+ /// ``` func test_spanStart_spanEnd_profileStart_profileEnd_spanDoesNotIncludeProfileID() { - fixture.options.enableContinuousProfiling = true + fixture.options.profilesSampleRate = nil SentrySDK.setStart(fixture.options) SentryContinuousProfiler.start() SentryContinuousProfiler.stop() let span = fixture.getSut() span.finish() - + let serialized = span.serialize() XCTAssertNil(serialized["profile_id"]) } #endif // os(iOS) || os(macOS) || targetEnvironment(macCatalyst) - + func testInitAndCheckForTimestamps() { let span = fixture.getSut() XCTAssertNotNil(span.startTimestamp) @@ -327,22 +325,22 @@ class SentrySpanTests: XCTestCase { XCTAssertEqual(lastEvent.startTimestamp, TestData.timestamp) XCTAssertEqual(lastEvent.type, SentryEnvelopeItemTypeTransaction) } - + func testFinishSpanWithDefaultTimestamp() { let span = fixture.getSutWithTracer() span.finish() - + XCTAssertEqual(span.startTimestamp, TestData.timestamp) XCTAssertEqual(span.timestamp, TestData.timestamp) XCTAssertTrue(span.isFinished) XCTAssertEqual(span.status, .ok) } - + func testFinishSpanWithCustomTimestamp() { let span = fixture.getSutWithTracer() span.timestamp = Date(timeIntervalSince1970: 123) span.finish() - + XCTAssertEqual(span.startTimestamp, TestData.timestamp) XCTAssertEqual(span.timestamp, Date(timeIntervalSince1970: 123)) XCTAssertTrue(span.isFinished) @@ -395,25 +393,25 @@ class SentrySpanTests: XCTestCase { XCTAssertEqual(childSpan.operation, fixture.someOperation) XCTAssertEqual(childSpan.spanDescription, fixture.someDescription) } - + func testStartChildOnFinishedSpan() { let span = fixture.getSut() span.finish() - + let childSpan = span.startChild(operation: fixture.someOperation, description: fixture.someDescription) - + XCTAssertNil(childSpan.parentSpanId) XCTAssertEqual(childSpan.operation, "") XCTAssertNil(childSpan.spanDescription) XCTAssertFalse(logOutput.loggedMessages.filter({ $0.contains(" Starting a child on a finished span is not supported; it won\'t be sent to Sentry.") }).isEmpty) } - + func testStartGrandChildOnFinishedSpan() { let span = fixture.getSut() let childSpan = span.startChild(operation: fixture.someOperation) childSpan.finish() span.finish() - + let grandChild = childSpan.startChild(operation: fixture.someOperation, description: fixture.someDescription) XCTAssertNil(grandChild.parentSpanId) XCTAssertEqual(grandChild.operation, "") @@ -423,7 +421,7 @@ class SentrySpanTests: XCTestCase { func testAddAndRemoveData() { let span = fixture.getSut() - + span.setData(value: fixture.extraValue, key: fixture.extraKey) XCTAssertEqual(span.data.count, 3) @@ -457,7 +455,7 @@ class SentrySpanTests: XCTestCase { //Faking extra info to test serialization span.parentSpanId = SpanId() span.spanDescription = "Span Description" - + let serialization = span.serialize() XCTAssertEqual(serialization["span_id"] as? String, span.spanId.sentrySpanIdString) XCTAssertEqual(serialization["parent_span_id"] as? String, span.parentSpanId?.sentrySpanIdString) @@ -478,44 +476,44 @@ class SentrySpanTests: XCTestCase { XCTAssertEqual((serialization["tags"] as! Dictionary)[fixture.extraKey], fixture.extraValue) XCTAssertEqual("manual", serialization["origin"] as? String) } - + func testSerialization_NoStacktraceFrames() { let span = fixture.getSutWithTracer() let serialization = span.serialize() - + XCTAssertEqual(2, (serialization["data"] as? [String: Any])?.count, "Only expected thread.name and thread.id in data.") } - + func testSerialization_withStacktraceFrames() { let span = fixture.getSutWithTracer() span.frames = [TestData.mainFrame, TestData.testFrame] - + let serialization = span.serialize() - + XCTAssertNotNil(serialization["data"]) let callStack = (serialization["data"] as? [String: Any])?["call_stack"] as? [[String: Any]] XCTAssertNotNil(callStack) XCTAssertEqual(callStack?.first?["function"] as? String, TestData.mainFrame.function) XCTAssertEqual(callStack?.last?["function"] as? String, TestData.testFrame.function) } - + func testSanitizeData() { let span = fixture.getSut() - + span.setData(value: Date(timeIntervalSince1970: 10), key: "date") span.finish() - + let serialization = span.serialize() let data = serialization["data"] as? [String: Any] XCTAssertEqual(data?["date"] as? String, "1970-01-01T00:00:10.000Z") } - + func testSanitizeDataSpan() { let span = fixture.getSutWithTracer() - + span.setData(value: Date(timeIntervalSince1970: 10), key: "date") span.finish() - + let serialization = span.serialize() let data = serialization["data"] as? [String: Any] XCTAssertEqual(data?["date"] as? String, "1970-01-01T00:00:10.000Z") @@ -529,7 +527,7 @@ class SentrySpanTests: XCTestCase { XCTAssertNil(serialization["tag"]) } - func testInit_DoesNotIntializeLocalMetricAggregator() { + func testInit_DoesNotInitializeLocalMetricAggregator() { let sut = fixture.getSut() let serialized = sut.serialize() @@ -596,7 +594,7 @@ class SentrySpanTests: XCTestCase { let data = sut.data as [String: Any] XCTAssertEqual(0, data["key"] as? Int) } - + func testSpanWithoutTracer_StartChild_ReturnsNoOpSpan() { // Span has a weak reference to tracer. If we don't keep a reference // to the tracer ARC will deallocate the tracer. @@ -611,7 +609,7 @@ class SentrySpanTests: XCTestCase { } let sut = sutGenerator() - + let actual = sut.startChild(operation: fixture.someOperation) XCTAssertTrue(SentryNoOpSpan.shared() === actual) @@ -622,7 +620,7 @@ class SentrySpanTests: XCTestCase { func testModifyingExtraFromMultipleThreads() { let queue = DispatchQueue(label: "SentrySpanTests", qos: .userInteractive, attributes: [.concurrent, .initiallyInactive]) let group = DispatchGroup() - + let span = fixture.getSut() // The number is kept small for the CI to not take to long. @@ -649,7 +647,7 @@ class SentrySpanTests: XCTestCase { let threadDataItemCount = 2 XCTAssertEqual(span.data.count, outerLoop * innerLoop + threadDataItemCount) } - + func testSpanStatusNames() { XCTAssertEqual(nameForSentrySpanStatus(.undefined), kSentrySpanStatusNameUndefined) XCTAssertEqual(nameForSentrySpanStatus(.ok), kSentrySpanStatusNameOk) @@ -741,7 +739,7 @@ class SentrySpanTests: XCTestCase { expect(sut.data["frames.delay"]) == nil } - private func givenFramesTracker() -> (TestDisplayLinkWrapper, SentryFramesTracker) { + func givenFramesTracker() -> (TestDisplayLinkWrapper, SentryFramesTracker) { let displayLinkWrapper = TestDisplayLinkWrapper(dateProvider: self.fixture.currentDateProvider) let framesTracker = SentryFramesTracker(displayLinkWrapper: displayLinkWrapper, dateProvider: self.fixture.currentDateProvider, dispatchQueueWrapper: TestSentryDispatchQueueWrapper(), notificationCenter: TestNSNotificationCenterWrapper(), keepDelayedFramesDuration: 10) framesTracker.start() @@ -749,5 +747,5 @@ class SentrySpanTests: XCTestCase { return (displayLinkWrapper, framesTracker) } -#endif +#endif // os(iOS) || os(tvOS) || targetEnvironment(macCatalyst) } diff --git a/Tests/SentryTests/Transaction/SentryTransactionTests.swift b/Tests/SentryTests/Transaction/SentryTransactionTests.swift index 3f9d20fe62e..4b9bb7d7934 100644 --- a/Tests/SentryTests/Transaction/SentryTransactionTests.swift +++ b/Tests/SentryTests/Transaction/SentryTransactionTests.swift @@ -211,4 +211,32 @@ class SentryTransactionTests: XCTestCase { expect(metric["count"] as? Int) == 1 expect(metric["sum"] as? Double) == 1.0 } + + func testSerializedSpanData() throws { + let sut = fixture.getTransaction() + let serialized = sut.serialize() + let contexts = try XCTUnwrap(serialized["contexts"] as? [String: Any]) + let trace = try XCTUnwrap(contexts["trace"] as? [String: Any]) + let data = try XCTUnwrap(trace["data"] as? [String: Any]) + XCTAssertNotNil(try XCTUnwrap(data["thread.id"])) + XCTAssertNotNil(try XCTUnwrap(data["thread.name"])) + } + +#if os(iOS) || os(macOS) || targetEnvironment(macCatalyst) + // test that when a trace runs concurrently with the continuous profiler + // and is serialized to a transaction, that it contains the profile id at + // the keypath contexts.trace.data.profile_id + func testTransactionWithContinuousProfile() throws { + SentrySDK.setStart(Options()) + let transaction = fixture.getTransaction() + SentryContinuousProfiler.start() + let profileId = try XCTUnwrap(SentryContinuousProfiler.profiler()?.profilerId.sentryIdString) + let serialized = transaction.serialize() + let contexts = try XCTUnwrap(serialized["contexts"] as? [String: Any]) + let trace = try XCTUnwrap(contexts["trace"] as? [String: Any]) + let data = try XCTUnwrap(trace["data"] as? [String: Any]) + let profileIdFromContexts = try XCTUnwrap(data["profiler_id"] as? String) + XCTAssertEqual(profileId, profileIdFromContexts) + } +#endif // os(iOS) || os(macOS) || targetEnvironment(macCatalyst) }