Skip to content

Commit a537f8a

Browse files
authored
Report process init time for app start (getsentry#3159)
* Report process init time for app start * Rename op to process.load * Add Changelog * Ensure process init is only attached if it happened in the reasonable past
1 parent 749ed65 commit a537f8a

6 files changed

Lines changed: 94 additions & 5 deletions

File tree

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
### Features
66

77
- Add new threshold parameters to monitor config ([#3181](https://github.com/getsentry/sentry-java/pull/3181))
8+
- Report process init time as a span for app start performance ([#3159](https://github.com/getsentry/sentry-java/pull/3159))
89

910
## 7.3.0
1011

sentry-android-core/api/sentry-android-core.api

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -434,6 +434,7 @@ public class io/sentry/android/core/performance/AppStartMetrics {
434434
public fun getAppStartTimeSpanWithFallback (Lio/sentry/android/core/SentryAndroidOptions;)Lio/sentry/android/core/performance/TimeSpan;
435435
public fun getAppStartType ()Lio/sentry/android/core/performance/AppStartMetrics$AppStartType;
436436
public fun getApplicationOnCreateTimeSpan ()Lio/sentry/android/core/performance/TimeSpan;
437+
public fun getClassLoadedUptimeMs ()J
437438
public fun getContentProviderOnCreateTimeSpans ()Ljava/util/List;
438439
public static fun getInstance ()Lio/sentry/android/core/performance/AppStartMetrics;
439440
public fun getSdkInitTimeSpan ()Lio/sentry/android/core/performance/TimeSpan;
@@ -445,6 +446,7 @@ public class io/sentry/android/core/performance/AppStartMetrics {
445446
public fun setAppStartProfiler (Lio/sentry/ITransactionProfiler;)V
446447
public fun setAppStartSamplingDecision (Lio/sentry/TracesSamplingDecision;)V
447448
public fun setAppStartType (Lio/sentry/android/core/performance/AppStartMetrics$AppStartType;)V
449+
public fun setClassLoadedUptimeMs (J)V
448450
}
449451

450452
public final class io/sentry/android/core/performance/AppStartMetrics$AppStartType : java/lang/Enum {

sentry-android-core/src/main/java/io/sentry/android/core/PerformanceAndroidEventProcessor.java

Lines changed: 25 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,8 @@ final class PerformanceAndroidEventProcessor implements EventProcessor {
3333
private static final String APP_METRICS_CONTENT_PROVIDER_OP = "contentprovider.load";
3434
private static final String APP_METRICS_ACTIVITIES_OP = "activity.load";
3535
private static final String APP_METRICS_APPLICATION_OP = "application.load";
36+
private static final String APP_METRICS_PROCESS_INIT_OP = "process.load";
37+
private static final long MAX_PROCESS_INIT_APP_START_DIFF_MS = 10000;
3638

3739
private boolean sentStartMeasurement = false;
3840

@@ -77,13 +79,13 @@ public SentryEvent process(@NotNull SentryEvent event, @NotNull Hint hint) {
7779
if (!sentStartMeasurement && hasAppStartSpan(transaction)) {
7880
final @NotNull TimeSpan appStartTimeSpan =
7981
AppStartMetrics.getInstance().getAppStartTimeSpanWithFallback(options);
80-
final long appStartUpInterval = appStartTimeSpan.getDurationMs();
82+
final long appStartUpDurationMs = appStartTimeSpan.getDurationMs();
8183

82-
// if appStartUpInterval is 0, metrics are not ready to be sent
83-
if (appStartUpInterval != 0) {
84+
// if appStartUpDurationMs is 0, metrics are not ready to be sent
85+
if (appStartUpDurationMs != 0) {
8486
final MeasurementValue value =
8587
new MeasurementValue(
86-
(float) appStartUpInterval, MeasurementUnit.Duration.MILLISECOND.apiName());
88+
(float) appStartUpDurationMs, MeasurementUnit.Duration.MILLISECOND.apiName());
8789

8890
final String appStartKey =
8991
AppStartMetrics.getInstance().getAppStartType() == AppStartMetrics.AppStartType.COLD
@@ -155,6 +157,25 @@ private void attachColdAppStartSpans(
155157
}
156158
}
157159

160+
// Process init
161+
final long classInitUptimeMs = appStartMetrics.getClassLoadedUptimeMs();
162+
final @NotNull TimeSpan appStartTimeSpan = appStartMetrics.getAppStartTimeSpan();
163+
if (appStartTimeSpan.hasStarted()
164+
&& Math.abs(classInitUptimeMs - appStartTimeSpan.getStartUptimeMs())
165+
<= MAX_PROCESS_INIT_APP_START_DIFF_MS) {
166+
final @NotNull TimeSpan processInitTimeSpan = new TimeSpan();
167+
processInitTimeSpan.setStartedAt(appStartTimeSpan.getStartUptimeMs());
168+
processInitTimeSpan.setStartUnixTimeMs(appStartTimeSpan.getStartTimestampMs());
169+
170+
processInitTimeSpan.setStoppedAt(classInitUptimeMs);
171+
processInitTimeSpan.setDescription("Process Initialization");
172+
173+
txn.getSpans()
174+
.add(
175+
timeSpanToSentrySpan(
176+
processInitTimeSpan, parentSpanId, traceId, APP_METRICS_PROCESS_INIT_OP));
177+
}
178+
158179
// Content Providers
159180
final @NotNull List<TimeSpan> contentProviderOnCreates =
160181
appStartMetrics.getContentProviderOnCreateTimeSpans();

sentry-android-core/src/main/java/io/sentry/android/core/performance/AppStartMetrics.java

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,8 @@ public enum AppStartType {
3131
WARM
3232
}
3333

34+
private static long CLASS_LOADED_UPTIME_MS = SystemClock.uptimeMillis();
35+
3436
private static volatile @Nullable AppStartMetrics instance;
3537

3638
private @NotNull AppStartType appStartType = AppStartType.UNKNOWN;
@@ -121,6 +123,10 @@ public void addActivityLifecycleTimeSpans(final @NotNull ActivityLifecycleTimeSp
121123
activityLifecycles.add(timeSpan);
122124
}
123125

126+
public long getClassLoadedUptimeMs() {
127+
return CLASS_LOADED_UPTIME_MS;
128+
}
129+
124130
/**
125131
* @return the app start time span if it was started and perf-2 is enabled, falls back to the sdk
126132
* init time span otherwise
@@ -171,6 +177,12 @@ public void setAppStartSamplingDecision(
171177
return appStartSamplingDecision;
172178
}
173179

180+
@TestOnly
181+
@ApiStatus.Internal
182+
public void setClassLoadedUptimeMs(final long classLoadedUptimeMs) {
183+
CLASS_LOADED_UPTIME_MS = classLoadedUptimeMs;
184+
}
185+
174186
/**
175187
* Called by instrumentation
176188
*

sentry-android-core/src/test/java/io/sentry/android/core/PerformanceAndroidEventProcessorTest.kt

Lines changed: 48 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -246,6 +246,13 @@ class PerformanceAndroidEventProcessorTest {
246246
// then the app start metrics should be attached
247247
tr = sut.process(tr, Hint())
248248

249+
assertTrue(
250+
tr.spans.any {
251+
"process.load" == it.op &&
252+
appStartSpan.spanId == it.parentSpanId
253+
}
254+
)
255+
249256
assertTrue(
250257
tr.spans.any {
251258
"contentprovider.load" == it.op &&
@@ -300,7 +307,7 @@ class PerformanceAndroidEventProcessorTest {
300307

301308
@Test
302309
fun `does not add app start metrics more than once`() {
303-
// given some WARM app start metrics
310+
// given some cold app start metrics
304311
val appStartMetrics = AppStartMetrics.getInstance()
305312
appStartMetrics.appStartType = AppStartType.COLD
306313
appStartMetrics.appStartTimeSpan.setStartedAt(123)
@@ -351,6 +358,46 @@ class PerformanceAndroidEventProcessorTest {
351358
)
352359
}
353360

361+
@Test
362+
fun `does not add process init span if it happened too early`() {
363+
// given some cold app start metrics
364+
// where class loaded happened way before app start
365+
val appStartMetrics = AppStartMetrics.getInstance()
366+
appStartMetrics.appStartType = AppStartType.COLD
367+
appStartMetrics.appStartTimeSpan.setStartedAt(11001)
368+
appStartMetrics.appStartTimeSpan.setStoppedAt(12000)
369+
appStartMetrics.classLoadedUptimeMs = 1000
370+
371+
val sut = fixture.getSut(enablePerformanceV2 = true)
372+
val context = TransactionContext("Activity", UI_LOAD_OP)
373+
val tracer = SentryTracer(context, fixture.hub)
374+
var tr = SentryTransaction(tracer)
375+
val appStartSpan = SentrySpan(
376+
0.0,
377+
1.0,
378+
tr.contexts.trace!!.traceId,
379+
SpanId(),
380+
null,
381+
APP_START_COLD,
382+
"App Start",
383+
SpanStatus.OK,
384+
null,
385+
emptyMap(),
386+
null
387+
)
388+
tr.spans.add(appStartSpan)
389+
390+
// when the processor attaches the app start spans
391+
tr = sut.process(tr, Hint())
392+
393+
// process load should not be included
394+
assertFalse(
395+
tr.spans.any {
396+
"process.load" == it.op
397+
}
398+
)
399+
}
400+
354401
private fun setAppStart(options: SentryAndroidOptions, coldStart: Boolean = true) {
355402
AppStartMetrics.getInstance().apply {
356403
appStartType = when (coldStart) {

sentry-android-core/src/test/java/io/sentry/android/core/performance/AppStartMetricsTest.kt

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import org.mockito.kotlin.mock
1212
import org.robolectric.annotation.Config
1313
import kotlin.test.Test
1414
import kotlin.test.assertEquals
15+
import kotlin.test.assertNotEquals
1516
import kotlin.test.assertNull
1617
import kotlin.test.assertSame
1718
import kotlin.test.assertTrue
@@ -100,4 +101,9 @@ class AppStartMetricsTest {
100101
val sdkInitSpan = AppStartMetrics.getInstance().sdkInitTimeSpan
101102
assertSame(sdkInitSpan, timeSpan)
102103
}
104+
105+
@Test
106+
fun `class load time is set`() {
107+
assertNotEquals(0, AppStartMetrics.getInstance().classLoadedUptimeMs)
108+
}
103109
}

0 commit comments

Comments
 (0)