Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

Hook up Activity Open tracing component #1348

Draft
wants to merge 1 commit into
base: hho/log-app-startup-trace-failure
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -61,4 +61,9 @@ interface AutoDataCaptureBehavior {
* Gates whether the SDK should use the v2 storage implementation or the legacy one.
*/
fun isV2StorageEnabled(): Boolean

/**
* Whether the SDK is configured to capture traces for the performance of the opening of Activities.
*/
fun isActivityOpenPerfCaptureEnabled(): Boolean
}
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ class AutoDataCaptureBehaviorImpl(

override fun isNativeCrashCaptureEnabled(): Boolean = cfg.isNativeCrashCaptureEnabled()
override fun isDiskUsageCaptureEnabled(): Boolean = cfg.isDiskUsageCaptureEnabled()
override fun isActivityOpenPerfCaptureEnabled(): Boolean = cfg.isActivityOpenPerfCaptureEnabled()

// this should remain immutable across the process lifecycle
private val v2StorageImpl by lazy { remote?.killSwitchConfig?.v2Storage ?: false }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ internal class AutoDataCaptureBehaviorImplTest {
assertFalse(isNativeCrashCaptureEnabled())
assertTrue(isDiskUsageCaptureEnabled())
assertTrue(isThermalStatusCaptureEnabled())
assertFalse(isActivityOpenPerfCaptureEnabled())
assertTrue(isThermalStatusCaptureEnabled())
assertFalse(isV2StorageEnabled())
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import android.view.ViewTreeObserver
import android.view.Window
import io.embrace.android.embracesdk.annotation.StartupActivity
import io.embrace.android.embracesdk.internal.logging.EmbLogger
import io.embrace.android.embracesdk.internal.session.lifecycle.ActivityLifecycleListener
import io.embrace.android.embracesdk.internal.utils.VersionChecker

/**
Expand All @@ -37,6 +38,7 @@ import io.embrace.android.embracesdk.internal.utils.VersionChecker
*/
class StartupTracker(
private val appStartupDataCollector: AppStartupDataCollector,
private val activityOpenEventEmitter: ActivityLifecycleListener?,
private val logger: EmbLogger,
private val versionChecker: VersionChecker,
) : Application.ActivityLifecycleCallbacks {
Expand Down Expand Up @@ -117,6 +119,9 @@ class StartupTracker(
private fun startupComplete(application: Application) {
if (!startupDataCollectionComplete) {
application.unregisterActivityLifecycleCallbacks(this)
activityOpenEventEmitter?.apply {
application.registerActivityLifecycleCallbacks(this)
}
startupDataCollectionComplete = true
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package io.embrace.android.embracesdk.internal.injection

import io.embrace.android.embracesdk.internal.capture.activity.OpenEventEmitter
import io.embrace.android.embracesdk.internal.capture.activity.OpenEvents
import io.embrace.android.embracesdk.internal.capture.crumbs.ActivityBreadcrumbTracker
import io.embrace.android.embracesdk.internal.capture.crumbs.PushNotificationCaptureService
import io.embrace.android.embracesdk.internal.capture.startup.AppStartupDataCollector
Expand Down Expand Up @@ -35,7 +37,11 @@ interface DataCaptureServiceModule {
*/
val startupService: StartupService

val appStartupDataCollector: AppStartupDataCollector

val startupTracker: StartupTracker

val appStartupDataCollector: AppStartupDataCollector
val activityOpenTraceEmitter: OpenEvents

val activityOpenTracker: OpenEventEmitter?
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
package io.embrace.android.embracesdk.internal.injection

import io.embrace.android.embracesdk.internal.Systrace
import io.embrace.android.embracesdk.internal.capture.activity.OpenEventEmitter
import io.embrace.android.embracesdk.internal.capture.activity.OpenEvents
import io.embrace.android.embracesdk.internal.capture.activity.OpenTraceEmitter
import io.embrace.android.embracesdk.internal.capture.crumbs.ActivityBreadcrumbTracker
import io.embrace.android.embracesdk.internal.capture.crumbs.PushNotificationCaptureService
import io.embrace.android.embracesdk.internal.capture.startup.AppStartupDataCollector
Expand Down Expand Up @@ -63,8 +66,28 @@ internal class DataCaptureServiceModuleImpl @JvmOverloads constructor(
override val startupTracker: StartupTracker by singleton {
StartupTracker(
appStartupDataCollector = appStartupDataCollector,
activityOpenEventEmitter = activityOpenTracker,
logger = initModule.logger,
versionChecker = versionChecker,
)
}

override val activityOpenTraceEmitter: OpenEvents by singleton {
OpenTraceEmitter(
spanService = openTelemetryModule.spanService,
versionChecker = versionChecker,
)
}

override val activityOpenTracker: OpenEventEmitter? by singleton {
if (configService.autoDataCaptureBehavior.isActivityOpenPerfCaptureEnabled()) {
OpenEventEmitter(
openEvents = activityOpenTraceEmitter,
clock = openTelemetryModule.openTelemetryClock,
versionChecker = versionChecker,
)
} else {
null
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import io.embrace.android.embracesdk.fakes.FakeClock
import io.embrace.android.embracesdk.fakes.FakeEmbLogger
import io.embrace.android.embracesdk.fakes.FakeSplashScreenActivity
import io.embrace.android.embracesdk.internal.logging.EmbLogger
import io.embrace.android.embracesdk.internal.session.lifecycle.ActivityLifecycleListener
import io.embrace.android.embracesdk.internal.utils.BuildVersionChecker
import org.junit.Assert.assertEquals
import org.junit.Before
Expand Down Expand Up @@ -37,6 +38,7 @@ internal class StartupTrackerTest {
dataCollector = FakeAppStartupDataCollector(clock = clock)
startupTracker = StartupTracker(
appStartupDataCollector = dataCollector,
activityOpenEventEmitter = object : ActivityLifecycleListener { },
logger = logger,
versionChecker = BuildVersionChecker
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -153,4 +153,11 @@ object EnabledFeatureConfig {
* sdk_config.networking.enable_native_monitoring
*/
fun isHttpUrlConnectionCaptureEnabled(): Boolean = true

/**
* Gates whether the SDK will capture traces for the performance of the opening of Activities.
*
* sdk_config.automatic_data_capture.activity_open_perf_info
*/
fun isActivityOpenPerfCaptureEnabled(): Boolean = false
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,216 @@
package io.embrace.android.embracesdk.testcases.features

import android.app.Activity
import android.os.Build
import androidx.test.ext.junit.runners.AndroidJUnit4
import io.embrace.android.embracesdk.annotation.ObservedActivity
import io.embrace.android.embracesdk.assertions.assertEmbraceSpanData
import io.embrace.android.embracesdk.assertions.findSpansByName
import io.embrace.android.embracesdk.assertions.findSpansOfType
import io.embrace.android.embracesdk.fakes.behavior.FakeAutoDataCaptureBehavior
import io.embrace.android.embracesdk.internal.arch.schema.EmbType
import io.embrace.android.embracesdk.internal.capture.activity.OpenTraceEmitter.LifecycleEvent
import io.embrace.android.embracesdk.internal.payload.ApplicationState
import io.embrace.android.embracesdk.testframework.IntegrationTestRule
import io.opentelemetry.api.trace.SpanId
import org.junit.Assert.assertEquals
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import org.robolectric.Robolectric
import org.robolectric.annotation.Config

@RunWith(AndroidJUnit4::class)
internal class ActivityOpenTest {

@Rule
@JvmField
val testRule = IntegrationTestRule()

private val defaultAutoDataCaptureBehavior = FakeAutoDataCaptureBehavior(activityOpenPerfEnabled = true)

@Config(sdk = [Build.VERSION_CODES.LOLLIPOP])
@Test
fun `activity open does not create a trace if feature flag is disabled`() {
testRule.runTest(
setupAction = {
overriddenConfigService.autoDataCaptureBehavior = FakeAutoDataCaptureBehavior(
activityOpenPerfEnabled = false
)
},
testCaseAction = {
simulateOpeningActivities(
addStartupActivity = false,
startInBackground = true,
activitiesAndActions = listOf(
Robolectric.buildActivity(Activity1::class.java) to {},
Robolectric.buildActivity(Activity2::class.java) to {},
)
)
},
assertAction = {
val payload = getSingleSessionEnvelope()
assertEquals(0, payload.findSpansOfType(EmbType.Performance.ActivityOpen).size)
}
)
}

@Config(sdk = [Build.VERSION_CODES.LOLLIPOP])
@Test
fun `opening first non-startup activity creates cold open trace in L`() {
var preLaunchTimeMs = 0L
testRule.runTest(
setupAction = {
preLaunchTimeMs = overriddenClock.now()
overriddenConfigService.autoDataCaptureBehavior = defaultAutoDataCaptureBehavior
},
testCaseAction = {
simulateOpeningActivities(
addStartupActivity = false,
startInBackground = true,
activitiesAndActions = listOf(
Robolectric.buildActivity(Activity1::class.java) to {},
Robolectric.buildActivity(Activity2::class.java) to {},
)
)
},
assertAction = {
val payload = getSingleSessionEnvelope()
val trace = payload.findSpansOfType(EmbType.Performance.ActivityOpen).single()
assertEquals("emb-$ACTIVITY2_NAME-cold-open", trace.name)
val rootSpanId = checkNotNull(trace.spanId)

val expectedTraceStartTime = preLaunchTimeMs + 20301
assertEmbraceSpanData(
span = trace,
expectedStartTimeMs = expectedTraceStartTime,
expectedEndTimeMs = expectedTraceStartTime + 250,
expectedParentId = SpanId.getInvalid(),
key = true
)

assertEmbraceSpanData(
span = payload.findSpansByName("emb-${LifecycleEvent.CREATE.spanName(ACTIVITY2_NAME)}").single(),
expectedStartTimeMs = expectedTraceStartTime + 50,
expectedEndTimeMs = expectedTraceStartTime + 150,
expectedParentId = rootSpanId,
)

assertEmbraceSpanData(
span = payload.findSpansByName("emb-${LifecycleEvent.START.spanName(ACTIVITY2_NAME)}").single(),
expectedStartTimeMs = expectedTraceStartTime + 150,
expectedEndTimeMs = expectedTraceStartTime + 250,
expectedParentId = rootSpanId,
)
}
)
}

@Config(sdk = [Build.VERSION_CODES.LOLLIPOP])
@Test
fun `foregrounding and initializing new activity creates cold open trace in L`() {
var preLaunchTimeMs = 0L
testRule.runTest(
setupAction = {
preLaunchTimeMs = overriddenClock.now()
overriddenConfigService.autoDataCaptureBehavior = defaultAutoDataCaptureBehavior
},
testCaseAction = {
simulateOpeningActivities(
addStartupActivity = true,
startInBackground = true,
activitiesAndActions = listOf(
Robolectric.buildActivity(Activity1::class.java) to {}
)
)
},
assertAction = {
val payload = getSessionEnvelopes(2)[1]
val trace = payload.findSpansOfType(EmbType.Performance.ActivityOpen).single()
assertEquals("emb-$ACTIVITY1_NAME-cold-open", trace.name)
val rootSpanId = checkNotNull(trace.spanId)

val expectedTraceStartTime = preLaunchTimeMs + 10000
assertEmbraceSpanData(
span = trace,
expectedStartTimeMs = expectedTraceStartTime,
expectedEndTimeMs = expectedTraceStartTime + 200,
expectedParentId = SpanId.getInvalid(),
key = true
)

val lastBackgroundActivity = getSessionEnvelopes(2, ApplicationState.BACKGROUND)[1]
assertEmbraceSpanData(
span = lastBackgroundActivity
.findSpansByName("emb-${LifecycleEvent.CREATE.spanName(ACTIVITY1_NAME)}")
.single(),
expectedStartTimeMs = expectedTraceStartTime,
expectedEndTimeMs = expectedTraceStartTime + 100,
expectedParentId = rootSpanId,
)

assertEmbraceSpanData(
span = payload.findSpansByName("emb-${LifecycleEvent.START.spanName(ACTIVITY1_NAME)}").single(),
expectedStartTimeMs = expectedTraceStartTime + 100,
expectedEndTimeMs = expectedTraceStartTime + 200,
expectedParentId = rootSpanId,
)
}
)
}

@Config(sdk = [Build.VERSION_CODES.LOLLIPOP])
@Test
fun `foregrounding already-created activity creates hot open trace in L`() {
var preLaunchTimeMs = 0L
testRule.runTest(
setupAction = {
preLaunchTimeMs = overriddenClock.now()
overriddenConfigService.autoDataCaptureBehavior = defaultAutoDataCaptureBehavior
},
testCaseAction = {
simulateOpeningActivities(
addStartupActivity = true,
startInBackground = true,
createFirstActivity = false,
activitiesAndActions = listOf(
Robolectric.buildActivity(Activity1::class.java) to {},
)
)
},
assertAction = {
val payload = getSessionEnvelopes(2)[1]
val trace = payload.findSpansOfType(EmbType.Performance.ActivityOpen).single()
assertEquals("emb-$ACTIVITY1_NAME-hot-open", trace.name)
val rootSpanId = checkNotNull(trace.spanId)

val expectedTraceStartTime = preLaunchTimeMs + 10000
assertEmbraceSpanData(
span = trace,
expectedStartTimeMs = expectedTraceStartTime,
expectedEndTimeMs = expectedTraceStartTime + 100,
expectedParentId = SpanId.getInvalid(),
key = true
)

assertEmbraceSpanData(
span = payload.findSpansByName("emb-${LifecycleEvent.START.spanName(ACTIVITY1_NAME)}").single(),
expectedStartTimeMs = expectedTraceStartTime,
expectedEndTimeMs = expectedTraceStartTime + 100,
expectedParentId = rootSpanId,
)
}
)
}

private companion object {
@ObservedActivity
class Activity1 : Activity()

@ObservedActivity
class Activity2 : Activity()

val ACTIVITY1_NAME = Robolectric.buildActivity(Activity1::class.java).get().localClassName
val ACTIVITY2_NAME = Robolectric.buildActivity(Activity2::class.java).get().localClassName
}
}
Loading