Skip to content

Commit

Permalink
DRAFT
Browse files Browse the repository at this point in the history
  • Loading branch information
bidetofevil committed Jul 18, 2024
1 parent 74a2e3d commit a3f657c
Show file tree
Hide file tree
Showing 10 changed files with 878 additions and 14 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
package io.embrace.android.embracesdk.internal.capture.activity

import android.app.Activity
import android.os.Build
import android.os.Bundle
import io.embrace.android.embracesdk.internal.clock.nanosToMillis
import io.embrace.android.embracesdk.internal.session.lifecycle.ActivityLifecycleListener
import io.embrace.android.embracesdk.internal.utils.VersionChecker
import io.opentelemetry.sdk.common.Clock

internal class LoadEventEmitter(
private val openEvents: LoadEvents,
private val clock: Clock,
private val versionChecker: VersionChecker,
): ActivityLifecycleListener {
override fun onActivityPreCreated(activity: Activity, savedInstanceState: Bundle?) {
create(activity)
}

override fun onActivityCreated(activity: Activity, bundle: Bundle?) {
if (!versionChecker.hasPrePostEvents()) {
create(activity)
}
}

override fun onActivityPostCreated(activity: Activity, savedInstanceState: Bundle?) {
createEnd(activity)
}

override fun onActivityPreStarted(activity: Activity) {
start(activity)
}

override fun onActivityStarted(activity: Activity) {
if (!versionChecker.hasPrePostEvents()) {
createEnd(activity)
start(activity)
}
}

override fun onActivityPostStarted(activity: Activity) {
startEnd(activity)
}

override fun onActivityPreResumed(activity: Activity) {
resume(activity)
}

override fun onActivityResumed(activity: Activity) {
if (!versionChecker.hasPrePostEvents()) {
startEnd(activity)
resumeEnd(activity)
}
}

override fun onActivityPostResumed(activity: Activity) {
resumeEnd(activity)
}

private fun create(activity: Activity) {
openEvents.create(
instanceId = traceInstanceId(activity),
activityName = activity.localClassName,
timestampMs = nowMs()
)
}

private fun createEnd(activity: Activity) {
openEvents.createEnd(
instanceId = traceInstanceId(activity),
timestampMs = nowMs()
)
}

private fun start(activity: Activity) {
openEvents.start(
instanceId = traceInstanceId(activity),
activityName = activity.localClassName,
timestampMs = nowMs()
)
}

private fun startEnd(activity: Activity) {
openEvents.startEnd(
instanceId = traceInstanceId(activity),
timestampMs = nowMs()
)
}

private fun resume(activity: Activity) {
openEvents.resume(
instanceId = traceInstanceId(activity),
timestampMs = nowMs()
)
}

private fun resumeEnd(activity: Activity) {
openEvents.resumeEnd(
instanceId = traceInstanceId(activity),
timestampMs = nowMs()
)
}

private fun VersionChecker.hasPrePostEvents(): Boolean = isAtLeast(Build.VERSION_CODES.Q)

private fun traceInstanceId(activity: Activity): Int = activity.hashCode()

private fun nowMs(): Long = clock.now().nanosToMillis()
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package io.embrace.android.embracesdk.internal.capture.activity

internal interface LoadEvents {

fun create(instanceId: Int, activityName: String, timestampMs: Long)

fun createEnd(instanceId: Int, timestampMs: Long)

fun start(instanceId: Int, activityName: String, timestampMs: Long)

fun startEnd(instanceId: Int, timestampMs: Long)

fun resume(instanceId: Int, timestampMs: Long)

fun resumeEnd(instanceId: Int, timestampMs: Long)

fun firstRender(instanceId: Int, timestampMs: Long)

fun firstRenderEnd(instanceId: Int, timestampMs: Long)

enum class OpenType(val typeName: String) {
COLD("cold"), HOT("hot")
}

enum class EndEvent(val eventName: String) {
RENDER("render"), RESUME("open")
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,182 @@
package io.embrace.android.embracesdk.internal.capture.activity

import android.os.Build
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.internal.worker.BackgroundWorker
import java.util.concurrent.ConcurrentHashMap

internal class LoadTraceEmitter(
private val spanService: SpanService,
private val backgroundWorker: BackgroundWorker,
private val versionChecker: VersionChecker,
) : LoadEvents {

private val activeTraces: MutableMap<Int, ActivityLoadTrace> = ConcurrentHashMap()

override fun create(instanceId: Int, activityName: String, timestampMs: Long) {
startTrace(
openType = LoadEvents.OpenType.COLD,
instanceId = instanceId,
activityName = activityName,
timestampMs = timestampMs
)
startChildSpan(
instanceId = instanceId,
timestampMs = timestampMs,
childType = ChildType.CREATE
)
}

override fun createEnd(instanceId: Int, timestampMs: Long) {
endChildSpan(
instanceId = instanceId,
timestampMs = timestampMs,
childType = ChildType.CREATE
)
}

override fun start(instanceId: Int, activityName: String, timestampMs: Long) {
startTrace(
openType = LoadEvents.OpenType.HOT,
instanceId = instanceId,
activityName = activityName,
timestampMs = timestampMs
)
startChildSpan(
instanceId = instanceId,
timestampMs = timestampMs,
childType = ChildType.START
)
}

override fun startEnd(instanceId: Int, timestampMs: Long) {
endChildSpan(
instanceId = instanceId,
timestampMs = timestampMs,
childType = ChildType.START
)
}

override fun resume(instanceId: Int, timestampMs: Long) {
startChildSpan(
instanceId = instanceId,
timestampMs = timestampMs,
childType = ChildType.RESUME
)
}

override fun resumeEnd(instanceId: Int, timestampMs: Long) {
endChildSpan(
instanceId = instanceId,
timestampMs = timestampMs,
childType = ChildType.RESUME
)

if (getEndEvent() == LoadEvents.EndEvent.RESUME) {
endTrace(
instanceId = instanceId,
timestampMs = timestampMs,
)
}
}

override fun firstRender(instanceId: Int, timestampMs: Long) {
startChildSpan(
instanceId = instanceId,
timestampMs = timestampMs,
childType = ChildType.RENDER
)
}

override fun firstRenderEnd(instanceId: Int, timestampMs: Long) {
endChildSpan(
instanceId = instanceId,
timestampMs = timestampMs,
childType = ChildType.RENDER
)
endTrace(
instanceId = instanceId,
timestampMs = timestampMs,
)
}

private fun startTrace(
openType: LoadEvents.OpenType,
instanceId: Int,
activityName: String,
timestampMs: Long
) {
if (!activeTraces.containsKey(instanceId)) {
spanService.startSpan(
name = traceName(activityName, openType),
startTimeMs = timestampMs,
)?.let { root ->
activeTraces[instanceId] = ActivityLoadTrace(root = root, activityName = activityName)
}
}
}

private fun endTrace(instanceId: Int, timestampMs: Long) {
activeTraces[instanceId]?.let { trace ->
backgroundWorker.submit {
trace.root.stop(timestampMs)
activeTraces.remove(instanceId)
}
}
}

private fun startChildSpan(instanceId: Int, timestampMs: Long, childType: ChildType) {
val trace = activeTraces[instanceId]
if (trace != null && !trace.children.containsKey(childType)) {
backgroundWorker.submit {
spanService.startSpan(
name = childType.spanName(trace.activityName),
parent = trace.root,
startTimeMs = timestampMs,
)?.let { newSpan ->
activeTraces[instanceId] = trace.copy(
children = trace.children.plus(childType to newSpan)
)
}
}
}
}

private fun endChildSpan(instanceId: Int, timestampMs: Long, childType: ChildType) {
activeTraces[instanceId]?.let { trace ->
backgroundWorker.submit {
trace.children[childType]?.stop(timestampMs)
}
}
}

private fun getEndEvent(): LoadEvents.EndEvent {
return if (versionChecker.isAtLeast(Build.VERSION_CODES.Q)) {
LoadEvents.EndEvent.RENDER
} else {
LoadEvents.EndEvent.RESUME
}
}

private fun traceName(
activityName: String,
openType: LoadEvents.OpenType
): String = "$activityName-${openType.typeName}-${getEndEvent().eventName}"

private enum class ChildType(val typeName: String) {
CREATE("create"),
START("start"),
RESUME("resume"),
RENDER("render");

fun spanName(activityName: String): String = "activity-$typeName-$activityName"
}

private data class ActivityLoadTrace(
val activityName: String,
val root: PersistableEmbraceSpan,
val children: Map<ChildType, PersistableEmbraceSpan> = emptyMap(),
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import io.embrace.android.embracesdk.internal.capture.startup.AppStartupDataColl
import io.embrace.android.embracesdk.internal.capture.startup.StartupService
import io.embrace.android.embracesdk.internal.capture.startup.StartupTracker
import io.embrace.android.embracesdk.internal.capture.webview.WebViewService
import io.embrace.android.embracesdk.internal.session.lifecycle.ActivityLifecycleListener

/**
* This modules provides services that capture data from within an application. It could be argued
Expand Down Expand Up @@ -38,4 +39,6 @@ internal interface DataCaptureServiceModule {
val startupTracker: StartupTracker

val appStartupDataCollector: AppStartupDataCollector

val activityCreateTraceEmitter: ActivityLifecycleListener
}
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,15 @@ internal class DataCaptureServiceModuleImpl @JvmOverloads constructor(
)
}

override val activityCreateTraceEmitter: ActivityCreateTraceEmitter by singleton {
ActivityCreateTraceEmitter(
clock = initModule.openTelemetryClock,
spanService = openTelemetryModule.spanService,
backgroundWorker = workerThreadModule.backgroundWorker(WorkerName.BACKGROUND_REGISTRATION),
versionChecker = versionChecker,
)
}

override val startupTracker: StartupTracker by singleton {
StartupTracker(
appStartupDataCollector = appStartupDataCollector,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -254,7 +254,8 @@ internal class ModuleInitBootstrapper(
serviceRegistry.registerServices(
dataCaptureServiceModule.webviewService,
dataCaptureServiceModule.breadcrumbService,
dataCaptureServiceModule.pushNotificationService
dataCaptureServiceModule.pushNotificationService,
dataCaptureServiceModule.activityCreateTraceEmitter
)
}

Expand Down
Loading

0 comments on commit a3f657c

Please sign in to comment.