Skip to content

Commit 00c8eeb

Browse files
authored
Warm starts cleanup (#3954)
* removed Activity callback from SentryPerformanceProvider * moved activity lifecycle spans logic into a separate class ActivityLifecycleSpanHelper * moved processInitSpan creation to AppStartMetrics * ActivityLifecycleIntegration now create regular spans, and adds TimeSpans to AppStartMetrics to handle hybrid SDKs * PerformanceAndroidEventProcessor does not add activity lifecycle spans to the transaction, as they are added by ActivityLifecycleIntegration directly
1 parent 7b7bb5f commit 00c8eeb

14 files changed

+508
-215
lines changed

CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,11 @@
11
# Changelog
22

3+
## Unreleased
4+
5+
### Internal
6+
7+
- Warm starts cleanup ([#3954](https://github.com/getsentry/sentry-java/pull/3954))
8+
39
## 7.20.0
410

511
### Features

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

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -434,6 +434,20 @@ public class io/sentry/android/core/performance/ActivityLifecycleCallbacksAdapte
434434
public fun onActivityStopped (Landroid/app/Activity;)V
435435
}
436436

437+
public class io/sentry/android/core/performance/ActivityLifecycleSpanHelper {
438+
public fun <init> (Ljava/lang/String;)V
439+
public fun clear ()V
440+
public fun createAndStopOnCreateSpan (Lio/sentry/ISpan;)V
441+
public fun createAndStopOnStartSpan (Lio/sentry/ISpan;)V
442+
public fun getOnCreateSpan ()Lio/sentry/ISpan;
443+
public fun getOnCreateStartTimestamp ()Lio/sentry/SentryDate;
444+
public fun getOnStartSpan ()Lio/sentry/ISpan;
445+
public fun getOnStartStartTimestamp ()Lio/sentry/SentryDate;
446+
public fun saveSpanToAppStartMetrics ()V
447+
public fun setOnCreateStartTimestamp (Lio/sentry/SentryDate;)V
448+
public fun setOnStartStartTimestamp (Lio/sentry/SentryDate;)V
449+
}
450+
437451
public class io/sentry/android/core/performance/ActivityLifecycleTimeSpan : java/lang/Comparable {
438452
public fun <init> ()V
439453
public fun compareTo (Lio/sentry/android/core/performance/ActivityLifecycleTimeSpan;)I
@@ -446,6 +460,7 @@ public class io/sentry/android/core/performance/AppStartMetrics : io/sentry/andr
446460
public fun <init> ()V
447461
public fun addActivityLifecycleTimeSpans (Lio/sentry/android/core/performance/ActivityLifecycleTimeSpan;)V
448462
public fun clear ()V
463+
public fun createProcessInitSpan ()Lio/sentry/android/core/performance/TimeSpan;
449464
public fun getActivityLifecycleTimeSpans ()Ljava/util/List;
450465
public fun getAppStartProfiler ()Lio/sentry/ITransactionProfiler;
451466
public fun getAppStartSamplingDecision ()Lio/sentry/TracesSamplingDecision;
@@ -505,6 +520,7 @@ public class io/sentry/android/core/performance/TimeSpan : java/lang/Comparable
505520
public fun setStartUnixTimeMs (J)V
506521
public fun setStartedAt (J)V
507522
public fun setStoppedAt (J)V
523+
public fun setup (Ljava/lang/String;JJJ)V
508524
public fun start ()V
509525
public fun stop ()V
510526
}

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

Lines changed: 28 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@
2828
import io.sentry.TransactionOptions;
2929
import io.sentry.android.core.internal.util.ClassUtil;
3030
import io.sentry.android.core.internal.util.FirstDrawDoneListener;
31-
import io.sentry.android.core.performance.ActivityLifecycleTimeSpan;
31+
import io.sentry.android.core.performance.ActivityLifecycleSpanHelper;
3232
import io.sentry.android.core.performance.AppStartMetrics;
3333
import io.sentry.android.core.performance.TimeSpan;
3434
import io.sentry.protocol.MeasurementValue;
@@ -77,7 +77,7 @@ public final class ActivityLifecycleIntegration
7777
private @Nullable ISpan appStartSpan;
7878
private final @NotNull WeakHashMap<Activity, ISpan> ttidSpanMap = new WeakHashMap<>();
7979
private final @NotNull WeakHashMap<Activity, ISpan> ttfdSpanMap = new WeakHashMap<>();
80-
private final @NotNull WeakHashMap<Activity, ActivityLifecycleTimeSpan> activityLifecycleMap =
80+
private final @NotNull WeakHashMap<Activity, ActivityLifecycleSpanHelper> activitySpanHelpers =
8181
new WeakHashMap<>();
8282
private @NotNull SentryDate lastPausedTime = new SentryNanotimeDate(new Date(0), 0);
8383
private long lastPausedUptimeMillis = 0;
@@ -374,6 +374,9 @@ private void finishTransaction(
374374
@Override
375375
public void onActivityPreCreated(
376376
final @NotNull Activity activity, final @Nullable Bundle savedInstanceState) {
377+
final ActivityLifecycleSpanHelper helper =
378+
new ActivityLifecycleSpanHelper(activity.getClass().getName());
379+
activitySpanHelpers.put(activity, helper);
377380
// The very first activity start timestamp cannot be set to the class instantiation time, as it
378381
// may happen before an activity is started (service, broadcast receiver, etc). So we set it
379382
// here.
@@ -385,10 +388,7 @@ public void onActivityPreCreated(
385388
? hub.getOptions().getDateProvider().now()
386389
: AndroidDateUtils.getCurrentSentryDateTime();
387390
lastPausedUptimeMillis = SystemClock.uptimeMillis();
388-
389-
final @NotNull ActivityLifecycleTimeSpan timeSpan = new ActivityLifecycleTimeSpan();
390-
timeSpan.getOnCreate().setStartedAt(lastPausedUptimeMillis);
391-
activityLifecycleMap.put(activity, timeSpan);
391+
helper.setOnCreateStartTimestamp(lastPausedTime);
392392
}
393393

394394
@Override
@@ -415,26 +415,20 @@ public synchronized void onActivityCreated(
415415
@Override
416416
public void onActivityPostCreated(
417417
final @NotNull Activity activity, final @Nullable Bundle savedInstanceState) {
418-
if (appStartSpan == null) {
419-
activityLifecycleMap.remove(activity);
420-
return;
421-
}
422-
423-
final @Nullable ActivityLifecycleTimeSpan timeSpan = activityLifecycleMap.get(activity);
424-
if (timeSpan != null) {
425-
timeSpan.getOnCreate().stop();
426-
timeSpan.getOnCreate().setDescription(activity.getClass().getName() + ".onCreate");
418+
final ActivityLifecycleSpanHelper helper = activitySpanHelpers.get(activity);
419+
if (helper != null) {
420+
helper.createAndStopOnCreateSpan(appStartSpan);
427421
}
428422
}
429423

430424
@Override
431425
public void onActivityPreStarted(final @NotNull Activity activity) {
432-
if (appStartSpan == null) {
433-
return;
434-
}
435-
final @Nullable ActivityLifecycleTimeSpan timeSpan = activityLifecycleMap.get(activity);
436-
if (timeSpan != null) {
437-
timeSpan.getOnStart().setStartedAt(SystemClock.uptimeMillis());
426+
final ActivityLifecycleSpanHelper helper = activitySpanHelpers.get(activity);
427+
if (helper != null) {
428+
helper.setOnStartStartTimestamp(
429+
options != null
430+
? options.getDateProvider().now()
431+
: AndroidDateUtils.getCurrentSentryDateTime());
438432
}
439433
}
440434

@@ -457,14 +451,11 @@ public synchronized void onActivityStarted(final @NotNull Activity activity) {
457451

458452
@Override
459453
public void onActivityPostStarted(final @NotNull Activity activity) {
460-
final @Nullable ActivityLifecycleTimeSpan timeSpan = activityLifecycleMap.remove(activity);
461-
if (appStartSpan == null) {
462-
return;
463-
}
464-
if (timeSpan != null) {
465-
timeSpan.getOnStart().stop();
466-
timeSpan.getOnStart().setDescription(activity.getClass().getName() + ".onStart");
467-
AppStartMetrics.getInstance().addActivityLifecycleTimeSpans(timeSpan);
454+
final ActivityLifecycleSpanHelper helper = activitySpanHelpers.get(activity);
455+
if (helper != null) {
456+
helper.createAndStopOnStartSpan(appStartSpan);
457+
// Needed to handle hybrid SDKs
458+
helper.saveSpanToAppStartMetrics();
468459
}
469460
}
470461

@@ -523,7 +514,10 @@ public void onActivitySaveInstanceState(
523514

524515
@Override
525516
public synchronized void onActivityDestroyed(final @NotNull Activity activity) {
526-
activityLifecycleMap.remove(activity);
517+
final ActivityLifecycleSpanHelper helper = activitySpanHelpers.remove(activity);
518+
if (helper != null) {
519+
helper.clear();
520+
}
527521
if (performanceEnabled) {
528522

529523
// in case the appStartSpan isn't completed yet, we finish it as cancelled to avoid
@@ -563,7 +557,7 @@ private void clear() {
563557
firstActivityCreated = false;
564558
lastPausedTime = new SentryNanotimeDate(new Date(0), 0);
565559
lastPausedUptimeMillis = 0;
566-
activityLifecycleMap.clear();
560+
activitySpanHelpers.clear();
567561
}
568562

569563
private void finishSpan(final @Nullable ISpan span) {
@@ -608,8 +602,7 @@ private void onFirstFrameDrawn(final @Nullable ISpan ttfdSpan, final @Nullable I
608602
final @NotNull TimeSpan appStartTimeSpan = appStartMetrics.getAppStartTimeSpan();
609603
final @NotNull TimeSpan sdkInitTimeSpan = appStartMetrics.getSdkInitTimeSpan();
610604

611-
// in case the SentryPerformanceProvider is disabled it does not set the app start end times,
612-
// and we need to set the end time manually here
605+
// and we need to set the end time of the app start here, after the first frame is drawn.
613606
if (appStartTimeSpan.hasStarted() && appStartTimeSpan.hasNotStopped()) {
614607
appStartTimeSpan.stop();
615608
}
@@ -672,8 +665,8 @@ WeakHashMap<Activity, ITransaction> getActivitiesWithOngoingTransactions() {
672665

673666
@TestOnly
674667
@NotNull
675-
WeakHashMap<Activity, ActivityLifecycleTimeSpan> getActivityLifecycleMap() {
676-
return activityLifecycleMap;
668+
WeakHashMap<Activity, ActivityLifecycleSpanHelper> getActivitySpanHelpers() {
669+
return activitySpanHelpers;
677670
}
678671

679672
@TestOnly

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

Lines changed: 1 addition & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -213,14 +213,7 @@ public static Map<String, Object> getAppStartMeasurement() {
213213
final @NotNull AppStartMetrics metrics = AppStartMetrics.getInstance();
214214
final @NotNull List<Map<String, Object>> spans = new ArrayList<>();
215215

216-
final @NotNull TimeSpan processInitNativeSpan = new TimeSpan();
217-
processInitNativeSpan.setStartedAt(metrics.getAppStartTimeSpan().getStartUptimeMs());
218-
processInitNativeSpan.setStartUnixTimeMs(
219-
metrics.getAppStartTimeSpan().getStartTimestampMs()); // This has to go after setStartedAt
220-
processInitNativeSpan.setStoppedAt(metrics.getClassLoadedUptimeMs());
221-
processInitNativeSpan.setDescription("Process Initialization");
222-
223-
addTimeSpanToSerializedSpans(processInitNativeSpan, spans);
216+
addTimeSpanToSerializedSpans(metrics.createProcessInitSpan(), spans);
224217
addTimeSpanToSerializedSpans(metrics.getApplicationOnCreateTimeSpan(), spans);
225218

226219
for (final TimeSpan span : metrics.getContentProviderOnCreateTimeSpans()) {

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

Lines changed: 24 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@
1313
import io.sentry.SpanDataConvention;
1414
import io.sentry.SpanId;
1515
import io.sentry.SpanStatus;
16-
import io.sentry.android.core.performance.ActivityLifecycleTimeSpan;
1716
import io.sentry.android.core.performance.AppStartMetrics;
1817
import io.sentry.android.core.performance.TimeSpan;
1918
import io.sentry.protocol.App;
@@ -219,8 +218,8 @@ private boolean hasAppStartSpan(final @NotNull SentryTransaction txn) {
219218
private void attachAppStartSpans(
220219
final @NotNull AppStartMetrics appStartMetrics, final @NotNull SentryTransaction txn) {
221220

222-
// data will be filled only for cold and warm app starts
223-
if (appStartMetrics.getAppStartType() == AppStartMetrics.AppStartType.UNKNOWN) {
221+
// We include process init, content providers and application.onCreate spans only on cold start
222+
if (appStartMetrics.getAppStartType() != AppStartMetrics.AppStartType.COLD) {
224223
return;
225224
}
226225

@@ -234,80 +233,40 @@ private void attachAppStartSpans(
234233
@Nullable SpanId parentSpanId = null;
235234
final @NotNull List<SentrySpan> spans = txn.getSpans();
236235
for (final @NotNull SentrySpan span : spans) {
237-
if (span.getOp().contentEquals(APP_START_COLD)
238-
|| span.getOp().contentEquals(APP_START_WARM)) {
236+
if (span.getOp().contentEquals(APP_START_COLD)) {
239237
parentSpanId = span.getSpanId();
240238
break;
241239
}
242240
}
243241

244-
// We include process init, content providers and application.onCreate spans only on cold start
245-
if (appStartMetrics.getAppStartType() == AppStartMetrics.AppStartType.COLD) {
246-
// Process init
247-
final long classInitUptimeMs = appStartMetrics.getClassLoadedUptimeMs();
248-
final @NotNull TimeSpan appStartTimeSpan = appStartMetrics.getAppStartTimeSpan();
249-
if (appStartTimeSpan.hasStarted()
250-
&& Math.abs(classInitUptimeMs - appStartTimeSpan.getStartUptimeMs())
251-
<= MAX_PROCESS_INIT_APP_START_DIFF_MS) {
252-
final @NotNull TimeSpan processInitTimeSpan = new TimeSpan();
253-
processInitTimeSpan.setStartedAt(appStartTimeSpan.getStartUptimeMs());
254-
processInitTimeSpan.setStartUnixTimeMs(appStartTimeSpan.getStartTimestampMs());
255-
256-
processInitTimeSpan.setStoppedAt(classInitUptimeMs);
257-
processInitTimeSpan.setDescription("Process Initialization");
258-
259-
txn.getSpans()
260-
.add(
261-
timeSpanToSentrySpan(
262-
processInitTimeSpan, parentSpanId, traceId, APP_METRICS_PROCESS_INIT_OP));
263-
}
264-
265-
// Content Providers
266-
final @NotNull List<TimeSpan> contentProviderOnCreates =
267-
appStartMetrics.getContentProviderOnCreateTimeSpans();
268-
if (!contentProviderOnCreates.isEmpty()) {
269-
for (final @NotNull TimeSpan contentProvider : contentProviderOnCreates) {
270-
txn.getSpans()
271-
.add(
272-
timeSpanToSentrySpan(
273-
contentProvider, parentSpanId, traceId, APP_METRICS_CONTENT_PROVIDER_OP));
274-
}
275-
}
242+
// Process init
243+
final @NotNull TimeSpan processInitTimeSpan = appStartMetrics.createProcessInitSpan();
244+
if (processInitTimeSpan.hasStarted()
245+
&& Math.abs(processInitTimeSpan.getDurationMs()) <= MAX_PROCESS_INIT_APP_START_DIFF_MS) {
246+
txn.getSpans()
247+
.add(
248+
timeSpanToSentrySpan(
249+
processInitTimeSpan, parentSpanId, traceId, APP_METRICS_PROCESS_INIT_OP));
250+
}
276251

277-
// Application.onCreate
278-
final @NotNull TimeSpan appOnCreate = appStartMetrics.getApplicationOnCreateTimeSpan();
279-
if (appOnCreate.hasStopped()) {
252+
// Content Providers
253+
final @NotNull List<TimeSpan> contentProviderOnCreates =
254+
appStartMetrics.getContentProviderOnCreateTimeSpans();
255+
if (!contentProviderOnCreates.isEmpty()) {
256+
for (final @NotNull TimeSpan contentProvider : contentProviderOnCreates) {
280257
txn.getSpans()
281258
.add(
282259
timeSpanToSentrySpan(
283-
appOnCreate, parentSpanId, traceId, APP_METRICS_APPLICATION_OP));
260+
contentProvider, parentSpanId, traceId, APP_METRICS_CONTENT_PROVIDER_OP));
284261
}
285262
}
286263

287-
// Activities
288-
final @NotNull List<ActivityLifecycleTimeSpan> activityLifecycleTimeSpans =
289-
appStartMetrics.getActivityLifecycleTimeSpans();
290-
for (ActivityLifecycleTimeSpan activityTimeSpan : activityLifecycleTimeSpans) {
291-
if (activityTimeSpan.getOnCreate().hasStarted()
292-
&& activityTimeSpan.getOnCreate().hasStopped()) {
293-
txn.getSpans()
294-
.add(
295-
timeSpanToSentrySpan(
296-
activityTimeSpan.getOnCreate(),
297-
parentSpanId,
298-
traceId,
299-
APP_METRICS_ACTIVITIES_OP));
300-
}
301-
if (activityTimeSpan.getOnStart().hasStarted()
302-
&& activityTimeSpan.getOnStart().hasStopped()) {
303-
txn.getSpans()
304-
.add(
305-
timeSpanToSentrySpan(
306-
activityTimeSpan.getOnStart(),
307-
parentSpanId,
308-
traceId,
309-
APP_METRICS_ACTIVITIES_OP));
310-
}
264+
// Application.onCreate
265+
final @NotNull TimeSpan appOnCreate = appStartMetrics.getApplicationOnCreateTimeSpan();
266+
if (appOnCreate.hasStopped()) {
267+
txn.getSpans()
268+
.add(
269+
timeSpanToSentrySpan(appOnCreate, parentSpanId, traceId, APP_METRICS_APPLICATION_OP));
311270
}
312271
}
313272

0 commit comments

Comments
 (0)