From 3426ba174679080b33999e19b2b03ade36515645 Mon Sep 17 00:00:00 2001 From: Tom Mulcahy Date: Tue, 11 Nov 2025 11:32:57 -0800 Subject: [PATCH] Remove Papa/Safe prefixes from names We are no longer using papa safe trace, so this commit renames SafeTraceInterface to TraceInterface, PapaSafeTrace to WorkflowTrace, WorkflowPapaTracer to WorkflowTracer, and FakeSafeTrace to FakeTrace to better reflect their purpose without legacy naming conventions. Maintains backward compatibility by keeping deprecated versions of the old class names that delegate to the new implementations. All test files updated to use the new naming convention. workflow-tracing-papa is misleading, but it's the name of a jar published to maven, so I've preserved it. --- .../api/workflow-tracing-papa.api | 31 +- .../workflow1/tracing/WorkflowTrace.kt | 44 ++ .../workflow1/tracing/WorkflowTracer.kt | 476 +++++++++++++++++ .../workflow1/tracing/papa/PapaSafeTrace.kt | 54 +- .../tracing/papa/WorkflowPapaTracer.kt | 486 +----------------- .../squareup/workflow1/tracing/FakeTrace.kt | 51 ++ .../workflow1/tracing/papa/FakeSafeTrace.kt | 67 +-- .../tracing/papa/WorkflowPapaTracerTest.kt | 61 +-- .../papa/WorkflowTracingIntegrationTest.kt | 38 +- workflow-tracing/api/workflow-tracing.api | 18 +- .../workflow1/tracing/SafeTraceInterface.kt | 11 +- 11 files changed, 710 insertions(+), 627 deletions(-) create mode 100644 workflow-tracing-papa/src/main/java/com/squareup/workflow1/tracing/WorkflowTrace.kt create mode 100644 workflow-tracing-papa/src/main/java/com/squareup/workflow1/tracing/WorkflowTracer.kt create mode 100644 workflow-tracing-papa/src/test/java/com/squareup/workflow1/tracing/FakeTrace.kt diff --git a/workflow-tracing-papa/api/workflow-tracing-papa.api b/workflow-tracing-papa/api/workflow-tracing-papa.api index 99a19b30a6..15a70fcd91 100644 --- a/workflow-tracing-papa/api/workflow-tracing-papa.api +++ b/workflow-tracing-papa/api/workflow-tracing-papa.api @@ -1,4 +1,4 @@ -public final class com/squareup/workflow1/tracing/papa/PapaSafeTrace : com/squareup/workflow1/tracing/SafeTraceInterface { +public final class com/squareup/workflow1/tracing/WorkflowTrace : com/squareup/workflow1/tracing/TraceInterface { public fun ()V public fun (Z)V public synthetic fun (ZILkotlin/jvm/internal/DefaultConstructorMarker;)V @@ -11,11 +11,11 @@ public final class com/squareup/workflow1/tracing/papa/PapaSafeTrace : com/squar public fun logSection (Ljava/lang/String;)V } -public final class com/squareup/workflow1/tracing/papa/WorkflowPapaTracer : com/squareup/workflow1/tracing/WorkflowRuntimeTracer { - public static final field Companion Lcom/squareup/workflow1/tracing/papa/WorkflowPapaTracer$Companion; +public class com/squareup/workflow1/tracing/WorkflowTracer : com/squareup/workflow1/tracing/WorkflowRuntimeTracer { + public static final field Companion Lcom/squareup/workflow1/tracing/WorkflowTracer$Companion; public fun ()V - public fun (Lcom/squareup/workflow1/tracing/SafeTraceInterface;)V - public synthetic fun (Lcom/squareup/workflow1/tracing/SafeTraceInterface;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public fun (Lcom/squareup/workflow1/tracing/TraceInterface;)V + public synthetic fun (Lcom/squareup/workflow1/tracing/TraceInterface;ILkotlin/jvm/internal/DefaultConstructorMarker;)V public fun onInitialState (Ljava/lang/Object;Lcom/squareup/workflow1/Snapshot;Lkotlinx/coroutines/CoroutineScope;Lkotlin/jvm/functions/Function3;Lcom/squareup/workflow1/WorkflowInterceptor$WorkflowSession;)Ljava/lang/Object; public fun onPropsChanged (Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;Lkotlin/jvm/functions/Function3;Lcom/squareup/workflow1/WorkflowInterceptor$WorkflowSession;)Ljava/lang/Object; public fun onRender (Ljava/lang/Object;Ljava/lang/Object;Lcom/squareup/workflow1/BaseRenderContext;Lkotlin/jvm/functions/Function3;Lcom/squareup/workflow1/WorkflowInterceptor$WorkflowSession;)Ljava/lang/Object; @@ -27,6 +27,25 @@ public final class com/squareup/workflow1/tracing/papa/WorkflowPapaTracer : com/ public fun onWorkflowSessionStopped (J)V } -public final class com/squareup/workflow1/tracing/papa/WorkflowPapaTracer$Companion { +public final class com/squareup/workflow1/tracing/WorkflowTracer$Companion { +} + +public final class com/squareup/workflow1/tracing/papa/PapaSafeTrace : com/squareup/workflow1/tracing/TraceInterface { + public fun ()V + public fun (Z)V + public synthetic fun (ZILkotlin/jvm/internal/DefaultConstructorMarker;)V + public fun beginAsyncSection (Ljava/lang/String;I)V + public fun beginSection (Ljava/lang/String;)V + public fun endAsyncSection (Ljava/lang/String;I)V + public fun endSection ()V + public fun isCurrentlyTracing ()Z + public fun isTraceable ()Z + public fun logSection (Ljava/lang/String;)V +} + +public final class com/squareup/workflow1/tracing/papa/WorkflowPapaTracer : com/squareup/workflow1/tracing/WorkflowTracer { + public fun ()V + public fun (Lcom/squareup/workflow1/tracing/TraceInterface;)V + public synthetic fun (Lcom/squareup/workflow1/tracing/TraceInterface;ILkotlin/jvm/internal/DefaultConstructorMarker;)V } diff --git a/workflow-tracing-papa/src/main/java/com/squareup/workflow1/tracing/WorkflowTrace.kt b/workflow-tracing-papa/src/main/java/com/squareup/workflow1/tracing/WorkflowTrace.kt new file mode 100644 index 0000000000..b2fe8f70c4 --- /dev/null +++ b/workflow-tracing-papa/src/main/java/com/squareup/workflow1/tracing/WorkflowTrace.kt @@ -0,0 +1,44 @@ +package com.squareup.workflow1.tracing + +import androidx.tracing.Trace +import androidx.tracing.trace + +/** + * Production implementation of [TraceInterface] that uses androidx.tracing.Trace. + * + * @param isTraceable Whether tracing is enabled. Clients should configure this directly. + * Defaults to false for backwards compatibility. + */ +class WorkflowTrace( + override val isTraceable: Boolean = false +) : TraceInterface { + + override val isCurrentlyTracing: Boolean + get() = Trace.isEnabled() + + override fun beginSection(label: String) { + Trace.beginSection(label) + } + + override fun endSection() { + Trace.endSection() + } + + override fun beginAsyncSection( + name: String, + cookie: Int + ) { + Trace.beginAsyncSection(name, cookie) + } + + override fun endAsyncSection( + name: String, + cookie: Int + ) { + Trace.endAsyncSection(name, cookie) + } + + override fun logSection(info: String) { + trace(info) {} + } +} diff --git a/workflow-tracing-papa/src/main/java/com/squareup/workflow1/tracing/WorkflowTracer.kt b/workflow-tracing-papa/src/main/java/com/squareup/workflow1/tracing/WorkflowTracer.kt new file mode 100644 index 0000000000..55a0cd757d --- /dev/null +++ b/workflow-tracing-papa/src/main/java/com/squareup/workflow1/tracing/WorkflowTracer.kt @@ -0,0 +1,476 @@ +package com.squareup.workflow1.tracing + +import androidx.collection.mutableLongObjectMapOf +import com.squareup.workflow1.BaseRenderContext +import com.squareup.workflow1.RenderingAndSnapshot +import com.squareup.workflow1.Snapshot +import com.squareup.workflow1.TreeSnapshot +import com.squareup.workflow1.Worker +import com.squareup.workflow1.Workflow +import com.squareup.workflow1.WorkflowAction +import com.squareup.workflow1.WorkflowInterceptor.RenderContextInterceptor +import com.squareup.workflow1.WorkflowInterceptor.RenderPassSkipped +import com.squareup.workflow1.WorkflowInterceptor.RuntimeSettled +import com.squareup.workflow1.WorkflowInterceptor.RuntimeUpdate +import com.squareup.workflow1.WorkflowInterceptor.WorkflowSession +import com.squareup.workflow1.applyTo +import com.squareup.workflow1.tracing.ConfigSnapshot +// TraceInterface is in same package now +import com.squareup.workflow1.tracing.WorkflowRuntimeMonitor.ActionType +import com.squareup.workflow1.tracing.WorkflowRuntimeMonitor.ActionType.CascadeAction +import com.squareup.workflow1.tracing.WorkflowRuntimeMonitor.ActionType.QueuedAction +import com.squareup.workflow1.tracing.WorkflowRuntimeTracer +import com.squareup.workflow1.tracing.getWfLogString +import com.squareup.workflow1.tracing.toLoggingShortName +import com.squareup.workflow1.tracing.workerKey +import kotlinx.coroutines.CoroutineScope +import kotlin.reflect.KType + +/** + * [WorkflowRuntimeTracer] plugin to add [TraceInterface] traces. + * By default this uses [WorkflowTrace] which will use [androidx.tracing.Trace] calls that + * will be received by the system and included in Perfetto traces. + * + * @param safeTrace The [TraceInterface] implementation to use for tracing. + */ +open class WorkflowTracer( + private val safeTrace: TraceInterface = WorkflowTrace(isTraceable = false) +) : WorkflowRuntimeTracer() { + + private data class NameAndCookie( + val name: String, + val cookie: Int + ) + + private class SystemTraceState { + var renderPassCount = 0 + + // Some render passes are skipped if they have no state change. Count all triggers separately. + var renderPassTriggerCount = 0 + val workflowAsyncSections = mutableLongObjectMapOf() + val workflowShortNamesById = mutableLongObjectMapOf() + } + + private val systemTraceState = if (safeTrace.isTraceable) { + SystemTraceState() + } else { + null + } + + private val isSystemTraceable: Boolean + get() = systemTraceState != null + + private val isCurrentlySystemTracing: Boolean + get() = systemTraceState != null && safeTrace.isCurrentlyTracing + + /** + * If the build is traceable but we're not currently tracing, reset so that we start at 0 in + * new traces. + */ + private fun SystemTraceState.resetTraceCountsIfNotTracing() { + if (!safeTrace.isCurrentlyTracing) { + renderPassCount = 0 + renderPassTriggerCount = 0 + actionIndex = 0 + effectIndex = 0 + } + } + + private fun renderPassNumber(): String { + return ":${systemTraceState?.renderPassTriggerCount ?: 0}:" + } + + /** + * Log a Perfetto trace section that is simply meta-data that we use in post-processing. + * + * Format for labels in these sections: + * - "W:" is for a Workflow + * - "R:" is for a Worker + * - "A:" is for an Action + */ + private fun infoSection(info: String) { + safeTrace.logSection(info) + } + + /** SECTION: [WorkflowRuntimeTracer] specific methods. **/ + + override fun onWorkflowSessionStarted( + workflowScope: CoroutineScope, + session: WorkflowSession + ) { + systemTraceState?.let { + val sessionId = session.sessionId + // We are tracing, so set up some initial components for this workflow. + val shortName = "WKF$sessionId ${session.name}" + val nameWithKey = "WKF$sessionId ${session.logName}" + + val parentPart = session.parent?.let { parentSession -> + " parent:${parentSession.traceName}" + } ?: "" + val asyncSectionName = "$nameWithKey$parentPart" + + val atraceCookie = workflowRuntimeTraceContext.runtimeName.hashCode() * sessionId.toInt() + it.workflowAsyncSections[sessionId] = NameAndCookie(asyncSectionName, atraceCookie) + it.workflowShortNamesById[sessionId] = shortName + + safeTrace.beginAsyncSection(asyncSectionName, atraceCookie) + + // Reason for render pass if we are the root. + if (session.isRootWorkflow) { + // This could be the first thing that happens after a trace has finished, so check if we need + // to reset it here. + it.resetTraceCountsIfNotTracing() + it.renderPassTriggerCount++ + safeTrace.beginSection( + "CREATE_RENDER${it.renderPassTriggerCount}, Runner:$workflowRuntimeTraceContext.runtimeName" + ) + // These are both short, but CAUSE: is long in the regular case, so leave it as a separate + // info section. + infoSection("SUM:${it.renderPassTriggerCount}: Skipped:N, StateChange:Y") + infoSection("CAUSE:${it.renderPassTriggerCount}: RootWFCreated:W($${session.name})") + } + } + } + + override fun onWorkflowSessionStopped( + sessionId: Long + ) { + systemTraceState?.let { + val asyncSection = it.workflowAsyncSections.remove(sessionId) + // TODO (RF-9493) Investigate asyncSection being null instead of ignoring the problem + if (asyncSection != null) { + safeTrace.endAsyncSection(asyncSection.name, asyncSection.cookie) + } + } + } + + override fun onRuntimeUpdateEnhanced( + runtimeUpdate: RuntimeUpdate, + currentActionHandlingChangedState: Boolean, + configSnapshot: ConfigSnapshot + ) { + if (!isSystemTraceable) return + if (runtimeUpdate == RenderPassSkipped) { + // Helps understanding traces. + infoSection("CAUSE${renderPassNumber()} ${workflowRuntimeTraceContext.previousRenderCause}") + // Skipping, end the section started when renderIncomingCause was set. + safeTrace.endSection() + } + if (runtimeUpdate == RuntimeSettled) { + // Build and add the summary! + val summary = buildString { + append("SUM${renderPassNumber()} ") + append(configSnapshot.shortConfigAsString) + append("StateChange:") + if (currentActionHandlingChangedState) { + append("Y, ") + } else { + append("N, ") + } + } + infoSection(summary) + } + } + + override fun onRootPropsChanged(session: WorkflowSession) { + if (systemTraceState != null) { + safeTrace.beginSection( + "PROPS_RENDER${++systemTraceState.renderPassTriggerCount}, Runner:${workflowRuntimeTraceContext.runtimeName}" + ) + infoSection("SUM:${systemTraceState.renderPassTriggerCount}: Skipped:N, StateChange:Y") + infoSection("CAUSE:${systemTraceState.renderPassTriggerCount}: RootWFProps:${session.name}") + } + } + + /** END SECTION: [WorkflowRuntimeTracer] specific methods. **/ + + /** SECTION: [WorkflowInterceptor] override methods. **/ + + override fun onInitialState( + props: P, + snapshot: Snapshot?, + workflowScope: CoroutineScope, + proceed: (P, Snapshot?, CoroutineScope) -> S, + session: WorkflowSession + ): S { + return trace( + systemTraceLabel = { "InitialState ${session.traceName}" }, + ) { + proceed(props, snapshot, workflowScope) + } + } + + override fun onPropsChanged( + old: P, + new: P, + state: S, + proceed: (P, P, S) -> S, + session: WorkflowSession + ): S { + return trace( + systemTraceLabel = { "PropsChanged ${session.traceName}" }, + ) { + proceed(old, new, state) + } + } + + override fun onRenderAndSnapshot( + renderProps: P, + proceed: (P) -> RenderingAndSnapshot, + session: WorkflowSession + ): RenderingAndSnapshot { + systemTraceState?.resetTraceCountsIfNotTracing() + return trace( + systemTraceLabel = { + "RENDER${++systemTraceState!!.renderPassCount}" + + " ${workflowRuntimeTraceContext.runtimeName}" + }, + ) { + proceed(renderProps).also { + if (systemTraceState != null) { + // Ends the section that's always started right when a [QueuedAction] is applied. + safeTrace.endSection() + } + } + } + } + + override fun onRender( + renderProps: P, + renderState: S, + context: BaseRenderContext, + proceed: (P, S, RenderContextInterceptor?) -> R, + session: WorkflowSession + ): R { + val workflowName = session.traceName + return trace( + systemTraceLabel = { "Render $workflowName" } + ) { + proceed( + renderProps, + renderState, + TracingRenderContextInterceptor( + isRoot = session.isRootWorkflow, + workflowName = workflowName + ) + ) + } + } + + override fun onSnapshotStateWithChildren( + proceed: () -> TreeSnapshot, + session: WorkflowSession + ): TreeSnapshot { + return trace( + systemTraceLabel = { "Snapshot ${workflowRuntimeTraceContext.runtimeName}" } + ) { + proceed() + } + } + + /** END SECTION: [WorkflowInterceptor] override methods. **/ + + /** + * [RenderContextInterceptor] that adds Perfetto tracing. + */ + private inner class TracingRenderContextInterceptor( + private val isRoot: Boolean, + private val workflowName: String + ) : RenderContextInterceptor { + + override fun onActionSent( + action: WorkflowAction, + proceed: (WorkflowAction) -> Unit + ) { + val actionName = action.toLoggingShortName() + val actionIndexLabel = "ACT${actionIndex++}" + val traceActionName = if (isSystemTraceable) { + "$actionIndexLabel A(${actionName.ifBlank { "" }})/W($workflowName)" + } else { + null + } + val queuedActionDetails = QueuedAction + trace( + systemTraceLabel = { "Send $traceActionName}" } + ) { + proceed( + PerfettoTraceWorkflowAction( + delegateAction = action, + actionName = actionName, + actionType = queuedActionDetails, + actionIndex = actionIndexLabel, + ) + ) + } + } + + override fun onRunningSideEffect( + key: String, + sideEffect: suspend () -> Unit, + proceed: (key: String, sideEffect: suspend () -> Unit) -> Unit + ) { + val label = if (isSystemTraceable) { + "EFF${effectIndex++} Key[$key]" + } else { + null + } + trace( + systemTraceLabel = { "SideEffect $label" } + ) { + proceed(key, sideEffect) + } + } + + override fun onRenderChild( + child: Workflow, + childProps: CP, + key: String, + handler: (CO) -> WorkflowAction, + proceed: ( + child: Workflow, + childProps: CP, + key: String, + handler: (CO) -> WorkflowAction + ) -> CR + ): CR { + // onRenderChild is not traced (the child's own render will be traced), + // but we trace the action handler. + return proceed(child, childProps, key) { output -> + val childOutputString = getWfLogString(output) + trace( + systemTraceLabel = { "Send Output[$childOutputString] to $workflowName" } + ) { + val delegateAction = handler(output) + val actionName = delegateAction.toLoggingShortName() + PerfettoTraceWorkflowAction( + delegateAction = delegateAction, + actionName = actionName, + actionType = CascadeAction( + childOutputString = childOutputString + ), + ) + } + } + } + + override fun onRemember( + key: String, + resultType: KType, + inputs: Array, + calculation: () -> CResult, + proceed: ( + key: String, + resultType: KType, + inputs: Array, + calculation: () -> CResult + ) -> CResult + ): CResult { + return trace( + systemTraceLabel = { "Remember $key" } + ) { + proceed(key, resultType, inputs, calculation) + } + } + + /** + * Class to trace the application of actions. + */ + private inner class PerfettoTraceWorkflowAction( + private val delegateAction: WorkflowAction, + private val actionName: String, + private val actionType: ActionType, + private val actionIndex: String? = null + ) : WorkflowAction() { + // Forward debugging name so we do not include anything about this tracing action. + override val debuggingName: String + get() = delegateAction.debuggingName + + /** + * Trace application of the action. + */ + override fun Updater.apply() { + // See https://github.com/square/workflow-kotlin/issues/391. We have to listen to the 2nd + // action in the cascade to get a useful ref on which Worker's handler was firing. This is + // because Workers use an underlying Workflow and an intermediate action, which is the + // QueuedAction, in their implementation. So yes, we use an implementation detail here to + // detect that. The issue still tracks upstreaming this into the library. + val isWorkerQueuedAction = actionName.contains(Worker.WORKER_OUTPUT_ACTION_NAME) + if (actionType is QueuedAction) { + if (isSystemTraceable) { + safeTrace.beginSection( + "MAYBE_RENDER${++systemTraceState!!.renderPassTriggerCount}:" + + " $actionIndex," + + " Runner:${workflowRuntimeTraceContext.runtimeName}" + ) + } + } + val (_, actionApplied) = trace( + systemTraceLabel = { + val actionNameOrBlank = actionName.ifBlank { "" } + val queuedApplyName = if (isWorkerQueuedAction) { + "$workflowName(key=${actionNameOrBlank.workerKey()})" + } else { + actionNameOrBlank + } + if (actionType is CascadeAction) { + "CascadeApply:$queuedApplyName," + + " Cause:${workflowRuntimeTraceContext.renderIncomingCauses.lastOrNull()}" + } else { + "QueuedApply:$queuedApplyName" + } + }, + ) { + delegateAction.applyTo(props, state).also { (newState, actionApplied) -> + state = newState + actionApplied.output?.let { setOutput(it.value) } + } + } + + if (isRoot || actionApplied.output == null) { + // This action's application is ending a cascade, let's sum up what happened + sumUpActionCascade() + } + } + + private fun sumUpActionCascade() { + if (isSystemTraceable) { + val causeLabel = buildString { + append("CAUSE${renderPassNumber()} ") + if (workflowRuntimeTraceContext.renderIncomingCauses.isEmpty()) { + append("Unknown") + } else { + append(workflowRuntimeTraceContext.renderIncomingCauses.last()) + } + } + infoSection(causeLabel) + } + } + } + } + + /** + * This method is inlined, so that when tracing is disabled there's no additional lambda creation. + */ + private inline fun trace( + crossinline systemTraceLabel: () -> String, + crossinline block: () -> T + ): T { + val systemTrace = isCurrentlySystemTracing + if (systemTrace) { + safeTrace.beginSection(systemTraceLabel()) + } + try { + return block() + } finally { + if (systemTrace) { + safeTrace.endSection() + } + } + } + + companion object { + // Ensure index is unique across all workflow runtimes + private var actionIndex = 0 + private var effectIndex = 0 + } +} diff --git a/workflow-tracing-papa/src/main/java/com/squareup/workflow1/tracing/papa/PapaSafeTrace.kt b/workflow-tracing-papa/src/main/java/com/squareup/workflow1/tracing/papa/PapaSafeTrace.kt index 4a1f03fde2..6a1f137259 100644 --- a/workflow-tracing-papa/src/main/java/com/squareup/workflow1/tracing/papa/PapaSafeTrace.kt +++ b/workflow-tracing-papa/src/main/java/com/squareup/workflow1/tracing/papa/PapaSafeTrace.kt @@ -1,45 +1,15 @@ package com.squareup.workflow1.tracing.papa -import androidx.tracing.Trace -import androidx.tracing.trace -import com.squareup.workflow1.tracing.SafeTraceInterface - -/** - * Production implementation of [SafeTraceInterface] that uses androidx.tracing.Trace. - * - * @param isTraceable Whether tracing is enabled. Clients should configure this directly. - * Defaults to false for backwards compatibility. - */ +import com.squareup.workflow1.tracing.TraceInterface +import com.squareup.workflow1.tracing.WorkflowTrace + +@Deprecated( + message = "Renamed to WorkflowTrace and moved to com.squareup.workflow1.tracing package", + replaceWith = ReplaceWith( + expression = "WorkflowTrace", + imports = arrayOf("com.squareup.workflow1.tracing.WorkflowTrace") + ) +) class PapaSafeTrace( - override val isTraceable: Boolean = false -) : SafeTraceInterface { - - override val isCurrentlyTracing: Boolean - get() = Trace.isEnabled() - - override fun beginSection(label: String) { - Trace.beginSection(label) - } - - override fun endSection() { - Trace.endSection() - } - - override fun beginAsyncSection( - name: String, - cookie: Int - ) { - Trace.beginAsyncSection(name, cookie) - } - - override fun endAsyncSection( - name: String, - cookie: Int - ) { - Trace.endAsyncSection(name, cookie) - } - - override fun logSection(info: String) { - trace(info) {} - } -} + isTraceable: Boolean = false +) : TraceInterface by WorkflowTrace(isTraceable) diff --git a/workflow-tracing-papa/src/main/java/com/squareup/workflow1/tracing/papa/WorkflowPapaTracer.kt b/workflow-tracing-papa/src/main/java/com/squareup/workflow1/tracing/papa/WorkflowPapaTracer.kt index 7071b427a6..d4e4097ce1 100644 --- a/workflow-tracing-papa/src/main/java/com/squareup/workflow1/tracing/papa/WorkflowPapaTracer.kt +++ b/workflow-tracing-papa/src/main/java/com/squareup/workflow1/tracing/papa/WorkflowPapaTracer.kt @@ -1,476 +1,16 @@ package com.squareup.workflow1.tracing.papa -import androidx.collection.mutableLongObjectMapOf -import com.squareup.workflow1.BaseRenderContext -import com.squareup.workflow1.RenderingAndSnapshot -import com.squareup.workflow1.Snapshot -import com.squareup.workflow1.TreeSnapshot -import com.squareup.workflow1.Worker -import com.squareup.workflow1.Workflow -import com.squareup.workflow1.WorkflowAction -import com.squareup.workflow1.WorkflowInterceptor.RenderContextInterceptor -import com.squareup.workflow1.WorkflowInterceptor.RenderPassSkipped -import com.squareup.workflow1.WorkflowInterceptor.RuntimeSettled -import com.squareup.workflow1.WorkflowInterceptor.RuntimeUpdate -import com.squareup.workflow1.WorkflowInterceptor.WorkflowSession -import com.squareup.workflow1.applyTo -import com.squareup.workflow1.tracing.ConfigSnapshot -import com.squareup.workflow1.tracing.SafeTraceInterface -import com.squareup.workflow1.tracing.WorkflowRuntimeMonitor.ActionType -import com.squareup.workflow1.tracing.WorkflowRuntimeMonitor.ActionType.CascadeAction -import com.squareup.workflow1.tracing.WorkflowRuntimeMonitor.ActionType.QueuedAction -import com.squareup.workflow1.tracing.WorkflowRuntimeTracer -import com.squareup.workflow1.tracing.getWfLogString -import com.squareup.workflow1.tracing.toLoggingShortName -import com.squareup.workflow1.tracing.workerKey -import kotlinx.coroutines.CoroutineScope -import kotlin.reflect.KType - -/** - * [WorkflowRuntimeTracer] plugin to add [SafeTraceInterface] traces. - * By default this uses [PapaSafeTrace] which will use [androidx.tracing.Trace] calls that - * will be received by the system and included in Perfetto traces. - * - * @param safeTrace The [SafeTraceInterface] implementation to use for tracing. - */ -class WorkflowPapaTracer( - private val safeTrace: SafeTraceInterface = PapaSafeTrace(isTraceable = false) -) : WorkflowRuntimeTracer() { - - private data class NameAndCookie( - val name: String, - val cookie: Int +import com.squareup.workflow1.tracing.TraceInterface +import com.squareup.workflow1.tracing.WorkflowTrace +import com.squareup.workflow1.tracing.WorkflowTracer + +@Deprecated( + message = "Renamed to WorkflowTracer and moved to com.squareup.workflow1.tracing package", + replaceWith = ReplaceWith( + expression = "WorkflowTracer", + imports = arrayOf("com.squareup.workflow1.tracing.WorkflowTracer") ) - - private class SystemTraceState { - var renderPassCount = 0 - - // Some render passes are skipped if they have no state change. Count all triggers separately. - var renderPassTriggerCount = 0 - val workflowAsyncSections = mutableLongObjectMapOf() - val workflowShortNamesById = mutableLongObjectMapOf() - } - - private val systemTraceState = if (safeTrace.isTraceable) { - SystemTraceState() - } else { - null - } - - private val isSystemTraceable: Boolean - get() = systemTraceState != null - - private val isCurrentlySystemTracing: Boolean - get() = systemTraceState != null && safeTrace.isCurrentlyTracing - - /** - * If the build is traceable but we're not currently tracing, reset so that we start at 0 in - * new traces. - */ - private fun SystemTraceState.resetTraceCountsIfNotTracing() { - if (!safeTrace.isCurrentlyTracing) { - renderPassCount = 0 - renderPassTriggerCount = 0 - actionIndex = 0 - effectIndex = 0 - } - } - - private fun renderPassNumber(): String { - return ":${systemTraceState?.renderPassTriggerCount ?: 0}:" - } - - /** - * Log a Perfetto trace section that is simply meta-data that we use in post-processing. - * - * Format for labels in these sections: - * - "W:" is for a Workflow - * - "R:" is for a Worker - * - "A:" is for an Action - */ - private fun infoSection(info: String) { - safeTrace.logSection(info) - } - - /** SECTION: [WorkflowRuntimeTracer] specific methods. **/ - - override fun onWorkflowSessionStarted( - workflowScope: CoroutineScope, - session: WorkflowSession - ) { - systemTraceState?.let { - val sessionId = session.sessionId - // We are tracing, so set up some initial components for this workflow. - val shortName = "WKF$sessionId ${session.name}" - val nameWithKey = "WKF$sessionId ${session.logName}" - - val parentPart = session.parent?.let { parentSession -> - " parent:${parentSession.traceName}" - } ?: "" - val asyncSectionName = "$nameWithKey$parentPart" - - val atraceCookie = workflowRuntimeTraceContext.runtimeName.hashCode() * sessionId.toInt() - it.workflowAsyncSections[sessionId] = NameAndCookie(asyncSectionName, atraceCookie) - it.workflowShortNamesById[sessionId] = shortName - - safeTrace.beginAsyncSection(asyncSectionName, atraceCookie) - - // Reason for render pass if we are the root. - if (session.isRootWorkflow) { - // This could be the first thing that happens after a trace has finished, so check if we need - // to reset it here. - it.resetTraceCountsIfNotTracing() - it.renderPassTriggerCount++ - safeTrace.beginSection( - "CREATE_RENDER${it.renderPassTriggerCount}, Runner:$workflowRuntimeTraceContext.runtimeName" - ) - // These are both short, but CAUSE: is long in the regular case, so leave it as a separate - // info section. - infoSection("SUM:${it.renderPassTriggerCount}: Skipped:N, StateChange:Y") - infoSection("CAUSE:${it.renderPassTriggerCount}: RootWFCreated:W($${session.name})") - } - } - } - - override fun onWorkflowSessionStopped( - sessionId: Long - ) { - systemTraceState?.let { - val asyncSection = it.workflowAsyncSections.remove(sessionId) - // TODO (RF-9493) Investigate asyncSection being null instead of ignoring the problem - if (asyncSection != null) { - safeTrace.endAsyncSection(asyncSection.name, asyncSection.cookie) - } - } - } - - override fun onRuntimeUpdateEnhanced( - runtimeUpdate: RuntimeUpdate, - currentActionHandlingChangedState: Boolean, - configSnapshot: ConfigSnapshot - ) { - if (!isSystemTraceable) return - if (runtimeUpdate == RenderPassSkipped) { - // Helps understanding traces. - infoSection("CAUSE${renderPassNumber()} ${workflowRuntimeTraceContext.previousRenderCause}") - // Skipping, end the section started when renderIncomingCause was set. - safeTrace.endSection() - } - if (runtimeUpdate == RuntimeSettled) { - // Build and add the summary! - val summary = buildString { - append("SUM${renderPassNumber()} ") - append(configSnapshot.shortConfigAsString) - append("StateChange:") - if (currentActionHandlingChangedState) { - append("Y, ") - } else { - append("N, ") - } - } - infoSection(summary) - } - } - - override fun onRootPropsChanged(session: WorkflowSession) { - if (systemTraceState != null) { - safeTrace.beginSection( - "PROPS_RENDER${++systemTraceState.renderPassTriggerCount}, Runner:${workflowRuntimeTraceContext.runtimeName}" - ) - infoSection("SUM:${systemTraceState.renderPassTriggerCount}: Skipped:N, StateChange:Y") - infoSection("CAUSE:${systemTraceState.renderPassTriggerCount}: RootWFProps:${session.name}") - } - } - - /** END SECTION: [WorkflowRuntimeTracer] specific methods. **/ - - /** SECTION: [WorkflowInterceptor] override methods. **/ - - override fun onInitialState( - props: P, - snapshot: Snapshot?, - workflowScope: CoroutineScope, - proceed: (P, Snapshot?, CoroutineScope) -> S, - session: WorkflowSession - ): S { - return trace( - systemTraceLabel = { "InitialState ${session.traceName}" }, - ) { - proceed(props, snapshot, workflowScope) - } - } - - override fun onPropsChanged( - old: P, - new: P, - state: S, - proceed: (P, P, S) -> S, - session: WorkflowSession - ): S { - return trace( - systemTraceLabel = { "PropsChanged ${session.traceName}" }, - ) { - proceed(old, new, state) - } - } - - override fun onRenderAndSnapshot( - renderProps: P, - proceed: (P) -> RenderingAndSnapshot, - session: WorkflowSession - ): RenderingAndSnapshot { - systemTraceState?.resetTraceCountsIfNotTracing() - return trace( - systemTraceLabel = { - "RENDER${++systemTraceState!!.renderPassCount}" + - " ${workflowRuntimeTraceContext.runtimeName}" - }, - ) { - proceed(renderProps).also { - if (systemTraceState != null) { - // Ends the section that's always started right when a [QueuedAction] is applied. - safeTrace.endSection() - } - } - } - } - - override fun onRender( - renderProps: P, - renderState: S, - context: BaseRenderContext, - proceed: (P, S, RenderContextInterceptor?) -> R, - session: WorkflowSession - ): R { - val workflowName = session.traceName - return trace( - systemTraceLabel = { "Render $workflowName" } - ) { - proceed( - renderProps, - renderState, - PapaRenderContextInterceptor( - isRoot = session.isRootWorkflow, - workflowName = workflowName - ) - ) - } - } - - override fun onSnapshotStateWithChildren( - proceed: () -> TreeSnapshot, - session: WorkflowSession - ): TreeSnapshot { - return trace( - systemTraceLabel = { "Snapshot ${workflowRuntimeTraceContext.runtimeName}" } - ) { - proceed() - } - } - - /** END SECTION: [WorkflowInterceptor] override methods. **/ - - /** - * [RenderContextInterceptor] that adds Perfetto tracing through Papa. - */ - private inner class PapaRenderContextInterceptor( - private val isRoot: Boolean, - private val workflowName: String - ) : RenderContextInterceptor { - - override fun onActionSent( - action: WorkflowAction, - proceed: (WorkflowAction) -> Unit - ) { - val actionName = action.toLoggingShortName() - val actionIndexLabel = "ACT${actionIndex++}" - val traceActionName = if (isSystemTraceable) { - "$actionIndexLabel A(${actionName.ifBlank { "" }})/W($workflowName)" - } else { - null - } - val queuedActionDetails = QueuedAction - trace( - systemTraceLabel = { "Send $traceActionName}" } - ) { - proceed( - PerfettoTraceWorkflowAction( - delegateAction = action, - actionName = actionName, - actionType = queuedActionDetails, - actionIndex = actionIndexLabel, - ) - ) - } - } - - override fun onRunningSideEffect( - key: String, - sideEffect: suspend () -> Unit, - proceed: (key: String, sideEffect: suspend () -> Unit) -> Unit - ) { - val label = if (isSystemTraceable) { - "EFF${effectIndex++} Key[$key]" - } else { - null - } - trace( - systemTraceLabel = { "SideEffect $label" } - ) { - proceed(key, sideEffect) - } - } - - override fun onRenderChild( - child: Workflow, - childProps: CP, - key: String, - handler: (CO) -> WorkflowAction, - proceed: ( - child: Workflow, - childProps: CP, - key: String, - handler: (CO) -> WorkflowAction - ) -> CR - ): CR { - // onRenderChild is not traced (the child's own render will be traced), - // but we trace the action handler. - return proceed(child, childProps, key) { output -> - val childOutputString = getWfLogString(output) - trace( - systemTraceLabel = { "Send Output[$childOutputString] to $workflowName" } - ) { - val delegateAction = handler(output) - val actionName = delegateAction.toLoggingShortName() - PerfettoTraceWorkflowAction( - delegateAction = delegateAction, - actionName = actionName, - actionType = CascadeAction( - childOutputString = childOutputString - ), - ) - } - } - } - - override fun onRemember( - key: String, - resultType: KType, - inputs: Array, - calculation: () -> CResult, - proceed: ( - key: String, - resultType: KType, - inputs: Array, - calculation: () -> CResult - ) -> CResult - ): CResult { - return trace( - systemTraceLabel = { "Remember $key" } - ) { - proceed(key, resultType, inputs, calculation) - } - } - - /** - * Class to trace the application of actions. - */ - private inner class PerfettoTraceWorkflowAction( - private val delegateAction: WorkflowAction, - private val actionName: String, - private val actionType: ActionType, - private val actionIndex: String? = null - ) : WorkflowAction() { - // Forward debugging name so we do not include anything about this tracing action. - override val debuggingName: String - get() = delegateAction.debuggingName - - /** - * Trace application of the action. - */ - override fun Updater.apply() { - // See https://github.com/square/workflow-kotlin/issues/391. We have to listen to the 2nd - // action in the cascade to get a useful ref on which Worker's handler was firing. This is - // because Workers use an underlying Workflow and an intermediate action, which is the - // QueuedAction, in their implementation. So yes, we use an implementation detail here to - // detect that. The issue still tracks upstreaming this into the library. - val isWorkerQueuedAction = actionName.contains(Worker.WORKER_OUTPUT_ACTION_NAME) - if (actionType is QueuedAction) { - if (isSystemTraceable) { - safeTrace.beginSection( - "MAYBE_RENDER${++systemTraceState!!.renderPassTriggerCount}:" + - " $actionIndex," + - " Runner:${workflowRuntimeTraceContext.runtimeName}" - ) - } - } - val (_, actionApplied) = trace( - systemTraceLabel = { - val actionNameOrBlank = actionName.ifBlank { "" } - val queuedApplyName = if (isWorkerQueuedAction) { - "$workflowName(key=${actionNameOrBlank.workerKey()})" - } else { - actionNameOrBlank - } - if (actionType is CascadeAction) { - "CascadeApply:$queuedApplyName," + - " Cause:${workflowRuntimeTraceContext.renderIncomingCauses.lastOrNull()}" - } else { - "QueuedApply:$queuedApplyName" - } - }, - ) { - delegateAction.applyTo(props, state).also { (newState, actionApplied) -> - state = newState - actionApplied.output?.let { setOutput(it.value) } - } - } - - if (isRoot || actionApplied.output == null) { - // This action's application is ending a cascade, let's sum up what happened - sumUpActionCascade() - } - } - - private fun sumUpActionCascade() { - if (isSystemTraceable) { - val causeLabel = buildString { - append("CAUSE${renderPassNumber()} ") - if (workflowRuntimeTraceContext.renderIncomingCauses.isEmpty()) { - append("Unknown") - } else { - append(workflowRuntimeTraceContext.renderIncomingCauses.last()) - } - } - infoSection(causeLabel) - } - } - } - } - - /** - * This method is inlined, so that when tracing is disabled there's no additional lambda creation. - */ - private inline fun trace( - crossinline systemTraceLabel: () -> String, - crossinline block: () -> T - ): T { - val systemTrace = isCurrentlySystemTracing - if (systemTrace) { - safeTrace.beginSection(systemTraceLabel()) - } - try { - return block() - } finally { - if (systemTrace) { - safeTrace.endSection() - } - } - } - - companion object { - // Ensure index is unique across all workflow runtimes - private var actionIndex = 0 - private var effectIndex = 0 - } -} +) +class WorkflowPapaTracer( + safeTrace: TraceInterface = WorkflowTrace(isTraceable = false) +) : WorkflowTracer(safeTrace) diff --git a/workflow-tracing-papa/src/test/java/com/squareup/workflow1/tracing/FakeTrace.kt b/workflow-tracing-papa/src/test/java/com/squareup/workflow1/tracing/FakeTrace.kt new file mode 100644 index 0000000000..09a9060ade --- /dev/null +++ b/workflow-tracing-papa/src/test/java/com/squareup/workflow1/tracing/FakeTrace.kt @@ -0,0 +1,51 @@ +package com.squareup.workflow1.tracing + +/** + * Fake implementation of [TraceInterface] for testing purposes. + * Records all trace calls for verification in tests. + */ +class FakeTrace( + override val isTraceable: Boolean = true, + override val isCurrentlyTracing: Boolean = true +) : TraceInterface { + + data class TraceCall( + val type: String, + val label: String? = null, + val name: String? = null, + val cookie: Int? = null + ) + + private val _traceCalls = mutableListOf() + val traceCalls: List get() = _traceCalls.toList() + + fun clearTraceCalls() { + _traceCalls.clear() + } + + override fun beginSection(label: String) { + _traceCalls.add(TraceCall("beginSection", label = label)) + } + + override fun endSection() { + _traceCalls.add(TraceCall("endSection")) + } + + override fun beginAsyncSection( + name: String, + cookie: Int + ) { + _traceCalls.add(TraceCall("beginAsyncSection", name = name, cookie = cookie)) + } + + override fun endAsyncSection( + name: String, + cookie: Int + ) { + _traceCalls.add(TraceCall("endAsyncSection", name = name, cookie = cookie)) + } + + override fun logSection(info: String) { + _traceCalls.add(TraceCall("logSection", label = info)) + } +} diff --git a/workflow-tracing-papa/src/test/java/com/squareup/workflow1/tracing/papa/FakeSafeTrace.kt b/workflow-tracing-papa/src/test/java/com/squareup/workflow1/tracing/papa/FakeSafeTrace.kt index 7ca6ae2c9c..eb7041078f 100644 --- a/workflow-tracing-papa/src/test/java/com/squareup/workflow1/tracing/papa/FakeSafeTrace.kt +++ b/workflow-tracing-papa/src/test/java/com/squareup/workflow1/tracing/papa/FakeSafeTrace.kt @@ -1,53 +1,22 @@ package com.squareup.workflow1.tracing.papa -import com.squareup.workflow1.tracing.SafeTraceInterface - -/** - * Fake implementation of [SafeTraceInterface] for testing purposes. - * Records all trace calls for verification in tests. - */ -class FakeSafeTrace( - override val isTraceable: Boolean = true, - override val isCurrentlyTracing: Boolean = true -) : SafeTraceInterface { - - data class TraceCall( - val type: String, - val label: String? = null, - val name: String? = null, - val cookie: Int? = null +import com.squareup.workflow1.tracing.FakeTrace +import com.squareup.workflow1.tracing.TraceInterface + +@Deprecated( + message = "Renamed to FakeTrace and moved to com.squareup.workflow1.tracing package", + replaceWith = ReplaceWith( + expression = "FakeTrace", + imports = arrayOf("com.squareup.workflow1.tracing.FakeTrace") ) - - private val _traceCalls = mutableListOf() - val traceCalls: List get() = _traceCalls.toList() - - fun clearTraceCalls() { - _traceCalls.clear() - } - - override fun beginSection(label: String) { - _traceCalls.add(TraceCall("beginSection", label = label)) - } - - override fun endSection() { - _traceCalls.add(TraceCall("endSection")) - } - - override fun beginAsyncSection( - name: String, - cookie: Int - ) { - _traceCalls.add(TraceCall("beginAsyncSection", name = name, cookie = cookie)) - } - - override fun endAsyncSection( - name: String, - cookie: Int - ) { - _traceCalls.add(TraceCall("endAsyncSection", name = name, cookie = cookie)) - } - - override fun logSection(info: String) { - _traceCalls.add(TraceCall("logSection", label = info)) - } +) +class FakeSafeTrace( + isTraceable: Boolean = true, + isCurrentlyTracing: Boolean = true +) : TraceInterface by FakeTrace(isTraceable, isCurrentlyTracing) { + private val delegate = FakeTrace(isTraceable, isCurrentlyTracing) + + // These aren't part of TraceInterface + val traceCalls: List get() = delegate.traceCalls + fun clearTraceCalls() = delegate.clearTraceCalls() } diff --git a/workflow-tracing-papa/src/test/java/com/squareup/workflow1/tracing/papa/WorkflowPapaTracerTest.kt b/workflow-tracing-papa/src/test/java/com/squareup/workflow1/tracing/papa/WorkflowPapaTracerTest.kt index 4d5839767b..fb853ba89b 100644 --- a/workflow-tracing-papa/src/test/java/com/squareup/workflow1/tracing/papa/WorkflowPapaTracerTest.kt +++ b/workflow-tracing-papa/src/test/java/com/squareup/workflow1/tracing/papa/WorkflowPapaTracerTest.kt @@ -1,6 +1,9 @@ package com.squareup.workflow1.tracing.papa import com.squareup.workflow1.RenderingAndSnapshot +import com.squareup.workflow1.tracing.FakeTrace +import com.squareup.workflow1.tracing.WorkflowTrace +import com.squareup.workflow1.tracing.WorkflowTracer import com.squareup.workflow1.RuntimeConfig import com.squareup.workflow1.RuntimeConfigOptions import com.squareup.workflow1.Snapshot @@ -23,8 +26,8 @@ import kotlin.test.assertTrue internal class WorkflowPapaTracerTest { - private val fakeTrace = FakeSafeTrace() - private val papaTracer = WorkflowPapaTracer(fakeTrace) + private val fakeTrace = FakeTrace() + private val tracer = WorkflowTracer(fakeTrace) @Test fun `onWorkflowSessionStarted creates async section for root workflow`() { @@ -34,12 +37,12 @@ internal class WorkflowPapaTracerTest { // Attach runtime context to tracer val testContext = TestRuntimeTraceContext() - papaTracer.attachRuntimeContext(testContext) + tracer.attachRuntimeContext(testContext) // Add session info to the context as would normally be done by WorkflowRuntimeMonitor testContext.workflowSessionInfo[rootSession.sessionId] = WorkflowSessionInfo(rootSession) - papaTracer.onWorkflowSessionStarted(testScope, rootSession) + tracer.onWorkflowSessionStarted(testScope, rootSession) val asyncSectionCalls = fakeTrace.traceCalls.filter { it.type == "beginAsyncSection" } assertEquals(1, asyncSectionCalls.size) @@ -54,12 +57,12 @@ internal class WorkflowPapaTracerTest { // Attach runtime context to tracer val testContext = TestRuntimeTraceContext() - papaTracer.attachRuntimeContext(testContext) + tracer.attachRuntimeContext(testContext) // Add session info to the context as would normally be done by WorkflowRuntimeMonitor testContext.workflowSessionInfo[rootSession.sessionId] = WorkflowSessionInfo(rootSession) - val result = papaTracer.onInitialState( + val result = tracer.onInitialState( props = "testProps", snapshot = null, workflowScope = testScope, @@ -81,12 +84,12 @@ internal class WorkflowPapaTracerTest { // Attach runtime context to tracer val testContext = TestRuntimeTraceContext() - papaTracer.attachRuntimeContext(testContext) + tracer.attachRuntimeContext(testContext) val expectedSnapshot = TreeSnapshot.forRootOnly(null) val renderingAndSnapshot = RenderingAndSnapshot("rendering", expectedSnapshot) - val result = papaTracer.onRenderAndSnapshot( + val result = tracer.onRenderAndSnapshot( renderProps = "props", proceed = { renderingAndSnapshot }, session = rootSession @@ -99,22 +102,22 @@ internal class WorkflowPapaTracerTest { @Test fun `tracer can be instantiated`() { - assertNotNull(papaTracer) + assertNotNull(tracer) } @Test - fun `PapaSafeTrace can be configured with isTraceable`() { - val traceableTrace = PapaSafeTrace(isTraceable = true) + fun `WorkflowTrace can be configured with isTraceable`() { + val traceableTrace = WorkflowTrace(isTraceable = true) assertEquals(true, traceableTrace.isTraceable) - val nonTraceableTrace = PapaSafeTrace(isTraceable = false) + val nonTraceableTrace = WorkflowTrace(isTraceable = false) assertEquals(false, nonTraceableTrace.isTraceable) } @Test - fun `WorkflowPapaTracer can be configured with custom SafeTrace`() { - val customTrace = FakeSafeTrace(isTraceable = true) - val tracer = WorkflowPapaTracer(safeTrace = customTrace) + fun `WorkflowTracer can be configured with custom TraceInterface`() { + val customTrace = FakeTrace(isTraceable = true) + val tracer = WorkflowTracer(safeTrace = customTrace) assertNotNull(tracer) } @@ -125,12 +128,12 @@ internal class WorkflowPapaTracerTest { // Attach runtime context to tracer val testContext = TestRuntimeTraceContext() - papaTracer.attachRuntimeContext(testContext) + tracer.attachRuntimeContext(testContext) // Add session info to the context as would normally be done by WorkflowRuntimeMonitor testContext.workflowSessionInfo[mockSession.sessionId] = WorkflowSessionInfo(mockSession) - val result = papaTracer.onPropsChanged( + val result = tracer.onPropsChanged( old = "old", new = "new", state = "current", @@ -148,12 +151,12 @@ internal class WorkflowPapaTracerTest { // Attach runtime context to tracer val testContext = TestRuntimeTraceContext() - papaTracer.attachRuntimeContext(testContext) + tracer.attachRuntimeContext(testContext) val expectedSnapshot = TreeSnapshot.forRootOnly(null) val renderingAndSnapshot = RenderingAndSnapshot("rendering", expectedSnapshot) - val result = papaTracer.onRenderAndSnapshot( + val result = tracer.onRenderAndSnapshot( renderProps = "props", proceed = { renderingAndSnapshot }, session = mockSession @@ -169,11 +172,11 @@ internal class WorkflowPapaTracerTest { // Attach runtime context to tracer val testContext = TestRuntimeTraceContext() - papaTracer.attachRuntimeContext(testContext) + tracer.attachRuntimeContext(testContext) val treeSnapshot = TreeSnapshot.forRootOnly(null) - val result = papaTracer.onSnapshotStateWithChildren( + val result = tracer.onSnapshotStateWithChildren( proceed = { treeSnapshot }, session = mockSession ) @@ -184,15 +187,15 @@ internal class WorkflowPapaTracerTest { @Test fun `onRuntimeUpdateEnhanced handles different runtime updates`() { val testContext = TestRuntimeTraceContext() - papaTracer.attachRuntimeContext(testContext) + tracer.attachRuntimeContext(testContext) val configSnapshot = ConfigSnapshot(TestRuntimeConfig()) // Should not throw for RenderPassSkipped - papaTracer.onRuntimeUpdateEnhanced(RenderPassSkipped, false, configSnapshot) + tracer.onRuntimeUpdateEnhanced(RenderPassSkipped, false, configSnapshot) // Should not throw for RuntimeLoopSettled - papaTracer.onRuntimeUpdateEnhanced(RuntimeSettled, true, configSnapshot) + tracer.onRuntimeUpdateEnhanced(RuntimeSettled, true, configSnapshot) } @Test @@ -202,14 +205,14 @@ internal class WorkflowPapaTracerTest { // Attach runtime context to tracer val testContext = TestRuntimeTraceContext() - papaTracer.attachRuntimeContext(testContext) + tracer.attachRuntimeContext(testContext) // Add session info to the context as would normally be done by WorkflowRuntimeMonitor testContext.workflowSessionInfo[mockSession.sessionId] = WorkflowSessionInfo(mockSession) // Should not throw - papaTracer.onWorkflowSessionStarted(TestScope(), mockSession) - papaTracer.onWorkflowSessionStopped(123L) + tracer.onWorkflowSessionStarted(TestScope(), mockSession) + tracer.onWorkflowSessionStopped(123L) } @Test @@ -219,13 +222,13 @@ internal class WorkflowPapaTracerTest { // Attach runtime context to tracer val testContext = TestRuntimeTraceContext() - papaTracer.attachRuntimeContext(testContext) + tracer.attachRuntimeContext(testContext) // Add session info to the context as would normally be done by WorkflowRuntimeMonitor testContext.workflowSessionInfo[mockSession.sessionId] = WorkflowSessionInfo(mockSession) // Should not throw - papaTracer.onRootPropsChanged(mockSession) + tracer.onRootPropsChanged(mockSession) } private class TestWorkflow : StatefulWorkflow() { diff --git a/workflow-tracing-papa/src/test/java/com/squareup/workflow1/tracing/papa/WorkflowTracingIntegrationTest.kt b/workflow-tracing-papa/src/test/java/com/squareup/workflow1/tracing/papa/WorkflowTracingIntegrationTest.kt index 7663cc7162..cc34e9b61f 100644 --- a/workflow-tracing-papa/src/test/java/com/squareup/workflow1/tracing/papa/WorkflowTracingIntegrationTest.kt +++ b/workflow-tracing-papa/src/test/java/com/squareup/workflow1/tracing/papa/WorkflowTracingIntegrationTest.kt @@ -1,6 +1,8 @@ package com.squareup.workflow1.tracing.papa import com.squareup.workflow1.Snapshot +import com.squareup.workflow1.tracing.FakeTrace +import com.squareup.workflow1.tracing.WorkflowTracer import com.squareup.workflow1.StatefulWorkflow import com.squareup.workflow1.WorkflowAction import com.squareup.workflow1.action @@ -38,11 +40,11 @@ internal class WorkflowTracingIntegrationTest { val runtimeLoopListener = WorkflowRuntimeLoopListener { _, _ -> runtimeLoopMutex.unlock() } - val fakeTrace = FakeSafeTrace() - val papaTracer = WorkflowPapaTracer(fakeTrace) + val fakeTrace = FakeTrace() + val tracer = WorkflowTracer(fakeTrace) val monitor = WorkflowRuntimeMonitor( runtimeName = runtimeName, - workflowRuntimeTracers = listOf(papaTracer), + workflowRuntimeTracers = listOf(tracer), runtimeLoopListener = runtimeLoopListener, ) @@ -99,11 +101,11 @@ internal class WorkflowTracingIntegrationTest { val runtimeLoopListener = WorkflowRuntimeLoopListener { _, _ -> runtimeLoopMutex.unlock() } - val fakeTrace = FakeSafeTrace() - val papaTracer = WorkflowPapaTracer(fakeTrace) + val fakeTrace = FakeTrace() + val tracer = WorkflowTracer(fakeTrace) val monitor = WorkflowRuntimeMonitor( runtimeName = runtimeName, - workflowRuntimeTracers = listOf(papaTracer), + workflowRuntimeTracers = listOf(tracer), runtimeLoopListener = runtimeLoopListener, ) @@ -152,11 +154,11 @@ internal class WorkflowTracingIntegrationTest { val runtimeLoopListener = WorkflowRuntimeLoopListener { _, _ -> runtimeLoopMutex.unlock() } - val fakeTrace = FakeSafeTrace() - val papaTracer = WorkflowPapaTracer(fakeTrace) + val fakeTrace = FakeTrace() + val tracer = WorkflowTracer(fakeTrace) val monitor = WorkflowRuntimeMonitor( runtimeName = runtimeName, - workflowRuntimeTracers = listOf(papaTracer), + workflowRuntimeTracers = listOf(tracer), runtimeLoopListener = runtimeLoopListener, ) @@ -208,11 +210,11 @@ internal class WorkflowTracingIntegrationTest { val runtimeLoopListener = WorkflowRuntimeLoopListener { _, _ -> runtimeLoopMutex.unlock() } - val fakeTrace = FakeSafeTrace() - val papaTracer = WorkflowPapaTracer(fakeTrace) + val fakeTrace = FakeTrace() + val tracer = WorkflowTracer(fakeTrace) val monitor = WorkflowRuntimeMonitor( runtimeName = runtimeName, - workflowRuntimeTracers = listOf(papaTracer), + workflowRuntimeTracers = listOf(tracer), runtimeLoopListener = runtimeLoopListener ) @@ -259,11 +261,11 @@ internal class WorkflowTracingIntegrationTest { val runtimeLoopListener = WorkflowRuntimeLoopListener { _, _ -> runtimeLoopMutex.unlock() } - val fakeTrace = FakeSafeTrace() - val papaTracer = WorkflowPapaTracer(fakeTrace) + val fakeTrace = FakeTrace() + val tracer = WorkflowTracer(fakeTrace) val monitor = WorkflowRuntimeMonitor( runtimeName = runtimeName, - workflowRuntimeTracers = listOf(papaTracer), + workflowRuntimeTracers = listOf(tracer), runtimeLoopListener = runtimeLoopListener ) @@ -307,12 +309,12 @@ internal class WorkflowTracingIntegrationTest { fun `integration test - runtime loop processing is traced`() = runTest { val runtimeLoopMutex = Mutex(locked = true) - val fakeTrace = FakeSafeTrace() - val papaTracer = WorkflowPapaTracer(fakeTrace) + val fakeTrace = FakeTrace() + val tracer = WorkflowTracer(fakeTrace) val runtimeListener = TestWorkflowRuntimeLoopListener(runtimeLoopMutex) val monitor = WorkflowRuntimeMonitor( runtimeName = runtimeName, - workflowRuntimeTracers = listOf(papaTracer), + workflowRuntimeTracers = listOf(tracer), runtimeLoopListener = runtimeListener ) diff --git a/workflow-tracing/api/workflow-tracing.api b/workflow-tracing/api/workflow-tracing.api index 3141eae641..341f7fc4a5 100644 --- a/workflow-tracing/api/workflow-tracing.api +++ b/workflow-tracing/api/workflow-tracing.api @@ -122,7 +122,15 @@ public final class com/squareup/workflow1/tracing/RuntimeUpdates { public final fun readAndClear ()Ljava/util/List; } -public abstract interface class com/squareup/workflow1/tracing/SafeTraceInterface { +public final class com/squareup/workflow1/tracing/SkipLogLine : com/squareup/workflow1/tracing/RuntimeUpdateLogLine { + public static final field INSTANCE Lcom/squareup/workflow1/tracing/SkipLogLine; + public fun equals (Ljava/lang/Object;)Z + public fun hashCode ()I + public fun log (Ljava/lang/StringBuilder;)V + public fun toString ()Ljava/lang/String; +} + +public abstract interface class com/squareup/workflow1/tracing/TraceInterface { public abstract fun beginAsyncSection (Ljava/lang/String;I)V public abstract fun beginSection (Ljava/lang/String;)V public abstract fun endAsyncSection (Ljava/lang/String;I)V @@ -132,14 +140,6 @@ public abstract interface class com/squareup/workflow1/tracing/SafeTraceInterfac public abstract fun logSection (Ljava/lang/String;)V } -public final class com/squareup/workflow1/tracing/SkipLogLine : com/squareup/workflow1/tracing/RuntimeUpdateLogLine { - public static final field INSTANCE Lcom/squareup/workflow1/tracing/SkipLogLine; - public fun equals (Ljava/lang/Object;)Z - public fun hashCode ()I - public fun log (Ljava/lang/StringBuilder;)V - public fun toString ()Ljava/lang/String; -} - public final class com/squareup/workflow1/tracing/UiUpdateLogLine : com/squareup/workflow1/tracing/RuntimeUpdateLogLine { public fun (Ljava/lang/String;)V public final fun getNote ()Ljava/lang/String; diff --git a/workflow-tracing/src/main/java/com/squareup/workflow1/tracing/SafeTraceInterface.kt b/workflow-tracing/src/main/java/com/squareup/workflow1/tracing/SafeTraceInterface.kt index b381da9bc2..c1e5c03459 100644 --- a/workflow-tracing/src/main/java/com/squareup/workflow1/tracing/SafeTraceInterface.kt +++ b/workflow-tracing/src/main/java/com/squareup/workflow1/tracing/SafeTraceInterface.kt @@ -3,7 +3,7 @@ package com.squareup.workflow1.tracing /** * Interface abstracting tracing functionality to allow for testing with fake implementations. */ -public interface SafeTraceInterface { +public interface TraceInterface { public val isTraceable: Boolean public val isCurrentlyTracing: Boolean @@ -21,3 +21,12 @@ public interface SafeTraceInterface { public fun logSection(info: String) } + +@Deprecated( + message = "Renamed to TraceInterface", + replaceWith = ReplaceWith( + expression = "TraceInterface", + imports = arrayOf("com.squareup.workflow1.tracing.TraceInterface") + ) +) +public typealias SafeTraceInterface = TraceInterface