Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Continuous Profiling isStartProfilerOnAppStart option #4226

Open
wants to merge 3 commits into
base: feat/continuous-profiling-lifecycle
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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";

Expand Down Expand Up @@ -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()));

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
Expand All @@ -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) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 ->
Expand All @@ -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()
Expand All @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -112,8 +112,12 @@
<!-- how to enable profiling when starting transactions -->
<!-- <meta-data android:name="io.sentry.traces.profiling.sample-rate" android:value="1.0" />-->

<!-- how to enable app start profiling -->
<meta-data android:name="io.sentry.traces.profiling.enable-app-start" android:value="true" />
<!-- Enable profiling, adjust in production env -->
<meta-data android:name="io.sentry.traces.profiling.session-sample-rate" android:value="1.0" />
<!-- Set profiling lifecycle, can be `manual` or `trace` -->
<meta-data android:name="io.sentry.traces.profiling.lifecycle" android:value="manual" />
<!-- Enable profiling on app start -->
<meta-data android:name="io.sentry.traces.profiling.start-on-app-start" android:value="true" />

<!-- how to disable the Activity auto instrumentation for tracing-->
<!-- <meta-data android:name="io.sentry.traces.activity.enable" android:value="false" />-->
Expand Down
9 changes: 9 additions & 0 deletions sentry/api/sentry.api
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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
Expand All @@ -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;
Expand Down Expand Up @@ -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
Expand Down
21 changes: 21 additions & 0 deletions sentry/src/main/java/io/sentry/ExperimentalOptions.java
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
Expand Down Expand Up @@ -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;
}
}
13 changes: 10 additions & 3 deletions sentry/src/main/java/io/sentry/Sentry.java
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand All @@ -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 =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<String, Object> unknown;
Expand All @@ -37,6 +39,8 @@ public SentryAppStartProfilingOptions() {
isContinuousProfilingEnabled = false;
profileLifecycle = ProfileLifecycle.MANUAL;
profilingTracesHz = 0;
isEnableAppStartProfiling = true;
isStartProfilerOnAppStart = false;
}

SentryAppStartProfilingOptions(
Expand All @@ -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) {
Expand Down Expand Up @@ -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 {
Expand All @@ -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
Expand All @@ -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()) {
Expand Down Expand Up @@ -266,6 +292,18 @@ public static final class Deserializer
options.profilingTracesHz = profilingTracesHz;
}
break;
case JsonKeys.IS_ENABLE_APP_START_PROFILING:
Boolean isEnableAppStartProfiling = reader.nextBooleanOrNull();
if (isEnableAppStartProfiling != null) {
options.isEnableAppStartProfiling = isEnableAppStartProfiling;
}
break;
case JsonKeys.IS_START_PROFILER_ON_APP_START:
Boolean isStartProfilerOnAppStart = reader.nextBooleanOrNull();
if (isStartProfilerOnAppStart != null) {
options.isStartProfilerOnAppStart = isStartProfilerOnAppStart;
}
break;
default:
if (unknown == null) {
unknown = new ConcurrentHashMap<>();
Expand Down
8 changes: 8 additions & 0 deletions sentry/src/main/java/io/sentry/SentryOptions.java
Original file line number Diff line number Diff line change
Expand Up @@ -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
*
Expand Down
11 changes: 8 additions & 3 deletions sentry/src/test/java/io/sentry/JsonSerializerTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -1234,16 +1234,17 @@ 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)
}

@Test
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)
Expand All @@ -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)
}

Expand Down Expand Up @@ -1562,6 +1565,8 @@ class JsonSerializerTest {
isContinuousProfilingEnabled = false
profilingTracesHz = 65
profileLifecycle = ProfileLifecycle.TRACE
isEnableAppStartProfiling = false
isStartProfilerOnAppStart = true
}

private fun createSpan(): ISpan {
Expand Down
Loading
Loading