Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Warm starts cleanup #3954

Open
wants to merge 6 commits into
base: fix/warm-starts
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
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 @@ -424,6 +424,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 @@ -436,6 +450,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 @@ -495,6 +510,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,27 +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) {
final long now = SystemClock.uptimeMillis();
if (appStartSpan == null) {
return;
}
final @Nullable ActivityLifecycleTimeSpan timeSpan = activityLifecycleMap.get(activity);
if (timeSpan != null) {
timeSpan.getOnStart().setStartedAt(now);
final ActivityLifecycleSpanHelper helper = activitySpanHelpers.get(activity);
if (helper != null) {
helper.setOnStartStartTimestamp(
options != null
? options.getDateProvider().now()
: AndroidDateUtils.getCurrentSentryDateTime());
}
}

Expand All @@ -458,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 @@ -524,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 @@ -564,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 @@ -609,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 @@ -673,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,44 @@ 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 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 = appStartMetrics.createProcessInitSpan();

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
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,13 @@
import static io.sentry.Sentry.APP_START_PROFILING_CONFIG_FILE_NAME;

import android.annotation.SuppressLint;
import android.app.Activity;
import android.app.Application;
import android.content.Context;
import android.content.pm.ProviderInfo;
import android.net.Uri;
import android.os.Build;
import android.os.Handler;
import android.os.Looper;
import android.os.Process;
import android.os.SystemClock;
import androidx.annotation.NonNull;
import io.sentry.ILogger;
import io.sentry.ITransactionProfiler;
import io.sentry.JsonSerializer;
Expand All @@ -22,9 +18,7 @@
import io.sentry.SentryLevel;
import io.sentry.SentryOptions;
import io.sentry.TracesSamplingDecision;
import io.sentry.android.core.internal.util.FirstDrawDoneListener;
import io.sentry.android.core.internal.util.SentryFrameMetricsCollector;
import io.sentry.android.core.performance.ActivityLifecycleCallbacksAdapter;
import io.sentry.android.core.performance.AppStartMetrics;
import io.sentry.android.core.performance.TimeSpan;
import java.io.BufferedReader;
Expand All @@ -33,7 +27,6 @@
import java.io.FileNotFoundException;
import java.io.InputStreamReader;
import java.io.Reader;
import java.util.concurrent.atomic.AtomicBoolean;
import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
Expand All @@ -47,7 +40,6 @@ public final class SentryPerformanceProvider extends EmptySecureContentProvider
private static final long sdkInitMillis = SystemClock.uptimeMillis();

private @Nullable Application app;
private @Nullable Application.ActivityLifecycleCallbacks activityCallback;

private final @NotNull ILogger logger;
private final @NotNull BuildInfoProvider buildInfoProvider;
Expand Down Expand Up @@ -199,37 +191,5 @@ private void onAppLaunched(
final @NotNull TimeSpan appStartTimespan = appStartMetrics.getAppStartTimeSpan();
appStartTimespan.setStartedAt(Process.getStartUptimeMillis());
appStartMetrics.registerApplicationForegroundCheck(app);

final AtomicBoolean firstDrawDone = new AtomicBoolean(false);

activityCallback =
new ActivityLifecycleCallbacksAdapter() {
@Override
public void onActivityStarted(@NonNull Activity activity) {
if (firstDrawDone.get()) {
return;
}
if (activity.getWindow() != null) {
FirstDrawDoneListener.registerForNextDraw(
activity, () -> onAppStartDone(), buildInfoProvider);
} else {
new Handler(Looper.getMainLooper()).post(() -> onAppStartDone());
}
}
};

app.registerActivityLifecycleCallbacks(activityCallback);
}

synchronized void onAppStartDone() {
final @NotNull AppStartMetrics appStartMetrics = AppStartMetrics.getInstance();
appStartMetrics.getSdkInitTimeSpan().stop();
appStartMetrics.getAppStartTimeSpan().stop();

if (app != null) {
if (activityCallback != null) {
app.unregisterActivityLifecycleCallbacks(activityCallback);
}
}
}
}
Loading
Loading