From 1ac1c1b41ee8f6cc9f1287c8da02c1a608f00f16 Mon Sep 17 00:00:00 2001 From: bidetofevil Date: Tue, 27 Aug 2024 23:15:15 -0700 Subject: [PATCH 1/4] Add comopnent to create an activity open trace based on a sequence of events --- .../api/embrace-android-api.api | 21 ++ .../internal/capture/activity/OpenEvents.kt | 62 ++++ .../capture/activity/OpenTraceEmitter.kt | 264 +++++++++++++++ .../capture/activity/OpenTraceEmitterTest.kt | 310 ++++++++++++++++++ 4 files changed, 657 insertions(+) create mode 100644 embrace-android-api/src/main/kotlin/io/embrace/android/embracesdk/internal/capture/activity/OpenEvents.kt create mode 100644 embrace-android-features/src/main/kotlin/io/embrace/android/embracesdk/internal/capture/activity/OpenTraceEmitter.kt create mode 100644 embrace-android-features/src/test/java/io/embrace/android/embracesdk/internal/capture/activity/OpenTraceEmitterTest.kt diff --git a/embrace-android-api/api/embrace-android-api.api b/embrace-android-api/api/embrace-android-api.api index 027f8aa9a8..dd0482ec89 100644 --- a/embrace-android-api/api/embrace-android-api.api +++ b/embrace-android-api/api/embrace-android-api.api @@ -118,6 +118,27 @@ public abstract interface class io/embrace/android/embracesdk/internal/api/UserA public abstract fun setUsername (Ljava/lang/String;)V } +public abstract interface class io/embrace/android/embracesdk/internal/capture/activity/OpenEvents { + public abstract fun create (ILjava/lang/String;J)V + public abstract fun createEnd (IJ)V + public abstract fun hibernate (ILjava/lang/String;J)V + public abstract fun render (ILjava/lang/String;J)V + public abstract fun renderEnd (IJ)V + public abstract fun resetTrace (ILjava/lang/String;J)V + public abstract fun resume (ILjava/lang/String;J)V + public abstract fun resumeEnd (IJ)V + public abstract fun start (ILjava/lang/String;J)V + public abstract fun startEnd (IJ)V +} + +public final class io/embrace/android/embracesdk/internal/capture/activity/OpenEvents$OpenType : java/lang/Enum { + public static final field COLD Lio/embrace/android/embracesdk/internal/capture/activity/OpenEvents$OpenType; + public static final field HOT Lio/embrace/android/embracesdk/internal/capture/activity/OpenEvents$OpenType; + public final fun getTypeName ()Ljava/lang/String; + public static fun valueOf (Ljava/lang/String;)Lio/embrace/android/embracesdk/internal/capture/activity/OpenEvents$OpenType; + public static fun values ()[Lio/embrace/android/embracesdk/internal/capture/activity/OpenEvents$OpenType; +} + public class io/embrace/android/embracesdk/internal/network/http/EmbraceHttpPathOverride { protected static final field PATH_OVERRIDE Ljava/lang/String; public fun ()V diff --git a/embrace-android-api/src/main/kotlin/io/embrace/android/embracesdk/internal/capture/activity/OpenEvents.kt b/embrace-android-api/src/main/kotlin/io/embrace/android/embracesdk/internal/capture/activity/OpenEvents.kt new file mode 100644 index 0000000000..c7ba1031ff --- /dev/null +++ b/embrace-android-api/src/main/kotlin/io/embrace/android/embracesdk/internal/capture/activity/OpenEvents.kt @@ -0,0 +1,62 @@ +package io.embrace.android.embracesdk.internal.capture.activity + +/** + * The relevant stages in the lifecycle of Activities pertaining to observing the performance of their loading + */ +public interface OpenEvents { + + /** + * When a previously in-progress Activity Open trace should be abandoned, and that the component managing + * the trace recording should prepare itself to start tracing the opening of a new Activity instance. + */ + public fun resetTrace(instanceId: Int, activityName: String, timestampMs: Long) + + /** + * When the app is no longer in a state where it is trying to open up a new Activity + */ + public fun hibernate(instanceId: Int, activityName: String, timestampMs: Long) + + /** + * When the given Activity is entering the CREATE stage of its lifecycle. + */ + public fun create(instanceId: Int, activityName: String, timestampMs: Long) + + /** + * When the given Activity has exited the CREATE stage of its lifecycle. + */ + public fun createEnd(instanceId: Int, timestampMs: Long) + + /** + * When the given Activity is entering the START stage of its lifecycle. + */ + public fun start(instanceId: Int, activityName: String, timestampMs: Long) + + /** + * When the given Activity has exited the START stage of its lifecycle. + */ + public fun startEnd(instanceId: Int, timestampMs: Long) + + /** + * When the given Activity is entering the RESUME stage of its lifecycle. + */ + public fun resume(instanceId: Int, activityName: String, timestampMs: Long) + + /** + * When the given Activity has exited the RESUME stage of its lifecycle. + */ + public fun resumeEnd(instanceId: Int, timestampMs: Long) + + /** + * When the given Activity's first UI frame starts to be rendered. + */ + public fun render(instanceId: Int, activityName: String, timestampMs: Long) + + /** + * When the given Activity's first UI frame has been displayed. + */ + public fun renderEnd(instanceId: Int, timestampMs: Long) + + public enum class OpenType(public val typeName: String) { + COLD("cold"), HOT("hot") + } +} diff --git a/embrace-android-features/src/main/kotlin/io/embrace/android/embracesdk/internal/capture/activity/OpenTraceEmitter.kt b/embrace-android-features/src/main/kotlin/io/embrace/android/embracesdk/internal/capture/activity/OpenTraceEmitter.kt new file mode 100644 index 0000000000..bdc80949c2 --- /dev/null +++ b/embrace-android-features/src/main/kotlin/io/embrace/android/embracesdk/internal/capture/activity/OpenTraceEmitter.kt @@ -0,0 +1,264 @@ +package io.embrace.android.embracesdk.internal.capture.activity + +import android.app.Application.ActivityLifecycleCallbacks +import android.os.Build +import io.embrace.android.embracesdk.internal.arch.schema.EmbType +import io.embrace.android.embracesdk.internal.spans.PersistableEmbraceSpan +import io.embrace.android.embracesdk.internal.spans.SpanService +import io.embrace.android.embracesdk.internal.utils.VersionChecker +import io.embrace.android.embracesdk.spans.ErrorCode +import java.util.concurrent.ConcurrentHashMap +import java.util.concurrent.atomic.AtomicReference + +/** + * Observes Activity lifecycle and rendering events to create traces that model the workflow for showing an Activity on screen. + * Depending on the version of Android and the state of the app, the start, end, and intermediate stages of the workflow will use + * timestamps from different events, which affects to preciseness of the measurement. + * + * Trace Start: + * + * - On Android 10+, when [ActivityLifecycleCallbacks.onActivityPostPaused] is fired, denoting that the previous activity has completed + * its [ActivityLifecycleCallbacks.onActivityPaused] callbacks and a new Activity is ready to be created. + * - Android 9 and lower, when [ActivityLifecycleCallbacks.onActivityPaused] is fired, denoting that the previous activity is in the + * process of exiting. This will possibly result in some cleanup work of exiting the previous activity being included in the duration + * of the next trace that is logged. + * + * Trace End: + * + * - Android 10+, when the Activity's first UI frame finishes rendering and is delivered to the screen + * - Android 9 and lower.... TODO + */ +public class OpenTraceEmitter( + private val spanService: SpanService, + private val versionChecker: VersionChecker, +) : OpenEvents { + + private val activeTraces: MutableMap = ConcurrentHashMap() + private val traceIds: MutableMap = ConcurrentHashMap() + private val traceZygoteHolder: AtomicReference = AtomicReference(INITIAL) + + override fun resetTrace(instanceId: Int, activityName: String, timestampMs: Long) { + traceIds[activityName]?.let { existingTraceId -> + if (instanceId != existingTraceId) { + endTrace(instanceId = existingTraceId, timestampMs = timestampMs, errorCode = ErrorCode.USER_ABANDON) + } + } + traceZygoteHolder.set( + OpenTraceZygote( + lastActivityName = activityName, + lastActivityInstanceId = instanceId, + lastActivityPausedTimeMs = timestampMs + ) + ) + } + + override fun hibernate(instanceId: Int, activityName: String, timestampMs: Long) { + if (traceZygoteHolder.get().lastActivityInstanceId == instanceId) { + traceZygoteHolder.set(BACKGROUNDED) + } + } + + override fun create(instanceId: Int, activityName: String, timestampMs: Long) { + startTrace( + openType = OpenEvents.OpenType.COLD, + instanceId = instanceId, + activityName = activityName, + timestampMs = timestampMs + ) + startChildSpan( + instanceId = instanceId, + timestampMs = timestampMs, + lifecycleEvent = LifecycleEvent.CREATE + ) + } + + override fun createEnd(instanceId: Int, timestampMs: Long) { + endChildSpan( + instanceId = instanceId, + timestampMs = timestampMs, + lifecycleEvent = LifecycleEvent.CREATE + ) + } + + override fun start(instanceId: Int, activityName: String, timestampMs: Long) { + startTrace( + openType = OpenEvents.OpenType.HOT, + instanceId = instanceId, + activityName = activityName, + timestampMs = timestampMs + ) + startChildSpan( + instanceId = instanceId, + timestampMs = timestampMs, + lifecycleEvent = LifecycleEvent.START + ) + } + + override fun startEnd(instanceId: Int, timestampMs: Long) { + endChildSpan( + instanceId = instanceId, + timestampMs = timestampMs, + lifecycleEvent = LifecycleEvent.START + ) + } + + override fun resume(instanceId: Int, activityName: String, timestampMs: Long) { + if (!hasRenderEvent()) { + endTrace( + instanceId = instanceId, + timestampMs = timestampMs, + ) + } else { + startChildSpan( + instanceId = instanceId, + timestampMs = timestampMs, + lifecycleEvent = LifecycleEvent.RESUME + ) + } + traceZygoteHolder.set(READY) + } + + override fun resumeEnd(instanceId: Int, timestampMs: Long) { + endChildSpan( + instanceId = instanceId, + timestampMs = timestampMs, + lifecycleEvent = LifecycleEvent.RESUME + ) + } + + override fun render(instanceId: Int, activityName: String, timestampMs: Long) { + startChildSpan( + instanceId = instanceId, + timestampMs = timestampMs, + lifecycleEvent = LifecycleEvent.RENDER + ) + } + + override fun renderEnd(instanceId: Int, timestampMs: Long) { + endChildSpan( + instanceId = instanceId, + timestampMs = timestampMs, + lifecycleEvent = LifecycleEvent.RENDER + ) + endTrace( + instanceId = instanceId, + timestampMs = timestampMs, + ) + } + + private fun startTrace( + openType: OpenEvents.OpenType, + instanceId: Int, + activityName: String, + timestampMs: Long + ) { + if (traceZygoteHolder.get() == INITIAL) { + return + } + + if (!activeTraces.containsKey(instanceId)) { + val zygote = traceZygoteHolder.getAndSet(READY) + val startTimeMs = if (zygote.lastActivityPausedTimeMs != -1L) { + zygote.lastActivityPausedTimeMs + } else { + timestampMs + } + + spanService.startSpan( + name = traceName(activityName, openType), + type = EmbType.Performance.ActivityOpen, + startTimeMs = startTimeMs, + )?.let { root -> + if (zygote.lastActivityInstanceId != -1) { + root.addSystemAttribute("last_activity", zygote.lastActivityName) + } + activeTraces[instanceId] = ActivityOpenTrace(root = root, activityName = activityName) + } + } + } + + private fun endTrace(instanceId: Int, timestampMs: Long, errorCode: ErrorCode? = null) { + activeTraces[instanceId]?.let { trace -> + with(trace) { + children.values.filter { it.isRecording }.forEach { span -> + span.stop(endTimeMs = timestampMs, errorCode = errorCode) + } + root.stop(endTimeMs = timestampMs, errorCode = errorCode) + } + activeTraces.remove(instanceId) + } + } + + private fun startChildSpan(instanceId: Int, timestampMs: Long, lifecycleEvent: LifecycleEvent) { + val trace = activeTraces[instanceId] + if (trace != null && !trace.children.containsKey(lifecycleEvent)) { + spanService.startSpan( + name = lifecycleEvent.spanName(trace.activityName), + parent = trace.root, + startTimeMs = timestampMs, + )?.let { newSpan -> + val newChildren = trace.children.plus(lifecycleEvent to newSpan) + activeTraces[instanceId] = trace.copy( + children = newChildren + ) + } + } + } + + private fun endChildSpan(instanceId: Int, timestampMs: Long, lifecycleEvent: LifecycleEvent) { + activeTraces[instanceId]?.let { trace -> + trace.children[lifecycleEvent]?.stop(timestampMs) + } + } + + private fun hasRenderEvent(): Boolean = versionChecker.isAtLeast(Build.VERSION_CODES.Q) + + private fun traceName( + activityName: String, + openType: OpenEvents.OpenType + ): String = "$activityName-${openType.typeName}-open" + + public enum class LifecycleEvent(private val typeName: String) { + CREATE("create"), + START("start"), + RESUME("resume"), + RENDER("render"); + + public fun spanName(activityName: String): String = "$activityName-$typeName" + } + + private data class ActivityOpenTrace( + val activityName: String, + val root: PersistableEmbraceSpan, + val children: Map = ConcurrentHashMap(), + ) + + private data class OpenTraceZygote( + val lastActivityName: String, + val lastActivityInstanceId: Int, + val lastActivityPausedTimeMs: Long, + ) + + private companion object { + const val INVALID_INSTANCE: Int = -1 + const val INVALID_TIME: Long = -1L + + val INITIAL = OpenTraceZygote( + lastActivityName = "NEW_APP_LAUNCH", + lastActivityInstanceId = INVALID_INSTANCE, + lastActivityPausedTimeMs = INVALID_TIME + ) + + val READY = OpenTraceZygote( + lastActivityName = "READY", + lastActivityInstanceId = INVALID_INSTANCE, + lastActivityPausedTimeMs = INVALID_TIME + ) + + val BACKGROUNDED = OpenTraceZygote( + lastActivityName = "BACKGROUNDED", + lastActivityInstanceId = INVALID_INSTANCE, + lastActivityPausedTimeMs = INVALID_TIME + ) + } +} diff --git a/embrace-android-features/src/test/java/io/embrace/android/embracesdk/internal/capture/activity/OpenTraceEmitterTest.kt b/embrace-android-features/src/test/java/io/embrace/android/embracesdk/internal/capture/activity/OpenTraceEmitterTest.kt new file mode 100644 index 0000000000..60a5256daa --- /dev/null +++ b/embrace-android-features/src/test/java/io/embrace/android/embracesdk/internal/capture/activity/OpenTraceEmitterTest.kt @@ -0,0 +1,310 @@ +package io.embrace.android.embracesdk.internal.capture.activity + +import android.os.Build +import androidx.test.ext.junit.runners.AndroidJUnit4 +import io.embrace.android.embracesdk.assertions.assertEmbraceSpanData +import io.embrace.android.embracesdk.fakes.FakeClock +import io.embrace.android.embracesdk.fakes.injection.FakeInitModule +import io.embrace.android.embracesdk.internal.payload.toNewPayload +import io.embrace.android.embracesdk.internal.spans.SpanService +import io.embrace.android.embracesdk.internal.spans.SpanSink +import io.embrace.android.embracesdk.internal.utils.BuildVersionChecker +import io.opentelemetry.api.trace.SpanId +import org.junit.Assert.assertNull +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.robolectric.annotation.Config +import kotlin.math.max +import kotlin.math.min + +@RunWith(AndroidJUnit4::class) +internal class OpenTraceEmitterTest { + private lateinit var clock: FakeClock + private lateinit var spanSink: SpanSink + private lateinit var spanService: SpanService + private lateinit var traceEmitter: OpenTraceEmitter + + @Before + fun setUp() { + clock = FakeClock() + val initModule = FakeInitModule(clock = clock) + spanSink = initModule.openTelemetryModule.spanSink + spanService = initModule.openTelemetryModule.spanService + spanService.initializeService(clock.now()) + clock.tick(100L) + traceEmitter = OpenTraceEmitter( + spanService = spanService, + versionChecker = BuildVersionChecker, + ) + } + + @Config(sdk = [Build.VERSION_CODES.UPSIDE_DOWN_CAKE]) + @Test + fun `verify cold open trace from another activity in U`() { + verifyOpen( + fromBackground = false, + openType = OpenEvents.OpenType.COLD, + firePreAndPost = true, + hasRenderEvent = true, + ) + } + + @Config(sdk = [Build.VERSION_CODES.UPSIDE_DOWN_CAKE]) + @Test + fun `verify cold open trace from background in U`() { + verifyOpen( + fromBackground = true, + openType = OpenEvents.OpenType.COLD, + firePreAndPost = true, + hasRenderEvent = true, + ) + } + + @Config(sdk = [Build.VERSION_CODES.UPSIDE_DOWN_CAKE]) + @Test + fun `verify hot open trace in from background in U`() { + verifyOpen( + fromBackground = true, + openType = OpenEvents.OpenType.HOT, + firePreAndPost = true, + hasRenderEvent = true, + ) + } + + @Config(sdk = [Build.VERSION_CODES.LOLLIPOP]) + @Test + fun `verify cold open trace in from another activity L`() { + verifyOpen( + fromBackground = false, + openType = OpenEvents.OpenType.COLD, + firePreAndPost = false, + hasRenderEvent = false, + ) + } + + @Config(sdk = [Build.VERSION_CODES.LOLLIPOP]) + @Test + fun `verify cold open trace from background in L`() { + verifyOpen( + fromBackground = true, + openType = OpenEvents.OpenType.COLD, + firePreAndPost = false, + hasRenderEvent = false, + ) + } + + @Config(sdk = [Build.VERSION_CODES.LOLLIPOP]) + @Test + fun `verify hot open trace in L from background`() { + verifyOpen( + fromBackground = true, + openType = OpenEvents.OpenType.HOT, + firePreAndPost = false, + hasRenderEvent = false, + ) + } + + private fun verifyOpen( + fromBackground: Boolean, + openType: OpenEvents.OpenType, + firePreAndPost: Boolean, + hasRenderEvent: Boolean, + ) { + openActivity( + fromBackground = fromBackground, + openType = openType, + firePreAndPost = firePreAndPost, + hasRenderEvent = hasRenderEvent + ).let { timestamps -> + val spanMap = spanSink.completedSpans().associateBy { it.name } + val trace = checkNotNull(spanMap["emb-${ACTIVITY_NAME}-${openType.typeName}-open"]) + + assertEmbraceSpanData( + span = trace.toNewPayload(), + expectedStartTimeMs = timestamps.first, + expectedEndTimeMs = timestamps.second, + expectedParentId = SpanId.getInvalid(), + key = true, + ) + + val events = timestamps.third + if (openType == OpenEvents.OpenType.COLD) { + checkNotNull(events[OpenTraceEmitter.LifecycleEvent.CREATE]).run { + assertEmbraceSpanData( + span = checkNotNull(spanMap["emb-$ACTIVITY_NAME-create"]).toNewPayload(), + expectedStartTimeMs = startMs(), + expectedEndTimeMs = endMs(), + expectedParentId = trace.spanId + ) + } + } else { + assertNull(spanMap["emb-$ACTIVITY_NAME-create"]) + } + + checkNotNull(events[OpenTraceEmitter.LifecycleEvent.START]).run { + assertEmbraceSpanData( + span = checkNotNull(spanMap["emb-$ACTIVITY_NAME-start"]).toNewPayload(), + expectedStartTimeMs = startMs(), + expectedEndTimeMs = endMs(), + expectedParentId = trace.spanId + ) + } + + if (hasRenderEvent) { + checkNotNull(events[OpenTraceEmitter.LifecycleEvent.RESUME]).run { + assertEmbraceSpanData( + span = checkNotNull(spanMap["emb-$ACTIVITY_NAME-resume"]).toNewPayload(), + expectedStartTimeMs = startMs(), + expectedEndTimeMs = endMs(), + expectedParentId = trace.spanId + ) + } + checkNotNull(events[OpenTraceEmitter.LifecycleEvent.RENDER]).run { + assertEmbraceSpanData( + span = checkNotNull(spanMap["emb-$ACTIVITY_NAME-render"]).toNewPayload(), + expectedStartTimeMs = startMs(), + expectedEndTimeMs = endMs(), + expectedParentId = trace.spanId + ) + } + } else { + assertNull(spanMap["emb-$ACTIVITY_NAME-resume"]) + assertNull(spanMap["emb-$ACTIVITY_NAME-render"]) + } + } + } + + @Suppress("CyclomaticComplexMethod", "ComplexMethod") + private fun openActivity( + fromBackground: Boolean, + openType: OpenEvents.OpenType, + firePreAndPost: Boolean, + hasRenderEvent: Boolean + ): Triple> { + val events = mutableMapOf() + val lastActivityExitMs = clock.now() + traceEmitter.resetTrace(LAST_ACTIVITY_INSTANCE, LAST_ACTIVITY, lastActivityExitMs) + clock.tick(100L) + + if (fromBackground) { + traceEmitter.hibernate(LAST_ACTIVITY_INSTANCE, LAST_ACTIVITY, clock.now()) + } + + val createEvents = if (openType == OpenEvents.OpenType.COLD) { + activityCreate(firePreAndPost) + } else { + null + }?.apply { + events[OpenTraceEmitter.LifecycleEvent.CREATE] = this + } + + val startEvents = activityStart(firePreAndPost).apply { + events[OpenTraceEmitter.LifecycleEvent.START] = this + } + + val resumeEvents = activityResume(firePreAndPost).apply { + events[OpenTraceEmitter.LifecycleEvent.RESUME] = this + } + + val renderEvents = if (hasRenderEvent) { + activityRender(firePreAndPost) + } else { + null + }?.apply { + events[OpenTraceEmitter.LifecycleEvent.RENDER] = this + } + + val traceStartMs = if (!fromBackground) { + lastActivityExitMs + } else { + createEvents?.run { + if (firePreAndPost) { + pre + } else { + eventStart + } + } ?: if (firePreAndPost) { + startEvents.pre + } else { + startEvents.eventStart + } + } + + val traceEndMs = renderEvents?.run { + endMs() + } ?: resumeEvents.startMs() + + return Triple(traceStartMs, traceEndMs, events) + } + + private fun activityCreate(firePreAndPost: Boolean = true): LifecycleEvents { + return runLifecycleEvent( + startCallback = traceEmitter::create, + endCallback = traceEmitter::createEnd, + firePreAndPost = firePreAndPost, + ) + } + + private fun activityStart(firePreAndPost: Boolean = true): LifecycleEvents { + return runLifecycleEvent( + startCallback = traceEmitter::start, + endCallback = traceEmitter::startEnd, + firePreAndPost = firePreAndPost, + ) + } + + private fun activityResume(firePreAndPost: Boolean = true): LifecycleEvents { + return runLifecycleEvent( + startCallback = traceEmitter::resume, + endCallback = traceEmitter::resumeEnd, + firePreAndPost = firePreAndPost, + ) + } + + private fun activityRender(firePreAndPost: Boolean = true): LifecycleEvents { + return runLifecycleEvent( + startCallback = traceEmitter::render, + endCallback = traceEmitter::renderEnd, + firePreAndPost = firePreAndPost, + ) + } + + private fun runLifecycleEvent( + startCallback: (instanceId: Int, activityName: String, startMs: Long) -> Unit, + endCallback: (instanceId: Int, startMs: Long) -> Unit, + firePreAndPost: Boolean = true + ): LifecycleEvents { + val events = LifecycleEvents() + if (firePreAndPost) { + events.pre = clock.now() + clock.tick() + } + events.eventStart = clock.now() + startCallback(INSTANCE_1, ACTIVITY_NAME, events.startMs()) + events.eventEnd = clock.tick(100L) + if (firePreAndPost) { + events.post = clock.tick() + } + endCallback(INSTANCE_1, events.endMs()) + return events + } + + private data class LifecycleEvents( + var pre: Long = Long.MAX_VALUE, + var eventStart: Long = Long.MAX_VALUE, + var eventEnd: Long = Long.MIN_VALUE, + var post: Long = Long.MIN_VALUE, + ) + + private fun LifecycleEvents.startMs(): Long = min(pre, eventStart) + + private fun LifecycleEvents.endMs(): Long = max(post, eventEnd) + + companion object { + const val INSTANCE_1 = 1 + const val ACTIVITY_NAME = "com.my.CoolActivity" + const val LAST_ACTIVITY = "com.my.Activity" + const val LAST_ACTIVITY_INSTANCE = 99 + } +} From ae012ec4297800def58db8c3dc39c701e4f8f6b1 Mon Sep 17 00:00:00 2001 From: bidetofevil Date: Wed, 11 Sep 2024 09:53:38 -0700 Subject: [PATCH 2/4] WIP --- .../api/embrace-android-api.api | 21 -- .../internal/capture/activity/OpenEvents.kt | 26 +-- .../capture/activity/OpenTraceEmitter.kt | 45 ++-- .../internal/capture/activity/OpenType.kt | 16 ++ .../capture/activity/OpenTraceEmitterTest.kt | 207 ++++++++++++++---- 5 files changed, 218 insertions(+), 97 deletions(-) rename {embrace-android-api => embrace-android-features}/src/main/kotlin/io/embrace/android/embracesdk/internal/capture/activity/OpenEvents.kt (59%) create mode 100644 embrace-android-features/src/main/kotlin/io/embrace/android/embracesdk/internal/capture/activity/OpenType.kt diff --git a/embrace-android-api/api/embrace-android-api.api b/embrace-android-api/api/embrace-android-api.api index dd0482ec89..027f8aa9a8 100644 --- a/embrace-android-api/api/embrace-android-api.api +++ b/embrace-android-api/api/embrace-android-api.api @@ -118,27 +118,6 @@ public abstract interface class io/embrace/android/embracesdk/internal/api/UserA public abstract fun setUsername (Ljava/lang/String;)V } -public abstract interface class io/embrace/android/embracesdk/internal/capture/activity/OpenEvents { - public abstract fun create (ILjava/lang/String;J)V - public abstract fun createEnd (IJ)V - public abstract fun hibernate (ILjava/lang/String;J)V - public abstract fun render (ILjava/lang/String;J)V - public abstract fun renderEnd (IJ)V - public abstract fun resetTrace (ILjava/lang/String;J)V - public abstract fun resume (ILjava/lang/String;J)V - public abstract fun resumeEnd (IJ)V - public abstract fun start (ILjava/lang/String;J)V - public abstract fun startEnd (IJ)V -} - -public final class io/embrace/android/embracesdk/internal/capture/activity/OpenEvents$OpenType : java/lang/Enum { - public static final field COLD Lio/embrace/android/embracesdk/internal/capture/activity/OpenEvents$OpenType; - public static final field HOT Lio/embrace/android/embracesdk/internal/capture/activity/OpenEvents$OpenType; - public final fun getTypeName ()Ljava/lang/String; - public static fun valueOf (Ljava/lang/String;)Lio/embrace/android/embracesdk/internal/capture/activity/OpenEvents$OpenType; - public static fun values ()[Lio/embrace/android/embracesdk/internal/capture/activity/OpenEvents$OpenType; -} - public class io/embrace/android/embracesdk/internal/network/http/EmbraceHttpPathOverride { protected static final field PATH_OVERRIDE Ljava/lang/String; public fun ()V diff --git a/embrace-android-api/src/main/kotlin/io/embrace/android/embracesdk/internal/capture/activity/OpenEvents.kt b/embrace-android-features/src/main/kotlin/io/embrace/android/embracesdk/internal/capture/activity/OpenEvents.kt similarity index 59% rename from embrace-android-api/src/main/kotlin/io/embrace/android/embracesdk/internal/capture/activity/OpenEvents.kt rename to embrace-android-features/src/main/kotlin/io/embrace/android/embracesdk/internal/capture/activity/OpenEvents.kt index c7ba1031ff..595530012a 100644 --- a/embrace-android-api/src/main/kotlin/io/embrace/android/embracesdk/internal/capture/activity/OpenEvents.kt +++ b/embrace-android-features/src/main/kotlin/io/embrace/android/embracesdk/internal/capture/activity/OpenEvents.kt @@ -3,60 +3,56 @@ package io.embrace.android.embracesdk.internal.capture.activity /** * The relevant stages in the lifecycle of Activities pertaining to observing the performance of their loading */ -public interface OpenEvents { +interface OpenEvents { /** * When a previously in-progress Activity Open trace should be abandoned, and that the component managing * the trace recording should prepare itself to start tracing the opening of a new Activity instance. */ - public fun resetTrace(instanceId: Int, activityName: String, timestampMs: Long) + fun resetTrace(instanceId: Int, activityName: String, timestampMs: Long) /** * When the app is no longer in a state where it is trying to open up a new Activity */ - public fun hibernate(instanceId: Int, activityName: String, timestampMs: Long) + fun hibernate(instanceId: Int, activityName: String, timestampMs: Long) /** * When the given Activity is entering the CREATE stage of its lifecycle. */ - public fun create(instanceId: Int, activityName: String, timestampMs: Long) + fun create(instanceId: Int, activityName: String, timestampMs: Long) /** * When the given Activity has exited the CREATE stage of its lifecycle. */ - public fun createEnd(instanceId: Int, timestampMs: Long) + fun createEnd(instanceId: Int, timestampMs: Long) /** * When the given Activity is entering the START stage of its lifecycle. */ - public fun start(instanceId: Int, activityName: String, timestampMs: Long) + fun start(instanceId: Int, activityName: String, timestampMs: Long) /** * When the given Activity has exited the START stage of its lifecycle. */ - public fun startEnd(instanceId: Int, timestampMs: Long) + fun startEnd(instanceId: Int, timestampMs: Long) /** * When the given Activity is entering the RESUME stage of its lifecycle. */ - public fun resume(instanceId: Int, activityName: String, timestampMs: Long) + fun resume(instanceId: Int, activityName: String, timestampMs: Long) /** * When the given Activity has exited the RESUME stage of its lifecycle. */ - public fun resumeEnd(instanceId: Int, timestampMs: Long) + fun resumeEnd(instanceId: Int, timestampMs: Long) /** * When the given Activity's first UI frame starts to be rendered. */ - public fun render(instanceId: Int, activityName: String, timestampMs: Long) + fun render(instanceId: Int, activityName: String, timestampMs: Long) /** * When the given Activity's first UI frame has been displayed. */ - public fun renderEnd(instanceId: Int, timestampMs: Long) - - public enum class OpenType(public val typeName: String) { - COLD("cold"), HOT("hot") - } + fun renderEnd(instanceId: Int, timestampMs: Long) } diff --git a/embrace-android-features/src/main/kotlin/io/embrace/android/embracesdk/internal/capture/activity/OpenTraceEmitter.kt b/embrace-android-features/src/main/kotlin/io/embrace/android/embracesdk/internal/capture/activity/OpenTraceEmitter.kt index bdc80949c2..6f62d64af7 100644 --- a/embrace-android-features/src/main/kotlin/io/embrace/android/embracesdk/internal/capture/activity/OpenTraceEmitter.kt +++ b/embrace-android-features/src/main/kotlin/io/embrace/android/embracesdk/internal/capture/activity/OpenTraceEmitter.kt @@ -11,36 +11,49 @@ import java.util.concurrent.ConcurrentHashMap import java.util.concurrent.atomic.AtomicReference /** - * Observes Activity lifecycle and rendering events to create traces that model the workflow for showing an Activity on screen. + * Observes Activity lifecycle and rendering events to create traces that model the workflow for showing an Activity on screen after + * app startup has completed. This creates traces for both types of Activity opening specified in [OpenType]. + * * Depending on the version of Android and the state of the app, the start, end, and intermediate stages of the workflow will use - * timestamps from different events, which affects to preciseness of the measurement. + * timestamps from different events, which affects the precision of the measurement. * - * Trace Start: + * The start for [OpenType.COLD]: * * - On Android 10+, when [ActivityLifecycleCallbacks.onActivityPostPaused] is fired, denoting that the previous activity has completed * its [ActivityLifecycleCallbacks.onActivityPaused] callbacks and a new Activity is ready to be created. + * * - Android 9 and lower, when [ActivityLifecycleCallbacks.onActivityPaused] is fired, denoting that the previous activity is in the * process of exiting. This will possibly result in some cleanup work of exiting the previous activity being included in the duration * of the next trace that is logged. * - * Trace End: + * The start for [OpenType.HOT] + * + * - On Android 10+, when [ActivityLifecycleCallbacks.onActivityPreStarted] is fired, denoting that an existing Activity instance is ready + * to be started + * + * - Android 9 and lower, when [ActivityLifecycleCallbacks.onActivityStarted] is fired, denoting that an existing activity is in the + * process of starting. This will possibly result in some of the work to start the activity already having happened depending on the + * other callbacks that have been registered. + * + * The end for both [OpenType.COLD] and [OpenType.HOT]: * * - Android 10+, when the Activity's first UI frame finishes rendering and is delivered to the screen - * - Android 9 and lower.... TODO + * + * - Android 9 and lower, when [ActivityLifecycleCallbacks.onActivityResumed] is fired. */ -public class OpenTraceEmitter( +class OpenTraceEmitter( private val spanService: SpanService, private val versionChecker: VersionChecker, ) : OpenEvents { private val activeTraces: MutableMap = ConcurrentHashMap() - private val traceIds: MutableMap = ConcurrentHashMap() private val traceZygoteHolder: AtomicReference = AtomicReference(INITIAL) + private var currentTracedInstanceId: Int? = null override fun resetTrace(instanceId: Int, activityName: String, timestampMs: Long) { - traceIds[activityName]?.let { existingTraceId -> - if (instanceId != existingTraceId) { - endTrace(instanceId = existingTraceId, timestampMs = timestampMs, errorCode = ErrorCode.USER_ABANDON) + currentTracedInstanceId?.let { currentlyTracedInstanceId -> + if (instanceId != currentlyTracedInstanceId) { + endTrace(instanceId = currentlyTracedInstanceId, timestampMs = timestampMs, errorCode = ErrorCode.USER_ABANDON) } } traceZygoteHolder.set( @@ -60,7 +73,7 @@ public class OpenTraceEmitter( override fun create(instanceId: Int, activityName: String, timestampMs: Long) { startTrace( - openType = OpenEvents.OpenType.COLD, + openType = OpenType.COLD, instanceId = instanceId, activityName = activityName, timestampMs = timestampMs @@ -82,7 +95,7 @@ public class OpenTraceEmitter( override fun start(instanceId: Int, activityName: String, timestampMs: Long) { startTrace( - openType = OpenEvents.OpenType.HOT, + openType = OpenType.HOT, instanceId = instanceId, activityName = activityName, timestampMs = timestampMs @@ -147,7 +160,7 @@ public class OpenTraceEmitter( } private fun startTrace( - openType: OpenEvents.OpenType, + openType: OpenType, instanceId: Int, activityName: String, timestampMs: Long @@ -215,16 +228,16 @@ public class OpenTraceEmitter( private fun traceName( activityName: String, - openType: OpenEvents.OpenType + openType: OpenType ): String = "$activityName-${openType.typeName}-open" - public enum class LifecycleEvent(private val typeName: String) { + enum class LifecycleEvent(private val typeName: String) { CREATE("create"), START("start"), RESUME("resume"), RENDER("render"); - public fun spanName(activityName: String): String = "$activityName-$typeName" + fun spanName(activityName: String): String = "$activityName-$typeName" } private data class ActivityOpenTrace( diff --git a/embrace-android-features/src/main/kotlin/io/embrace/android/embracesdk/internal/capture/activity/OpenType.kt b/embrace-android-features/src/main/kotlin/io/embrace/android/embracesdk/internal/capture/activity/OpenType.kt new file mode 100644 index 0000000000..135cf95b5a --- /dev/null +++ b/embrace-android-features/src/main/kotlin/io/embrace/android/embracesdk/internal/capture/activity/OpenType.kt @@ -0,0 +1,16 @@ +package io.embrace.android.embracesdk.internal.capture.activity + +/** + * The type of activity opening being traced + */ +enum class OpenType(val typeName: String) { + /** + * Activity opening where the instance has to be created + */ + COLD("cold"), + + /** + * Activity opening where the instance has already been created and just needs to be started and resumed (e.g. app foregrounding) + */ + HOT("hot") +} diff --git a/embrace-android-features/src/test/java/io/embrace/android/embracesdk/internal/capture/activity/OpenTraceEmitterTest.kt b/embrace-android-features/src/test/java/io/embrace/android/embracesdk/internal/capture/activity/OpenTraceEmitterTest.kt index 60a5256daa..b76a590b29 100644 --- a/embrace-android-features/src/test/java/io/embrace/android/embracesdk/internal/capture/activity/OpenTraceEmitterTest.kt +++ b/embrace-android-features/src/test/java/io/embrace/android/embracesdk/internal/capture/activity/OpenTraceEmitterTest.kt @@ -12,6 +12,7 @@ import io.embrace.android.embracesdk.internal.utils.BuildVersionChecker import io.opentelemetry.api.trace.SpanId import org.junit.Assert.assertNull import org.junit.Before +import org.junit.Ignore import org.junit.Test import org.junit.runner.RunWith import org.robolectric.annotation.Config @@ -43,8 +44,45 @@ internal class OpenTraceEmitterTest { @Test fun `verify cold open trace from another activity in U`() { verifyOpen( - fromBackground = false, - openType = OpenEvents.OpenType.COLD, + previousState = PreviousState.FROM_ACTIVITY, + openType = OpenType.COLD, + firePreAndPost = true, + hasRenderEvent = true, + ) + } + + @Config(sdk = [Build.VERSION_CODES.UPSIDE_DOWN_CAKE]) + @Test + fun `verify cold open trace from the same activity in U`() { + verifyOpen( + lastActivityName = ACTIVITY_NAME, + previousState = PreviousState.FROM_ACTIVITY, + openType = OpenType.COLD, + firePreAndPost = true, + hasRenderEvent = true, + ) + } + + @Ignore("Not working yet") + @Config(sdk = [Build.VERSION_CODES.UPSIDE_DOWN_CAKE]) + @Test + fun `verify cold open trace from an interrupted opening of another activity in U`() { + verifyOpen( + previousState = PreviousState.FROM_INTERRUPTED_ACTIVITY_OPEN, + openType = OpenType.COLD, + firePreAndPost = true, + hasRenderEvent = true, + ) + } + + @Ignore("Not working yet") + @Config(sdk = [Build.VERSION_CODES.UPSIDE_DOWN_CAKE]) + @Test + fun `verify cold open trace from an interrupted opening of the same activity in U`() { + verifyOpen( + lastActivityName = ACTIVITY_NAME, + previousState = PreviousState.FROM_INTERRUPTED_ACTIVITY_OPEN, + openType = OpenType.COLD, firePreAndPost = true, hasRenderEvent = true, ) @@ -54,8 +92,8 @@ internal class OpenTraceEmitterTest { @Test fun `verify cold open trace from background in U`() { verifyOpen( - fromBackground = true, - openType = OpenEvents.OpenType.COLD, + previousState = PreviousState.FROM_BACKGROUND, + openType = OpenType.COLD, firePreAndPost = true, hasRenderEvent = true, ) @@ -65,8 +103,8 @@ internal class OpenTraceEmitterTest { @Test fun `verify hot open trace in from background in U`() { verifyOpen( - fromBackground = true, - openType = OpenEvents.OpenType.HOT, + previousState = PreviousState.FROM_BACKGROUND, + openType = OpenType.HOT, firePreAndPost = true, hasRenderEvent = true, ) @@ -76,8 +114,8 @@ internal class OpenTraceEmitterTest { @Test fun `verify cold open trace in from another activity L`() { verifyOpen( - fromBackground = false, - openType = OpenEvents.OpenType.COLD, + previousState = PreviousState.FROM_ACTIVITY, + openType = OpenType.COLD, firePreAndPost = false, hasRenderEvent = false, ) @@ -87,8 +125,8 @@ internal class OpenTraceEmitterTest { @Test fun `verify cold open trace from background in L`() { verifyOpen( - fromBackground = true, - openType = OpenEvents.OpenType.COLD, + previousState = PreviousState.FROM_BACKGROUND, + openType = OpenType.COLD, firePreAndPost = false, hasRenderEvent = false, ) @@ -98,27 +136,35 @@ internal class OpenTraceEmitterTest { @Test fun `verify hot open trace in L from background`() { verifyOpen( - fromBackground = true, - openType = OpenEvents.OpenType.HOT, + previousState = PreviousState.FROM_BACKGROUND, + openType = OpenType.HOT, firePreAndPost = false, hasRenderEvent = false, ) } private fun verifyOpen( - fromBackground: Boolean, - openType: OpenEvents.OpenType, + activityName: String = ACTIVITY_NAME, + instanceId: Int = NEW_INSTANCE_ID, + lastActivityName: String = LAST_ACTIVITY_NAME, + lastInstanceId: Int = LAST_ACTIVITY_INSTANCE_ID, + previousState: PreviousState, + openType: OpenType, firePreAndPost: Boolean, hasRenderEvent: Boolean, ) { openActivity( - fromBackground = fromBackground, + activityName = activityName, + instanceId = instanceId, + lastActivityName = lastActivityName, + lastInstanceId = lastInstanceId, + previousState = previousState, openType = openType, firePreAndPost = firePreAndPost, hasRenderEvent = hasRenderEvent ).let { timestamps -> val spanMap = spanSink.completedSpans().associateBy { it.name } - val trace = checkNotNull(spanMap["emb-${ACTIVITY_NAME}-${openType.typeName}-open"]) + val trace = checkNotNull(spanMap["emb-$activityName-${openType.typeName}-open"]) assertEmbraceSpanData( span = trace.toNewPayload(), @@ -129,22 +175,22 @@ internal class OpenTraceEmitterTest { ) val events = timestamps.third - if (openType == OpenEvents.OpenType.COLD) { + if (openType == OpenType.COLD) { checkNotNull(events[OpenTraceEmitter.LifecycleEvent.CREATE]).run { assertEmbraceSpanData( - span = checkNotNull(spanMap["emb-$ACTIVITY_NAME-create"]).toNewPayload(), + span = checkNotNull(spanMap["emb-$activityName-create"]).toNewPayload(), expectedStartTimeMs = startMs(), expectedEndTimeMs = endMs(), expectedParentId = trace.spanId ) } } else { - assertNull(spanMap["emb-$ACTIVITY_NAME-create"]) + assertNull(spanMap["emb-$activityName-create"]) } checkNotNull(events[OpenTraceEmitter.LifecycleEvent.START]).run { assertEmbraceSpanData( - span = checkNotNull(spanMap["emb-$ACTIVITY_NAME-start"]).toNewPayload(), + span = checkNotNull(spanMap["emb-$activityName-start"]).toNewPayload(), expectedStartTimeMs = startMs(), expectedEndTimeMs = endMs(), expectedParentId = trace.spanId @@ -154,7 +200,7 @@ internal class OpenTraceEmitterTest { if (hasRenderEvent) { checkNotNull(events[OpenTraceEmitter.LifecycleEvent.RESUME]).run { assertEmbraceSpanData( - span = checkNotNull(spanMap["emb-$ACTIVITY_NAME-resume"]).toNewPayload(), + span = checkNotNull(spanMap["emb-$activityName-resume"]).toNewPayload(), expectedStartTimeMs = startMs(), expectedEndTimeMs = endMs(), expectedParentId = trace.spanId @@ -162,60 +208,99 @@ internal class OpenTraceEmitterTest { } checkNotNull(events[OpenTraceEmitter.LifecycleEvent.RENDER]).run { assertEmbraceSpanData( - span = checkNotNull(spanMap["emb-$ACTIVITY_NAME-render"]).toNewPayload(), + span = checkNotNull(spanMap["emb-$activityName-render"]).toNewPayload(), expectedStartTimeMs = startMs(), expectedEndTimeMs = endMs(), expectedParentId = trace.spanId ) } } else { - assertNull(spanMap["emb-$ACTIVITY_NAME-resume"]) - assertNull(spanMap["emb-$ACTIVITY_NAME-render"]) + assertNull(spanMap["emb-$activityName-resume"]) + assertNull(spanMap["emb-$activityName-render"]) } } } @Suppress("CyclomaticComplexMethod", "ComplexMethod") private fun openActivity( - fromBackground: Boolean, - openType: OpenEvents.OpenType, + activityName: String, + instanceId: Int, + lastActivityName: String, + lastInstanceId: Int, + previousState: PreviousState, + openType: OpenType, firePreAndPost: Boolean, - hasRenderEvent: Boolean + hasRenderEvent: Boolean, ): Triple> { val events = mutableMapOf() val lastActivityExitMs = clock.now() - traceEmitter.resetTrace(LAST_ACTIVITY_INSTANCE, LAST_ACTIVITY, lastActivityExitMs) clock.tick(100L) - if (fromBackground) { - traceEmitter.hibernate(LAST_ACTIVITY_INSTANCE, LAST_ACTIVITY, clock.now()) + when (previousState) { + PreviousState.FROM_ACTIVITY -> { + traceEmitter.resetTrace(lastInstanceId, lastActivityName, lastActivityExitMs) + } + + PreviousState.FROM_BACKGROUND -> { + traceEmitter.resetTrace(lastInstanceId, lastActivityName, lastActivityExitMs) + traceEmitter.hibernate(lastInstanceId, lastActivityName, clock.now()) + } + + PreviousState.FROM_INTERRUPTED_ACTIVITY_OPEN -> { + activityCreate( + activityName = lastActivityName, + instanceId = lastInstanceId, + firePreAndPost = firePreAndPost + ) + activityStart( + activityName = lastActivityName, + instanceId = lastInstanceId, + firePreAndPost = firePreAndPost + ) + } } - val createEvents = if (openType == OpenEvents.OpenType.COLD) { - activityCreate(firePreAndPost) + val createEvents = if (openType == OpenType.COLD) { + activityCreate( + activityName = activityName, + instanceId = instanceId, + firePreAndPost = firePreAndPost + ) } else { null }?.apply { events[OpenTraceEmitter.LifecycleEvent.CREATE] = this } - val startEvents = activityStart(firePreAndPost).apply { + val startEvents = activityStart( + activityName = activityName, + instanceId = instanceId, + firePreAndPost = firePreAndPost + ).apply { events[OpenTraceEmitter.LifecycleEvent.START] = this } - val resumeEvents = activityResume(firePreAndPost).apply { + val resumeEvents = activityResume( + activityName = activityName, + instanceId = instanceId, + firePreAndPost = firePreAndPost + ).apply { events[OpenTraceEmitter.LifecycleEvent.RESUME] = this } val renderEvents = if (hasRenderEvent) { - activityRender(firePreAndPost) + activityRender( + activityName = activityName, + instanceId = instanceId, + firePreAndPost = firePreAndPost + ) } else { null }?.apply { events[OpenTraceEmitter.LifecycleEvent.RENDER] = this } - val traceStartMs = if (!fromBackground) { + val traceStartMs = if (previousState != PreviousState.FROM_BACKGROUND) { lastActivityExitMs } else { createEvents?.run { @@ -238,32 +323,56 @@ internal class OpenTraceEmitterTest { return Triple(traceStartMs, traceEndMs, events) } - private fun activityCreate(firePreAndPost: Boolean = true): LifecycleEvents { + private fun activityCreate( + activityName: String, + instanceId: Int, + firePreAndPost: Boolean = true + ): LifecycleEvents { return runLifecycleEvent( + activityName = activityName, + instanceId = instanceId, startCallback = traceEmitter::create, endCallback = traceEmitter::createEnd, firePreAndPost = firePreAndPost, ) } - private fun activityStart(firePreAndPost: Boolean = true): LifecycleEvents { + private fun activityStart( + activityName: String, + instanceId: Int, + firePreAndPost: Boolean = true + ): LifecycleEvents { return runLifecycleEvent( + activityName = activityName, + instanceId = instanceId, startCallback = traceEmitter::start, endCallback = traceEmitter::startEnd, firePreAndPost = firePreAndPost, ) } - private fun activityResume(firePreAndPost: Boolean = true): LifecycleEvents { + private fun activityResume( + activityName: String, + instanceId: Int, + firePreAndPost: Boolean = true + ): LifecycleEvents { return runLifecycleEvent( + activityName = activityName, + instanceId = instanceId, startCallback = traceEmitter::resume, endCallback = traceEmitter::resumeEnd, firePreAndPost = firePreAndPost, ) } - private fun activityRender(firePreAndPost: Boolean = true): LifecycleEvents { + private fun activityRender( + activityName: String, + instanceId: Int, + firePreAndPost: Boolean = true + ): LifecycleEvents { return runLifecycleEvent( + activityName = activityName, + instanceId = instanceId, startCallback = traceEmitter::render, endCallback = traceEmitter::renderEnd, firePreAndPost = firePreAndPost, @@ -271,6 +380,8 @@ internal class OpenTraceEmitterTest { } private fun runLifecycleEvent( + activityName: String, + instanceId: Int, startCallback: (instanceId: Int, activityName: String, startMs: Long) -> Unit, endCallback: (instanceId: Int, startMs: Long) -> Unit, firePreAndPost: Boolean = true @@ -281,12 +392,12 @@ internal class OpenTraceEmitterTest { clock.tick() } events.eventStart = clock.now() - startCallback(INSTANCE_1, ACTIVITY_NAME, events.startMs()) + startCallback(instanceId, activityName, events.startMs()) events.eventEnd = clock.tick(100L) if (firePreAndPost) { events.post = clock.tick() } - endCallback(INSTANCE_1, events.endMs()) + endCallback(instanceId, events.endMs()) return events } @@ -301,10 +412,16 @@ internal class OpenTraceEmitterTest { private fun LifecycleEvents.endMs(): Long = max(post, eventEnd) + private enum class PreviousState { + FROM_ACTIVITY, + FROM_BACKGROUND, + FROM_INTERRUPTED_ACTIVITY_OPEN + } + companion object { - const val INSTANCE_1 = 1 + const val NEW_INSTANCE_ID = 1 const val ACTIVITY_NAME = "com.my.CoolActivity" - const val LAST_ACTIVITY = "com.my.Activity" - const val LAST_ACTIVITY_INSTANCE = 99 + const val LAST_ACTIVITY_NAME = "com.my.Activity" + const val LAST_ACTIVITY_INSTANCE_ID = 99 } } From eacfa2d319eb4498cb6a040983b734141a922fdc Mon Sep 17 00:00:00 2001 From: bidetofevil Date: Fri, 8 Nov 2024 14:49:53 -0800 Subject: [PATCH 3/4] Rename file and tweak method names --- .../internal/arch/schema/EmbType.kt | 2 +- .../internal/spans/EmbraceSpanBuilder.kt | 2 +- .../internal/spans/EmbraceSpanBuilderTest.kt | 4 +- .../internal/capture/activity/OpenType.kt | 16 ----- .../{OpenEvents.kt => UiLoadEvents.kt} | 13 ++-- ...nTraceEmitter.kt => UiLoadTraceEmitter.kt} | 56 ++++++++------- .../internal/capture/activity/UiLoadType.kt | 16 +++++ ...itterTest.kt => UiLoadTraceEmitterTest.kt} | 70 +++++++++---------- 8 files changed, 92 insertions(+), 87 deletions(-) delete mode 100644 embrace-android-features/src/main/kotlin/io/embrace/android/embracesdk/internal/capture/activity/OpenType.kt rename embrace-android-features/src/main/kotlin/io/embrace/android/embracesdk/internal/capture/activity/{OpenEvents.kt => UiLoadEvents.kt} (73%) rename embrace-android-features/src/main/kotlin/io/embrace/android/embracesdk/internal/capture/activity/{OpenTraceEmitter.kt => UiLoadTraceEmitter.kt} (83%) create mode 100644 embrace-android-features/src/main/kotlin/io/embrace/android/embracesdk/internal/capture/activity/UiLoadType.kt rename embrace-android-features/src/test/java/io/embrace/android/embracesdk/internal/capture/activity/{OpenTraceEmitterTest.kt => UiLoadTraceEmitterTest.kt} (86%) diff --git a/embrace-android-core/src/main/kotlin/io/embrace/android/embracesdk/internal/arch/schema/EmbType.kt b/embrace-android-core/src/main/kotlin/io/embrace/android/embracesdk/internal/arch/schema/EmbType.kt index db4acec94d..00dc4b30fe 100644 --- a/embrace-android-core/src/main/kotlin/io/embrace/android/embracesdk/internal/arch/schema/EmbType.kt +++ b/embrace-android-core/src/main/kotlin/io/embrace/android/embracesdk/internal/arch/schema/EmbType.kt @@ -26,7 +26,7 @@ sealed class EmbType(type: String, subtype: String?) : TelemetryType { object ThermalState : Performance("thermal_state") - object ActivityOpen : Performance("activity_open") + object UiLoad : Performance("ui_load") } /** diff --git a/embrace-android-core/src/main/kotlin/io/embrace/android/embracesdk/internal/spans/EmbraceSpanBuilder.kt b/embrace-android-core/src/main/kotlin/io/embrace/android/embracesdk/internal/spans/EmbraceSpanBuilder.kt index 6e8d8060fc..59dec00240 100644 --- a/embrace-android-core/src/main/kotlin/io/embrace/android/embracesdk/internal/spans/EmbraceSpanBuilder.kt +++ b/embrace-android-core/src/main/kotlin/io/embrace/android/embracesdk/internal/spans/EmbraceSpanBuilder.kt @@ -85,7 +85,7 @@ class EmbraceSpanBuilder( } private fun updateKeySpan() { - if (fixedAttributes.contains(EmbType.Performance.Default) || fixedAttributes.contains(EmbType.Performance.ActivityOpen)) { + if (fixedAttributes.contains(EmbType.Performance.Default) || fixedAttributes.contains(EmbType.Performance.UiLoad)) { if (getParentSpan() == null) { fixedAttributes.add(KeySpan) } else { diff --git a/embrace-android-core/src/test/java/io/embrace/android/embracesdk/internal/spans/EmbraceSpanBuilderTest.kt b/embrace-android-core/src/test/java/io/embrace/android/embracesdk/internal/spans/EmbraceSpanBuilderTest.kt index ec273cee5f..a354bf3678 100644 --- a/embrace-android-core/src/test/java/io/embrace/android/embracesdk/internal/spans/EmbraceSpanBuilderTest.kt +++ b/embrace-android-core/src/test/java/io/embrace/android/embracesdk/internal/spans/EmbraceSpanBuilderTest.kt @@ -156,7 +156,7 @@ internal class EmbraceSpanBuilderTest { } @Test - fun `perf and activity_open spans are key spans if parent is null`() { + fun `perf and ui_load spans are key spans if parent is null`() { val perfSpanBuilder = EmbraceSpanBuilder( tracer = tracer, name = "test", @@ -171,7 +171,7 @@ internal class EmbraceSpanBuilderTest { val activityOpenSpanBuilder = EmbraceSpanBuilder( tracer = tracer, name = "test", - telemetryType = EmbType.Performance.ActivityOpen, + telemetryType = EmbType.Performance.UiLoad, internal = false, private = false, parentSpan = null, diff --git a/embrace-android-features/src/main/kotlin/io/embrace/android/embracesdk/internal/capture/activity/OpenType.kt b/embrace-android-features/src/main/kotlin/io/embrace/android/embracesdk/internal/capture/activity/OpenType.kt deleted file mode 100644 index 135cf95b5a..0000000000 --- a/embrace-android-features/src/main/kotlin/io/embrace/android/embracesdk/internal/capture/activity/OpenType.kt +++ /dev/null @@ -1,16 +0,0 @@ -package io.embrace.android.embracesdk.internal.capture.activity - -/** - * The type of activity opening being traced - */ -enum class OpenType(val typeName: String) { - /** - * Activity opening where the instance has to be created - */ - COLD("cold"), - - /** - * Activity opening where the instance has already been created and just needs to be started and resumed (e.g. app foregrounding) - */ - HOT("hot") -} diff --git a/embrace-android-features/src/main/kotlin/io/embrace/android/embracesdk/internal/capture/activity/OpenEvents.kt b/embrace-android-features/src/main/kotlin/io/embrace/android/embracesdk/internal/capture/activity/UiLoadEvents.kt similarity index 73% rename from embrace-android-features/src/main/kotlin/io/embrace/android/embracesdk/internal/capture/activity/OpenEvents.kt rename to embrace-android-features/src/main/kotlin/io/embrace/android/embracesdk/internal/capture/activity/UiLoadEvents.kt index 595530012a..3953262e04 100644 --- a/embrace-android-features/src/main/kotlin/io/embrace/android/embracesdk/internal/capture/activity/OpenEvents.kt +++ b/embrace-android-features/src/main/kotlin/io/embrace/android/embracesdk/internal/capture/activity/UiLoadEvents.kt @@ -1,20 +1,21 @@ package io.embrace.android.embracesdk.internal.capture.activity /** - * The relevant stages in the lifecycle of Activities pertaining to observing the performance of their loading + * Relevant events in during the lifecycle of UI loading. Listeners to these events should gather the data and log + * the appropriate loading traces and spans given the associated Activity. */ -interface OpenEvents { +interface UiLoadEvents { /** - * When a previously in-progress Activity Open trace should be abandoned, and that the component managing - * the trace recording should prepare itself to start tracing the opening of a new Activity instance. + * When we no longer wish to observe the loading of the given Activity instance. This may be called during its load + * or after it has loaded. Calls to this for a given Activity instance is idempotent */ - fun resetTrace(instanceId: Int, activityName: String, timestampMs: Long) + fun abandon(instanceId: Int, activityName: String, timestampMs: Long) /** * When the app is no longer in a state where it is trying to open up a new Activity */ - fun hibernate(instanceId: Int, activityName: String, timestampMs: Long) + fun reset(instanceId: Int) /** * When the given Activity is entering the CREATE stage of its lifecycle. diff --git a/embrace-android-features/src/main/kotlin/io/embrace/android/embracesdk/internal/capture/activity/OpenTraceEmitter.kt b/embrace-android-features/src/main/kotlin/io/embrace/android/embracesdk/internal/capture/activity/UiLoadTraceEmitter.kt similarity index 83% rename from embrace-android-features/src/main/kotlin/io/embrace/android/embracesdk/internal/capture/activity/OpenTraceEmitter.kt rename to embrace-android-features/src/main/kotlin/io/embrace/android/embracesdk/internal/capture/activity/UiLoadTraceEmitter.kt index 6f62d64af7..c2fc97041a 100644 --- a/embrace-android-features/src/main/kotlin/io/embrace/android/embracesdk/internal/capture/activity/OpenTraceEmitter.kt +++ b/embrace-android-features/src/main/kotlin/io/embrace/android/embracesdk/internal/capture/activity/UiLoadTraceEmitter.kt @@ -11,13 +11,17 @@ import java.util.concurrent.ConcurrentHashMap import java.util.concurrent.atomic.AtomicReference /** - * Observes Activity lifecycle and rendering events to create traces that model the workflow for showing an Activity on screen after - * app startup has completed. This creates traces for both types of Activity opening specified in [OpenType]. + * Observes [UiLoadEvents] to create traces that model the workflow for displaying UI on screen. + * This will record traces for all [UiLoadType] but will ignore any UI load that is part of the app startup workflow. * * Depending on the version of Android and the state of the app, the start, end, and intermediate stages of the workflow will use - * timestamps from different events, which affects the precision of the measurement. + * timestamps from different events, which affects the precision of the measurements as well as the child spans contained in the trace. * - * The start for [OpenType.COLD]: + * An assumption that there can only be one activity going through its the activity lifecycle at a time. If we see events + * that come from a different Activity instance than the last one processed, we assume that last one's loading has been + * interrupted so any load traces associated with it can be abandoned. + * + * The start for [UiLoadType.COLD]: * * - On Android 10+, when [ActivityLifecycleCallbacks.onActivityPostPaused] is fired, denoting that the previous activity has completed * its [ActivityLifecycleCallbacks.onActivityPaused] callbacks and a new Activity is ready to be created. @@ -26,7 +30,7 @@ import java.util.concurrent.atomic.AtomicReference * process of exiting. This will possibly result in some cleanup work of exiting the previous activity being included in the duration * of the next trace that is logged. * - * The start for [OpenType.HOT] + * The start for [UiLoadType.HOT] * * - On Android 10+, when [ActivityLifecycleCallbacks.onActivityPreStarted] is fired, denoting that an existing Activity instance is ready * to be started @@ -35,29 +39,29 @@ import java.util.concurrent.atomic.AtomicReference * process of starting. This will possibly result in some of the work to start the activity already having happened depending on the * other callbacks that have been registered. * - * The end for both [OpenType.COLD] and [OpenType.HOT]: + * The end for both [UiLoadType.COLD] and [UiLoadType.HOT]: * * - Android 10+, when the Activity's first UI frame finishes rendering and is delivered to the screen * * - Android 9 and lower, when [ActivityLifecycleCallbacks.onActivityResumed] is fired. */ -class OpenTraceEmitter( +class UiLoadTraceEmitter( private val spanService: SpanService, private val versionChecker: VersionChecker, -) : OpenEvents { +) : UiLoadEvents { - private val activeTraces: MutableMap = ConcurrentHashMap() - private val traceZygoteHolder: AtomicReference = AtomicReference(INITIAL) + private val activeTraces: MutableMap = ConcurrentHashMap() + private val traceZygoteHolder: AtomicReference = AtomicReference(INITIAL) private var currentTracedInstanceId: Int? = null - override fun resetTrace(instanceId: Int, activityName: String, timestampMs: Long) { + override fun abandon(instanceId: Int, activityName: String, timestampMs: Long) { currentTracedInstanceId?.let { currentlyTracedInstanceId -> if (instanceId != currentlyTracedInstanceId) { endTrace(instanceId = currentlyTracedInstanceId, timestampMs = timestampMs, errorCode = ErrorCode.USER_ABANDON) } } traceZygoteHolder.set( - OpenTraceZygote( + UiLoadTraceZygote( lastActivityName = activityName, lastActivityInstanceId = instanceId, lastActivityPausedTimeMs = timestampMs @@ -65,7 +69,7 @@ class OpenTraceEmitter( ) } - override fun hibernate(instanceId: Int, activityName: String, timestampMs: Long) { + override fun reset(instanceId: Int) { if (traceZygoteHolder.get().lastActivityInstanceId == instanceId) { traceZygoteHolder.set(BACKGROUNDED) } @@ -73,7 +77,7 @@ class OpenTraceEmitter( override fun create(instanceId: Int, activityName: String, timestampMs: Long) { startTrace( - openType = OpenType.COLD, + uiLoadType = UiLoadType.COLD, instanceId = instanceId, activityName = activityName, timestampMs = timestampMs @@ -95,7 +99,7 @@ class OpenTraceEmitter( override fun start(instanceId: Int, activityName: String, timestampMs: Long) { startTrace( - openType = OpenType.HOT, + uiLoadType = UiLoadType.HOT, instanceId = instanceId, activityName = activityName, timestampMs = timestampMs @@ -160,7 +164,7 @@ class OpenTraceEmitter( } private fun startTrace( - openType: OpenType, + uiLoadType: UiLoadType, instanceId: Int, activityName: String, timestampMs: Long @@ -178,14 +182,14 @@ class OpenTraceEmitter( } spanService.startSpan( - name = traceName(activityName, openType), - type = EmbType.Performance.ActivityOpen, + name = traceName(activityName, uiLoadType), + type = EmbType.Performance.UiLoad, startTimeMs = startTimeMs, )?.let { root -> if (zygote.lastActivityInstanceId != -1) { root.addSystemAttribute("last_activity", zygote.lastActivityName) } - activeTraces[instanceId] = ActivityOpenTrace(root = root, activityName = activityName) + activeTraces[instanceId] = UiLoadTrace(root = root, activityName = activityName) } } } @@ -228,8 +232,8 @@ class OpenTraceEmitter( private fun traceName( activityName: String, - openType: OpenType - ): String = "$activityName-${openType.typeName}-open" + uiLoadType: UiLoadType + ): String = "$activityName-${uiLoadType.typeName}-time-to-initial-display" enum class LifecycleEvent(private val typeName: String) { CREATE("create"), @@ -240,13 +244,13 @@ class OpenTraceEmitter( fun spanName(activityName: String): String = "$activityName-$typeName" } - private data class ActivityOpenTrace( + private data class UiLoadTrace( val activityName: String, val root: PersistableEmbraceSpan, val children: Map = ConcurrentHashMap(), ) - private data class OpenTraceZygote( + private data class UiLoadTraceZygote( val lastActivityName: String, val lastActivityInstanceId: Int, val lastActivityPausedTimeMs: Long, @@ -256,19 +260,19 @@ class OpenTraceEmitter( const val INVALID_INSTANCE: Int = -1 const val INVALID_TIME: Long = -1L - val INITIAL = OpenTraceZygote( + val INITIAL = UiLoadTraceZygote( lastActivityName = "NEW_APP_LAUNCH", lastActivityInstanceId = INVALID_INSTANCE, lastActivityPausedTimeMs = INVALID_TIME ) - val READY = OpenTraceZygote( + val READY = UiLoadTraceZygote( lastActivityName = "READY", lastActivityInstanceId = INVALID_INSTANCE, lastActivityPausedTimeMs = INVALID_TIME ) - val BACKGROUNDED = OpenTraceZygote( + val BACKGROUNDED = UiLoadTraceZygote( lastActivityName = "BACKGROUNDED", lastActivityInstanceId = INVALID_INSTANCE, lastActivityPausedTimeMs = INVALID_TIME diff --git a/embrace-android-features/src/main/kotlin/io/embrace/android/embracesdk/internal/capture/activity/UiLoadType.kt b/embrace-android-features/src/main/kotlin/io/embrace/android/embracesdk/internal/capture/activity/UiLoadType.kt new file mode 100644 index 0000000000..853d7d8c4c --- /dev/null +++ b/embrace-android-features/src/main/kotlin/io/embrace/android/embracesdk/internal/capture/activity/UiLoadType.kt @@ -0,0 +1,16 @@ +package io.embrace.android.embracesdk.internal.capture.activity + +/** + * The type of UI load being traced + */ +enum class UiLoadType(val typeName: String) { + /** + * Load where the Activity instance has to be created + */ + COLD("cold"), + + /** + * Load where the instance has already been created and just needs to be started and resumed (e.g. foregrounding) + */ + HOT("hot") +} diff --git a/embrace-android-features/src/test/java/io/embrace/android/embracesdk/internal/capture/activity/OpenTraceEmitterTest.kt b/embrace-android-features/src/test/java/io/embrace/android/embracesdk/internal/capture/activity/UiLoadTraceEmitterTest.kt similarity index 86% rename from embrace-android-features/src/test/java/io/embrace/android/embracesdk/internal/capture/activity/OpenTraceEmitterTest.kt rename to embrace-android-features/src/test/java/io/embrace/android/embracesdk/internal/capture/activity/UiLoadTraceEmitterTest.kt index b76a590b29..4e59e2c40e 100644 --- a/embrace-android-features/src/test/java/io/embrace/android/embracesdk/internal/capture/activity/OpenTraceEmitterTest.kt +++ b/embrace-android-features/src/test/java/io/embrace/android/embracesdk/internal/capture/activity/UiLoadTraceEmitterTest.kt @@ -20,11 +20,11 @@ import kotlin.math.max import kotlin.math.min @RunWith(AndroidJUnit4::class) -internal class OpenTraceEmitterTest { +internal class UiLoadTraceEmitterTest { private lateinit var clock: FakeClock private lateinit var spanSink: SpanSink private lateinit var spanService: SpanService - private lateinit var traceEmitter: OpenTraceEmitter + private lateinit var traceEmitter: UiLoadTraceEmitter @Before fun setUp() { @@ -34,7 +34,7 @@ internal class OpenTraceEmitterTest { spanService = initModule.openTelemetryModule.spanService spanService.initializeService(clock.now()) clock.tick(100L) - traceEmitter = OpenTraceEmitter( + traceEmitter = UiLoadTraceEmitter( spanService = spanService, versionChecker = BuildVersionChecker, ) @@ -45,7 +45,7 @@ internal class OpenTraceEmitterTest { fun `verify cold open trace from another activity in U`() { verifyOpen( previousState = PreviousState.FROM_ACTIVITY, - openType = OpenType.COLD, + uiLoadType = UiLoadType.COLD, firePreAndPost = true, hasRenderEvent = true, ) @@ -57,7 +57,7 @@ internal class OpenTraceEmitterTest { verifyOpen( lastActivityName = ACTIVITY_NAME, previousState = PreviousState.FROM_ACTIVITY, - openType = OpenType.COLD, + uiLoadType = UiLoadType.COLD, firePreAndPost = true, hasRenderEvent = true, ) @@ -68,8 +68,8 @@ internal class OpenTraceEmitterTest { @Test fun `verify cold open trace from an interrupted opening of another activity in U`() { verifyOpen( - previousState = PreviousState.FROM_INTERRUPTED_ACTIVITY_OPEN, - openType = OpenType.COLD, + previousState = PreviousState.FROM_INTERRUPTED_LOAD, + uiLoadType = UiLoadType.COLD, firePreAndPost = true, hasRenderEvent = true, ) @@ -81,8 +81,8 @@ internal class OpenTraceEmitterTest { fun `verify cold open trace from an interrupted opening of the same activity in U`() { verifyOpen( lastActivityName = ACTIVITY_NAME, - previousState = PreviousState.FROM_INTERRUPTED_ACTIVITY_OPEN, - openType = OpenType.COLD, + previousState = PreviousState.FROM_INTERRUPTED_LOAD, + uiLoadType = UiLoadType.COLD, firePreAndPost = true, hasRenderEvent = true, ) @@ -93,7 +93,7 @@ internal class OpenTraceEmitterTest { fun `verify cold open trace from background in U`() { verifyOpen( previousState = PreviousState.FROM_BACKGROUND, - openType = OpenType.COLD, + uiLoadType = UiLoadType.COLD, firePreAndPost = true, hasRenderEvent = true, ) @@ -104,7 +104,7 @@ internal class OpenTraceEmitterTest { fun `verify hot open trace in from background in U`() { verifyOpen( previousState = PreviousState.FROM_BACKGROUND, - openType = OpenType.HOT, + uiLoadType = UiLoadType.HOT, firePreAndPost = true, hasRenderEvent = true, ) @@ -115,7 +115,7 @@ internal class OpenTraceEmitterTest { fun `verify cold open trace in from another activity L`() { verifyOpen( previousState = PreviousState.FROM_ACTIVITY, - openType = OpenType.COLD, + uiLoadType = UiLoadType.COLD, firePreAndPost = false, hasRenderEvent = false, ) @@ -126,7 +126,7 @@ internal class OpenTraceEmitterTest { fun `verify cold open trace from background in L`() { verifyOpen( previousState = PreviousState.FROM_BACKGROUND, - openType = OpenType.COLD, + uiLoadType = UiLoadType.COLD, firePreAndPost = false, hasRenderEvent = false, ) @@ -137,7 +137,7 @@ internal class OpenTraceEmitterTest { fun `verify hot open trace in L from background`() { verifyOpen( previousState = PreviousState.FROM_BACKGROUND, - openType = OpenType.HOT, + uiLoadType = UiLoadType.HOT, firePreAndPost = false, hasRenderEvent = false, ) @@ -149,7 +149,7 @@ internal class OpenTraceEmitterTest { lastActivityName: String = LAST_ACTIVITY_NAME, lastInstanceId: Int = LAST_ACTIVITY_INSTANCE_ID, previousState: PreviousState, - openType: OpenType, + uiLoadType: UiLoadType, firePreAndPost: Boolean, hasRenderEvent: Boolean, ) { @@ -159,12 +159,12 @@ internal class OpenTraceEmitterTest { lastActivityName = lastActivityName, lastInstanceId = lastInstanceId, previousState = previousState, - openType = openType, + uiLoadType = uiLoadType, firePreAndPost = firePreAndPost, hasRenderEvent = hasRenderEvent ).let { timestamps -> val spanMap = spanSink.completedSpans().associateBy { it.name } - val trace = checkNotNull(spanMap["emb-$activityName-${openType.typeName}-open"]) + val trace = checkNotNull(spanMap["emb-$activityName-${uiLoadType.typeName}-time-to-initial-display"]) assertEmbraceSpanData( span = trace.toNewPayload(), @@ -175,8 +175,8 @@ internal class OpenTraceEmitterTest { ) val events = timestamps.third - if (openType == OpenType.COLD) { - checkNotNull(events[OpenTraceEmitter.LifecycleEvent.CREATE]).run { + if (uiLoadType == UiLoadType.COLD) { + checkNotNull(events[UiLoadTraceEmitter.LifecycleEvent.CREATE]).run { assertEmbraceSpanData( span = checkNotNull(spanMap["emb-$activityName-create"]).toNewPayload(), expectedStartTimeMs = startMs(), @@ -188,7 +188,7 @@ internal class OpenTraceEmitterTest { assertNull(spanMap["emb-$activityName-create"]) } - checkNotNull(events[OpenTraceEmitter.LifecycleEvent.START]).run { + checkNotNull(events[UiLoadTraceEmitter.LifecycleEvent.START]).run { assertEmbraceSpanData( span = checkNotNull(spanMap["emb-$activityName-start"]).toNewPayload(), expectedStartTimeMs = startMs(), @@ -198,7 +198,7 @@ internal class OpenTraceEmitterTest { } if (hasRenderEvent) { - checkNotNull(events[OpenTraceEmitter.LifecycleEvent.RESUME]).run { + checkNotNull(events[UiLoadTraceEmitter.LifecycleEvent.RESUME]).run { assertEmbraceSpanData( span = checkNotNull(spanMap["emb-$activityName-resume"]).toNewPayload(), expectedStartTimeMs = startMs(), @@ -206,7 +206,7 @@ internal class OpenTraceEmitterTest { expectedParentId = trace.spanId ) } - checkNotNull(events[OpenTraceEmitter.LifecycleEvent.RENDER]).run { + checkNotNull(events[UiLoadTraceEmitter.LifecycleEvent.RENDER]).run { assertEmbraceSpanData( span = checkNotNull(spanMap["emb-$activityName-render"]).toNewPayload(), expectedStartTimeMs = startMs(), @@ -228,25 +228,25 @@ internal class OpenTraceEmitterTest { lastActivityName: String, lastInstanceId: Int, previousState: PreviousState, - openType: OpenType, + uiLoadType: UiLoadType, firePreAndPost: Boolean, hasRenderEvent: Boolean, - ): Triple> { - val events = mutableMapOf() + ): Triple> { + val events = mutableMapOf() val lastActivityExitMs = clock.now() clock.tick(100L) when (previousState) { PreviousState.FROM_ACTIVITY -> { - traceEmitter.resetTrace(lastInstanceId, lastActivityName, lastActivityExitMs) + traceEmitter.abandon(lastInstanceId, lastActivityName, lastActivityExitMs) } PreviousState.FROM_BACKGROUND -> { - traceEmitter.resetTrace(lastInstanceId, lastActivityName, lastActivityExitMs) - traceEmitter.hibernate(lastInstanceId, lastActivityName, clock.now()) + traceEmitter.abandon(lastInstanceId, lastActivityName, lastActivityExitMs) + traceEmitter.reset(lastInstanceId) } - PreviousState.FROM_INTERRUPTED_ACTIVITY_OPEN -> { + PreviousState.FROM_INTERRUPTED_LOAD -> { activityCreate( activityName = lastActivityName, instanceId = lastInstanceId, @@ -260,7 +260,7 @@ internal class OpenTraceEmitterTest { } } - val createEvents = if (openType == OpenType.COLD) { + val createEvents = if (uiLoadType == UiLoadType.COLD) { activityCreate( activityName = activityName, instanceId = instanceId, @@ -269,7 +269,7 @@ internal class OpenTraceEmitterTest { } else { null }?.apply { - events[OpenTraceEmitter.LifecycleEvent.CREATE] = this + events[UiLoadTraceEmitter.LifecycleEvent.CREATE] = this } val startEvents = activityStart( @@ -277,7 +277,7 @@ internal class OpenTraceEmitterTest { instanceId = instanceId, firePreAndPost = firePreAndPost ).apply { - events[OpenTraceEmitter.LifecycleEvent.START] = this + events[UiLoadTraceEmitter.LifecycleEvent.START] = this } val resumeEvents = activityResume( @@ -285,7 +285,7 @@ internal class OpenTraceEmitterTest { instanceId = instanceId, firePreAndPost = firePreAndPost ).apply { - events[OpenTraceEmitter.LifecycleEvent.RESUME] = this + events[UiLoadTraceEmitter.LifecycleEvent.RESUME] = this } val renderEvents = if (hasRenderEvent) { @@ -297,7 +297,7 @@ internal class OpenTraceEmitterTest { } else { null }?.apply { - events[OpenTraceEmitter.LifecycleEvent.RENDER] = this + events[UiLoadTraceEmitter.LifecycleEvent.RENDER] = this } val traceStartMs = if (previousState != PreviousState.FROM_BACKGROUND) { @@ -415,7 +415,7 @@ internal class OpenTraceEmitterTest { private enum class PreviousState { FROM_ACTIVITY, FROM_BACKGROUND, - FROM_INTERRUPTED_ACTIVITY_OPEN + FROM_INTERRUPTED_LOAD } companion object { From 475b2a2782e47381760b8d3626455e93fe218822 Mon Sep 17 00:00:00 2001 From: bidetofevil Date: Fri, 29 Nov 2024 10:02:24 -0800 Subject: [PATCH 4/4] Remove ignored test cases - add back later --- .../activity/UiLoadTraceEmitterTest.kt | 26 ------------------- 1 file changed, 26 deletions(-) diff --git a/embrace-android-features/src/test/java/io/embrace/android/embracesdk/internal/capture/activity/UiLoadTraceEmitterTest.kt b/embrace-android-features/src/test/java/io/embrace/android/embracesdk/internal/capture/activity/UiLoadTraceEmitterTest.kt index 4e59e2c40e..fbf5b9d74e 100644 --- a/embrace-android-features/src/test/java/io/embrace/android/embracesdk/internal/capture/activity/UiLoadTraceEmitterTest.kt +++ b/embrace-android-features/src/test/java/io/embrace/android/embracesdk/internal/capture/activity/UiLoadTraceEmitterTest.kt @@ -12,7 +12,6 @@ import io.embrace.android.embracesdk.internal.utils.BuildVersionChecker import io.opentelemetry.api.trace.SpanId import org.junit.Assert.assertNull import org.junit.Before -import org.junit.Ignore import org.junit.Test import org.junit.runner.RunWith import org.robolectric.annotation.Config @@ -63,31 +62,6 @@ internal class UiLoadTraceEmitterTest { ) } - @Ignore("Not working yet") - @Config(sdk = [Build.VERSION_CODES.UPSIDE_DOWN_CAKE]) - @Test - fun `verify cold open trace from an interrupted opening of another activity in U`() { - verifyOpen( - previousState = PreviousState.FROM_INTERRUPTED_LOAD, - uiLoadType = UiLoadType.COLD, - firePreAndPost = true, - hasRenderEvent = true, - ) - } - - @Ignore("Not working yet") - @Config(sdk = [Build.VERSION_CODES.UPSIDE_DOWN_CAKE]) - @Test - fun `verify cold open trace from an interrupted opening of the same activity in U`() { - verifyOpen( - lastActivityName = ACTIVITY_NAME, - previousState = PreviousState.FROM_INTERRUPTED_LOAD, - uiLoadType = UiLoadType.COLD, - firePreAndPost = true, - hasRenderEvent = true, - ) - } - @Config(sdk = [Build.VERSION_CODES.UPSIDE_DOWN_CAKE]) @Test fun `verify cold open trace from background in U`() {