Skip to content

Commit

Permalink
Add option to manually end UI load trace
Browse files Browse the repository at this point in the history
  • Loading branch information
bidetofevil committed Dec 20, 2024
1 parent e1fd316 commit 1e384d0
Show file tree
Hide file tree
Showing 5 changed files with 198 additions and 39 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,10 @@ interface UiLoadEventListener {
* When the given UI instance is starting to be created.
*
* For an Activity, it means it has entered the CREATED state of its lifecycle.
*
* Set [manualEnd] to true to signal that the load of this UI instance will be ended manually by calling [complete]
*/
fun create(instanceId: Int, activityName: String, timestampMs: Long)
fun create(instanceId: Int, activityName: String, timestampMs: Long, manualEnd: Boolean)

/**
* When the given UI instance has been fully created and is ready to be displayed on screen.
Expand All @@ -24,8 +26,10 @@ interface UiLoadEventListener {
* When the given UI instance is starting to be displayed on screen
*
* For an Activity, it means it is about to enter the STARTED state of its lifecycle.
*
* Set [manualEnd] to true to signal that the load of this UI instance will be ended manually by calling [complete]
*/
fun start(instanceId: Int, activityName: String, timestampMs: Long)
fun start(instanceId: Int, activityName: String, timestampMs: Long, manualEnd: Boolean)

/**
* When the given UI instance is displayed on screen and its views are ready to be rendered
Expand Down Expand Up @@ -62,6 +66,12 @@ interface UiLoadEventListener {
*/
fun renderEnd(instanceId: Int, timestampMs: Long)

/**
* When the app manually signals that the load of the given UI instance is complete. This will only be respected
* if the load is expected to be ended manually.
*/
fun complete(instanceId: Int, timestampMs: Long)

/**
* When we no longer wish to observe the loading of the given UI instance. This may be called during its load
* or after it has loaded. Calls to this for a given instance should be idempotent.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -148,7 +148,8 @@ private class UiLoadEventEmitter(
uiLoadEventListener.create(
instanceId = traceInstanceId(activity),
activityName = activity.localClassName,
timestampMs = nowMs()
timestampMs = nowMs(),
manualEnd = false,
)
}

Expand All @@ -163,7 +164,8 @@ private class UiLoadEventEmitter(
uiLoadEventListener.start(
instanceId = traceInstanceId(activity),
activityName = activity.localClassName,
timestampMs = nowMs()
timestampMs = nowMs(),
manualEnd = false,
)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,12 +54,13 @@ class UiLoadTraceEmitter(
private val traceZygoteHolder: AtomicReference<UiLoadTraceZygote> = AtomicReference(INITIAL)
private var currentTracedInstanceId: Int? = null

override fun create(instanceId: Int, activityName: String, timestampMs: Long) {
override fun create(instanceId: Int, activityName: String, timestampMs: Long, manualEnd: Boolean) {
startTrace(
uiLoadType = UiLoadType.COLD,
instanceId = instanceId,
activityName = activityName,
timestampMs = timestampMs
timestampMs = timestampMs,
manualEnd = manualEnd
)
startChildSpan(
instanceId = instanceId,
Expand All @@ -76,12 +77,13 @@ class UiLoadTraceEmitter(
)
}

override fun start(instanceId: Int, activityName: String, timestampMs: Long) {
override fun start(instanceId: Int, activityName: String, timestampMs: Long, manualEnd: Boolean) {
startTrace(
uiLoadType = UiLoadType.HOT,
instanceId = instanceId,
activityName = activityName,
timestampMs = timestampMs
timestampMs = timestampMs,
manualEnd = manualEnd
)
startChildSpan(
instanceId = instanceId,
Expand All @@ -99,12 +101,12 @@ class UiLoadTraceEmitter(
}

override fun resume(instanceId: Int, activityName: String, timestampMs: Long) {
if (!hasRenderEvent()) {
if (getEndEvent(instanceId) == EndEvent.RESUME) {
endTrace(
instanceId = instanceId,
timestampMs = timestampMs,
)
} else {
} else if (hasRenderEvent()) {
startChildSpan(
instanceId = instanceId,
timestampMs = timestampMs,
Expand Down Expand Up @@ -136,16 +138,32 @@ class UiLoadTraceEmitter(
timestampMs = timestampMs,
lifecycleStage = LifecycleStage.RENDER
)
endTrace(
instanceId = instanceId,
timestampMs = timestampMs,
)

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

override fun complete(instanceId: Int, timestampMs: Long) {
if (getEndEvent(instanceId) == EndEvent.MANUAL) {
endTrace(
instanceId = instanceId,
timestampMs = timestampMs,
)
}
}

override fun abandon(instanceId: Int, activityName: String, timestampMs: Long) {
currentTracedInstanceId?.let { currentlyTracedInstanceId ->
if (instanceId != currentlyTracedInstanceId) {
endTrace(instanceId = currentlyTracedInstanceId, timestampMs = timestampMs, errorCode = ErrorCode.USER_ABANDON)
endTrace(
instanceId = currentlyTracedInstanceId,
timestampMs = timestampMs,
errorCode = ErrorCode.USER_ABANDON
)
}
}
traceZygoteHolder.set(
Expand All @@ -167,7 +185,8 @@ class UiLoadTraceEmitter(
uiLoadType: UiLoadType,
instanceId: Int,
activityName: String,
timestampMs: Long
timestampMs: Long,
manualEnd: Boolean,
) {
if (traceZygoteHolder.get() == INITIAL) {
return
Expand All @@ -189,11 +208,25 @@ class UiLoadTraceEmitter(
if (zygote.lastActivityInstanceId != -1) {
root.addSystemAttribute("last_activity", zygote.lastActivityName)
}
activeTraces[instanceId] = UiLoadTrace(root = root, activityName = activityName)
activeTraces[instanceId] = UiLoadTrace(
root = root,
endEvent = determineEndEvent(manualEnd),
activityName = activityName
)
}
}
}

private fun determineEndEvent(manualEnd: Boolean): EndEvent {
return if (manualEnd) {
EndEvent.MANUAL
} else if (hasRenderEvent()) {
EndEvent.RENDER
} else {
EndEvent.RESUME
}
}

private fun endTrace(instanceId: Int, timestampMs: Long, errorCode: ErrorCode? = null) {
activeTraces[instanceId]?.let { trace ->
with(trace) {
Expand Down Expand Up @@ -228,15 +261,18 @@ class UiLoadTraceEmitter(
}
}

private fun getEndEvent(instanceId: Int): EndEvent? = activeTraces[instanceId]?.endEvent

private fun hasRenderEvent(): Boolean = versionChecker.isAtLeast(Build.VERSION_CODES.Q)

private fun traceName(
activityName: String,
uiLoadType: UiLoadType
uiLoadType: UiLoadType,
): String = "$activityName-${uiLoadType.typeName}-time-to-initial-display"

private data class UiLoadTrace(
val activityName: String,
val endEvent: EndEvent,
val root: PersistableEmbraceSpan,
val children: Map<LifecycleStage, PersistableEmbraceSpan> = ConcurrentHashMap(),
)
Expand All @@ -251,6 +287,12 @@ class UiLoadTraceEmitter(
const val INVALID_INSTANCE: Int = -1
const val INVALID_TIME: Long = -1L

enum class EndEvent {
RESUME,
RENDER,
MANUAL,
}

val INITIAL = UiLoadTraceZygote(
lastActivityName = "NEW_APP_LAUNCH",
lastActivityInstanceId = INVALID_INSTANCE,
Expand Down
Loading

0 comments on commit 1e384d0

Please sign in to comment.