Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
50505ef
Set continuousProfilesSampleRate and startProfiler() and stopProfiler…
stefanosiano Jan 13, 2025
def8f0c
Added chunk start timestamp to ProfileChunk
stefanosiano Jan 20, 2025
6c07e33
updated changelog
stefanosiano Jan 21, 2025
6eeed8e
Moved setContinuousProfilesSampleRate into ExperimentalOptions
stefanosiano Jan 24, 2025
c9fbfbb
increased continuous profiling chunk duration to 1 minute
stefanosiano Feb 11, 2025
018391a
Merge branch 'feat/continuous-profiling-part1' into feat/continuous-p…
stefanosiano Feb 18, 2025
dcee57a
replaced continuousProfilesSampleRate with profileSessionSampleRate (…
stefanosiano Feb 18, 2025
04e99f9
renamed Sentry.startProfiler with Sentry.startProfileSession and Sent…
stefanosiano Feb 19, 2025
c0279e2
renamed Sentry.startProfiler with Sentry.startProfileSession and Sent…
stefanosiano Feb 19, 2025
fbe53eb
Added ProfileLifecycle
stefanosiano Feb 24, 2025
5c2c846
Merge branch 'feat/continuous-profiling-part1' into feat/continuous-p…
stefanosiano Feb 27, 2025
79742ca
merged base branch
stefanosiano Feb 27, 2025
dd735c2
added isStartProfilerOnAppStart experimental option
stefanosiano Feb 28, 2025
d790035
added isStartProfilerOnAppStart logic and tests
stefanosiano Mar 4, 2025
d72b09f
added app start option checks in SentryPerformanceProvider
stefanosiano Mar 7, 2025
6b168b6
added @Nullable annotation to SentryAppStartProfilingOptions json dec…
stefanosiano Mar 12, 2025
1381d4a
added @Nullable annotation to ManifestMetadataReader json decoding
stefanosiano Mar 12, 2025
23ee0ae
Merge branch 'feat/continuous-profiling-part1' into feat/continuous-p…
stefanosiano Mar 14, 2025
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 @@ -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,
Expand All @@ -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);
}
Expand All @@ -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())) {
Expand Down Expand Up @@ -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);
}
Expand All @@ -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);
}
Expand All @@ -329,7 +331,7 @@ static void applyMetadata(
}
}

final String profileLifecycle =
final @Nullable String profileLifecycle =
readString(
metadata,
logger,
Expand All @@ -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 Expand Up @@ -382,6 +393,7 @@ static void applyMetadata(

// sdkInfo.addIntegration();

@Nullable
List<String> integrationsFromGradlePlugin =
readList(metadata, logger, SENTRY_GRADLE_PLUGIN_INTEGRATIONS);
if (integrationsFromGradlePlugin != null) {
Expand All @@ -407,15 +419,15 @@ 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);
}
}

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);
}
Expand Down Expand Up @@ -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;
}
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()) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

shouldn't this be an OR?

Suggested change
if (!options.isEnableAppStartProfiling() && !options.isStartProfilerOnAppStart()) {
if (!options.isEnableAppStartProfiling() || !options.isStartProfilerOnAppStart()) {

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nope, if at least one of the options is true, then the profiler will start on next app start, and we need to create the config file for that

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
Loading
Loading