diff --git a/workflow-tracing-papa/api/workflow-tracing-papa.api b/workflow-tracing-papa/api/workflow-tracing-papa.api index 99a19b30a..15a70fcd9 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 000000000..b2fe8f70c --- /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 000000000..55a0cd757 --- /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 4a1f03fde..6a1f13725 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 7071b427a..d4e4097ce 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 000000000..09a9060ad --- /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 7ca6ae2c9..eb7041078 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 4d5839767..fb853ba89 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 7663cc716..cc34e9b61 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 3141eae64..341f7fc4a 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 b381da9bc..c1e5c0345 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