diff --git a/sentry-android-core/src/main/java/io/sentry/android/core/ManifestMetadataReader.java b/sentry-android-core/src/main/java/io/sentry/android/core/ManifestMetadataReader.java index e85e5de9234..bf1eb79be84 100644 --- a/sentry-android-core/src/main/java/io/sentry/android/core/ManifestMetadataReader.java +++ b/sentry-android-core/src/main/java/io/sentry/android/core/ManifestMetadataReader.java @@ -70,6 +70,8 @@ final class ManifestMetadataReader { static final String PROFILE_LIFECYCLE = "io.sentry.traces.profiling.lifecycle"; + static final String PROFILER_START_ON_APP_START = "io.sentry.traces.profiling.start-on-app-start"; + @ApiStatus.Experimental static final String TRACE_SAMPLING = "io.sentry.traces.trace-sampling"; static final String TRACE_PROPAGATION_TARGETS = "io.sentry.traces.trace-propagation-targets"; @@ -137,7 +139,7 @@ static void applyMetadata( options.setDebug(readBool(metadata, logger, DEBUG, options.isDebug())); if (options.isDebug()) { - final String level = + final @Nullable String level = readString( metadata, logger, @@ -159,7 +161,7 @@ static void applyMetadata( options.isEnableAutoSessionTracking())); if (options.getSampleRate() == null) { - final Double sampleRate = readDouble(metadata, logger, SAMPLE_RATE); + final double sampleRate = readDouble(metadata, logger, SAMPLE_RATE); if (sampleRate != -1) { options.setSampleRate(sampleRate); } @@ -178,7 +180,7 @@ static void applyMetadata( options.setAttachAnrThreadDump( readBool(metadata, logger, ANR_ATTACH_THREAD_DUMPS, options.isAttachAnrThreadDump())); - final String dsn = readString(metadata, logger, DSN, options.getDsn()); + final @Nullable String dsn = readString(metadata, logger, DSN, options.getDsn()); final boolean enabled = readBool(metadata, logger, ENABLE_SENTRY, options.isEnabled()); if (!enabled || (dsn != null && dsn.isEmpty())) { @@ -291,7 +293,7 @@ static void applyMetadata( options.isCollectAdditionalContext())); if (options.getTracesSampleRate() == null) { - final Double tracesSampleRate = readDouble(metadata, logger, TRACES_SAMPLE_RATE); + final double tracesSampleRate = readDouble(metadata, logger, TRACES_SAMPLE_RATE); if (tracesSampleRate != -1) { options.setTracesSampleRate(tracesSampleRate); } @@ -315,7 +317,7 @@ static void applyMetadata( options.isEnableActivityLifecycleTracingAutoFinish())); if (options.getProfilesSampleRate() == null) { - final Double profilesSampleRate = readDouble(metadata, logger, PROFILES_SAMPLE_RATE); + final double profilesSampleRate = readDouble(metadata, logger, PROFILES_SAMPLE_RATE); if (profilesSampleRate != -1) { options.setProfilesSampleRate(profilesSampleRate); } @@ -329,7 +331,7 @@ static void applyMetadata( } } - final String profileLifecycle = + final @Nullable String profileLifecycle = readString( metadata, logger, @@ -342,6 +344,15 @@ static void applyMetadata( ProfileLifecycle.valueOf(profileLifecycle.toUpperCase(Locale.ROOT))); } + options + .getExperimental() + .setStartProfilerOnAppStart( + readBool( + metadata, + logger, + PROFILER_START_ON_APP_START, + options.isStartProfilerOnAppStart())); + options.setEnableUserInteractionTracing( readBool(metadata, logger, TRACES_UI_ENABLE, options.isEnableUserInteractionTracing())); @@ -382,6 +393,7 @@ static void applyMetadata( // sdkInfo.addIntegration(); + @Nullable List integrationsFromGradlePlugin = readList(metadata, logger, SENTRY_GRADLE_PLUGIN_INTEGRATIONS); if (integrationsFromGradlePlugin != null) { @@ -407,7 +419,7 @@ static void applyMetadata( metadata, logger, ENABLE_SCOPE_PERSISTENCE, options.isEnableScopePersistence())); if (options.getExperimental().getSessionReplay().getSessionSampleRate() == null) { - final Double sessionSampleRate = + final double sessionSampleRate = readDouble(metadata, logger, REPLAYS_SESSION_SAMPLE_RATE); if (sessionSampleRate != -1) { options.getExperimental().getSessionReplay().setSessionSampleRate(sessionSampleRate); @@ -415,7 +427,7 @@ static void applyMetadata( } if (options.getExperimental().getSessionReplay().getOnErrorSampleRate() == null) { - final Double onErrorSampleRate = readDouble(metadata, logger, REPLAYS_ERROR_SAMPLE_RATE); + final double onErrorSampleRate = readDouble(metadata, logger, REPLAYS_ERROR_SAMPLE_RATE); if (onErrorSampleRate != -1) { options.getExperimental().getSessionReplay().setOnErrorSampleRate(onErrorSampleRate); } @@ -501,10 +513,10 @@ private static boolean readBool( } } - private static @NotNull Double readDouble( + private static double readDouble( final @NotNull Bundle metadata, final @NotNull ILogger logger, final @NotNull String key) { // manifest meta-data only reads float - final Double value = ((Number) metadata.getFloat(key, metadata.getInt(key, -1))).doubleValue(); + final double value = ((Number) metadata.getFloat(key, metadata.getInt(key, -1))).doubleValue(); logger.log(SentryLevel.DEBUG, key + " read: " + value); return value; } diff --git a/sentry-android-core/src/main/java/io/sentry/android/core/SentryPerformanceProvider.java b/sentry-android-core/src/main/java/io/sentry/android/core/SentryPerformanceProvider.java index df05f880d8a..93801b0cf5d 100644 --- a/sentry-android-core/src/main/java/io/sentry/android/core/SentryPerformanceProvider.java +++ b/sentry-android-core/src/main/java/io/sentry/android/core/SentryPerformanceProvider.java @@ -139,7 +139,8 @@ private void launchAppStartProfiler(final @NotNull AppStartMetrics appStartMetri return; } - if (profilingOptions.isContinuousProfilingEnabled()) { + if (profilingOptions.isContinuousProfilingEnabled() + && profilingOptions.isStartProfilerOnAppStart()) { createAndStartContinuousProfiler(context, profilingOptions, appStartMetrics); return; } @@ -150,8 +151,9 @@ private void launchAppStartProfiler(final @NotNull AppStartMetrics appStartMetri return; } - createAndStartTransactionProfiler(context, profilingOptions, appStartMetrics); - + if (profilingOptions.isEnableAppStartProfiling()) { + createAndStartTransactionProfiler(context, profilingOptions, appStartMetrics); + } } catch (FileNotFoundException e) { logger.log(SentryLevel.ERROR, "App start profiling config file not found. ", e); } catch (Throwable e) { diff --git a/sentry-android-core/src/test/java/io/sentry/android/core/ManifestMetadataReaderTest.kt b/sentry-android-core/src/test/java/io/sentry/android/core/ManifestMetadataReaderTest.kt index 9190beb916d..40143a74c3c 100644 --- a/sentry-android-core/src/test/java/io/sentry/android/core/ManifestMetadataReaderTest.kt +++ b/sentry-android-core/src/test/java/io/sentry/android/core/ManifestMetadataReaderTest.kt @@ -868,6 +868,31 @@ class ManifestMetadataReaderTest { assertEquals(ProfileLifecycle.TRACE, fixture.options.profileLifecycle) } + @Test + fun `applyMetadata without specifying isStartProfilerOnAppStart, stays false`() { + // Arrange + val context = fixture.getContext() + + // Act + ManifestMetadataReader.applyMetadata(context, fixture.options, fixture.buildInfoProvider) + + // Assert + assertFalse(fixture.options.isStartProfilerOnAppStart) + } + + @Test + fun `applyMetadata reads isStartProfilerOnAppStart from metadata`() { + // Arrange + val bundle = bundleOf(ManifestMetadataReader.PROFILER_START_ON_APP_START to true) + val context = fixture.getContext(metaData = bundle) + + // Act + ManifestMetadataReader.applyMetadata(context, fixture.options, fixture.buildInfoProvider) + + // Assert + assertTrue(fixture.options.isStartProfilerOnAppStart) + } + @Test fun `applyMetadata reads tracePropagationTargets to options`() { // Arrange diff --git a/sentry-android-core/src/test/java/io/sentry/android/core/SentryPerformanceProviderTest.kt b/sentry-android-core/src/test/java/io/sentry/android/core/SentryPerformanceProviderTest.kt index 16cce1a2093..83f5795f13e 100644 --- a/sentry-android-core/src/test/java/io/sentry/android/core/SentryPerformanceProviderTest.kt +++ b/sentry-android-core/src/test/java/io/sentry/android/core/SentryPerformanceProviderTest.kt @@ -326,6 +326,22 @@ class SentryPerformanceProviderTest { assertFalse(AppStartMetrics.getInstance().appStartProfiler!!.isRunning) } + @Test + fun `when isEnableAppStartProfiling is false, transaction profiler is not started`() { + fixture.getSut { config -> + writeConfig(config, profilingEnabled = true, continuousProfilingEnabled = false, isEnableAppStartProfiling = false) + } + assertNull(AppStartMetrics.getInstance().appStartProfiler) + } + + @Test + fun `when isStartProfilerOnAppStart is false, continuous profiler is not started`() { + fixture.getSut { config -> + writeConfig(config, profilingEnabled = false, continuousProfilingEnabled = true, isStartProfilerOnAppStart = false) + } + assertNull(AppStartMetrics.getInstance().appStartContinuousProfiler) + } + @Test fun `when provider is closed, continuous profiler is stopped`() { val provider = fixture.getSut { config -> @@ -345,6 +361,8 @@ class SentryPerformanceProviderTest { profileSampled: Boolean = true, profileSampleRate: Double = 1.0, continuousProfileSampled: Boolean = true, + isEnableAppStartProfiling: Boolean = true, + isStartProfilerOnAppStart: Boolean = true, profilingTracesDirPath: String = traceDir.absolutePath ) { val appStartProfilingOptions = SentryAppStartProfilingOptions() @@ -357,6 +375,8 @@ class SentryPerformanceProviderTest { appStartProfilingOptions.isContinuousProfileSampled = continuousProfileSampled appStartProfilingOptions.profilingTracesDirPath = profilingTracesDirPath appStartProfilingOptions.profilingTracesHz = 101 + appStartProfilingOptions.isEnableAppStartProfiling = isEnableAppStartProfiling + appStartProfilingOptions.isStartProfilerOnAppStart = isStartProfilerOnAppStart JsonSerializer(SentryOptions.empty()).serialize(appStartProfilingOptions, FileWriter(configFile)) } //endregion diff --git a/sentry-samples/sentry-samples-android/src/main/AndroidManifest.xml b/sentry-samples/sentry-samples-android/src/main/AndroidManifest.xml index 5afe6ac1808..03fb4e5f208 100644 --- a/sentry-samples/sentry-samples-android/src/main/AndroidManifest.xml +++ b/sentry-samples/sentry-samples-android/src/main/AndroidManifest.xml @@ -112,8 +112,12 @@ - - + + + + + + diff --git a/sentry/api/sentry.api b/sentry/api/sentry.api index 70f60b8350c..31a4d7d6da8 100644 --- a/sentry/api/sentry.api +++ b/sentry/api/sentry.api @@ -446,9 +446,11 @@ public final class io/sentry/ExperimentalOptions { public fun getProfileLifecycle ()Lio/sentry/ProfileLifecycle; public fun getProfileSessionSampleRate ()Ljava/lang/Double; public fun getSessionReplay ()Lio/sentry/SentryReplayOptions; + public fun isStartProfilerOnAppStart ()Z public fun setProfileLifecycle (Lio/sentry/ProfileLifecycle;)V public fun setProfileSessionSampleRate (Ljava/lang/Double;)V public fun setSessionReplay (Lio/sentry/SentryReplayOptions;)V + public fun setStartProfilerOnAppStart (Z)V } public final class io/sentry/ExternalOptions { @@ -2506,18 +2508,22 @@ public final class io/sentry/SentryAppStartProfilingOptions : io/sentry/JsonSeri public fun getUnknown ()Ljava/util/Map; public fun isContinuousProfileSampled ()Z public fun isContinuousProfilingEnabled ()Z + public fun isEnableAppStartProfiling ()Z public fun isProfileSampled ()Z public fun isProfilingEnabled ()Z + public fun isStartProfilerOnAppStart ()Z public fun isTraceSampled ()Z public fun serialize (Lio/sentry/ObjectWriter;Lio/sentry/ILogger;)V public fun setContinuousProfileSampled (Z)V public fun setContinuousProfilingEnabled (Z)V + public fun setEnableAppStartProfiling (Z)V public fun setProfileLifecycle (Lio/sentry/ProfileLifecycle;)V public fun setProfileSampleRate (Ljava/lang/Double;)V public fun setProfileSampled (Z)V public fun setProfilingEnabled (Z)V public fun setProfilingTracesDirPath (Ljava/lang/String;)V public fun setProfilingTracesHz (I)V + public fun setStartProfilerOnAppStart (Z)V public fun setTraceSampleRate (Ljava/lang/Double;)V public fun setTraceSampled (Z)V public fun setUnknown (Ljava/util/Map;)V @@ -2532,7 +2538,9 @@ public final class io/sentry/SentryAppStartProfilingOptions$Deserializer : io/se public final class io/sentry/SentryAppStartProfilingOptions$JsonKeys { public static final field CONTINUOUS_PROFILE_SAMPLED Ljava/lang/String; public static final field IS_CONTINUOUS_PROFILING_ENABLED Ljava/lang/String; + public static final field IS_ENABLE_APP_START_PROFILING Ljava/lang/String; public static final field IS_PROFILING_ENABLED Ljava/lang/String; + public static final field IS_START_PROFILER_ON_APP_START Ljava/lang/String; public static final field PROFILE_LIFECYCLE Ljava/lang/String; public static final field PROFILE_SAMPLED Ljava/lang/String; public static final field PROFILE_SAMPLE_RATE Ljava/lang/String; @@ -3065,6 +3073,7 @@ public class io/sentry/SentryOptions { public fun isSendClientReports ()Z public fun isSendDefaultPii ()Z public fun isSendModules ()Z + public fun isStartProfilerOnAppStart ()Z public fun isTraceOptionsRequests ()Z public fun isTraceSampling ()Z public fun isTracingEnabled ()Z diff --git a/sentry/src/main/java/io/sentry/ExperimentalOptions.java b/sentry/src/main/java/io/sentry/ExperimentalOptions.java index 4a99c003778..fc93416d02d 100644 --- a/sentry/src/main/java/io/sentry/ExperimentalOptions.java +++ b/sentry/src/main/java/io/sentry/ExperimentalOptions.java @@ -27,6 +27,17 @@ public final class ExperimentalOptions { */ private @NotNull ProfileLifecycle profileLifecycle = ProfileLifecycle.MANUAL; + /** + * Whether profiling can automatically be started as early as possible during the app lifecycle, + * to capture more of app startup. If {@link ExperimentalOptions#profileLifecycle} is {@link + * ProfileLifecycle#MANUAL} Profiling is started automatically on startup and stopProfileSession + * must be called manually whenever the app startup is completed If {@link + * ExperimentalOptions#profileLifecycle} is {@link ProfileLifecycle#TRACE} Profiling is started + * automatically on startup, and will automatically be stopped when the root span that is + * associated with app startup ends + */ + private boolean startProfilerOnAppStart = false; + public ExperimentalOptions(final boolean empty) { this.sessionReplay = new SentryReplayOptions(empty); } @@ -74,4 +85,14 @@ public void setProfileSessionSampleRate(final @Nullable Double profileSessionSam } this.profileSessionSampleRate = profileSessionSampleRate; } + + @ApiStatus.Experimental + public boolean isStartProfilerOnAppStart() { + return startProfilerOnAppStart; + } + + @ApiStatus.Experimental + public void setStartProfilerOnAppStart(boolean startProfilerOnAppStart) { + this.startProfilerOnAppStart = startProfilerOnAppStart; + } } diff --git a/sentry/src/main/java/io/sentry/Sentry.java b/sentry/src/main/java/io/sentry/Sentry.java index 02e7d168c79..a3ecbacf8d0 100644 --- a/sentry/src/main/java/io/sentry/Sentry.java +++ b/sentry/src/main/java/io/sentry/Sentry.java @@ -370,10 +370,12 @@ private static void handleAppStartProfilingConfig( try { // We always delete the config file for app start profiling FileUtils.deleteRecursively(appStartProfilingConfigFile); - if (!options.isEnableAppStartProfiling()) { + if (!options.isEnableAppStartProfiling() && !options.isStartProfilerOnAppStart()) { return; } - if (!options.isTracingEnabled()) { + // isStartProfilerOnAppStart doesn't need tracing, as it can be started/stopped + // manually + if (!options.isStartProfilerOnAppStart() && !options.isTracingEnabled()) { options .getLogger() .log( @@ -382,8 +384,13 @@ private static void handleAppStartProfilingConfig( return; } if (appStartProfilingConfigFile.createNewFile()) { + // If old app start profiling is false, it means the transaction will not be + // sampled, but we create the file anyway to allow continuous profiling on app + // start final @NotNull TracesSamplingDecision appStartSamplingDecision = - sampleAppStartProfiling(options); + options.isEnableAppStartProfiling() + ? sampleAppStartProfiling(options) + : new TracesSamplingDecision(false); final @NotNull SentryAppStartProfilingOptions appStartProfilingOptions = new SentryAppStartProfilingOptions(options, appStartSamplingDecision); try (final OutputStream outputStream = diff --git a/sentry/src/main/java/io/sentry/SentryAppStartProfilingOptions.java b/sentry/src/main/java/io/sentry/SentryAppStartProfilingOptions.java index e764a42218d..3c16504eb7c 100644 --- a/sentry/src/main/java/io/sentry/SentryAppStartProfilingOptions.java +++ b/sentry/src/main/java/io/sentry/SentryAppStartProfilingOptions.java @@ -21,6 +21,8 @@ public final class SentryAppStartProfilingOptions implements JsonUnknown, JsonSe boolean isContinuousProfilingEnabled; int profilingTracesHz; boolean continuousProfileSampled; + boolean isEnableAppStartProfiling; + boolean isStartProfilerOnAppStart; @NotNull ProfileLifecycle profileLifecycle; private @Nullable Map unknown; @@ -37,6 +39,8 @@ public SentryAppStartProfilingOptions() { isContinuousProfilingEnabled = false; profileLifecycle = ProfileLifecycle.MANUAL; profilingTracesHz = 0; + isEnableAppStartProfiling = true; + isStartProfilerOnAppStart = false; } SentryAppStartProfilingOptions( @@ -52,6 +56,8 @@ public SentryAppStartProfilingOptions() { isContinuousProfilingEnabled = options.isContinuousProfilingEnabled(); profileLifecycle = options.getProfileLifecycle(); profilingTracesHz = options.getProfilingTracesHz(); + isEnableAppStartProfiling = options.isEnableAppStartProfiling(); + isStartProfilerOnAppStart = options.isStartProfilerOnAppStart(); } public void setProfileSampled(final boolean profileSampled) { @@ -134,6 +140,22 @@ public int getProfilingTracesHz() { return profilingTracesHz; } + public void setEnableAppStartProfiling(final boolean enableAppStartProfiling) { + isEnableAppStartProfiling = enableAppStartProfiling; + } + + public boolean isEnableAppStartProfiling() { + return isEnableAppStartProfiling; + } + + public void setStartProfilerOnAppStart(final boolean startProfilerOnAppStart) { + isStartProfilerOnAppStart = startProfilerOnAppStart; + } + + public boolean isStartProfilerOnAppStart() { + return isStartProfilerOnAppStart; + } + // JsonSerializable public static final class JsonKeys { @@ -147,6 +169,8 @@ public static final class JsonKeys { public static final String IS_CONTINUOUS_PROFILING_ENABLED = "is_continuous_profiling_enabled"; public static final String PROFILE_LIFECYCLE = "profile_lifecycle"; public static final String PROFILING_TRACES_HZ = "profiling_traces_hz"; + public static final String IS_ENABLE_APP_START_PROFILING = "is_enable_app_start_profiling"; + public static final String IS_START_PROFILER_ON_APP_START = "is_start_profiler_on_app_start"; } @Override @@ -165,6 +189,8 @@ public void serialize(final @NotNull ObjectWriter writer, final @NotNull ILogger .value(logger, isContinuousProfilingEnabled); writer.name(JsonKeys.PROFILE_LIFECYCLE).value(logger, profileLifecycle.name()); writer.name(JsonKeys.PROFILING_TRACES_HZ).value(logger, profilingTracesHz); + writer.name(JsonKeys.IS_ENABLE_APP_START_PROFILING).value(logger, isEnableAppStartProfiling); + writer.name(JsonKeys.IS_START_PROFILER_ON_APP_START).value(logger, isStartProfilerOnAppStart); if (unknown != null) { for (String key : unknown.keySet()) { @@ -201,55 +227,55 @@ public static final class Deserializer final String nextName = reader.nextName(); switch (nextName) { case JsonKeys.PROFILE_SAMPLED: - Boolean profileSampled = reader.nextBooleanOrNull(); + @Nullable Boolean profileSampled = reader.nextBooleanOrNull(); if (profileSampled != null) { options.profileSampled = profileSampled; } break; case JsonKeys.PROFILE_SAMPLE_RATE: - Double profileSampleRate = reader.nextDoubleOrNull(); + @Nullable Double profileSampleRate = reader.nextDoubleOrNull(); if (profileSampleRate != null) { options.profileSampleRate = profileSampleRate; } break; case JsonKeys.CONTINUOUS_PROFILE_SAMPLED: - Boolean continuousProfileSampled = reader.nextBooleanOrNull(); + @Nullable Boolean continuousProfileSampled = reader.nextBooleanOrNull(); if (continuousProfileSampled != null) { options.continuousProfileSampled = continuousProfileSampled; } break; case JsonKeys.TRACE_SAMPLED: - Boolean traceSampled = reader.nextBooleanOrNull(); + @Nullable Boolean traceSampled = reader.nextBooleanOrNull(); if (traceSampled != null) { options.traceSampled = traceSampled; } break; case JsonKeys.TRACE_SAMPLE_RATE: - Double traceSampleRate = reader.nextDoubleOrNull(); + @Nullable Double traceSampleRate = reader.nextDoubleOrNull(); if (traceSampleRate != null) { options.traceSampleRate = traceSampleRate; } break; case JsonKeys.PROFILING_TRACES_DIR_PATH: - String profilingTracesDirPath = reader.nextStringOrNull(); + @Nullable String profilingTracesDirPath = reader.nextStringOrNull(); if (profilingTracesDirPath != null) { options.profilingTracesDirPath = profilingTracesDirPath; } break; case JsonKeys.IS_PROFILING_ENABLED: - Boolean isProfilingEnabled = reader.nextBooleanOrNull(); + @Nullable Boolean isProfilingEnabled = reader.nextBooleanOrNull(); if (isProfilingEnabled != null) { options.isProfilingEnabled = isProfilingEnabled; } break; case JsonKeys.IS_CONTINUOUS_PROFILING_ENABLED: - Boolean isContinuousProfilingEnabled = reader.nextBooleanOrNull(); + @Nullable Boolean isContinuousProfilingEnabled = reader.nextBooleanOrNull(); if (isContinuousProfilingEnabled != null) { options.isContinuousProfilingEnabled = isContinuousProfilingEnabled; } break; case JsonKeys.PROFILE_LIFECYCLE: - String profileLifecycle = reader.nextStringOrNull(); + @Nullable String profileLifecycle = reader.nextStringOrNull(); if (profileLifecycle != null) { try { options.profileLifecycle = ProfileLifecycle.valueOf(profileLifecycle); @@ -261,11 +287,23 @@ public static final class Deserializer } break; case JsonKeys.PROFILING_TRACES_HZ: - Integer profilingTracesHz = reader.nextIntegerOrNull(); + @Nullable Integer profilingTracesHz = reader.nextIntegerOrNull(); if (profilingTracesHz != null) { options.profilingTracesHz = profilingTracesHz; } break; + case JsonKeys.IS_ENABLE_APP_START_PROFILING: + @Nullable Boolean isEnableAppStartProfiling = reader.nextBooleanOrNull(); + if (isEnableAppStartProfiling != null) { + options.isEnableAppStartProfiling = isEnableAppStartProfiling; + } + break; + case JsonKeys.IS_START_PROFILER_ON_APP_START: + @Nullable Boolean isStartProfilerOnAppStart = reader.nextBooleanOrNull(); + if (isStartProfilerOnAppStart != null) { + options.isStartProfilerOnAppStart = isStartProfilerOnAppStart; + } + break; default: if (unknown == null) { unknown = new ConcurrentHashMap<>(); diff --git a/sentry/src/main/java/io/sentry/SentryOptions.java b/sentry/src/main/java/io/sentry/SentryOptions.java index b7b57f4bebd..c7087dbb36d 100644 --- a/sentry/src/main/java/io/sentry/SentryOptions.java +++ b/sentry/src/main/java/io/sentry/SentryOptions.java @@ -1813,6 +1813,14 @@ public void setProfilesSampleRate(final @Nullable Double profilesSampleRate) { return experimental.getProfileLifecycle(); } + /** + * Whether profiling can automatically be started as early as possible during the app lifecycle. + */ + @ApiStatus.Experimental + public boolean isStartProfilerOnAppStart() { + return experimental.isStartProfilerOnAppStart(); + } + /** * Returns the profiling traces dir. path if set * diff --git a/sentry/src/test/java/io/sentry/JsonSerializerTest.kt b/sentry/src/test/java/io/sentry/JsonSerializerTest.kt index a8f1a1e80c5..38a3f49f24c 100644 --- a/sentry/src/test/java/io/sentry/JsonSerializerTest.kt +++ b/sentry/src/test/java/io/sentry/JsonSerializerTest.kt @@ -1234,8 +1234,8 @@ class JsonSerializerTest { val expected = "{\"profile_sampled\":true,\"profile_sample_rate\":0.8,\"continuous_profile_sampled\":true," + "\"trace_sampled\":false,\"trace_sample_rate\":0.1,\"profiling_traces_dir_path\":null,\"is_profiling_enabled\":false," + - "\"is_continuous_profiling_enabled\":false,\"profile_lifecycle\":\"TRACE\",\"profiling_traces_hz\":65}" - + "\"is_continuous_profiling_enabled\":false,\"profile_lifecycle\":\"TRACE\",\"profiling_traces_hz\":65," + + "\"is_enable_app_start_profiling\":false,\"is_start_profiler_on_app_start\":true}" assertEquals(expected, actual) } @@ -1243,7 +1243,8 @@ class JsonSerializerTest { fun `deserializing SentryAppStartProfilingOptions`() { val jsonAppStartProfilingOptions = "{\"profile_sampled\":true,\"profile_sample_rate\":0.8,\"trace_sampled\"" + ":false,\"trace_sample_rate\":0.1,\"profiling_traces_dir_path\":null,\"is_profiling_enabled\":false," + - "\"profile_lifecycle\":\"TRACE\",\"profiling_traces_hz\":65,\"continuous_profile_sampled\":true}" + "\"profile_lifecycle\":\"TRACE\",\"profiling_traces_hz\":65,\"continuous_profile_sampled\":true," + + "\"is_enable_app_start_profiling\":false,\"is_start_profiler_on_app_start\":true}" val actual = fixture.serializer.deserialize(StringReader(jsonAppStartProfilingOptions), SentryAppStartProfilingOptions::class.java) assertNotNull(actual) @@ -1257,6 +1258,8 @@ class JsonSerializerTest { assertEquals(appStartProfilingOptions.profilingTracesHz, actual.profilingTracesHz) assertEquals(appStartProfilingOptions.profilingTracesDirPath, actual.profilingTracesDirPath) assertEquals(appStartProfilingOptions.profileLifecycle, actual.profileLifecycle) + assertEquals(appStartProfilingOptions.isEnableAppStartProfiling, actual.isEnableAppStartProfiling) + assertEquals(appStartProfilingOptions.isStartProfilerOnAppStart, actual.isStartProfilerOnAppStart) assertNull(actual.unknown) } @@ -1562,6 +1565,8 @@ class JsonSerializerTest { isContinuousProfilingEnabled = false profilingTracesHz = 65 profileLifecycle = ProfileLifecycle.TRACE + isEnableAppStartProfiling = false + isStartProfilerOnAppStart = true } private fun createSpan(): ISpan { diff --git a/sentry/src/test/java/io/sentry/SentryOptionsTest.kt b/sentry/src/test/java/io/sentry/SentryOptionsTest.kt index 22aec0c9a08..fc850e4bd4d 100644 --- a/sentry/src/test/java/io/sentry/SentryOptionsTest.kt +++ b/sentry/src/test/java/io/sentry/SentryOptionsTest.kt @@ -303,6 +303,20 @@ class SentryOptionsTest { assertEquals(ProfileLifecycle.MANUAL, options.profileLifecycle) } + @Test + fun `when isStartProfilerOnAppStart is set to a value, value is set`() { + val options = SentryOptions().apply { + this.experimental.isStartProfilerOnAppStart = true + } + assertTrue(options.isStartProfilerOnAppStart) + } + + @Test + fun `isStartProfilerOnAppStart defaults to false`() { + val options = SentryOptions() + assertFalse(options.isStartProfilerOnAppStart) + } + @Test fun `when options is initialized, compositePerformanceCollector is set`() { assertIs(SentryOptions().compositePerformanceCollector) diff --git a/sentry/src/test/java/io/sentry/SentryTest.kt b/sentry/src/test/java/io/sentry/SentryTest.kt index cf7f7384f89..bf68c63d077 100644 --- a/sentry/src/test/java/io/sentry/SentryTest.kt +++ b/sentry/src/test/java/io/sentry/SentryTest.kt @@ -1130,6 +1130,25 @@ class SentryTest { ) } + @Test + fun `init calls samplers if isStartProfilerOnAppStart is true`() { + val mockSampleTracer = mock() + val mockProfilesSampler = mock() + Sentry.init { + it.dsn = dsn + it.tracesSampleRate = 1.0 + it.experimental.isStartProfilerOnAppStart = true + it.profilesSampleRate = 1.0 + it.tracesSampler = mockSampleTracer + it.profilesSampler = mockProfilesSampler + it.executorService = ImmediateExecutorService() + it.cacheDirPath = getTempPath() + } + // Samplers are not called + verify(mockSampleTracer, never()).sample(any()) + verify(mockProfilesSampler, never()).sample(any()) + } + @Test fun `init calls app start profiling samplers in the background`() { val mockSampleTracer = mock() @@ -1220,6 +1239,24 @@ class SentryTest { assertTrue(appStartProfilingConfigFile.exists()) } + @Test + fun `init creates app start profiling config if isStartProfilerOnAppStart, even with performance disabled`() { + val path = getTempPath() + File(path).mkdirs() + val appStartProfilingConfigFile = File(path, "app_start_profiling_config") + appStartProfilingConfigFile.createNewFile() + assertTrue(appStartProfilingConfigFile.exists()) + Sentry.init { + it.dsn = dsn + it.cacheDirPath = path + it.isEnableAppStartProfiling = false + it.experimental.isStartProfilerOnAppStart = true + it.tracesSampleRate = 0.0 + it.executorService = ImmediateExecutorService() + } + assertTrue(appStartProfilingConfigFile.exists()) + } + @Test fun `init saves SentryAppStartProfilingOptions to disk`() { var options = SentryOptions() @@ -1227,9 +1264,9 @@ class SentryTest { Sentry.init { it.dsn = dsn it.cacheDirPath = path - it.tracesSampleRate = 1.0 it.tracesSampleRate = 0.5 it.isEnableAppStartProfiling = true + it.experimental.isStartProfilerOnAppStart = true it.profilesSampleRate = 0.2 it.executorService = ImmediateExecutorService() options = it @@ -1242,6 +1279,8 @@ class SentryTest { assertEquals(0.5, appStartOption.traceSampleRate) assertEquals(0.2, appStartOption.profileSampleRate) assertTrue(appStartOption.isProfilingEnabled) + assertTrue(appStartOption.isEnableAppStartProfiling) + assertTrue(appStartOption.isStartProfilerOnAppStart) } @Test