Skip to content

Commit

Permalink
Warm starts cleanup (#3954)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
stefanosiano authored Jan 3, 2025
1 parent 7b7bb5f commit 00c8eeb
Show file tree
Hide file tree
Showing 14 changed files with 508 additions and 215 deletions.
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
# Changelog

## Unreleased

### Internal

- Warm starts cleanup ([#3954](https://github.com/getsentry/sentry-java/pull/3954))

## 7.20.0

### Features
Expand Down
16 changes: 16 additions & 0 deletions sentry-android-core/api/sentry-android-core.api
Original file line number Diff line number Diff line change
Expand Up @@ -434,6 +434,20 @@ public class io/sentry/android/core/performance/ActivityLifecycleCallbacksAdapte
public fun onActivityStopped (Landroid/app/Activity;)V
}

public class io/sentry/android/core/performance/ActivityLifecycleSpanHelper {
public fun <init> (Ljava/lang/String;)V
public fun clear ()V
public fun createAndStopOnCreateSpan (Lio/sentry/ISpan;)V
public fun createAndStopOnStartSpan (Lio/sentry/ISpan;)V
public fun getOnCreateSpan ()Lio/sentry/ISpan;
public fun getOnCreateStartTimestamp ()Lio/sentry/SentryDate;
public fun getOnStartSpan ()Lio/sentry/ISpan;
public fun getOnStartStartTimestamp ()Lio/sentry/SentryDate;
public fun saveSpanToAppStartMetrics ()V
public fun setOnCreateStartTimestamp (Lio/sentry/SentryDate;)V
public fun setOnStartStartTimestamp (Lio/sentry/SentryDate;)V
}

public class io/sentry/android/core/performance/ActivityLifecycleTimeSpan : java/lang/Comparable {
public fun <init> ()V
public fun compareTo (Lio/sentry/android/core/performance/ActivityLifecycleTimeSpan;)I
Expand All @@ -446,6 +460,7 @@ public class io/sentry/android/core/performance/AppStartMetrics : io/sentry/andr
public fun <init> ()V
public fun addActivityLifecycleTimeSpans (Lio/sentry/android/core/performance/ActivityLifecycleTimeSpan;)V
public fun clear ()V
public fun createProcessInitSpan ()Lio/sentry/android/core/performance/TimeSpan;
public fun getActivityLifecycleTimeSpans ()Ljava/util/List;
public fun getAppStartProfiler ()Lio/sentry/ITransactionProfiler;
public fun getAppStartSamplingDecision ()Lio/sentry/TracesSamplingDecision;
Expand Down Expand Up @@ -505,6 +520,7 @@ public class io/sentry/android/core/performance/TimeSpan : java/lang/Comparable
public fun setStartUnixTimeMs (J)V
public fun setStartedAt (J)V
public fun setStoppedAt (J)V
public fun setup (Ljava/lang/String;JJJ)V
public fun start ()V
public fun stop ()V
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@
import io.sentry.TransactionOptions;
import io.sentry.android.core.internal.util.ClassUtil;
import io.sentry.android.core.internal.util.FirstDrawDoneListener;
import io.sentry.android.core.performance.ActivityLifecycleTimeSpan;
import io.sentry.android.core.performance.ActivityLifecycleSpanHelper;
import io.sentry.android.core.performance.AppStartMetrics;
import io.sentry.android.core.performance.TimeSpan;
import io.sentry.protocol.MeasurementValue;
Expand Down Expand Up @@ -77,7 +77,7 @@ public final class ActivityLifecycleIntegration
private @Nullable ISpan appStartSpan;
private final @NotNull WeakHashMap<Activity, ISpan> ttidSpanMap = new WeakHashMap<>();
private final @NotNull WeakHashMap<Activity, ISpan> ttfdSpanMap = new WeakHashMap<>();
private final @NotNull WeakHashMap<Activity, ActivityLifecycleTimeSpan> activityLifecycleMap =
private final @NotNull WeakHashMap<Activity, ActivityLifecycleSpanHelper> activitySpanHelpers =
new WeakHashMap<>();
private @NotNull SentryDate lastPausedTime = new SentryNanotimeDate(new Date(0), 0);
private long lastPausedUptimeMillis = 0;
Expand Down Expand Up @@ -374,6 +374,9 @@ private void finishTransaction(
@Override
public void onActivityPreCreated(
final @NotNull Activity activity, final @Nullable Bundle savedInstanceState) {
final ActivityLifecycleSpanHelper helper =
new ActivityLifecycleSpanHelper(activity.getClass().getName());
activitySpanHelpers.put(activity, helper);
// The very first activity start timestamp cannot be set to the class instantiation time, as it
// may happen before an activity is started (service, broadcast receiver, etc). So we set it
// here.
Expand All @@ -385,10 +388,7 @@ public void onActivityPreCreated(
? hub.getOptions().getDateProvider().now()
: AndroidDateUtils.getCurrentSentryDateTime();
lastPausedUptimeMillis = SystemClock.uptimeMillis();

final @NotNull ActivityLifecycleTimeSpan timeSpan = new ActivityLifecycleTimeSpan();
timeSpan.getOnCreate().setStartedAt(lastPausedUptimeMillis);
activityLifecycleMap.put(activity, timeSpan);
helper.setOnCreateStartTimestamp(lastPausedTime);
}

@Override
Expand All @@ -415,26 +415,20 @@ public synchronized void onActivityCreated(
@Override
public void onActivityPostCreated(
final @NotNull Activity activity, final @Nullable Bundle savedInstanceState) {
if (appStartSpan == null) {
activityLifecycleMap.remove(activity);
return;
}

final @Nullable ActivityLifecycleTimeSpan timeSpan = activityLifecycleMap.get(activity);
if (timeSpan != null) {
timeSpan.getOnCreate().stop();
timeSpan.getOnCreate().setDescription(activity.getClass().getName() + ".onCreate");
final ActivityLifecycleSpanHelper helper = activitySpanHelpers.get(activity);
if (helper != null) {
helper.createAndStopOnCreateSpan(appStartSpan);
}
}

@Override
public void onActivityPreStarted(final @NotNull Activity activity) {
if (appStartSpan == null) {
return;
}
final @Nullable ActivityLifecycleTimeSpan timeSpan = activityLifecycleMap.get(activity);
if (timeSpan != null) {
timeSpan.getOnStart().setStartedAt(SystemClock.uptimeMillis());
final ActivityLifecycleSpanHelper helper = activitySpanHelpers.get(activity);
if (helper != null) {
helper.setOnStartStartTimestamp(
options != null
? options.getDateProvider().now()
: AndroidDateUtils.getCurrentSentryDateTime());
}
}

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

@Override
public void onActivityPostStarted(final @NotNull Activity activity) {
final @Nullable ActivityLifecycleTimeSpan timeSpan = activityLifecycleMap.remove(activity);
if (appStartSpan == null) {
return;
}
if (timeSpan != null) {
timeSpan.getOnStart().stop();
timeSpan.getOnStart().setDescription(activity.getClass().getName() + ".onStart");
AppStartMetrics.getInstance().addActivityLifecycleTimeSpans(timeSpan);
final ActivityLifecycleSpanHelper helper = activitySpanHelpers.get(activity);
if (helper != null) {
helper.createAndStopOnStartSpan(appStartSpan);
// Needed to handle hybrid SDKs
helper.saveSpanToAppStartMetrics();
}
}

Expand Down Expand Up @@ -523,7 +514,10 @@ public void onActivitySaveInstanceState(

@Override
public synchronized void onActivityDestroyed(final @NotNull Activity activity) {
activityLifecycleMap.remove(activity);
final ActivityLifecycleSpanHelper helper = activitySpanHelpers.remove(activity);
if (helper != null) {
helper.clear();
}
if (performanceEnabled) {

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

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

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

@TestOnly
@NotNull
WeakHashMap<Activity, ActivityLifecycleTimeSpan> getActivityLifecycleMap() {
return activityLifecycleMap;
WeakHashMap<Activity, ActivityLifecycleSpanHelper> getActivitySpanHelpers() {
return activitySpanHelpers;
}

@TestOnly
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -213,14 +213,7 @@ public static Map<String, Object> getAppStartMeasurement() {
final @NotNull AppStartMetrics metrics = AppStartMetrics.getInstance();
final @NotNull List<Map<String, Object>> spans = new ArrayList<>();

final @NotNull TimeSpan processInitNativeSpan = new TimeSpan();
processInitNativeSpan.setStartedAt(metrics.getAppStartTimeSpan().getStartUptimeMs());
processInitNativeSpan.setStartUnixTimeMs(
metrics.getAppStartTimeSpan().getStartTimestampMs()); // This has to go after setStartedAt
processInitNativeSpan.setStoppedAt(metrics.getClassLoadedUptimeMs());
processInitNativeSpan.setDescription("Process Initialization");

addTimeSpanToSerializedSpans(processInitNativeSpan, spans);
addTimeSpanToSerializedSpans(metrics.createProcessInitSpan(), spans);
addTimeSpanToSerializedSpans(metrics.getApplicationOnCreateTimeSpan(), spans);

for (final TimeSpan span : metrics.getContentProviderOnCreateTimeSpans()) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@
import io.sentry.SpanDataConvention;
import io.sentry.SpanId;
import io.sentry.SpanStatus;
import io.sentry.android.core.performance.ActivityLifecycleTimeSpan;
import io.sentry.android.core.performance.AppStartMetrics;
import io.sentry.android.core.performance.TimeSpan;
import io.sentry.protocol.App;
Expand Down Expand Up @@ -219,8 +218,8 @@ private boolean hasAppStartSpan(final @NotNull SentryTransaction txn) {
private void attachAppStartSpans(
final @NotNull AppStartMetrics appStartMetrics, final @NotNull SentryTransaction txn) {

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

Expand All @@ -234,80 +233,40 @@ private void attachAppStartSpans(
@Nullable SpanId parentSpanId = null;
final @NotNull List<SentrySpan> spans = txn.getSpans();
for (final @NotNull SentrySpan span : spans) {
if (span.getOp().contentEquals(APP_START_COLD)
|| span.getOp().contentEquals(APP_START_WARM)) {
if (span.getOp().contentEquals(APP_START_COLD)) {
parentSpanId = span.getSpanId();
break;
}
}

// We include process init, content providers and application.onCreate spans only on cold start
if (appStartMetrics.getAppStartType() == AppStartMetrics.AppStartType.COLD) {
// Process init
final long classInitUptimeMs = appStartMetrics.getClassLoadedUptimeMs();
final @NotNull TimeSpan appStartTimeSpan = appStartMetrics.getAppStartTimeSpan();
if (appStartTimeSpan.hasStarted()
&& Math.abs(classInitUptimeMs - appStartTimeSpan.getStartUptimeMs())
<= MAX_PROCESS_INIT_APP_START_DIFF_MS) {
final @NotNull TimeSpan processInitTimeSpan = new TimeSpan();
processInitTimeSpan.setStartedAt(appStartTimeSpan.getStartUptimeMs());
processInitTimeSpan.setStartUnixTimeMs(appStartTimeSpan.getStartTimestampMs());

processInitTimeSpan.setStoppedAt(classInitUptimeMs);
processInitTimeSpan.setDescription("Process Initialization");

txn.getSpans()
.add(
timeSpanToSentrySpan(
processInitTimeSpan, parentSpanId, traceId, APP_METRICS_PROCESS_INIT_OP));
}

// Content Providers
final @NotNull List<TimeSpan> contentProviderOnCreates =
appStartMetrics.getContentProviderOnCreateTimeSpans();
if (!contentProviderOnCreates.isEmpty()) {
for (final @NotNull TimeSpan contentProvider : contentProviderOnCreates) {
txn.getSpans()
.add(
timeSpanToSentrySpan(
contentProvider, parentSpanId, traceId, APP_METRICS_CONTENT_PROVIDER_OP));
}
}
// Process init
final @NotNull TimeSpan processInitTimeSpan = appStartMetrics.createProcessInitSpan();
if (processInitTimeSpan.hasStarted()
&& Math.abs(processInitTimeSpan.getDurationMs()) <= MAX_PROCESS_INIT_APP_START_DIFF_MS) {
txn.getSpans()
.add(
timeSpanToSentrySpan(
processInitTimeSpan, parentSpanId, traceId, APP_METRICS_PROCESS_INIT_OP));
}

// Application.onCreate
final @NotNull TimeSpan appOnCreate = appStartMetrics.getApplicationOnCreateTimeSpan();
if (appOnCreate.hasStopped()) {
// Content Providers
final @NotNull List<TimeSpan> contentProviderOnCreates =
appStartMetrics.getContentProviderOnCreateTimeSpans();
if (!contentProviderOnCreates.isEmpty()) {
for (final @NotNull TimeSpan contentProvider : contentProviderOnCreates) {
txn.getSpans()
.add(
timeSpanToSentrySpan(
appOnCreate, parentSpanId, traceId, APP_METRICS_APPLICATION_OP));
contentProvider, parentSpanId, traceId, APP_METRICS_CONTENT_PROVIDER_OP));
}
}

// Activities
final @NotNull List<ActivityLifecycleTimeSpan> activityLifecycleTimeSpans =
appStartMetrics.getActivityLifecycleTimeSpans();
for (ActivityLifecycleTimeSpan activityTimeSpan : activityLifecycleTimeSpans) {
if (activityTimeSpan.getOnCreate().hasStarted()
&& activityTimeSpan.getOnCreate().hasStopped()) {
txn.getSpans()
.add(
timeSpanToSentrySpan(
activityTimeSpan.getOnCreate(),
parentSpanId,
traceId,
APP_METRICS_ACTIVITIES_OP));
}
if (activityTimeSpan.getOnStart().hasStarted()
&& activityTimeSpan.getOnStart().hasStopped()) {
txn.getSpans()
.add(
timeSpanToSentrySpan(
activityTimeSpan.getOnStart(),
parentSpanId,
traceId,
APP_METRICS_ACTIVITIES_OP));
}
// Application.onCreate
final @NotNull TimeSpan appOnCreate = appStartMetrics.getApplicationOnCreateTimeSpan();
if (appOnCreate.hasStopped()) {
txn.getSpans()
.add(
timeSpanToSentrySpan(appOnCreate, parentSpanId, traceId, APP_METRICS_APPLICATION_OP));
}
}

Expand Down
Loading

0 comments on commit 00c8eeb

Please sign in to comment.