From 0a51b769ff8c987b78de1bffca4730f0bb59bce1 Mon Sep 17 00:00:00 2001 From: stefanosiano Date: Mon, 2 Dec 2024 17:10:58 +0100 Subject: [PATCH 1/5] removed Activity callback from SentryPerformanceProvider --- .../core/ActivityLifecycleIntegration.java | 3 +- .../core/SentryPerformanceProvider.java | 33 ------------------- 2 files changed, 1 insertion(+), 35 deletions(-) diff --git a/sentry-android-core/src/main/java/io/sentry/android/core/ActivityLifecycleIntegration.java b/sentry-android-core/src/main/java/io/sentry/android/core/ActivityLifecycleIntegration.java index 2e1972abc0..0629becbee 100644 --- a/sentry-android-core/src/main/java/io/sentry/android/core/ActivityLifecycleIntegration.java +++ b/sentry-android-core/src/main/java/io/sentry/android/core/ActivityLifecycleIntegration.java @@ -609,8 +609,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(); } diff --git a/sentry-android-core/src/main/java/io/sentry/android/core/SentryPerformanceProvider.java b/sentry-android-core/src/main/java/io/sentry/android/core/SentryPerformanceProvider.java index 0aa946c255..5bd9358429 100644 --- a/sentry-android-core/src/main/java/io/sentry/android/core/SentryPerformanceProvider.java +++ b/sentry-android-core/src/main/java/io/sentry/android/core/SentryPerformanceProvider.java @@ -47,7 +47,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; @@ -199,37 +198,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); - } - } } } From 140fa4f41d9d258e3923f58504fd2fbf52a6f4e6 Mon Sep 17 00:00:00 2001 From: stefanosiano Date: Tue, 3 Dec 2024 17:57:34 +0100 Subject: [PATCH 2/5] 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 --- .../api/sentry-android-core.api | 16 ++ .../core/ActivityLifecycleIntegration.java | 61 ++++---- .../android/core/InternalSentrySdk.java | 9 +- .../PerformanceAndroidEventProcessor.java | 93 ++++-------- .../core/SentryPerformanceProvider.java | 7 - .../ActivityLifecycleSpanHelper.java | 138 ++++++++++++++++++ .../core/performance/AppStartMetrics.java | 14 ++ .../android/core/performance/TimeSpan.java | 17 ++- 8 files changed, 235 insertions(+), 120 deletions(-) create mode 100644 sentry-android-core/src/main/java/io/sentry/android/core/performance/ActivityLifecycleSpanHelper.java diff --git a/sentry-android-core/api/sentry-android-core.api b/sentry-android-core/api/sentry-android-core.api index b05ed1bb08..3df89647b1 100644 --- a/sentry-android-core/api/sentry-android-core.api +++ b/sentry-android-core/api/sentry-android-core.api @@ -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 (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 ()V public fun compareTo (Lio/sentry/android/core/performance/ActivityLifecycleTimeSpan;)I @@ -436,6 +450,7 @@ public class io/sentry/android/core/performance/AppStartMetrics : io/sentry/andr public fun ()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; @@ -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 } diff --git a/sentry-android-core/src/main/java/io/sentry/android/core/ActivityLifecycleIntegration.java b/sentry-android-core/src/main/java/io/sentry/android/core/ActivityLifecycleIntegration.java index 0629becbee..326a299997 100644 --- a/sentry-android-core/src/main/java/io/sentry/android/core/ActivityLifecycleIntegration.java +++ b/sentry-android-core/src/main/java/io/sentry/android/core/ActivityLifecycleIntegration.java @@ -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; @@ -77,7 +77,7 @@ public final class ActivityLifecycleIntegration private @Nullable ISpan appStartSpan; private final @NotNull WeakHashMap ttidSpanMap = new WeakHashMap<>(); private final @NotNull WeakHashMap ttfdSpanMap = new WeakHashMap<>(); - private final @NotNull WeakHashMap activityLifecycleMap = + private final @NotNull WeakHashMap activitySpanHelpers = new WeakHashMap<>(); private @NotNull SentryDate lastPausedTime = new SentryNanotimeDate(new Date(0), 0); private long lastPausedUptimeMillis = 0; @@ -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. @@ -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 @@ -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()); } } @@ -458,14 +451,10 @@ 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); + helper.saveSpanToAppStartMetrics(); } } @@ -524,7 +513,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 @@ -564,7 +556,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) { @@ -671,8 +663,9 @@ WeakHashMap getActivitiesWithOngoingTransactions() { } @TestOnly - @NotNull WeakHashMap getActivityLifecycleMap() { - return activityLifecycleMap; + @NotNull + WeakHashMap getActivitySpanHelpers() { + return activitySpanHelpers; } @TestOnly diff --git a/sentry-android-core/src/main/java/io/sentry/android/core/InternalSentrySdk.java b/sentry-android-core/src/main/java/io/sentry/android/core/InternalSentrySdk.java index a3a15d7326..4f2448d8d9 100644 --- a/sentry-android-core/src/main/java/io/sentry/android/core/InternalSentrySdk.java +++ b/sentry-android-core/src/main/java/io/sentry/android/core/InternalSentrySdk.java @@ -213,14 +213,7 @@ public static Map getAppStartMeasurement() { final @NotNull AppStartMetrics metrics = AppStartMetrics.getInstance(); final @NotNull List> 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()) { diff --git a/sentry-android-core/src/main/java/io/sentry/android/core/PerformanceAndroidEventProcessor.java b/sentry-android-core/src/main/java/io/sentry/android/core/PerformanceAndroidEventProcessor.java index 242902b339..7438a2136d 100644 --- a/sentry-android-core/src/main/java/io/sentry/android/core/PerformanceAndroidEventProcessor.java +++ b/sentry-android-core/src/main/java/io/sentry/android/core/PerformanceAndroidEventProcessor.java @@ -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; @@ -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; } @@ -234,80 +233,44 @@ private void attachAppStartSpans( @Nullable SpanId parentSpanId = null; final @NotNull List 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 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 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 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)); } } diff --git a/sentry-android-core/src/main/java/io/sentry/android/core/SentryPerformanceProvider.java b/sentry-android-core/src/main/java/io/sentry/android/core/SentryPerformanceProvider.java index 5bd9358429..8baf10ebc3 100644 --- a/sentry-android-core/src/main/java/io/sentry/android/core/SentryPerformanceProvider.java +++ b/sentry-android-core/src/main/java/io/sentry/android/core/SentryPerformanceProvider.java @@ -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; @@ -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; @@ -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; diff --git a/sentry-android-core/src/main/java/io/sentry/android/core/performance/ActivityLifecycleSpanHelper.java b/sentry-android-core/src/main/java/io/sentry/android/core/performance/ActivityLifecycleSpanHelper.java new file mode 100644 index 0000000000..7fed5e0fdb --- /dev/null +++ b/sentry-android-core/src/main/java/io/sentry/android/core/performance/ActivityLifecycleSpanHelper.java @@ -0,0 +1,138 @@ +package io.sentry.android.core.performance; + +import android.os.Looper; +import android.os.SystemClock; +import io.sentry.ISpan; +import io.sentry.Instrumenter; +import io.sentry.SentryDate; +import io.sentry.SpanDataConvention; +import io.sentry.SpanStatus; +import io.sentry.android.core.AndroidDateUtils; +import java.util.concurrent.TimeUnit; +import org.jetbrains.annotations.ApiStatus; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +@ApiStatus.Internal +public class ActivityLifecycleSpanHelper { + private static final String APP_METRICS_ACTIVITIES_OP = "activity.load"; + + private final @NotNull String activityName; + + private @Nullable SentryDate onCreateStartTimestamp = null; + private @Nullable SentryDate onStartStartTimestamp = null; + private @Nullable ISpan onCreateSpan = null; + private @Nullable ISpan onStartSpan = null; + + public ActivityLifecycleSpanHelper(final @NotNull String activityName) { + this.activityName = activityName; + } + + public void setOnCreateStartTimestamp(final @NotNull SentryDate onCreateStartTimestamp) { + this.onCreateStartTimestamp = onCreateStartTimestamp; + } + + public void setOnStartStartTimestamp(final @NotNull SentryDate onStartStartTimestamp) { + this.onStartStartTimestamp = onStartStartTimestamp; + } + + public void createAndStopOnCreateSpan(final @Nullable ISpan appStartSpan) { + if (onCreateStartTimestamp != null && appStartSpan != null) { + onCreateSpan = + createLifecycleSpan(appStartSpan, activityName + ".onCreate", onCreateStartTimestamp); + onCreateSpan.finish(); + } + } + + public void createAndStopOnStartSpan(final @Nullable ISpan appStartSpan) { + if (onStartStartTimestamp != null && appStartSpan != null) { + onStartSpan = + createLifecycleSpan(appStartSpan, activityName + ".onStart", onStartStartTimestamp); + onStartSpan.finish(); + } + } + + public @Nullable ISpan getOnCreateSpan() { + return onCreateSpan; + } + + public @Nullable ISpan getOnStartSpan() { + return onStartSpan; + } + + public @Nullable SentryDate getOnCreateStartTimestamp() { + return onCreateStartTimestamp; + } + + public @Nullable SentryDate getOnStartStartTimestamp() { + return onStartStartTimestamp; + } + + public void saveSpanToAppStartMetrics() { + if (onCreateSpan == null || onStartSpan == null) { + return; + } + final @Nullable SentryDate onCreateFinishDate = onCreateSpan.getFinishDate(); + final @Nullable SentryDate onStartFinishDate = onStartSpan.getFinishDate(); + if (onCreateFinishDate == null || onStartFinishDate == null) { + return; + } + final long now = SystemClock.uptimeMillis(); + final @NotNull SentryDate nowDate = AndroidDateUtils.getCurrentSentryDateTime(); + final long onCreateShiftMs = + TimeUnit.NANOSECONDS.toMillis(nowDate.diff(onCreateSpan.getStartDate())); + final long onCreateStopShiftMs = + TimeUnit.NANOSECONDS.toMillis(nowDate.diff(onCreateFinishDate)); + final long onStartShiftMs = + TimeUnit.NANOSECONDS.toMillis(nowDate.diff(onStartSpan.getStartDate())); + final long onStartStopShiftMs = TimeUnit.NANOSECONDS.toMillis(nowDate.diff(onStartFinishDate)); + + ActivityLifecycleTimeSpan activityLifecycleTimeSpan = new ActivityLifecycleTimeSpan(); + activityLifecycleTimeSpan + .getOnCreate() + .setup( + onCreateSpan.getDescription(), + TimeUnit.NANOSECONDS.toMillis(onCreateSpan.getStartDate().nanoTimestamp()), + now - onCreateShiftMs, + now - onCreateStopShiftMs); + activityLifecycleTimeSpan + .getOnStart() + .setup( + onStartSpan.getDescription(), + TimeUnit.NANOSECONDS.toMillis(onStartSpan.getStartDate().nanoTimestamp()), + now - onStartShiftMs, + now - onStartStopShiftMs); + AppStartMetrics.getInstance().addActivityLifecycleTimeSpans(activityLifecycleTimeSpan); + } + + private @NotNull ISpan createLifecycleSpan( + final @NotNull ISpan appStartSpan, + final @NotNull String description, + final @NotNull SentryDate startTimestamp) { + final @NotNull ISpan span = + appStartSpan.startChild( + APP_METRICS_ACTIVITIES_OP, description, startTimestamp, Instrumenter.SENTRY); + setDefaultStartSpanData(span); + return span; + } + + public void clear() { + // in case the appStartSpan isn't completed yet, we finish it as cancelled to avoid + // memory leak + if (onCreateSpan != null && !onCreateSpan.isFinished()) { + onCreateSpan.finish(SpanStatus.CANCELLED); + } + onCreateSpan = null; + if (onStartSpan != null && !onStartSpan.isFinished()) { + onStartSpan.finish(SpanStatus.CANCELLED); + } + onStartSpan = null; + } + + private void setDefaultStartSpanData(final @NotNull ISpan span) { + span.setData(SpanDataConvention.THREAD_ID, Looper.getMainLooper().getThread().getId()); + span.setData(SpanDataConvention.THREAD_NAME, "main"); + span.setData(SpanDataConvention.CONTRIBUTES_TTID, true); + span.setData(SpanDataConvention.CONTRIBUTES_TTFD, true); + } +} diff --git a/sentry-android-core/src/main/java/io/sentry/android/core/performance/AppStartMetrics.java b/sentry-android-core/src/main/java/io/sentry/android/core/performance/AppStartMetrics.java index 2e249d6dcc..70dd683a41 100644 --- a/sentry-android-core/src/main/java/io/sentry/android/core/performance/AppStartMetrics.java +++ b/sentry-android-core/src/main/java/io/sentry/android/core/performance/AppStartMetrics.java @@ -89,6 +89,20 @@ public AppStartMetrics() { return appStartSpan; } + /** + * @return the app start span Uses Process.getStartUptimeMillis() as start timestamp, which + * requires API level 24+ + */ + public @NotNull TimeSpan createProcessInitSpan() { + final @NotNull TimeSpan processInitSpan = new TimeSpan(); + processInitSpan.setup( + "Process Initialization", + appStartSpan.getStartTimestampMs(), + appStartSpan.getStartUptimeMs(), + CLASS_LOADED_UPTIME_MS); + return processInitSpan; + } + /** * @return the SDK init time span, as measured pre-performance-v2 Uses ContentProvider/Sdk init * time as start timestamp diff --git a/sentry-android-core/src/main/java/io/sentry/android/core/performance/TimeSpan.java b/sentry-android-core/src/main/java/io/sentry/android/core/performance/TimeSpan.java index dac78920f8..eb63173972 100644 --- a/sentry-android-core/src/main/java/io/sentry/android/core/performance/TimeSpan.java +++ b/sentry-android-core/src/main/java/io/sentry/android/core/performance/TimeSpan.java @@ -4,7 +4,6 @@ import io.sentry.DateUtils; import io.sentry.SentryDate; import io.sentry.SentryLongDate; -import java.util.concurrent.TimeUnit; import org.jetbrains.annotations.ApiStatus; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -21,17 +20,25 @@ public class TimeSpan implements Comparable { private @Nullable String description; - - private long startSystemNanos; private long startUnixTimeMs; private long startUptimeMs; private long stopUptimeMs; + public void setup( + final @Nullable String description, + final long startUnixTimeMs, + final long startUptimeMs, + final long stopUptimeMs) { + this.description = description; + this.startUnixTimeMs = startUnixTimeMs; + this.startUptimeMs = startUptimeMs; + this.stopUptimeMs = stopUptimeMs; + } + /** Start the time span */ public void start() { startUptimeMs = SystemClock.uptimeMillis(); startUnixTimeMs = System.currentTimeMillis(); - startSystemNanos = System.nanoTime(); } /** @@ -43,7 +50,6 @@ public void setStartedAt(final long uptimeMs) { final long shiftMs = SystemClock.uptimeMillis() - startUptimeMs; startUnixTimeMs = System.currentTimeMillis() - shiftMs; - startSystemNanos = System.nanoTime() - TimeUnit.MILLISECONDS.toNanos(shiftMs); } /** Stops the time span */ @@ -166,7 +172,6 @@ public void reset() { startUptimeMs = 0; stopUptimeMs = 0; startUnixTimeMs = 0; - startSystemNanos = 0; } @Override From 7eb96801c5348aabc5788b455cc55c539cacfdf5 Mon Sep 17 00:00:00 2001 From: stefanosiano Date: Thu, 5 Dec 2024 13:07:11 +0100 Subject: [PATCH 3/5] add tests --- .../core/ActivityLifecycleIntegrationTest.kt | 84 ++++++-- .../PerformanceAndroidEventProcessorTest.kt | 17 +- .../core/SentryPerformanceProviderTest.kt | 23 --- .../ActivityLifecycleSpanHelperTest.kt | 182 ++++++++++++++++++ .../core/performance/AppStartMetricsTest.kt | 24 ++- 5 files changed, 269 insertions(+), 61 deletions(-) create mode 100644 sentry-android-core/src/test/java/io/sentry/android/core/performance/ActivityLifecycleSpanHelperTest.kt diff --git a/sentry-android-core/src/test/java/io/sentry/android/core/ActivityLifecycleIntegrationTest.kt b/sentry-android-core/src/test/java/io/sentry/android/core/ActivityLifecycleIntegrationTest.kt index be000b7517..e9021c6830 100644 --- a/sentry-android-core/src/test/java/io/sentry/android/core/ActivityLifecycleIntegrationTest.kt +++ b/sentry-android-core/src/test/java/io/sentry/android/core/ActivityLifecycleIntegrationTest.kt @@ -784,6 +784,7 @@ class ActivityLifecycleIntegrationTest { val endDate = appStartMetrics.sdkInitTimeSpan.projectedStopTimestamp val activity = mock() + sut.onActivityPreCreated(activity, fixture.bundle) sut.onActivityCreated(activity, fixture.bundle) val appStartSpan = fixture.transaction.children.first { it.operation.startsWith("app.start.warm") } @@ -893,6 +894,7 @@ class ActivityLifecycleIntegrationTest { setAppStartTime(date) val activity = mock() + sut.onActivityPreCreated(activity, fixture.bundle) sut.onActivityCreated(activity, fixture.bundle) val span = fixture.transaction.children.first() @@ -934,6 +936,7 @@ class ActivityLifecycleIntegrationTest { setAppStartTime(date, stopDate) val activity = mock() + sut.onActivityPreCreated(activity, null) sut.onActivityCreated(activity, null) val span = fixture.transaction.children.first() @@ -954,6 +957,7 @@ class ActivityLifecycleIntegrationTest { setAppStartTime(date) val activity = mock() + sut.onActivityPreCreated(activity, null) sut.onActivityCreated(activity, null) val span = fixture.transaction.children.first() @@ -1424,7 +1428,7 @@ class ActivityLifecycleIntegrationTest { } @Test - fun `On activity preCreated onCreate span is created`() { + fun `On activity preCreated onCreate span is started`() { val sut = fixture.getSut() fixture.options.tracesSampleRate = 1.0 sut.register(fixture.hub, fixture.options) @@ -1432,16 +1436,14 @@ class ActivityLifecycleIntegrationTest { val date = SentryNanotimeDate(Date(1), 0) setAppStartTime(date) - assertTrue(sut.activityLifecycleMap.isEmpty()) + assertTrue(sut.activitySpanHelpers.isEmpty()) val activity = mock() - // Activity onCreate date will be used + // Activity onPreCreate date will be used sut.onActivityPreCreated(activity, fixture.bundle) - // sut.onActivityCreated(activity, fixture.bundle) - assertFalse(sut.activityLifecycleMap.isEmpty()) - assertTrue(sut.activityLifecycleMap.values.first().onCreate.hasStarted()) - assertFalse(sut.activityLifecycleMap.values.first().onCreate.hasStopped()) + assertFalse(sut.activitySpanHelpers.isEmpty()) + assertNotNull(sut.activitySpanHelpers[activity]!!.onCreateStartTimestamp) } @Test @@ -1456,29 +1458,50 @@ class ActivityLifecycleIntegrationTest { setAppStartTime(appStartDate) sut.register(fixture.hub, fixture.options) - assertTrue(sut.activityLifecycleMap.isEmpty()) + assertTrue(sut.activitySpanHelpers.isEmpty()) sut.onActivityPreCreated(activity, null) - assertFalse(sut.activityLifecycleMap.isEmpty()) - val activityLifecycleSpan = sut.activityLifecycleMap.values.first() - assertTrue(activityLifecycleSpan.onCreate.hasStarted()) + assertFalse(sut.activitySpanHelpers.isEmpty()) + val helper = sut.activitySpanHelpers.values.first() + assertNotNull(helper.onCreateStartTimestamp) assertEquals(startDate.nanoTimestamp(), sut.getProperty("lastPausedTime").nanoTimestamp()) sut.onActivityCreated(activity, null) assertNotNull(sut.appStartSpan) sut.onActivityPostCreated(activity, null) - assertTrue(activityLifecycleSpan.onCreate.hasStopped()) + assertTrue(helper.onCreateSpan!!.isFinished) sut.onActivityPreStarted(activity) - assertTrue(activityLifecycleSpan.onStart.hasStarted()) + assertNotNull(helper.onStartStartTimestamp) sut.onActivityStarted(activity) assertTrue(appStartMetrics.activityLifecycleTimeSpans.isEmpty()) sut.onActivityPostStarted(activity) - assertTrue(activityLifecycleSpan.onStart.hasStopped()) + assertTrue(helper.onStartSpan!!.isFinished) + assertFalse(appStartMetrics.activityLifecycleTimeSpans.isEmpty()) + } + + @Test + fun `Save activity lifecycle spans in AppStartMetrics onPostSarted`() { + val sut = fixture.getSut() + fixture.options.tracesSampleRate = 1.0 + val appStartMetrics = AppStartMetrics.getInstance() + val activity = mock() + setAppStartTime() + + sut.register(fixture.hub, fixture.options) + assertTrue(sut.activitySpanHelpers.isEmpty()) + + sut.onActivityPreCreated(activity, null) + sut.onActivityCreated(activity, null) + sut.onActivityPostCreated(activity, null) + sut.onActivityPreStarted(activity) + sut.onActivityStarted(activity) + assertTrue(appStartMetrics.activityLifecycleTimeSpans.isEmpty()) + sut.onActivityPostStarted(activity) assertFalse(appStartMetrics.activityLifecycleTimeSpans.isEmpty()) } @@ -1494,23 +1517,41 @@ class ActivityLifecycleIntegrationTest { setAppStartTime(appStartDate) sut.register(fixture.hub, fixture.options) - assertTrue(sut.activityLifecycleMap.isEmpty()) + assertTrue(sut.activitySpanHelpers.isEmpty()) sut.onActivityCreated(activity, null) - assertFalse(sut.activityLifecycleMap.isEmpty()) - val activityLifecycleSpan = sut.activityLifecycleMap.values.first() - assertTrue(activityLifecycleSpan.onCreate.hasStarted()) + assertFalse(sut.activitySpanHelpers.isEmpty()) + val helper = sut.activitySpanHelpers.values.first() + assertNotNull(helper.onCreateStartTimestamp) assertEquals(startDate.nanoTimestamp(), sut.getProperty("lastPausedTime").nanoTimestamp()) assertNotNull(sut.appStartSpan) sut.onActivityStarted(activity) - assertTrue(activityLifecycleSpan.onCreate.hasStopped()) - assertTrue(activityLifecycleSpan.onStart.hasStarted()) + assertTrue(helper.onCreateSpan!!.isFinished) + assertNotNull(helper.onStartStartTimestamp) assertTrue(appStartMetrics.activityLifecycleTimeSpans.isEmpty()) sut.onActivityResumed(activity) - assertTrue(activityLifecycleSpan.onStart.hasStopped()) + assertTrue(helper.onStartSpan!!.isFinished) + assertFalse(appStartMetrics.activityLifecycleTimeSpans.isEmpty()) + } + + @Test + fun `Save activity lifecycle spans in AppStartMetrics onResumed on API lower than 29`() { + val sut = fixture.getSut(apiVersion = Build.VERSION_CODES.P) + fixture.options.tracesSampleRate = 1.0 + val appStartMetrics = AppStartMetrics.getInstance() + val activity = mock() + setAppStartTime() + + sut.register(fixture.hub, fixture.options) + assertTrue(sut.activitySpanHelpers.isEmpty()) + + sut.onActivityCreated(activity, null) + sut.onActivityStarted(activity) + assertTrue(appStartMetrics.activityLifecycleTimeSpans.isEmpty()) + sut.onActivityResumed(activity) assertFalse(appStartMetrics.activityLifecycleTimeSpans.isEmpty()) } @@ -1556,6 +1597,7 @@ class ActivityLifecycleIntegrationTest { val lastUptimeMillis = sut.getProperty("lastPausedUptimeMillis") assertNotEquals(0, lastUptimeMillis) + sut.onActivityPreCreated(activity, null) sut.onActivityCreated(activity, null) // AppStartMetrics app start time is set to Activity preCreated timestamp assertEquals(lastUptimeMillis, appStartMetrics.appStartTimeSpan.startUptimeMs) diff --git a/sentry-android-core/src/test/java/io/sentry/android/core/PerformanceAndroidEventProcessorTest.kt b/sentry-android-core/src/test/java/io/sentry/android/core/PerformanceAndroidEventProcessorTest.kt index 35e0f5257b..12a500966f 100644 --- a/sentry-android-core/src/test/java/io/sentry/android/core/PerformanceAndroidEventProcessorTest.kt +++ b/sentry-android-core/src/test/java/io/sentry/android/core/PerformanceAndroidEventProcessorTest.kt @@ -280,21 +280,10 @@ class PerformanceAndroidEventProcessorTest { "application.load" == it.op } ) - - assertTrue( - tr.spans.any { - "activity.load" == it.op && "MainActivity.onCreate" == it.description - } - ) - assertTrue( - tr.spans.any { - "activity.load" == it.op && "MainActivity.onStart" == it.description - } - ) } @Test - fun `adds app start metrics to app warm start txn`() { + fun `does not add app start metrics to app warm start txn`() { // given some app start metrics val appStartMetrics = AppStartMetrics.getInstance() appStartMetrics.appStartType = AppStartType.WARM @@ -337,10 +326,6 @@ class PerformanceAndroidEventProcessorTest { assertFalse(tr.spans.any { "process.load" == it.op }) assertFalse(tr.spans.any { "contentprovider.load" == it.op }) assertFalse(tr.spans.any { "application.load" == it.op }) - - // activity spans should be attached - assertTrue(tr.spans.any { "activity.load" == it.op && "MainActivity.onCreate" == it.description }) - assertTrue(tr.spans.any { "activity.load" == it.op && "MainActivity.onStart" == it.description }) } @Test diff --git a/sentry-android-core/src/test/java/io/sentry/android/core/SentryPerformanceProviderTest.kt b/sentry-android-core/src/test/java/io/sentry/android/core/SentryPerformanceProviderTest.kt index 9f868d701b..d4469df071 100644 --- a/sentry-android-core/src/test/java/io/sentry/android/core/SentryPerformanceProviderTest.kt +++ b/sentry-android-core/src/test/java/io/sentry/android/core/SentryPerformanceProviderTest.kt @@ -101,29 +101,6 @@ class SentryPerformanceProviderTest { assertTrue(AppStartMetrics.getInstance().appStartTimeSpan.hasStarted()) } - @Test - fun `provider sets both appstart and sdk init start + end times`() { - val provider = fixture.getSut() - provider.onAppStartDone() - - val metrics = AppStartMetrics.getInstance() - assertTrue(metrics.appStartTimeSpan.hasStarted()) - assertTrue(metrics.appStartTimeSpan.hasStopped()) - - assertTrue(metrics.sdkInitTimeSpan.hasStarted()) - assertTrue(metrics.sdkInitTimeSpan.hasStopped()) - } - - @Test - fun `provider properly registers and unregisters ActivityLifecycleCallbacks`() { - val provider = fixture.getSut() - - // It register once for the provider itself and once for the appStartMetrics - verify(fixture.mockContext, times(2)).registerActivityLifecycleCallbacks(any()) - provider.onAppStartDone() - verify(fixture.mockContext).unregisterActivityLifecycleCallbacks(any()) - } - //region app start profiling @Test fun `when config file does not exists, nothing happens`() { diff --git a/sentry-android-core/src/test/java/io/sentry/android/core/performance/ActivityLifecycleSpanHelperTest.kt b/sentry-android-core/src/test/java/io/sentry/android/core/performance/ActivityLifecycleSpanHelperTest.kt new file mode 100644 index 0000000000..ed2ac1860a --- /dev/null +++ b/sentry-android-core/src/test/java/io/sentry/android/core/performance/ActivityLifecycleSpanHelperTest.kt @@ -0,0 +1,182 @@ +package io.sentry.android.core.performance + +import android.os.Looper +import androidx.test.ext.junit.runners.AndroidJUnit4 +import io.sentry.IHub +import io.sentry.ISpan +import io.sentry.SentryNanotimeDate +import io.sentry.SentryOptions +import io.sentry.SentryTracer +import io.sentry.Span +import io.sentry.SpanDataConvention +import io.sentry.SpanOptions +import io.sentry.TracesSamplingDecision +import io.sentry.TransactionContext +import org.junit.runner.RunWith +import org.mockito.kotlin.mock +import org.mockito.kotlin.whenever +import java.util.Date +import java.util.concurrent.TimeUnit +import kotlin.test.Test +import kotlin.test.assertEquals +import kotlin.test.assertFalse +import kotlin.test.assertNotNull +import kotlin.test.assertNull +import kotlin.test.assertTrue + +@RunWith(AndroidJUnit4::class) +class ActivityLifecycleSpanHelperTest { + private class Fixture { + val appStartSpan: ISpan + val hub = mock() + val options = SentryOptions() + val date = SentryNanotimeDate(Date(1), 1000000) + val endDate = SentryNanotimeDate(Date(3), 3000000) + + init { + whenever(hub.options).thenReturn(options) + appStartSpan = Span( + TransactionContext("name", "op", TracesSamplingDecision(true)), + SentryTracer(TransactionContext("name", "op", TracesSamplingDecision(true)), hub), + hub, + null, + SpanOptions() + ) + } + fun getSut(activityName: String = "ActivityName"): ActivityLifecycleSpanHelper { + return ActivityLifecycleSpanHelper(activityName) + } + } + private val fixture = Fixture() + + + @Test + fun `createAndStopOnCreateSpan creates and finishes onCreate span`() { + val helper = fixture.getSut() + val date = SentryNanotimeDate(Date(1), 1) + helper.setOnCreateStartTimestamp(date) + helper.createAndStopOnCreateSpan(fixture.appStartSpan) + + val onCreateSpan = helper.onCreateSpan + assertNotNull(onCreateSpan) + assertTrue(onCreateSpan.isFinished) + + assertEquals("activity.load", onCreateSpan.operation) + assertEquals("ActivityName.onCreate", onCreateSpan.description) + assertEquals(date.nanoTimestamp(), onCreateSpan.startDate.nanoTimestamp()) + assertEquals(date.nanoTimestamp(), onCreateSpan.startDate.nanoTimestamp()) + + assertEquals(Looper.getMainLooper().thread.id, onCreateSpan.getData(SpanDataConvention.THREAD_ID)) + assertEquals("main", onCreateSpan.getData(SpanDataConvention.THREAD_NAME)) + assertEquals(true, onCreateSpan.getData(SpanDataConvention.CONTRIBUTES_TTID)) + assertEquals(true, onCreateSpan.getData(SpanDataConvention.CONTRIBUTES_TTFD)) + } + + @Test + fun `createAndStopOnCreateSpan does nothing if no onCreate start timestamp is available`() { + val helper = fixture.getSut() + helper.createAndStopOnCreateSpan(fixture.appStartSpan) + assertNull(helper.onCreateSpan) + } + + @Test + fun `createAndStopOnCreateSpan does nothing if passed appStartSpan is null`() { + val helper = fixture.getSut() + helper.setOnCreateStartTimestamp(SentryNanotimeDate()) + helper.createAndStopOnCreateSpan(null) + assertNull(helper.onCreateSpan) + } + + @Test + fun `createAndStopOnStartSpan creates and finishes onStart span`() { + val helper = fixture.getSut() + val date = SentryNanotimeDate(Date(1), 1) + helper.setOnStartStartTimestamp(date) + helper.createAndStopOnStartSpan(fixture.appStartSpan) + + val onStartSpan = helper.onStartSpan + assertNotNull(onStartSpan) + assertTrue(onStartSpan.isFinished) + + assertEquals("activity.load", onStartSpan.operation) + assertEquals("ActivityName.onStart", onStartSpan.description) + assertEquals(date.nanoTimestamp(), onStartSpan.startDate.nanoTimestamp()) + assertEquals(date.nanoTimestamp(), onStartSpan.startDate.nanoTimestamp()) + + assertEquals(Looper.getMainLooper().thread.id, onStartSpan.getData(SpanDataConvention.THREAD_ID)) + assertEquals("main", onStartSpan.getData(SpanDataConvention.THREAD_NAME)) + assertEquals(true, onStartSpan.getData(SpanDataConvention.CONTRIBUTES_TTID)) + assertEquals(true, onStartSpan.getData(SpanDataConvention.CONTRIBUTES_TTFD)) + } + + @Test + fun `createAndStopOnStartSpan does nothing if no onStart start timestamp is available`() { + val helper = fixture.getSut() + helper.createAndStopOnStartSpan(fixture.appStartSpan) + assertNull(helper.onStartSpan) + } + + @Test + fun `createAndStopOnStartSpan does nothing if passed appStartSpan is null`() { + val helper = fixture.getSut() + helper.setOnStartStartTimestamp(SentryNanotimeDate()) + helper.createAndStopOnStartSpan(null) + assertNull(helper.onStartSpan) + } + + @Test + fun `saveSpanToAppStartMetrics does nothing if onCreate span is null`() { + val helper = fixture.getSut() + helper.setOnCreateStartTimestamp(fixture.date) + helper.setOnStartStartTimestamp(fixture.date) + helper.createAndStopOnStartSpan(fixture.appStartSpan) + assertNull(helper.onCreateSpan) + assertNotNull(helper.onStartSpan) + } + + @Test + fun `saveSpanToAppStartMetrics does nothing if onStart span is null`() { + val helper = fixture.getSut() + helper.setOnCreateStartTimestamp(fixture.date) + helper.createAndStopOnCreateSpan(fixture.appStartSpan) + helper.setOnStartStartTimestamp(fixture.date) + assertNotNull(helper.onCreateSpan) + assertNull(helper.onStartSpan) + } + + @Test + fun `saveSpanToAppStartMetrics saves spans to AppStartMetrics`() { + val helper = fixture.getSut() + helper.setOnCreateStartTimestamp(fixture.date) + helper.createAndStopOnCreateSpan(fixture.appStartSpan) + helper.onCreateSpan!!.updateEndDate(fixture.endDate) + helper.setOnStartStartTimestamp(fixture.date) + helper.createAndStopOnStartSpan(fixture.appStartSpan) + helper.onStartSpan!!.updateEndDate(fixture.endDate) + assertNotNull(helper.onCreateSpan) + assertNotNull(helper.onStartSpan) + + val appStartMetrics = AppStartMetrics.getInstance() + assertTrue(appStartMetrics.activityLifecycleTimeSpans.isEmpty()) + + // Save spans to AppStartMetrics + helper.saveSpanToAppStartMetrics() + assertFalse(appStartMetrics.activityLifecycleTimeSpans.isEmpty()) + val onCreate = appStartMetrics.activityLifecycleTimeSpans.first().onCreate + val onStart = appStartMetrics.activityLifecycleTimeSpans.first().onStart + + assertNotNull(onCreate) + assertEquals(helper.onCreateSpan!!.startDate.nanoTimestamp(), onCreate.startTimestamp!!.nanoTimestamp()) + val spanOnCreateDurationNanos = helper.onCreateSpan!!.finishDate!!.diff(helper.onCreateSpan!!.startDate) + assertEquals(onCreate.durationMs, TimeUnit.NANOSECONDS.toMillis(spanOnCreateDurationNanos)) + assertEquals(onCreate.description, helper.onCreateSpan!!.description) + + assertNotNull(onStart) + assertEquals(helper.onStartSpan!!.startDate.nanoTimestamp(), onStart.startTimestamp!!.nanoTimestamp()) + val spanOnStartDurationNanos = helper.onStartSpan!!.finishDate!!.diff(helper.onStartSpan!!.startDate) + assertEquals(onStart.durationMs, TimeUnit.NANOSECONDS.toMillis(spanOnStartDurationNanos)) + assertEquals(onStart.description, helper.onStartSpan!!.description) + } + + +} diff --git a/sentry-android-core/src/test/java/io/sentry/android/core/performance/AppStartMetricsTest.kt b/sentry-android-core/src/test/java/io/sentry/android/core/performance/AppStartMetricsTest.kt index d8b9e727e2..86edd79b4f 100644 --- a/sentry-android-core/src/test/java/io/sentry/android/core/performance/AppStartMetricsTest.kt +++ b/sentry-android-core/src/test/java/io/sentry/android/core/performance/AppStartMetricsTest.kt @@ -5,7 +5,9 @@ import android.content.ContentProvider import android.os.Build import android.os.Looper import androidx.test.ext.junit.runners.AndroidJUnit4 +import io.sentry.DateUtils import io.sentry.ITransactionProfiler +import io.sentry.SentryNanotimeDate import io.sentry.android.core.SentryAndroidOptions import io.sentry.android.core.SentryShadowProcess import org.junit.Before @@ -18,6 +20,7 @@ import org.mockito.kotlin.verify import org.mockito.kotlin.whenever import org.robolectric.Shadows import org.robolectric.annotation.Config +import java.util.Date import java.util.concurrent.TimeUnit import kotlin.test.Test import kotlin.test.assertEquals @@ -56,7 +59,7 @@ class AppStartMetricsTest { metrics.addActivityLifecycleTimeSpans(ActivityLifecycleTimeSpan()) AppStartMetrics.onApplicationCreate(mock()) AppStartMetrics.onContentProviderCreate(mock()) - metrics.setAppStartProfiler(mock()) + metrics.appStartProfiler = mock() metrics.appStartSamplingDecision = mock() metrics.clear() @@ -322,4 +325,23 @@ class AppStartMetricsTest { assertTrue(appStartMetrics.appStartTimeSpan.hasNotStopped()) assertEquals(10, appStartMetrics.appStartTimeSpan.startUptimeMs) } + + @Test + fun `createProcessInitSpan creates a span`() { + val appStartMetrics = AppStartMetrics.getInstance() + val startDate = SentryNanotimeDate(Date(1), 1000000) + appStartMetrics.classLoadedUptimeMs = 10 + val startMillis = DateUtils.nanosToMillis(startDate.nanoTimestamp().toDouble()).toLong() + appStartMetrics.appStartTimeSpan.setStartedAt(1) + appStartMetrics.appStartTimeSpan.setStartUnixTimeMs(startMillis) + val span = appStartMetrics.createProcessInitSpan() + + assertEquals("Process Initialization", span.description) + // Start timestampMs is taken by appStartSpan + assertEquals(startMillis, span.startTimestampMs) + // Start uptime is taken by appStartSpan and stop uptime is class loaded uptime: 10 - 1 + assertEquals(9, span.durationMs) + // Class loaded uptimeMs is 10 ms, and process init span should finish at the same ms + assertEquals(10, span.projectedStopTimestampMs) + } } From 5d30553cfbeb752afb61401b66ef3fa4ad6bf302 Mon Sep 17 00:00:00 2001 From: Sentry Github Bot Date: Thu, 5 Dec 2024 12:12:54 +0000 Subject: [PATCH 4/5] Format code --- .../core/performance/ActivityLifecycleSpanHelperTest.kt | 3 --- 1 file changed, 3 deletions(-) diff --git a/sentry-android-core/src/test/java/io/sentry/android/core/performance/ActivityLifecycleSpanHelperTest.kt b/sentry-android-core/src/test/java/io/sentry/android/core/performance/ActivityLifecycleSpanHelperTest.kt index ed2ac1860a..799ee543a9 100644 --- a/sentry-android-core/src/test/java/io/sentry/android/core/performance/ActivityLifecycleSpanHelperTest.kt +++ b/sentry-android-core/src/test/java/io/sentry/android/core/performance/ActivityLifecycleSpanHelperTest.kt @@ -49,7 +49,6 @@ class ActivityLifecycleSpanHelperTest { } private val fixture = Fixture() - @Test fun `createAndStopOnCreateSpan creates and finishes onCreate span`() { val helper = fixture.getSut() @@ -177,6 +176,4 @@ class ActivityLifecycleSpanHelperTest { assertEquals(onStart.durationMs, TimeUnit.NANOSECONDS.toMillis(spanOnStartDurationNanos)) assertEquals(onStart.description, helper.onStartSpan!!.description) } - - } From d0764bdb0e935d077782ac0e5f22e1e7ddcebb42 Mon Sep 17 00:00:00 2001 From: stefanosiano Date: Thu, 5 Dec 2024 13:19:36 +0100 Subject: [PATCH 5/5] add tests --- .../android/core/performance/ActivityLifecycleSpanHelperTest.kt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/sentry-android-core/src/test/java/io/sentry/android/core/performance/ActivityLifecycleSpanHelperTest.kt b/sentry-android-core/src/test/java/io/sentry/android/core/performance/ActivityLifecycleSpanHelperTest.kt index 799ee543a9..cb0602f016 100644 --- a/sentry-android-core/src/test/java/io/sentry/android/core/performance/ActivityLifecycleSpanHelperTest.kt +++ b/sentry-android-core/src/test/java/io/sentry/android/core/performance/ActivityLifecycleSpanHelperTest.kt @@ -164,12 +164,14 @@ class ActivityLifecycleSpanHelperTest { val onCreate = appStartMetrics.activityLifecycleTimeSpans.first().onCreate val onStart = appStartMetrics.activityLifecycleTimeSpans.first().onStart + // Check onCreate TimeSpan has same values as helper.onCreateSpan assertNotNull(onCreate) assertEquals(helper.onCreateSpan!!.startDate.nanoTimestamp(), onCreate.startTimestamp!!.nanoTimestamp()) val spanOnCreateDurationNanos = helper.onCreateSpan!!.finishDate!!.diff(helper.onCreateSpan!!.startDate) assertEquals(onCreate.durationMs, TimeUnit.NANOSECONDS.toMillis(spanOnCreateDurationNanos)) assertEquals(onCreate.description, helper.onCreateSpan!!.description) + // Check onStart TimeSpan has same values as helper.onStartSpan assertNotNull(onStart) assertEquals(helper.onStartSpan!!.startDate.nanoTimestamp(), onStart.startTimestamp!!.nanoTimestamp()) val spanOnStartDurationNanos = helper.onStartSpan!!.finishDate!!.diff(helper.onStartSpan!!.startDate)