diff --git a/Samples/iOS-Swift/iOS-Swift-UITests/ProfilingUITests.swift b/Samples/iOS-Swift/iOS-Swift-UITests/ProfilingUITests.swift index 8b5f31bb90..dc8d8c27a1 100644 --- a/Samples/iOS-Swift/iOS-Swift-UITests/ProfilingUITests.swift +++ b/Samples/iOS-Swift/iOS-Swift-UITests/ProfilingUITests.swift @@ -8,6 +8,7 @@ class ProfilingUITests: BaseUITest { func testProfiledAppLaunches() throws { if #available(iOS 16, *) { app.launchArguments.append("--io.sentry.wipe-data") + setDefaultLaunchArgs() launchApp() // First launch enables in-app profiling by setting traces/profiles sample rates to 1 (which is the default configuration in the sample app), but not launch profiling; assert that we did not write a config to allow the next launch to be profiled. @@ -162,6 +163,15 @@ extension ProfilingUITests { }) XCTAssert(try XCTUnwrap(sample["thread_id"] as? String) == "259") // the main thread is always ID 259 } + + /** + * These cause traces to run the profiler, which can overwrite the launch profile file we need to retrieve to make assertions in the UI test. + */ + func setDefaultLaunchArgs() { + app.launchArguments.append("--disable-swizzling") + app.launchArguments.append("--disable-auto-performance-tracing") + app.launchArguments.append("--disable-uiviewcontroller-tracing") + } /** * Performs the various operations for the launch profiler test case: @@ -216,16 +226,8 @@ extension ProfilingUITests { if shouldDisableTracing { app.launchArguments.append("--disable-tracing") } - if shouldDisableSwizzling { - app.launchArguments.append("--disable-swizzling") - } - if shouldDisableAutoPerformanceTracking { - app.launchArguments.append("--disable-auto-performance-tracing") - } - if shouldDisableUIViewControllerTracing { - app.launchArguments.append("--disable-uiviewcontroller-tracing") - } + setDefaultLaunchArgs() launchApp() let sdkOptionsConfigurationAllowsLaunchProfiling = !(shouldDisableTracing || shouldDisableSwizzling || shouldDisableAutoPerformanceTracking || shouldDisableUIViewControllerTracing) diff --git a/Samples/iOS-Swift/iOS-Swift/Profiling/ProfilingViewController.swift b/Samples/iOS-Swift/iOS-Swift/Profiling/ProfilingViewController.swift index bb25956119..7c0a450409 100644 --- a/Samples/iOS-Swift/iOS-Swift/Profiling/ProfilingViewController.swift +++ b/Samples/iOS-Swift/iOS-Swift/Profiling/ProfilingViewController.swift @@ -112,7 +112,7 @@ class ProfilingViewController: UIViewController, UITextFieldDelegate { @IBAction func viewLaunchProfile(_ sender: Any) { profilingUITestDataMarshalingTextField.text = "" - withProfile(fileName: "launchProfile") { file in + withProfile(fileName: "profile") { file in handleContents(file: file) } } diff --git a/Sentry.xcodeproj/project.pbxproj b/Sentry.xcodeproj/project.pbxproj index f9b40b3c7f..bb5f63496b 100644 --- a/Sentry.xcodeproj/project.pbxproj +++ b/Sentry.xcodeproj/project.pbxproj @@ -630,8 +630,8 @@ 7DC8310A2398283C0043DD9A /* SentryCrashIntegration.h in Headers */ = {isa = PBXBuildFile; fileRef = 7DC831082398283C0043DD9A /* SentryCrashIntegration.h */; }; 7DC8310C2398283C0043DD9A /* SentryCrashIntegration.m in Sources */ = {isa = PBXBuildFile; fileRef = 7DC831092398283C0043DD9A /* SentryCrashIntegration.m */; }; 840A11122B61E27500650D02 /* SentrySamplerDecision.m in Sources */ = {isa = PBXBuildFile; fileRef = 840A11102B61E27500650D02 /* SentrySamplerDecision.m */; }; - 840A11132B61FE5800650D02 /* SentryAppLaunchProfilingTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 840A11092B5F47F700650D02 /* SentryAppLaunchProfilingTests.m */; }; 841325BC2BF4184B0029228F /* TestHub.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B944FAD2469B43700A10721 /* TestHub.swift */; }; + 841325C52BF49EC40029228F /* SentryLaunchProfiling+Tests.h in Headers */ = {isa = PBXBuildFile; fileRef = 841325C42BF49EC40029228F /* SentryLaunchProfiling+Tests.h */; }; 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 */; }; @@ -681,6 +681,7 @@ 848A451D2BBF9504006AAAEC /* SentryProfilerTestHelpers.m in Sources */ = {isa = PBXBuildFile; fileRef = 848A451C2BBF9504006AAAEC /* SentryProfilerTestHelpers.m */; }; 848A451E2BBF9504006AAAEC /* SentryProfilerTestHelpers.h in Headers */ = {isa = PBXBuildFile; fileRef = 848A451B2BBF9504006AAAEC /* SentryProfilerTestHelpers.h */; }; 849AC40029E0C1FF00889C16 /* SentryFormatterTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 849AC3FF29E0C1FF00889C16 /* SentryFormatterTests.swift */; }; + 84A305492BC7328400D84283 /* SentryAppLaunchProfilingTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84A305472BC72A0A00D84283 /* SentryAppLaunchProfilingTests.swift */; }; 84A305572BC9EF8C00D84283 /* SentryLegacyProfiler.h in Headers */ = {isa = PBXBuildFile; fileRef = 84A305552BC9EF8C00D84283 /* SentryLegacyProfiler.h */; }; 84A305582BC9EF8C00D84283 /* SentryLegacyProfiler.mm in Sources */ = {isa = PBXBuildFile; fileRef = 84A305562BC9EF8C00D84283 /* SentryLegacyProfiler.mm */; }; 84A5D75B29D5170700388BFA /* TimeInterval+Sentry.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84A5D75A29D5170700388BFA /* TimeInterval+Sentry.swift */; }; @@ -1644,9 +1645,7 @@ 7DC27EC423997EB7006998B5 /* SentryAutoBreadcrumbTrackingIntegration.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SentryAutoBreadcrumbTrackingIntegration.m; sourceTree = ""; }; 7DC831082398283C0043DD9A /* SentryCrashIntegration.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = SentryCrashIntegration.h; path = include/SentryCrashIntegration.h; sourceTree = ""; }; 7DC831092398283C0043DD9A /* SentryCrashIntegration.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SentryCrashIntegration.m; sourceTree = ""; }; - 840A11092B5F47F700650D02 /* SentryAppLaunchProfilingTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SentryAppLaunchProfilingTests.m; sourceTree = ""; }; 840A11102B61E27500650D02 /* SentrySamplerDecision.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SentrySamplerDecision.m; sourceTree = ""; }; - 840A11142B62041600650D02 /* SentryLaunchProfiling+Tests.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = "SentryLaunchProfiling+Tests.h"; path = "../Tests/SentryTests/SentryLaunchProfiling+Tests.h"; sourceTree = ""; }; 840B7EEA2BBF2ABA008B8120 /* .slather.yml */ = {isa = PBXFileReference; lastKnownFileType = text.yaml; path = .slather.yml; sourceTree = ""; }; 840B7EEC2BBF2AFE008B8120 /* .gitattributes */ = {isa = PBXFileReference; lastKnownFileType = text; path = .gitattributes; sourceTree = ""; }; 840B7EED2BBF2B16008B8120 /* .pre-commit-config.yaml */ = {isa = PBXFileReference; lastKnownFileType = text.yaml; path = ".pre-commit-config.yaml"; sourceTree = ""; }; @@ -1654,6 +1653,7 @@ 840B7EEF2BBF2B2B008B8120 /* .spi.yml */ = {isa = PBXFileReference; lastKnownFileType = text.yaml; path = .spi.yml; sourceTree = ""; }; 840B7EF02BBF2B5F008B8120 /* MIGRATION.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = MIGRATION.md; sourceTree = ""; }; 840B7EF22BBF83DF008B8120 /* SentryProfiler+Private.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = "SentryProfiler+Private.h"; path = "Sources/Sentry/include/SentryProfiler+Private.h"; sourceTree = SOURCE_ROOT; }; + 841325C42BF49EC40029228F /* SentryLaunchProfiling+Tests.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "SentryLaunchProfiling+Tests.h"; sourceTree = ""; }; 8419C0C328C1889D001C8259 /* SentryLegacyProfilerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SentryLegacyProfilerTests.swift; sourceTree = ""; }; 84281C422A578E5600EE88F2 /* SentryProfilerState.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = SentryProfilerState.mm; sourceTree = ""; }; 84281C442A57905700EE88F2 /* SentrySample.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = SentrySample.h; path = ../include/SentrySample.h; sourceTree = ""; }; @@ -1721,6 +1721,7 @@ 849472822971C2CD002603DE /* SentryNSProcessInfoWrapperTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SentryNSProcessInfoWrapperTests.swift; sourceTree = ""; }; 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 = ""; }; + 84A305472BC72A0A00D84283 /* SentryAppLaunchProfilingTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SentryAppLaunchProfilingTests.swift; sourceTree = ""; }; 84A305552BC9EF8C00D84283 /* SentryLegacyProfiler.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = SentryLegacyProfiler.h; path = ../include/SentryLegacyProfiler.h; sourceTree = ""; }; 84A305562BC9EF8C00D84283 /* SentryLegacyProfiler.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = SentryLegacyProfiler.mm; sourceTree = ""; }; 84A305592BC9FD1600D84283 /* SentryLegacyProfiler+Test.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "SentryLegacyProfiler+Test.h"; sourceTree = ""; }; @@ -3404,7 +3405,7 @@ 035E73CB27D575B3005EEB11 /* SentrySamplingProfilerTests.mm */, 035E73CD27D5790A005EEB11 /* SentryThreadMetadataCacheTests.mm */, 03F9D37B2819A65C00602916 /* SentryProfilerTests.mm */, - 840A11092B5F47F700650D02 /* SentryAppLaunchProfilingTests.m */, + 84A305472BC72A0A00D84283 /* SentryAppLaunchProfilingTests.swift */, 8419C0C328C1889D001C8259 /* SentryLegacyProfilerTests.swift */, 8446F5182BE172290040D57E /* SentryContinuousProfilerTests.swift */, 8431D4522BE1741E009EAEC1 /* SentryProfileTestFixture.swift */, @@ -3426,10 +3427,10 @@ 8431F00B29B284F200D8DC56 /* SentryTestUtils */ = { isa = PBXGroup; children = ( + 841325C42BF49EC40029228F /* SentryLaunchProfiling+Tests.h */, 7B4C817124D1BC2B0076ACE4 /* SentryFileManager+Test.h */, 7B944FAD2469B43700A10721 /* TestHub.swift */, 7B7A30C924B48523005A4C6E /* SentryHub+Test.h */, - 840A11142B62041600650D02 /* SentryLaunchProfiling+Tests.h */, 7BD47B4C268F0B080076A663 /* ClearTestState.swift */, 7B18DE4328D9F8F6004845C6 /* TestNSNotificationCenterWrapper.swift */, 84AC61D829F7643B009EEF61 /* TestDispatchFactory.swift */, @@ -4150,6 +4151,7 @@ isa = PBXHeadersBuildPhase; buildActionMask = 2147483647; files = ( + 841325C52BF49EC40029228F /* SentryLaunchProfiling+Tests.h in Headers */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -4228,6 +4230,8 @@ 8431EED229B27B1100D8DC56 /* PBXTargetDependency */, ); name = SentryProfilerTests; + packageProductDependencies = ( + ); productName = "Tests-iOS"; productReference = 8431EFD929B27B1100D8DC56 /* SentryProfilerTests.xctest */; productType = "com.apple.product-type.bundle.unit-test"; @@ -4959,8 +4963,8 @@ 8431EFE129B27B5300D8DC56 /* SentryThreadMetadataCacheTests.mm in Sources */, 8431EFE029B27B5300D8DC56 /* SentryBacktraceTests.mm in Sources */, 8431EFDF29B27B5300D8DC56 /* SentryThreadHandleTests.mm in Sources */, - 840A11132B61FE5800650D02 /* SentryAppLaunchProfilingTests.m in Sources */, 8431EFE829B27BAD00D8DC56 /* SentrySystemWrapperTests.swift in Sources */, + 84A305492BC7328400D84283 /* SentryAppLaunchProfilingTests.swift in Sources */, 8431EFE529B27BAD00D8DC56 /* SentryNSProcessInfoWrapperTests.swift in Sources */, 8431EFDE29B27B5300D8DC56 /* SentryLegacyProfilerTests.swift in Sources */, ); diff --git a/SentryTestUtils/ClearTestState.swift b/SentryTestUtils/ClearTestState.swift index 465e3d45e1..5852225b19 100644 --- a/SentryTestUtils/ClearTestState.swift +++ b/SentryTestUtils/ClearTestState.swift @@ -42,6 +42,8 @@ class TestCleanup: NSObject { SentryLegacyProfiler.getCurrentProfiler()?.stop(for: SentryProfilerTruncationReason.normal) SentryLegacyProfiler.resetConcurrencyTracking() SentryContinuousProfiler.stop() + removeAppLaunchProfilingConfigFile() + sentry_stopAndDiscardLaunchProfileTracer() #endif // os(iOS) || os(macOS) || targetEnvironment(macCatalyst) #if os(iOS) || os(tvOS) || targetEnvironment(macCatalyst) diff --git a/SentryTestUtils/SentryLaunchProfiling+Tests.h b/SentryTestUtils/SentryLaunchProfiling+Tests.h new file mode 100644 index 0000000000..4403e65671 --- /dev/null +++ b/SentryTestUtils/SentryLaunchProfiling+Tests.h @@ -0,0 +1,46 @@ +#import "SentryLaunchProfiling.h" + +#if SENTRY_TARGET_PROFILING_SUPPORTED + +# import "SentryDefines.h" + +@class SentryOptions; +@class SentrySamplerDecision; +@class SentryTracer; + +NS_ASSUME_NONNULL_BEGIN + +typedef struct { + BOOL shouldProfile; + SentrySamplerDecision *_Nullable tracesDecision; + SentrySamplerDecision *_Nullable profilesDecision; +} SentryLaunchProfileConfig; + +SENTRY_EXTERN NSString *const kSentryLaunchProfileConfigKeyTracesSampleRate; +SENTRY_EXTERN NSString *const kSentryLaunchProfileConfigKeyProfilesSampleRate; +SENTRY_EXTERN NSString *const kSentryLaunchProfileConfigKeyContinuousProfiling; + +SENTRY_EXTERN SentryTracer *_Nullable sentry_launchTracer; + +SentryLaunchProfileConfig sentry_shouldProfileNextLaunch(SentryOptions *options); + +/** + * `sentry_shouldProfileNextLaunch` cannot be exposed to Swift tests because its return type is not + * expressible in Swift. This wraps it and only returns the `BOOL shouldProfile` value in the + * struct. + */ +BOOL sentry_willProfileNextLaunch(SentryOptions *options); + +/** + * Contains the logic to start a launch profile. Exposed separately from @c + * sentry_startLaunchProfile, because that function wraps everything in a @c dispatch_once , and + * that path is taken once when @c SenryProfiler.load is called at the start of the test suite, and + * can't be executed again by calling that function. + */ +void _sentry_nondeduplicated_startLaunchProfile(void); + +SentryTransactionContext *sentry_context(NSNumber *tracesRate); + +NS_ASSUME_NONNULL_END + +#endif // SENTRY_TARGET_PROFILING_SUPPORTED diff --git a/SentryTestUtils/SentryTestUtils-ObjC-BridgingHeader.h b/SentryTestUtils/SentryTestUtils-ObjC-BridgingHeader.h index 7b017f8a46..a9197a8e94 100644 --- a/SentryTestUtils/SentryTestUtils-ObjC-BridgingHeader.h +++ b/SentryTestUtils/SentryTestUtils-ObjC-BridgingHeader.h @@ -18,6 +18,7 @@ #if SENTRY_TARGET_PROFILING_SUPPORTED # import "SentryContinuousProfiler.h" +# import "SentryLaunchProfiling.h" # import "SentryLegacyProfiler+Test.h" # import "SentryProfiler+Private.h" #endif // SENTRY_TARGET_PROFILING_SUPPORTED diff --git a/Sources/Sentry/Profiling/SentryLaunchProfiling.m b/Sources/Sentry/Profiling/SentryLaunchProfiling.m index d7edd1b860..61e6b4eebf 100644 --- a/Sources/Sentry/Profiling/SentryLaunchProfiling.m +++ b/Sources/Sentry/Profiling/SentryLaunchProfiling.m @@ -2,13 +2,14 @@ #if SENTRY_TARGET_PROFILING_SUPPORTED +# import "SentryContinuousProfiler.h" # import "SentryDependencyContainer.h" # import "SentryDispatchQueueWrapper.h" # import "SentryFileManager.h" # import "SentryInternalDefines.h" # import "SentryLaunchProfiling.h" # import "SentryLog.h" -# import "SentryOptions.h" +# import "SentryOptions+Private.h" # import "SentryProfiler+Private.h" # import "SentryRandom.h" # import "SentrySamplerDecision.h" @@ -23,9 +24,13 @@ NS_ASSUME_NONNULL_BEGIN +NSString *const kSentryLaunchProfileConfigKeyTracesSampleRate = @"traces"; +NSString *const kSentryLaunchProfileConfigKeyProfilesSampleRate = @"profiles"; +NSString *const kSentryLaunchProfileConfigKeyContinuousProfiling = @"continuous-profiling"; + # pragma mark - Private -static SentryTracer *_Nullable sentry_launchTracer; +SentryTracer *_Nullable sentry_launchTracer; SentryTracerConfiguration * sentry_config(NSNumber *profilesRate) @@ -41,16 +46,18 @@ typedef struct { BOOL shouldProfile; + /** Only needed for legacy launch profiling; unused with continuous profiling. */ SentrySamplerDecision *_Nullable tracesDecision; SentrySamplerDecision *_Nullable profilesDecision; } SentryLaunchProfileConfig; -NSString *const kSentryLaunchProfileConfigKeyTracesSampleRate = @"traces"; -NSString *const kSentryLaunchProfileConfigKeyProfilesSampleRate = @"profiles"; - SentryLaunchProfileConfig sentry_shouldProfileNextLaunch(SentryOptions *options) { + if (options.enableAppLaunchProfiling && options.enableContinuousProfiling) { + return (SentryLaunchProfileConfig) { YES, nil, nil }; + } + BOOL shouldProfileNextLaunch = options.enableAppLaunchProfiling && options.enableTracing; if (!shouldProfileNextLaunch) { SENTRY_LOG_DEBUG(@"Won't profile next launch due to specified options configuration: " @@ -74,11 +81,11 @@ SentrySamplerDecision *profilesSamplerDecision = sentry_sampleTraceProfile(context, tracesSamplerDecision, options); if (profilesSamplerDecision.decision != kSentrySampleDecisionYes) { - SENTRY_LOG_DEBUG(@"Sampling out the launch profile."); + SENTRY_LOG_DEBUG(@"Sampling out the launch legacy profile."); return (SentryLaunchProfileConfig) { NO, nil, nil }; } - SENTRY_LOG_DEBUG(@"Will profile the next launch."); + SENTRY_LOG_DEBUG(@"Will start legacy profile next launch."); return (SentryLaunchProfileConfig) { YES, tracesSamplerDecision, profilesSamplerDecision }; } @@ -95,6 +102,60 @@ return context; } +# pragma mark - Testing only + +# if defined(TEST) || defined(TESTCI) || defined(DEBUG) +BOOL +sentry_willProfileNextLaunch(SentryOptions *options) +{ + return sentry_shouldProfileNextLaunch(options).shouldProfile; +} +# endif // defined(TEST) || defined(TESTCI) || defined(DEBUG) + +# pragma mark - Exposed only to tests + +void +_sentry_nondeduplicated_startLaunchProfile(void) +{ + if (!appLaunchProfileConfigFileExists()) { + return; + } + +# if defined(DEBUG) + // quick and dirty way to get debug logging this early in the process run. this will get + // overwritten once SentrySDK.startWithOptions is called according to the values of + // SentryOptions.debug and SentryOptions.diagnosticLevel + [SentryLog configure:YES diagnosticLevel:kSentryLevelDebug]; +# endif // defined(DEBUG) + + NSDictionary *launchConfig = appLaunchProfileConfiguration(); + if ([launchConfig[kSentryLaunchProfileConfigKeyContinuousProfiling] boolValue]) { + [SentryContinuousProfiler start]; + return; + } + + NSNumber *profilesRate = launchConfig[kSentryLaunchProfileConfigKeyProfilesSampleRate]; + if (profilesRate == nil) { + SENTRY_LOG_DEBUG(@"Received a nil configured launch profile sample rate, will not " + @"start trace profiler for launch."); + return; + } + + NSNumber *tracesRate = launchConfig[kSentryLaunchProfileConfigKeyTracesSampleRate]; + if (tracesRate == nil) { + SENTRY_LOG_DEBUG(@"Received a nil configured launch trace sample rate, will not start " + @"trace profiler for launch."); + return; + } + + SENTRY_LOG_INFO(@"Starting app launch trace profile at %llu.", getAbsoluteTime()); + sentry_isTracingAppLaunch = YES; + sentry_launchTracer = + [[SentryTracer alloc] initWithTransactionContext:sentry_context(tracesRate) + hub:nil + configuration:sentry_config(profilesRate)]; +} + # pragma mark - Public BOOL sentry_isTracingAppLaunch; @@ -111,8 +172,12 @@ NSMutableDictionary *configDict = [NSMutableDictionary dictionary]; - configDict[kSentryLaunchProfileConfigKeyTracesSampleRate] - = config.tracesDecision.sampleRate; + if (options.enableContinuousProfiling) { + configDict[kSentryLaunchProfileConfigKeyContinuousProfiling] = @YES; + } else { + configDict[kSentryLaunchProfileConfigKeyTracesSampleRate] + = config.tracesDecision.sampleRate; + } configDict[kSentryLaunchProfileConfigKeyProfilesSampleRate] = config.profilesDecision.sampleRate; writeAppLaunchProfilingConfigFile(configDict); @@ -122,39 +187,11 @@ void sentry_startLaunchProfile(void) { - static dispatch_once_t onceToken; // this function is called from SentryTracer.load but in the future we may expose access // directly to customers, and we'll want to ensure it only runs once. dispatch_once is an // efficient operation so it's fine to leave this in the launch path in any case. - dispatch_once(&onceToken, ^{ - sentry_isTracingAppLaunch = appLaunchProfileConfigFileExists(); - if (!sentry_isTracingAppLaunch) { - return; - } - -# if defined(DEBUG) - // quick and dirty way to get debug logging this early in the process run. this will get - // overwritten once SentrySDK.startWithOptions is called according to the values of - // SentryOptions.debug and SentryOptions.diagnosticLevel - [SentryLog configure:YES diagnosticLevel:kSentryLevelDebug]; -# endif // defined(DEBUG) - - NSDictionary *rates = appLaunchProfileConfiguration(); - NSNumber *profilesRate = rates[kSentryLaunchProfileConfigKeyProfilesSampleRate]; - NSNumber *tracesRate = rates[kSentryLaunchProfileConfigKeyTracesSampleRate]; - if (profilesRate == nil || tracesRate == nil) { - SENTRY_LOG_DEBUG( - @"Received a nil configured launch sample rate, will not trace or profile."); - return; - } - - SENTRY_LOG_INFO(@"Starting app launch profile at %llu.", getAbsoluteTime()); - sentry_launchTracer = - [[SentryTracer alloc] initWithTransactionContext:sentry_context(tracesRate) - hub:nil - configuration:sentry_config(profilesRate)]; - }); + dispatch_once(&onceToken, ^{ _sentry_nondeduplicated_startLaunchProfile(); }); } void @@ -174,6 +211,8 @@ { SENTRY_LOG_DEBUG(@"Finishing launch tracer."); [sentry_launchTracer finish]; + sentry_isTracingAppLaunch = NO; + sentry_launchTracer = nil; } NS_ASSUME_NONNULL_END diff --git a/Sources/Sentry/Profiling/SentryProfilerTestHelpers.m b/Sources/Sentry/Profiling/SentryProfilerTestHelpers.m index f1eb44fef1..48f76c6989 100644 --- a/Sources/Sentry/Profiling/SentryProfilerTestHelpers.m +++ b/Sources/Sentry/Profiling/SentryProfilerTestHelpers.m @@ -44,24 +44,15 @@ SENTRY_LOG_DEBUG(@"App support directory already exists."); } - NSString *pathToWrite; - if (sentry_isTracingAppLaunch) { - SENTRY_LOG_DEBUG(@"Writing app launch profile."); - pathToWrite = [appSupportDirPath stringByAppendingPathComponent:@"launchProfile"]; - } else { - SENTRY_LOG_DEBUG(@"Overwriting last non-launch profile."); - pathToWrite = [appSupportDirPath stringByAppendingPathComponent:@"profile"]; - } + NSString *pathToWrite = [appSupportDirPath stringByAppendingPathComponent:@"profile"]; if ([fm fileExistsAtPath:pathToWrite]) { - SENTRY_LOG_DEBUG(@"Already a %@ profile file present; make sure to remove them right after " + SENTRY_LOG_DEBUG(@"Already a profile file present; make sure to remove them right after " @"using them, and that tests clean state in between so there isn't " - @"leftover config producing one when it isn't expected.", - sentry_isTracingAppLaunch ? @" launch" : @""); - return; + @"leftover config producing one when it isn't expected."); } - SENTRY_LOG_DEBUG(@"Writing%@ profile to file.", sentry_isTracingAppLaunch ? @" launch" : @""); + SENTRY_LOG_DEBUG(@"Writing profile to file."); NSError *error; if (![data writeToFile:pathToWrite options:NSDataWritingAtomic error:&error]) { diff --git a/Sources/Sentry/SentryOptions.m b/Sources/Sentry/SentryOptions.m index e6ca2aaa6f..b74ce1620d 100644 --- a/Sources/Sentry/SentryOptions.m +++ b/Sources/Sentry/SentryOptions.m @@ -561,7 +561,7 @@ - (void)setSampleRate:(NSNumber *)sampleRate { if (sampleRate == nil) { _sampleRate = nil; - } else if (isValidSampleRate(sampleRate)) { + } else if (sentry_isValidSampleRate(sampleRate)) { _sampleRate = sampleRate; } else { _sampleRate = SENTRY_DEFAULT_SAMPLE_RATE; @@ -569,7 +569,7 @@ - (void)setSampleRate:(NSNumber *)sampleRate } BOOL -isValidSampleRate(NSNumber *sampleRate) +sentry_isValidSampleRate(NSNumber *sampleRate) { double rate = [sampleRate doubleValue]; return rate >= 0 && rate <= 1.0; @@ -592,7 +592,7 @@ - (void)setTracesSampleRate:(NSNumber *)tracesSampleRate { if (tracesSampleRate == nil) { _tracesSampleRate = nil; - } else if (isValidSampleRate(tracesSampleRate)) { + } else if (sentry_isValidSampleRate(tracesSampleRate)) { _tracesSampleRate = tracesSampleRate; if (!_enableTracingManual) { _enableTracing = YES; @@ -622,7 +622,7 @@ - (void)setProfilesSampleRate:(NSNumber *)profilesSampleRate { if (profilesSampleRate == nil) { _profilesSampleRate = nil; - } else if (isValidSampleRate(profilesSampleRate)) { + } else if (sentry_isValidSampleRate(profilesSampleRate)) { _profilesSampleRate = profilesSampleRate; } else { _profilesSampleRate = SENTRY_DEFAULT_PROFILES_SAMPLE_RATE; diff --git a/Sources/Sentry/SentryProfiler.mm b/Sources/Sentry/SentryProfiler.mm index e2bcef907c..a7e3a38867 100644 --- a/Sources/Sentry/SentryProfiler.mm +++ b/Sources/Sentry/SentryProfiler.mm @@ -40,23 +40,20 @@ void sentry_manageTraceProfilerOnStartSDK(SentryOptions *options, SentryHub *hub) { - if (!options.enableContinuousProfiling) { - BOOL shouldStopAndTransmitLaunchProfile = YES; + [SentryDependencyContainer.sharedInstance.dispatchQueueWrapper dispatchAsyncWithBlock:^{ + BOOL shouldStopAndTransmitLaunchProfile = !options.enableContinuousProfiling; # if SENTRY_HAS_UIKIT if (SentryUIViewControllerPerformanceTracker.shared.enableWaitForFullDisplay) { shouldStopAndTransmitLaunchProfile = NO; } # endif // SENTRY_HAS_UIKIT - - [SentryDependencyContainer.sharedInstance.dispatchQueueWrapper dispatchAsyncWithBlock:^{ - if (shouldStopAndTransmitLaunchProfile) { - SENTRY_LOG_DEBUG(@"Stopping launch profile in SentrySDK.start because there will " - @"be no automatic trace to attach it to."); - sentry_stopAndTransmitLaunchProfile(hub); - } - sentry_configureLaunchProfiling(options); - }]; - } + if (shouldStopAndTransmitLaunchProfile) { + SENTRY_LOG_DEBUG(@"Stopping launch profile in SentrySDK.start because there will " + @"be no automatic trace to attach it to."); + sentry_stopAndTransmitLaunchProfile(hub); + } + sentry_configureLaunchProfiling(options); + }]; } @implementation SentryProfiler { @@ -160,6 +157,7 @@ - (void)backgroundAbort - (void)stopForReason:(SentryProfilerTruncationReason)reason { + sentry_isTracingAppLaunch = NO; [_timeoutTimer invalidate]; [self.metricProfiler stop]; self.truncationReason = reason; diff --git a/Sources/Sentry/SentrySampling.m b/Sources/Sentry/SentrySampling.m index 4fc6de1942..01307e322b 100644 --- a/Sources/Sentry/SentrySampling.m +++ b/Sources/Sentry/SentrySampling.m @@ -17,7 +17,7 @@ * @return A sample rate if the specified sampler callback was defined on @c SentryOptions and * returned a valid value, @c nil otherwise. */ -NSNumber *_Nullable samplerCallbackRate(SentryTracesSamplerCallback _Nullable callback, +NSNumber *_Nullable _sentry_samplerCallbackRate(SentryTracesSamplerCallback _Nullable callback, SentrySamplingContext *context, NSNumber *defaultSampleRate) { if (callback == nil) { @@ -25,7 +25,7 @@ } NSNumber *callbackRate = callback(context); - if (!isValidSampleRate(callbackRate)) { + if (!sentry_isValidSampleRate(callbackRate)) { return defaultSampleRate; } @@ -33,7 +33,7 @@ } SentrySamplerDecision * -calcSample(NSNumber *rate) +_sentry_calcSample(NSNumber *rate) { double random = [SentryDependencyContainer.sharedInstance.random nextNumber]; SentrySampleDecision decision @@ -42,14 +42,14 @@ } SentrySamplerDecision * -calcSampleFromNumericalRate(NSNumber *rate) +_sentry_calcSampleFromNumericalRate(NSNumber *rate) { if (rate == nil) { return [[SentrySamplerDecision alloc] initWithDecision:kSentrySampleDecisionNo forSampleRate:nil]; } - return calcSample(rate); + return _sentry_calcSample(rate); } #pragma mark - Public @@ -64,10 +64,10 @@ forSampleRate:context.transactionContext.sampleRate]; } - NSNumber *callbackRate - = samplerCallbackRate(options.tracesSampler, context, SENTRY_DEFAULT_TRACES_SAMPLE_RATE); + NSNumber *callbackRate = _sentry_samplerCallbackRate( + options.tracesSampler, context, SENTRY_DEFAULT_TRACES_SAMPLE_RATE); if (callbackRate != nil) { - return calcSample(callbackRate); + return _sentry_calcSample(callbackRate); } // check the _parent_ transaction's sampling decision, if any @@ -77,7 +77,7 @@ forSampleRate:context.transactionContext.sampleRate]; } - return calcSampleFromNumericalRate(options.tracesSampleRate); + return _sentry_calcSampleFromNumericalRate(options.tracesSampleRate); } #if SENTRY_TARGET_PROFILING_SUPPORTED @@ -103,13 +103,13 @@ } # pragma clang diagnostic pop - NSNumber *callbackRate = samplerCallbackRate( + NSNumber *callbackRate = _sentry_samplerCallbackRate( options.profilesSampler, context, SENTRY_DEFAULT_PROFILES_SAMPLE_RATE); if (callbackRate != nil) { - return calcSample(callbackRate); + return _sentry_calcSample(callbackRate); } - return calcSampleFromNumericalRate(options.profilesSampleRate); + return _sentry_calcSampleFromNumericalRate(options.profilesSampleRate); } #endif // SENTRY_TARGET_PROFILING_SUPPORTED diff --git a/Sources/Sentry/SentryTimeToDisplayTracker.m b/Sources/Sentry/SentryTimeToDisplayTracker.m index 3a2c6ac4c6..dbc68ef993 100644 --- a/Sources/Sentry/SentryTimeToDisplayTracker.m +++ b/Sources/Sentry/SentryTimeToDisplayTracker.m @@ -7,7 +7,9 @@ # import "SentryFramesTracker.h" # import "SentryLog.h" # import "SentryMeasurementValue.h" +# import "SentryOptions+Private.h" # import "SentryProfilingConditionals.h" +# import "SentrySDK+Private.h" # import "SentrySpan.h" # import "SentrySpanContext.h" # import "SentrySpanId.h" @@ -140,7 +142,9 @@ - (void)framesTrackerHasNewFrame:(NSDate *)newFrameDate if (!_waitForFullDisplay) { [SentryDependencyContainer.sharedInstance.framesTracker removeListener:self]; # if SENTRY_TARGET_PROFILING_SUPPORTED - sentry_stopAndDiscardLaunchProfileTracer(); + if (!SentrySDK.options.enableContinuousProfiling) { + sentry_stopAndDiscardLaunchProfileTracer(); + } # endif // SENTRY_TARGET_PROFILING_SUPPORTED } } @@ -150,7 +154,9 @@ - (void)framesTrackerHasNewFrame:(NSDate *)newFrameDate self.fullDisplaySpan.timestamp = newFrameDate; [self.fullDisplaySpan finish]; # if SENTRY_TARGET_PROFILING_SUPPORTED - sentry_stopAndDiscardLaunchProfileTracer(); + if (!SentrySDK.options.enableContinuousProfiling) { + sentry_stopAndDiscardLaunchProfileTracer(); + } # endif // SENTRY_TARGET_PROFILING_SUPPORTED } diff --git a/Sources/Sentry/SentryTracer.m b/Sources/Sentry/SentryTracer.m index 52b79bb04c..02611fc39e 100644 --- a/Sources/Sentry/SentryTracer.m +++ b/Sources/Sentry/SentryTracer.m @@ -109,13 +109,6 @@ @implementation SentryTracer { static NSObject *appStartMeasurementLock; static BOOL appStartMeasurementRead; -#if SENTRY_TARGET_PROFILING_SUPPORTED -+ (void)load -{ - sentry_startLaunchProfile(); -} -#endif // SENTRY_TARGET_PROFILING_SUPPORTED - + (void)initialize { if (self == [SentryTracer class]) { diff --git a/Sources/Sentry/include/HybridPublic/SentryDependencyContainer.h b/Sources/Sentry/include/HybridPublic/SentryDependencyContainer.h index d9552fd218..e3c5a03b59 100644 --- a/Sources/Sentry/include/HybridPublic/SentryDependencyContainer.h +++ b/Sources/Sentry/include/HybridPublic/SentryDependencyContainer.h @@ -19,7 +19,6 @@ @class SentrySystemWrapper; @class SentryThreadWrapper; @class SentryThreadInspector; -@class SentryOptions; @protocol SentryRandom; #if SENTRY_HAS_METRIC_KIT diff --git a/Sources/Sentry/include/SentryLaunchProfiling.h b/Sources/Sentry/include/SentryLaunchProfiling.h index b02e1a7742..c7d5c3d157 100644 --- a/Sources/Sentry/include/SentryLaunchProfiling.h +++ b/Sources/Sentry/include/SentryLaunchProfiling.h @@ -13,6 +13,13 @@ NS_ASSUME_NONNULL_BEGIN +/** + * Whether or not the profiler started with the app launch. With legacy profiling, this means there + * is a tracer managing the profile that will eventually need to be stopped and either discarded (in + * the case of auto performance transactions) or also transmitted. With continuous profiling, this + * indicates whether or not the profiler that's currently running was started from app launch, or + * later with a manual profiler start from the SDK consumer. + */ SENTRY_EXTERN BOOL sentry_isTracingAppLaunch; /** Try to start a profiled trace for this app launch, if the configuration allows. */ diff --git a/Sources/Sentry/include/SentryOptions+Private.h b/Sources/Sentry/include/SentryOptions+Private.h index 848db06b3a..57fe707685 100644 --- a/Sources/Sentry/include/SentryOptions+Private.h +++ b/Sources/Sentry/include/SentryOptions+Private.h @@ -15,7 +15,7 @@ SentryOptions () @property (nonatomic, assign) BOOL enableContinuousProfiling; #endif // SENTRY_TARGET_PROFILING_SUPPORTED -SENTRY_EXTERN BOOL isValidSampleRate(NSNumber *sampleRate); +SENTRY_EXTERN BOOL sentry_isValidSampleRate(NSNumber *sampleRate); @end diff --git a/Sources/Sentry/include/SentrySampling.h b/Sources/Sentry/include/SentrySampling.h index a1f5f8348e..761b0e99f9 100644 --- a/Sources/Sentry/include/SentrySampling.h +++ b/Sources/Sentry/include/SentrySampling.h @@ -17,7 +17,8 @@ SENTRY_EXTERN SentrySamplerDecision *sentry_sampleTrace( #if SENTRY_TARGET_PROFILING_SUPPORTED /** * Determines whether a profile should be sampled based on the context, options, and - * whether the trace corresponding to the profile was sampled. + * whether the trace corresponding to the profile was sampled, to decide whether to configure the + * next launch to start a legacy profile. */ SENTRY_EXTERN SentrySamplerDecision *sentry_sampleTraceProfile(SentrySamplingContext *context, SentrySamplerDecision *tracesSamplerDecision, SentryOptions *options); diff --git a/Tests/SentryProfilerTests/SentryAppLaunchProfilingTests.m b/Tests/SentryProfilerTests/SentryAppLaunchProfilingTests.m deleted file mode 100644 index 1c91feea19..0000000000 --- a/Tests/SentryProfilerTests/SentryAppLaunchProfilingTests.m +++ /dev/null @@ -1,152 +0,0 @@ -#import "SentryLaunchProfiling+Tests.h" -#import "SentryOptions+HybridSDKs.h" -#import "SentryOptions+Private.h" -#import "SentryProfilingConditionals.h" -#import "SentrySDK+Tests.h" -#import "SentryTraceOrigins.h" -#import "SentryTransactionContext.h" -#import - -#if SENTRY_TARGET_PROFILING_SUPPORTED - -@interface SentryAppLaunchProfilingTests : XCTestCase -@end - -@implementation SentryAppLaunchProfilingTests - -- (void)testLaunchProfileTransactionContext -{ - SentryTransactionContext *actualContext = sentry_context(@1); - XCTAssertEqual(actualContext.nameSource, kSentryTransactionNameSourceCustom); - XCTAssert([actualContext.origin isEqualToString:SentryTraceOriginAutoAppStartProfile]); - XCTAssert(actualContext.sampled); -} - -# define SENTRY_OPTION(name, value) \ - NSStringFromSelector(@selector(name)) \ - : value - -- (void)testDefaultOptionsDoNotEnableLaunchProfiling -{ - XCTAssertFalse( - sentry_shouldProfileNextLaunch([self defaultOptionsWithOverrides:nil]).shouldProfile, - @"Default options should not enable launch profiling"); -} - -- (void)testAppLaunchProfilingNotSufficientToEnableLaunchProfiling -{ - XCTAssertFalse( - sentry_shouldProfileNextLaunch( - [self defaultOptionsWithOverrides:@{ SENTRY_OPTION(enableAppLaunchProfiling, @YES) }]) - .shouldProfile, - @"Default options with only launch profiling option set is not sufficient to enable launch " - @"profiling"); -} - -- (void)testAppLaunchProfilingAndTracingOptionsNotSufficientToEnableAppLaunchProfiling -{ - XCTAssertFalse( - sentry_shouldProfileNextLaunch( - [self defaultOptionsWithOverrides:@{ SENTRY_OPTION(enableAppLaunchProfiling, @YES), - SENTRY_OPTION(enableTracing, @YES) }]) - .shouldProfile, - @"Default options with app launch profiling and tracing enabled are not sufficient to " - @"enable launch profiling"); -} - -- (void) - testAppLaunchProfilingAndTracingAndTracesSampleRateOptionsNotSufficientToEnableAppLaunchProfiling -{ - XCTAssertFalse( - sentry_shouldProfileNextLaunch( - [self defaultOptionsWithOverrides:@{ SENTRY_OPTION(enableAppLaunchProfiling, @YES), - SENTRY_OPTION(enableTracing, @YES), - SENTRY_OPTION(tracesSampleRate, @1) }]) - .shouldProfile, - @"Default options with app launch profiling and tracing enabled with traces sample rate of " - @"1 are not sufficient to enable launch profiling"); -} - -- (void)testMinimumOptionsRequiredToEnableAppLaunchProfiling -{ - XCTAssert(sentry_shouldProfileNextLaunch([self defaultLaunchProfilingOptionsWithOverrides:nil]) - .shouldProfile, - @"Default options with app launch profiling and tracing enabled and traces and profiles " - @"sample rates of 1 should enable launch profiling"); -} - -- (void)testDisablingLaunchProfilingOptionDisablesAppLaunchProfiling -{ - XCTAssertFalse( - sentry_shouldProfileNextLaunch( - [self defaultLaunchProfilingOptionsWithOverrides:@{ SENTRY_OPTION( - enableAppLaunchProfiling, @NO) }]) - .shouldProfile, - @"Default options with tracing enabled, traces and profiles sample rates of 1, but app " - @"launch profiling disabled should not enable launch profiling"); -} - -- (void)testDisablingTracingOptionDisablesAppLaunchProfiling -{ - XCTAssertFalse(sentry_shouldProfileNextLaunch( - [self defaultLaunchProfilingOptionsWithOverrides:@{ SENTRY_OPTION( - enableTracing, @NO) }]) - .shouldProfile, - @"Default options with app launch profiling enabled, traces and profiles sample rates of " - @"1, but tracing disabled should not enable launch profiling"); -} - -- (void)testSettingTracesSampleRateTo0DisablesAppLaunchProfiling -{ - XCTAssertFalse( - sentry_shouldProfileNextLaunch( - [self defaultLaunchProfilingOptionsWithOverrides:@{ SENTRY_OPTION( - tracesSampleRate, @0) }]) - .shouldProfile, - @"Default options with app launch profiling and tracing enabled, profiles sample rate of " - @"1, but traces sample rate of 0 should not enable launch profiling"); -} - -- (void)testSettingProfilesSampleRateTo0DisablesAppLaunchProfiling -{ - XCTAssertFalse( - sentry_shouldProfileNextLaunch( - [self defaultLaunchProfilingOptionsWithOverrides:@{ SENTRY_OPTION( - profilesSampleRate, @0) }]) - .shouldProfile, - @"Default options with app launch profiling and tracing enabled, traces sample rate of 1, " - @"but profiles sample rate of 0 should not enable launch profiling"); -} - -# pragma mark - Private - -- (SentryOptions *)defaultLaunchProfilingOptionsWithOverrides: - (NSDictionary *)overrides -{ - NSMutableDictionary *options = [NSMutableDictionary - dictionaryWithDictionary:@{ SENTRY_OPTION(enableAppLaunchProfiling, @YES), - SENTRY_OPTION(enableTracing, @YES), - SENTRY_OPTION(tracesSampleRate, @1), - SENTRY_OPTION(profilesSampleRate, @1) }]; - [options addEntriesFromDictionary:overrides]; - return [self defaultOptionsWithOverrides:options]; -} - -- (SentryOptions *)defaultOptionsWithOverrides:(nullable NSDictionary *)overrides -{ - NSMutableDictionary *options = [NSMutableDictionary - dictionaryWithObject:@"https://username:password@sentry.io/1" - forKey:@"dsn"]; - [options addEntriesFromDictionary:overrides]; - NSError *error; - SentryOptions *sentryOptions = [[SentryOptions alloc] initWithDict:options - didFailWithError:&error]; - XCTAssertNil(error); - return sentryOptions; -} - -# undef SENTRY_OPTION - -@end - -#endif // SENTRY_TARGET_PROFILING_SUPPORTED diff --git a/Tests/SentryProfilerTests/SentryAppLaunchProfilingTests.swift b/Tests/SentryProfilerTests/SentryAppLaunchProfilingTests.swift new file mode 100644 index 0000000000..744fea0fa2 --- /dev/null +++ b/Tests/SentryProfilerTests/SentryAppLaunchProfilingTests.swift @@ -0,0 +1,204 @@ +import SentryTestUtils +import XCTest + +#if os(iOS) || os(macOS) || targetEnvironment(macCatalyst) +final class SentryAppLaunchProfilingSwiftTests: XCTestCase { + var fixture: SentryProfileTestFixture! + + override func setUp() { + super.setUp() + fixture = SentryProfileTestFixture() + } + + override func tearDown() { + super.tearDown() + clearTestState() + } + + func testContentsOfLegacyLaunchProfileTransactionContext() { + let context = sentry_context(NSNumber(value: 1)) + XCTAssertEqual(context.nameSource.rawValue, 0) + XCTAssertEqual(context.origin, "auto.app.start.profile") + XCTAssertEqual(context.sampled, .yes) + } + + // test that the launch trace instance is nil after stopping the launch + // profiler + func testStopLaunchTraceProfile() { + fixture.options.enableAppLaunchProfiling = true + fixture.options.profilesSampleRate = 1 + fixture.options.tracesSampleRate = 1 + sentry_configureLaunchProfiling(fixture.options) + _sentry_nondeduplicated_startLaunchProfile() + XCTAssertNotNil(sentry_launchTracer) + sentry_manageTraceProfilerOnStartSDK(fixture.options, TestHub(client: nil, andScope: nil)) + XCTAssertNil(sentry_launchTracer) + } + + func testLaunchTraceProfileConfiguration() throws { + let expectedProfilesSampleRate: NSNumber = 0.567 + let expectedTracesSampleRate: NSNumber = 0.789 + let options = Options() + options.enableAppLaunchProfiling = true + options.profilesSampleRate = expectedProfilesSampleRate + options.tracesSampleRate = expectedTracesSampleRate + XCTAssertFalse(appLaunchProfileConfigFileExists()) + sentry_manageTraceProfilerOnStartSDK(options, TestHub(client: nil, andScope: nil)) + XCTAssert(appLaunchProfileConfigFileExists()) + let dict = try XCTUnwrap(appLaunchProfileConfiguration()) + XCTAssertEqual(dict[kSentryLaunchProfileConfigKeyTracesSampleRate], expectedTracesSampleRate) + XCTAssertEqual(dict[kSentryLaunchProfileConfigKeyProfilesSampleRate], expectedProfilesSampleRate) + } + + // test that after configuring for a launch profile, a subsequent + // configuration with insufficient sample rates removes the configuration + // file + func testLaunchTraceProfileConfigurationRemoval() { + 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()) + options.profilesSampleRate = 0 + sentry_manageTraceProfilerOnStartSDK(options, TestHub(client: nil, andScope: nil)) + XCTAssertFalse(appLaunchProfileConfigFileExists()) + // ensure we get another config written, to test removal again + options.profilesSampleRate = 0.567 + sentry_manageTraceProfilerOnStartSDK(options, TestHub(client: nil, andScope: nil)) + XCTAssert(appLaunchProfileConfigFileExists()) + options.tracesSampleRate = 0 + sentry_manageTraceProfilerOnStartSDK(options, TestHub(client: nil, andScope: nil)) + XCTAssertFalse(appLaunchProfileConfigFileExists()) + } + + // test continuous launch profiling configuration + func testContinuousLaunchProfileConfiguration() throws { + let options = Options() + options.enableAppLaunchProfiling = true + options.enableContinuousProfiling = true + + // sample rates are not considered for continuous profiling + options.profilesSampleRate = 0 + options.tracesSampleRate = 0 + + XCTAssertFalse(appLaunchProfileConfigFileExists()) + sentry_manageTraceProfilerOnStartSDK(options, TestHub(client: nil, andScope: nil)) + XCTAssert(appLaunchProfileConfigFileExists()) + let dict = try XCTUnwrap(appLaunchProfileConfiguration()) + XCTAssertEqual(dict[kSentryLaunchProfileConfigKeyContinuousProfiling], true) + + _sentry_nondeduplicated_startLaunchProfile() + XCTAssert(SentryContinuousProfiler.isCurrentlyProfiling()) + } + + func testTraceProfilerStartsWhenBothSampleRatesAreSet() { + let options = Options() + options.enableAppLaunchProfiling = true + options.profilesSampleRate = 0.567 + options.tracesSampleRate = 0.789 + XCTAssertFalse(appLaunchProfileConfigFileExists()) + sentry_manageTraceProfilerOnStartSDK(options, TestHub(client: nil, andScope: nil)) + XCTAssertTrue(appLaunchProfileConfigFileExists()) + _sentry_nondeduplicated_startLaunchProfile() + XCTAssert(SentryLegacyProfiler.isCurrentlyProfiling()) + } + + /** + * 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` + */ + func testShouldProfileLaunchBasedOnOptionsCombinations() { + for testCase: (enableAppLaunchProfiling: Bool, enableTracing: Bool, enableContinuousProfiling: 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), + // change profilesSampleRate to 1 + (enableAppLaunchProfiling: false, enableTracing: false, enableContinuousProfiling: 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), + // 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), + // 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), + // 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) + ] { + 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) + } + 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) }") + } + } +} +#endif // os(iOS) || os(macOS) || targetEnvironment(macCatalyst) diff --git a/Tests/SentryProfilerTests/SentryLegacyProfilerTests.swift b/Tests/SentryProfilerTests/SentryLegacyProfilerTests.swift index 1ad06d96a3..1827c85ba3 100644 --- a/Tests/SentryProfilerTests/SentryLegacyProfilerTests.swift +++ b/Tests/SentryProfilerTests/SentryLegacyProfilerTests.swift @@ -624,7 +624,9 @@ private extension SentryLegacyProfilerTests { options(fixtureOptions) let span = try fixture.newTransaction() - try addMockSamples() + if expectedDecision == .yes { + try addMockSamples() + } fixture.currentDateProvider.advance(by: 5) span.finish() diff --git a/Tests/SentryTests/SentryLaunchProfiling+Tests.h b/Tests/SentryTests/SentryLaunchProfiling+Tests.h deleted file mode 100644 index 3dd4556eb1..0000000000 --- a/Tests/SentryTests/SentryLaunchProfiling+Tests.h +++ /dev/null @@ -1,27 +0,0 @@ -#import "SentryLaunchProfiling.h" - -#if SENTRY_TARGET_PROFILING_SUPPORTED - -# import "SentryDefines.h" - -@class SentrySamplerDecision; -@class SentryOptions; - -NS_ASSUME_NONNULL_BEGIN - -typedef struct { - BOOL shouldProfile; - SentrySamplerDecision *_Nullable tracesDecision; - SentrySamplerDecision *_Nullable profilesDecision; -} SentryLaunchProfileConfig; - -SENTRY_EXTERN NSString *const kSentryLaunchProfileConfigKeyTracesSampleRate; -SENTRY_EXTERN NSString *const kSentryLaunchProfileConfigKeyProfilesSampleRate; - -SentryLaunchProfileConfig sentry_shouldProfileNextLaunch(SentryOptions *options); - -SentryTransactionContext *sentry_context(NSNumber *tracesRate); - -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 7001475c7e..013d595f51 100644 --- a/Tests/SentryTests/SentryTests-Bridging-Header.h +++ b/Tests/SentryTests/SentryTests-Bridging-Header.h @@ -219,6 +219,7 @@ #import "SentryTime.h" #import "SentryTimeToDisplayTracker.h" #import "SentryTraceContext.h" +#import "SentryTraceOrigins.h" #import "SentryTracer+Private.h" #import "SentryTracer+Test.h" #import "SentryTracerConfiguration.h"