Skip to content

Commit 7eccfdb

Browse files
[Starfish] Attach app-start spans (#3057)
Co-authored-by: Sentry Github Bot <[email protected]>
1 parent 82884cb commit 7eccfdb

36 files changed

Lines changed: 1809 additions & 565 deletions

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,10 @@
55
### Features
66

77
- Support multiple debug-metadata.properties ([#3024](https://github.com/getsentry/sentry-java/pull/3024))
8+
- (Android) Experimental: Provide more detailed cold app start information ([#3057](https://github.com/getsentry/sentry-java/pull/3057))
9+
- Attaches spans for Application, ContentProvider, and Activities to app-start timings
10+
- Uses Process.startUptimeMillis to calculate app-start timings
11+
- To enable this feature set `options.isEnablePerformanceV2 = true`
812

913
### Fixes
1014

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

Lines changed: 82 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -119,17 +119,6 @@ public final class io/sentry/android/core/AppLifecycleIntegration : io/sentry/In
119119
public fun register (Lio/sentry/IHub;Lio/sentry/SentryOptions;)V
120120
}
121121

122-
public final class io/sentry/android/core/AppStartState {
123-
public fun getAppStartEndTime ()Lio/sentry/SentryDate;
124-
public fun getAppStartInterval ()Ljava/lang/Long;
125-
public fun getAppStartMillis ()Ljava/lang/Long;
126-
public fun getAppStartTime ()Lio/sentry/SentryDate;
127-
public static fun getInstance ()Lio/sentry/android/core/AppStartState;
128-
public fun isColdStart ()Ljava/lang/Boolean;
129-
public fun reset ()V
130-
public fun setAppStartMillis (J)V
131-
}
132-
133122
public final class io/sentry/android/core/AppState {
134123
public static fun getInstance ()Lio/sentry/android/core/AppState;
135124
public fun isInBackground ()Ljava/lang/Boolean;
@@ -155,6 +144,7 @@ public final class io/sentry/android/core/BuildInfoProvider {
155144
}
156145

157146
public final class io/sentry/android/core/ContextUtils {
147+
public static fun isForegroundImportance ()Z
158148
}
159149

160150
public class io/sentry/android/core/CurrentActivityHolder {
@@ -274,6 +264,7 @@ public final class io/sentry/android/core/SentryAndroidOptions : io/sentry/Sentr
274264
public fun isEnableFramesTracking ()Z
275265
public fun isEnableNdk ()Z
276266
public fun isEnableNetworkEventBreadcrumbs ()Z
267+
public fun isEnablePerformanceV2 ()Z
277268
public fun isEnableRootCheck ()Z
278269
public fun isEnableScopeSync ()Z
279270
public fun isEnableSystemEventBreadcrumbs ()Z
@@ -296,6 +287,7 @@ public final class io/sentry/android/core/SentryAndroidOptions : io/sentry/Sentr
296287
public fun setEnableFramesTracking (Z)V
297288
public fun setEnableNdk (Z)V
298289
public fun setEnableNetworkEventBreadcrumbs (Z)V
290+
public fun setEnablePerformanceV2 (Z)V
299291
public fun setEnableRootCheck (Z)V
300292
public fun setEnableScopeSync (Z)V
301293
public fun setEnableSystemEventBreadcrumbs (Z)V
@@ -335,17 +327,10 @@ public final class io/sentry/android/core/SentryLogcatAdapter {
335327
public static fun wtf (Ljava/lang/String;Ljava/lang/Throwable;)I
336328
}
337329

338-
public final class io/sentry/android/core/SentryPerformanceProvider : android/app/Application$ActivityLifecycleCallbacks {
330+
public final class io/sentry/android/core/SentryPerformanceProvider {
339331
public fun <init> ()V
340332
public fun attachInfo (Landroid/content/Context;Landroid/content/pm/ProviderInfo;)V
341333
public fun getType (Landroid/net/Uri;)Ljava/lang/String;
342-
public fun onActivityCreated (Landroid/app/Activity;Landroid/os/Bundle;)V
343-
public fun onActivityDestroyed (Landroid/app/Activity;)V
344-
public fun onActivityPaused (Landroid/app/Activity;)V
345-
public fun onActivityResumed (Landroid/app/Activity;)V
346-
public fun onActivitySaveInstanceState (Landroid/app/Activity;Landroid/os/Bundle;)V
347-
public fun onActivityStarted (Landroid/app/Activity;)V
348-
public fun onActivityStopped (Landroid/app/Activity;)V
349334
public fun onCreate ()Z
350335
}
351336

@@ -397,3 +382,81 @@ public final class io/sentry/android/core/cache/AndroidEnvelopeCache : io/sentry
397382
public fun store (Lio/sentry/SentryEnvelope;Lio/sentry/Hint;)V
398383
}
399384

385+
public class io/sentry/android/core/performance/ActivityLifecycleCallbacksAdapter : android/app/Application$ActivityLifecycleCallbacks {
386+
public fun <init> ()V
387+
public fun onActivityCreated (Landroid/app/Activity;Landroid/os/Bundle;)V
388+
public fun onActivityDestroyed (Landroid/app/Activity;)V
389+
public fun onActivityPaused (Landroid/app/Activity;)V
390+
public fun onActivityResumed (Landroid/app/Activity;)V
391+
public fun onActivitySaveInstanceState (Landroid/app/Activity;Landroid/os/Bundle;)V
392+
public fun onActivityStarted (Landroid/app/Activity;)V
393+
public fun onActivityStopped (Landroid/app/Activity;)V
394+
}
395+
396+
public class io/sentry/android/core/performance/ActivityLifecycleTimeSpan : java/lang/Comparable {
397+
public fun <init> ()V
398+
public fun compareTo (Lio/sentry/android/core/performance/ActivityLifecycleTimeSpan;)I
399+
public synthetic fun compareTo (Ljava/lang/Object;)I
400+
public final fun getOnCreate ()Lio/sentry/android/core/performance/TimeSpan;
401+
public final fun getOnStart ()Lio/sentry/android/core/performance/TimeSpan;
402+
}
403+
404+
public class io/sentry/android/core/performance/AppStartMetrics {
405+
public fun <init> ()V
406+
public fun addActivityLifecycleTimeSpans (Lio/sentry/android/core/performance/ActivityLifecycleTimeSpan;)V
407+
public fun clear ()V
408+
public fun getActivityLifecycleTimeSpans ()Ljava/util/List;
409+
public fun getAppStartTimeSpan ()Lio/sentry/android/core/performance/TimeSpan;
410+
public fun getAppStartTimeSpanWithFallback (Lio/sentry/android/core/SentryAndroidOptions;)Lio/sentry/android/core/performance/TimeSpan;
411+
public fun getAppStartType ()Lio/sentry/android/core/performance/AppStartMetrics$AppStartType;
412+
public fun getApplicationOnCreateTimeSpan ()Lio/sentry/android/core/performance/TimeSpan;
413+
public fun getContentProviderOnCreateTimeSpans ()Ljava/util/List;
414+
public static fun getInstance ()Lio/sentry/android/core/performance/AppStartMetrics;
415+
public fun getSdkInitTimeSpan ()Lio/sentry/android/core/performance/TimeSpan;
416+
public fun isAppLaunchedInForeground ()Z
417+
public static fun onApplicationCreate (Landroid/app/Application;)V
418+
public static fun onApplicationPostCreate (Landroid/app/Application;)V
419+
public static fun onContentProviderCreate (Landroid/content/ContentProvider;)V
420+
public static fun onContentProviderPostCreate (Landroid/content/ContentProvider;)V
421+
public fun setAppStartType (Lio/sentry/android/core/performance/AppStartMetrics$AppStartType;)V
422+
}
423+
424+
public final class io/sentry/android/core/performance/AppStartMetrics$AppStartType : java/lang/Enum {
425+
public static final field COLD Lio/sentry/android/core/performance/AppStartMetrics$AppStartType;
426+
public static final field UNKNOWN Lio/sentry/android/core/performance/AppStartMetrics$AppStartType;
427+
public static final field WARM Lio/sentry/android/core/performance/AppStartMetrics$AppStartType;
428+
public static fun valueOf (Ljava/lang/String;)Lio/sentry/android/core/performance/AppStartMetrics$AppStartType;
429+
public static fun values ()[Lio/sentry/android/core/performance/AppStartMetrics$AppStartType;
430+
}
431+
432+
public class io/sentry/android/core/performance/TimeSpan : java/lang/Comparable {
433+
public fun <init> ()V
434+
public fun compareTo (Lio/sentry/android/core/performance/TimeSpan;)I
435+
public synthetic fun compareTo (Ljava/lang/Object;)I
436+
public fun getDescription ()Ljava/lang/String;
437+
public fun getDurationMs ()J
438+
public fun getProjectedStopTimestamp ()Lio/sentry/SentryDate;
439+
public fun getProjectedStopTimestampMs ()J
440+
public fun getProjectedStopTimestampSecs ()D
441+
public fun getStartTimestamp ()Lio/sentry/SentryDate;
442+
public fun getStartTimestampMs ()J
443+
public fun getStartTimestampSecs ()D
444+
public fun getStartUptimeMs ()J
445+
public fun hasNotStarted ()Z
446+
public fun hasNotStopped ()Z
447+
public fun hasStarted ()Z
448+
public fun hasStopped ()Z
449+
public fun reset ()V
450+
public fun setDescription (Ljava/lang/String;)V
451+
public fun setStartUnixTimeMs (J)V
452+
public fun setStartedAt (J)V
453+
public fun setStoppedAt (J)V
454+
public fun start ()V
455+
public fun stop ()V
456+
}
457+
458+
public class io/sentry/android/core/performance/WindowContentChangedCallback : io/sentry/android/core/internal/gestures/WindowCallbackAdapter {
459+
public fun <init> (Landroid/view/Window$Callback;Ljava/lang/Runnable;)V
460+
public fun onContentChanged ()V
461+
}
462+

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

Lines changed: 44 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,8 @@
3030
import io.sentry.TransactionOptions;
3131
import io.sentry.android.core.internal.util.ClassUtil;
3232
import io.sentry.android.core.internal.util.FirstDrawDoneListener;
33+
import io.sentry.android.core.performance.AppStartMetrics;
34+
import io.sentry.android.core.performance.TimeSpan;
3335
import io.sentry.protocol.MeasurementValue;
3436
import io.sentry.protocol.TransactionNameSource;
3537
import io.sentry.util.Objects;
@@ -70,7 +72,6 @@ public final class ActivityLifecycleIntegration
7072
private boolean isAllActivityCallbacksAvailable;
7173

7274
private boolean firstActivityCreated = false;
73-
private final boolean foregroundImportance;
7475

7576
private @Nullable FullyDisplayedReporter fullyDisplayedReporter = null;
7677
private @Nullable ISpan appStartSpan;
@@ -100,10 +101,6 @@ public ActivityLifecycleIntegration(
100101
if (buildInfoProvider.getSdkInfoVersion() >= Build.VERSION_CODES.Q) {
101102
isAllActivityCallbacksAvailable = true;
102103
}
103-
104-
// we only track app start for processes that will show an Activity (full launch).
105-
// Here we check the process importance which will tell us that.
106-
foregroundImportance = ContextUtils.isForegroundImportance();
107104
}
108105

109106
@Override
@@ -182,15 +179,28 @@ private void startTracing(final @NotNull Activity activity) {
182179
if (!performanceEnabled) {
183180
activitiesWithOngoingTransactions.put(activity, NoOpTransaction.getInstance());
184181
TracingUtils.startNewTrace(hub);
185-
} else if (performanceEnabled) {
182+
} else {
186183
// as we allow a single transaction running on the bound Scope, we finish the previous ones
187184
stopPreviousTransactions();
188185

189186
final String activityName = getActivityName(activity);
190187

191-
final SentryDate appStartTime =
192-
foregroundImportance ? AppStartState.getInstance().getAppStartTime() : null;
193-
final Boolean coldStart = AppStartState.getInstance().isColdStart();
188+
final @Nullable SentryDate appStartTime;
189+
final @Nullable Boolean coldStart;
190+
final TimeSpan appStartTimeSpan =
191+
AppStartMetrics.getInstance().getAppStartTimeSpanWithFallback(options);
192+
193+
// we only track app start for processes that will show an Activity (full launch).
194+
// Here we check the process importance which will tell us that.
195+
final boolean foregroundImportance = ContextUtils.isForegroundImportance();
196+
if (foregroundImportance && appStartTimeSpan.hasStarted()) {
197+
appStartTime = appStartTimeSpan.getStartTimestamp();
198+
coldStart =
199+
AppStartMetrics.getInstance().getAppStartType() == AppStartMetrics.AppStartType.COLD;
200+
} else {
201+
appStartTime = null;
202+
coldStart = null;
203+
}
194204

195205
final TransactionOptions transactionOptions = new TransactionOptions();
196206
transactionOptions.setDeadlineTimeout(
@@ -407,6 +417,7 @@ public synchronized void onActivityStarted(final @NotNull Activity activity) {
407417
@Override
408418
public synchronized void onActivityResumed(final @NotNull Activity activity) {
409419
if (performanceEnabled) {
420+
410421
final @Nullable ISpan ttidSpan = ttidSpanMap.get(activity);
411422
final @Nullable ISpan ttfdSpan = ttfdSpanMap.get(activity);
412423
final View rootView = activity.findViewById(android.R.id.content);
@@ -536,13 +547,17 @@ private void cancelTtfdAutoClose() {
536547

537548
private void onFirstFrameDrawn(final @Nullable ISpan ttfdSpan, final @Nullable ISpan ttidSpan) {
538549
// app start span
539-
@Nullable final SentryDate appStartStartTime = AppStartState.getInstance().getAppStartTime();
540-
@Nullable final SentryDate appStartEndTime = AppStartState.getInstance().getAppStartEndTime();
541-
// in case the SentryPerformanceProvider is disabled it does not set the app start times,
542-
// and we need to set the end time manually here,
543-
// the start time gets set manually in SentryAndroid.init()
544-
if (appStartStartTime != null && appStartEndTime == null) {
545-
AppStartState.getInstance().setAppStartEnd();
550+
final @NotNull AppStartMetrics appStartMetrics = AppStartMetrics.getInstance();
551+
final @NotNull TimeSpan appStartTimeSpan = appStartMetrics.getAppStartTimeSpan();
552+
final @NotNull TimeSpan sdkInitTimeSpan = appStartMetrics.getSdkInitTimeSpan();
553+
554+
// in case the SentryPerformanceProvider is disabled it does not set the app start end times,
555+
// and we need to set the end time manually here
556+
if (appStartTimeSpan.hasStarted() && appStartTimeSpan.hasNotStopped()) {
557+
appStartTimeSpan.stop();
558+
}
559+
if (sdkInitTimeSpan.hasStarted() && sdkInitTimeSpan.hasNotStopped()) {
560+
sdkInitTimeSpan.stop();
546561
}
547562
finishAppStartSpan();
548563

@@ -626,7 +641,15 @@ private void setColdStart(final @Nullable Bundle savedInstanceState) {
626641
if (!firstActivityCreated) {
627642
// if Activity has savedInstanceState then its a warm start
628643
// https://developer.android.com/topic/performance/vitals/launch-time#warm
629-
AppStartState.getInstance().setColdStart(savedInstanceState == null);
644+
// SentryPerformanceProvider sets this already
645+
// pre-performance-v2: back-fill with best guess
646+
if (options != null && !options.isEnablePerformanceV2()) {
647+
AppStartMetrics.getInstance()
648+
.setAppStartType(
649+
savedInstanceState == null
650+
? AppStartMetrics.AppStartType.COLD
651+
: AppStartMetrics.AppStartType.WARM);
652+
}
630653
}
631654
}
632655

@@ -662,7 +685,10 @@ private void setColdStart(final @Nullable Bundle savedInstanceState) {
662685
}
663686

664687
private void finishAppStartSpan() {
665-
final @Nullable SentryDate appStartEndTime = AppStartState.getInstance().getAppStartEndTime();
688+
final @Nullable SentryDate appStartEndTime =
689+
AppStartMetrics.getInstance()
690+
.getAppStartTimeSpanWithFallback(options)
691+
.getProjectedStopTimestamp();
666692
if (performanceEnabled && appStartEndTime != null) {
667693
finishSpan(appStartSpan, appStartEndTime);
668694
}

0 commit comments

Comments
 (0)