diff --git a/workflow-core/api/workflow-core.api b/workflow-core/api/workflow-core.api index a419493fc..8afbb5c61 100644 --- a/workflow-core/api/workflow-core.api +++ b/workflow-core/api/workflow-core.api @@ -35,6 +35,7 @@ public abstract interface class com/squareup/workflow1/BaseRenderContext { public abstract fun eventHandler (Ljava/lang/String;Lkotlin/jvm/functions/Function8;)Lkotlin/jvm/functions/Function7; public abstract fun eventHandler (Ljava/lang/String;Lkotlin/jvm/functions/Function9;)Lkotlin/jvm/functions/Function8; public abstract fun getActionSink ()Lcom/squareup/workflow1/Sink; + public abstract fun getWorkflowTracer ()Lcom/squareup/workflow1/WorkflowTracer; public abstract fun renderChild (Lcom/squareup/workflow1/Workflow;Ljava/lang/Object;Ljava/lang/String;Lkotlin/jvm/functions/Function1;)Ljava/lang/Object; public abstract fun runningSideEffect (Ljava/lang/String;Lkotlin/jvm/functions/Function2;)V } @@ -167,6 +168,7 @@ public final class com/squareup/workflow1/StatefulWorkflow$RenderContext : com/s public fun eventHandler (Ljava/lang/String;Lkotlin/jvm/functions/Function8;)Lkotlin/jvm/functions/Function7; public fun eventHandler (Ljava/lang/String;Lkotlin/jvm/functions/Function9;)Lkotlin/jvm/functions/Function8; public fun getActionSink ()Lcom/squareup/workflow1/Sink; + public fun getWorkflowTracer ()Lcom/squareup/workflow1/WorkflowTracer; public fun renderChild (Lcom/squareup/workflow1/Workflow;Ljava/lang/Object;Ljava/lang/String;Lkotlin/jvm/functions/Function1;)Ljava/lang/Object; public fun runningSideEffect (Ljava/lang/String;Lkotlin/jvm/functions/Function2;)V } @@ -192,6 +194,7 @@ public final class com/squareup/workflow1/StatelessWorkflow$RenderContext : com/ public fun eventHandler (Ljava/lang/String;Lkotlin/jvm/functions/Function8;)Lkotlin/jvm/functions/Function7; public fun eventHandler (Ljava/lang/String;Lkotlin/jvm/functions/Function9;)Lkotlin/jvm/functions/Function8; public fun getActionSink ()Lcom/squareup/workflow1/Sink; + public fun getWorkflowTracer ()Lcom/squareup/workflow1/WorkflowTracer; public fun renderChild (Lcom/squareup/workflow1/Workflow;Ljava/lang/Object;Ljava/lang/String;Lkotlin/jvm/functions/Function1;)Ljava/lang/Object; public fun runningSideEffect (Ljava/lang/String;Lkotlin/jvm/functions/Function2;)V } @@ -307,6 +310,16 @@ public final class com/squareup/workflow1/WorkflowOutput { public fun toString ()Ljava/lang/String; } +public abstract interface class com/squareup/workflow1/WorkflowTracer { + public abstract fun beginSection (Ljava/lang/String;)V + public abstract fun endSection ()V +} + +public final class com/squareup/workflow1/WorkflowTracerKt { + public static final fun trace (Lcom/squareup/workflow1/WorkflowTracer;Ljava/lang/String;Lkotlin/jvm/functions/Function0;)Ljava/lang/Object; + public static final fun trace (Lcom/squareup/workflow1/WorkflowTracer;Lkotlin/jvm/functions/Function0;Lkotlin/jvm/functions/Function0;)Ljava/lang/Object; +} + public final class com/squareup/workflow1/Workflows { public static final fun RenderContext (Lcom/squareup/workflow1/BaseRenderContext;Lcom/squareup/workflow1/StatefulWorkflow;)Lcom/squareup/workflow1/StatefulWorkflow$RenderContext; public static final fun RenderContext (Lcom/squareup/workflow1/BaseRenderContext;Lcom/squareup/workflow1/StatelessWorkflow;)Lcom/squareup/workflow1/StatelessWorkflow$RenderContext; diff --git a/workflow-core/src/commonMain/kotlin/com/squareup/workflow1/BaseRenderContext.kt b/workflow-core/src/commonMain/kotlin/com/squareup/workflow1/BaseRenderContext.kt index 103256fc3..e26614557 100644 --- a/workflow-core/src/commonMain/kotlin/com/squareup/workflow1/BaseRenderContext.kt +++ b/workflow-core/src/commonMain/kotlin/com/squareup/workflow1/BaseRenderContext.kt @@ -55,6 +55,8 @@ public interface BaseRenderContext { */ public val actionSink: Sink> + public val workflowTracer: WorkflowTracer? + /** * Ensures [child] is running as a child of this workflow, and returns the result of its * `render` method. @@ -437,6 +439,8 @@ internal fun key: String = "", handler: (T) -> WorkflowAction ) { - val workerWorkflow = WorkerWorkflow(workerType, key) + val workerWorkflow = workflowTracer.trace("CreateWorkerWorkflow") { + WorkerWorkflow(workerType, key) + } renderChild(workerWorkflow, props = worker, key = key, handler = handler) } diff --git a/workflow-core/src/commonMain/kotlin/com/squareup/workflow1/WorkerWorkflow.kt b/workflow-core/src/commonMain/kotlin/com/squareup/workflow1/WorkerWorkflow.kt index 5219b5c86..64c6a33e3 100644 --- a/workflow-core/src/commonMain/kotlin/com/squareup/workflow1/WorkerWorkflow.kt +++ b/workflow-core/src/commonMain/kotlin/com/squareup/workflow1/WorkerWorkflow.kt @@ -30,11 +30,16 @@ import kotlin.reflect.KType */ internal class WorkerWorkflow( val workerType: KType, - private val key: String + private val key: String, + workflowTracer: WorkflowTracer? = null ) : StatefulWorkflow, Int, OutputT, Unit>(), ImpostorWorkflow { - override val realIdentifier: WorkflowIdentifier = unsnapshottableIdentifier(workerType) + override val realIdentifier: WorkflowIdentifier = + workflowTracer.trace("ComputeRealIdentifier" ) { + unsnapshottableIdentifier(workerType) + } + override fun describeRealIdentifier(): String = workerType.toString() override fun initialState( diff --git a/workflow-core/src/commonMain/kotlin/com/squareup/workflow1/WorkflowTracer.kt b/workflow-core/src/commonMain/kotlin/com/squareup/workflow1/WorkflowTracer.kt new file mode 100644 index 000000000..139c88373 --- /dev/null +++ b/workflow-core/src/commonMain/kotlin/com/squareup/workflow1/WorkflowTracer.kt @@ -0,0 +1,36 @@ +package com.squareup.workflow1 + +/** + * This is a very simple tracing interface that can be passed into a workflow runtime in order + * to inject span tracing throughout the workflow core and runtime internals. + */ +public interface WorkflowTracer { + public fun beginSection(label: String): Unit + public fun endSection(): Unit +} + +/** + * Convenience function to wrap [block] with a trace span as defined by [WorkflowTracer]. + * Only calls [label] if there is an active [WorkflowTracer] use this for any label other than + * a constant. + */ +public inline fun WorkflowTracer?.trace(label: () -> String, block: () -> T): T { + val optimizedLabel = if (this !== null) { + label() + } else { + "" + } + return trace(optimizedLabel, block) +} + +/** + * Convenience function to wrap [block] with a trace span as defined by [WorkflowTracer]. + */ +public inline fun WorkflowTracer?.trace(label: String, block: () -> T): T { + this?.beginSection(label) + try { + return block() + } finally { + this?.endSection() + } +} diff --git a/workflow-runtime/api/workflow-runtime.api b/workflow-runtime/api/workflow-runtime.api index 42f8d1f00..5591211f0 100644 --- a/workflow-runtime/api/workflow-runtime.api +++ b/workflow-runtime/api/workflow-runtime.api @@ -10,8 +10,8 @@ public final class com/squareup/workflow1/NoopWorkflowInterceptor : com/squareup } public final class com/squareup/workflow1/RenderWorkflowKt { - public static final fun renderWorkflowIn (Lcom/squareup/workflow1/Workflow;Lkotlinx/coroutines/CoroutineScope;Lkotlinx/coroutines/flow/StateFlow;Lcom/squareup/workflow1/TreeSnapshot;Ljava/util/List;Ljava/util/Set;Lkotlin/jvm/functions/Function2;)Lkotlinx/coroutines/flow/StateFlow; - public static synthetic fun renderWorkflowIn$default (Lcom/squareup/workflow1/Workflow;Lkotlinx/coroutines/CoroutineScope;Lkotlinx/coroutines/flow/StateFlow;Lcom/squareup/workflow1/TreeSnapshot;Ljava/util/List;Ljava/util/Set;Lkotlin/jvm/functions/Function2;ILjava/lang/Object;)Lkotlinx/coroutines/flow/StateFlow; + public static final fun renderWorkflowIn (Lcom/squareup/workflow1/Workflow;Lkotlinx/coroutines/CoroutineScope;Lkotlinx/coroutines/flow/StateFlow;Lcom/squareup/workflow1/TreeSnapshot;Ljava/util/List;Ljava/util/Set;Lcom/squareup/workflow1/WorkflowTracer;Lkotlin/jvm/functions/Function2;)Lkotlinx/coroutines/flow/StateFlow; + public static synthetic fun renderWorkflowIn$default (Lcom/squareup/workflow1/Workflow;Lkotlinx/coroutines/CoroutineScope;Lkotlinx/coroutines/flow/StateFlow;Lcom/squareup/workflow1/TreeSnapshot;Ljava/util/List;Ljava/util/Set;Lcom/squareup/workflow1/WorkflowTracer;Lkotlin/jvm/functions/Function2;ILjava/lang/Object;)Lkotlinx/coroutines/flow/StateFlow; } public final class com/squareup/workflow1/RenderingAndSnapshot { @@ -104,6 +104,7 @@ public abstract interface class com/squareup/workflow1/WorkflowInterceptor$Workf public abstract fun getRenderKey ()Ljava/lang/String; public abstract fun getRuntimeConfig ()Ljava/util/Set; public abstract fun getSessionId ()J + public abstract fun getWorkflowTracer ()Lcom/squareup/workflow1/WorkflowTracer; public abstract fun isRootWorkflow ()Z } diff --git a/workflow-runtime/src/commonMain/kotlin/com/squareup/workflow1/RenderWorkflow.kt b/workflow-runtime/src/commonMain/kotlin/com/squareup/workflow1/RenderWorkflow.kt index 78eb60c9a..f49bd1f03 100644 --- a/workflow-runtime/src/commonMain/kotlin/com/squareup/workflow1/RenderWorkflow.kt +++ b/workflow-runtime/src/commonMain/kotlin/com/squareup/workflow1/RenderWorkflow.kt @@ -110,12 +110,21 @@ public fun renderWorkflowIn( initialSnapshot: TreeSnapshot? = null, interceptors: List = emptyList(), runtimeConfig: RuntimeConfig = RuntimeConfigOptions.DEFAULT_CONFIG, + workflowTracer: WorkflowTracer? = null, onOutput: suspend (OutputT) -> Unit ): StateFlow> { val chainedInterceptor = interceptors.chained() val runner = - WorkflowRunner(scope, workflow, props, initialSnapshot, chainedInterceptor, runtimeConfig) + WorkflowRunner( + scope, + workflow, + props, + initialSnapshot, + chainedInterceptor, + runtimeConfig, + workflowTracer + ) // Rendering is synchronous, so we can run the first render pass before launching the runtime // coroutine to calculate the initial rendering. diff --git a/workflow-runtime/src/commonMain/kotlin/com/squareup/workflow1/WorkflowInterceptor.kt b/workflow-runtime/src/commonMain/kotlin/com/squareup/workflow1/WorkflowInterceptor.kt index 2363b7ad9..e86b5901e 100644 --- a/workflow-runtime/src/commonMain/kotlin/com/squareup/workflow1/WorkflowInterceptor.kt +++ b/workflow-runtime/src/commonMain/kotlin/com/squareup/workflow1/WorkflowInterceptor.kt @@ -160,6 +160,9 @@ public interface WorkflowInterceptor { /** The [RuntimeConfig] of the runtime this session is executing in. */ public val runtimeConfig: RuntimeConfig + + /** The optional [WorkflowTracer] of the runtime this session is executing in. */ + public val workflowTracer: WorkflowTracer? } /** @@ -314,6 +317,7 @@ private class InterceptedRenderContext( private val interceptor: RenderContextInterceptor ) : BaseRenderContext, Sink> { override val actionSink: Sink> get() = this + override val workflowTracer: WorkflowTracer? = baseRenderContext.workflowTracer override fun send(value: WorkflowAction) { interceptor.onActionSent(value) { interceptedAction -> diff --git a/workflow-runtime/src/commonMain/kotlin/com/squareup/workflow1/internal/RealRenderContext.kt b/workflow-runtime/src/commonMain/kotlin/com/squareup/workflow1/internal/RealRenderContext.kt index 1e4d60128..9129bb638 100644 --- a/workflow-runtime/src/commonMain/kotlin/com/squareup/workflow1/internal/RealRenderContext.kt +++ b/workflow-runtime/src/commonMain/kotlin/com/squareup/workflow1/internal/RealRenderContext.kt @@ -4,13 +4,15 @@ import com.squareup.workflow1.BaseRenderContext import com.squareup.workflow1.Sink import com.squareup.workflow1.Workflow import com.squareup.workflow1.WorkflowAction +import com.squareup.workflow1.WorkflowTracer import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.channels.SendChannel internal class RealRenderContext( private val renderer: Renderer, private val sideEffectRunner: SideEffectRunner, - private val eventActionsChannel: SendChannel> + private val eventActionsChannel: SendChannel>, + override val workflowTracer: WorkflowTracer? ) : BaseRenderContext, Sink> { interface Renderer { diff --git a/workflow-runtime/src/commonMain/kotlin/com/squareup/workflow1/internal/SubtreeManager.kt b/workflow-runtime/src/commonMain/kotlin/com/squareup/workflow1/internal/SubtreeManager.kt index 8311456bc..f5e6fe45f 100644 --- a/workflow-runtime/src/commonMain/kotlin/com/squareup/workflow1/internal/SubtreeManager.kt +++ b/workflow-runtime/src/commonMain/kotlin/com/squareup/workflow1/internal/SubtreeManager.kt @@ -9,7 +9,9 @@ import com.squareup.workflow1.Workflow import com.squareup.workflow1.WorkflowAction import com.squareup.workflow1.WorkflowInterceptor import com.squareup.workflow1.WorkflowInterceptor.WorkflowSession +import com.squareup.workflow1.WorkflowTracer import com.squareup.workflow1.identifier +import com.squareup.workflow1.trace import kotlinx.coroutines.selects.SelectBuilder import kotlin.coroutines.CoroutineContext @@ -91,6 +93,7 @@ internal class SubtreeManager( childResult: ActionApplied<*> ) -> ActionProcessingResult, private val runtimeConfig: RuntimeConfig, + private val workflowTracer: WorkflowTracer?, private val workflowSession: WorkflowSession? = null, private val interceptor: WorkflowInterceptor = NoopWorkflowInterceptor, private val idCounter: IdCounter? = null @@ -121,17 +124,22 @@ internal class SubtreeManager( handler: (ChildOutputT) -> WorkflowAction ): ChildRenderingT { // Prevent duplicate workflows with the same key. - children.forEachStaging { - require(!(it.matches(child, key))) { - "Expected keys to be unique for ${child.identifier}: key=\"$key\"" + workflowTracer.trace("CheckingUniqueMatches") { + children.forEachStaging { + require(!(it.matches(child, key))) { + "Expected keys to be unique for ${child.identifier}: key=\"$key\"" + } } } // Start tracking this case so we can be ready to render it. - val stagedChild = children.retainOrCreate( - predicate = { it.matches(child, key) }, - create = { createChildNode(child, props, key, handler) } - ) + val stagedChild = + workflowTracer.trace("RetainingChildren") { + children.retainOrCreate( + predicate = { it.matches(child, key) }, + create = { createChildNode(child, props, key, handler) } + ) + } stagedChild.setHandler(handler) return stagedChild.render(child.asStatefulWorkflow(), props) } @@ -188,6 +196,7 @@ internal class SubtreeManager( snapshot = childTreeSnapshots, baseContext = contextForChildren, runtimeConfig = runtimeConfig, + workflowTracer = workflowTracer, emitAppliedActionToParent = ::acceptChildActionResult, parent = workflowSession, interceptor = interceptor, diff --git a/workflow-runtime/src/commonMain/kotlin/com/squareup/workflow1/internal/WorkflowNode.kt b/workflow-runtime/src/commonMain/kotlin/com/squareup/workflow1/internal/WorkflowNode.kt index cd6a40599..96355b3ff 100644 --- a/workflow-runtime/src/commonMain/kotlin/com/squareup/workflow1/internal/WorkflowNode.kt +++ b/workflow-runtime/src/commonMain/kotlin/com/squareup/workflow1/internal/WorkflowNode.kt @@ -14,9 +14,11 @@ import com.squareup.workflow1.WorkflowExperimentalApi import com.squareup.workflow1.WorkflowIdentifier import com.squareup.workflow1.WorkflowInterceptor import com.squareup.workflow1.WorkflowInterceptor.WorkflowSession +import com.squareup.workflow1.WorkflowTracer import com.squareup.workflow1.applyTo import com.squareup.workflow1.intercept import com.squareup.workflow1.internal.RealRenderContext.SideEffectRunner +import com.squareup.workflow1.trace import kotlinx.coroutines.CancellationException import kotlinx.coroutines.CoroutineName import kotlinx.coroutines.CoroutineScope @@ -51,6 +53,7 @@ internal class WorkflowNode( baseContext: CoroutineContext, // Providing default value so we don't need to specify in test. override val runtimeConfig: RuntimeConfig = RuntimeConfigOptions.DEFAULT_CONFIG, + override val workflowTracer: WorkflowTracer? = null, private val emitAppliedActionToParent: (ActionApplied) -> ActionProcessingResult = { it }, override val parent: WorkflowSession? = null, @@ -74,6 +77,7 @@ internal class WorkflowNode( contextForChildren = coroutineContext, emitActionToParent = ::applyAction, runtimeConfig = runtimeConfig, + workflowTracer = workflowTracer, workflowSession = this, interceptor = interceptor, idCounter = idCounter @@ -87,7 +91,8 @@ internal class WorkflowNode( private val baseRenderContext = RealRenderContext( renderer = subtreeManager, sideEffectRunner = this, - eventActionsChannel = eventActionsChannel + eventActionsChannel = eventActionsChannel, + workflowTracer = workflowTracer, ) private val context = RenderContext(baseRenderContext, workflow) @@ -212,12 +217,14 @@ internal class WorkflowNode( .render(props, state, context) baseRenderContext.freeze() - // Tear down workflows and workers that are obsolete. - subtreeManager.commitRenderedChildren() - // Side effect jobs are launched lazily, since they can send actions to the sink, and can only - // be started after context is frozen. - sideEffects.forEachStaging { it.job.start() } - sideEffects.commitStaging { it.job.cancel() } + workflowTracer.trace("UpdateRuntimeTree") { + // Tear down workflows and workers that are obsolete. + subtreeManager.commitRenderedChildren() + // Side effect jobs are launched lazily, since they can send actions to the sink, and can only + // be started after context is frozen. + sideEffects.forEachStaging { it.job.start() } + sideEffects.commitStaging { it.job.cancel() } + } return rendering } @@ -261,8 +268,10 @@ internal class WorkflowNode( key: String, sideEffect: suspend CoroutineScope.() -> Unit ): SideEffectNode { - val scope = this + CoroutineName("sideEffect[$key] for $id") - val job = scope.launch(start = LAZY, block = sideEffect) - return SideEffectNode(key, job) + return workflowTracer.trace("CreateSideEffectNode") { + val scope = this + CoroutineName("sideEffect[$key] for $id") + val job = scope.launch(start = LAZY, block = sideEffect) + SideEffectNode(key, job) + } } } diff --git a/workflow-runtime/src/commonMain/kotlin/com/squareup/workflow1/internal/WorkflowRunner.kt b/workflow-runtime/src/commonMain/kotlin/com/squareup/workflow1/internal/WorkflowRunner.kt index e19f190d8..9eb66bb1b 100644 --- a/workflow-runtime/src/commonMain/kotlin/com/squareup/workflow1/internal/WorkflowRunner.kt +++ b/workflow-runtime/src/commonMain/kotlin/com/squareup/workflow1/internal/WorkflowRunner.kt @@ -10,6 +10,7 @@ import com.squareup.workflow1.TreeSnapshot import com.squareup.workflow1.Workflow import com.squareup.workflow1.WorkflowExperimentalRuntime import com.squareup.workflow1.WorkflowInterceptor +import com.squareup.workflow1.WorkflowTracer import kotlinx.coroutines.CancellationException import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.DelicateCoroutinesApi @@ -28,7 +29,8 @@ internal class WorkflowRunner( props: StateFlow, snapshot: TreeSnapshot?, private val interceptor: WorkflowInterceptor, - private val runtimeConfig: RuntimeConfig + private val runtimeConfig: RuntimeConfig, + private val workflowTracer: WorkflowTracer? ) { private val workflow = protoWorkflow.asStatefulWorkflow() private val idCounter = IdCounter() @@ -55,6 +57,7 @@ internal class WorkflowRunner( snapshot = snapshot, baseContext = scope.coroutineContext, runtimeConfig = runtimeConfig, + workflowTracer = workflowTracer, interceptor = interceptor, idCounter = idCounter ) diff --git a/workflow-runtime/src/commonTest/kotlin/com/squareup/workflow1/RenderWorkflowInTest.kt b/workflow-runtime/src/commonTest/kotlin/com/squareup/workflow1/RenderWorkflowInTest.kt index 4f91fef74..73fdf7116 100644 --- a/workflow-runtime/src/commonTest/kotlin/com/squareup/workflow1/RenderWorkflowInTest.kt +++ b/workflow-runtime/src/commonTest/kotlin/com/squareup/workflow1/RenderWorkflowInTest.kt @@ -69,7 +69,8 @@ class RenderWorkflowInTest { workflow = workflow, scope = pausedTestScope, props = props, - runtimeConfig = runtimeConfig + runtimeConfig = runtimeConfig, + workflowTracer = null, ) {} assertEquals("props: foo", renderings.value.rendering) } @@ -88,7 +89,8 @@ class RenderWorkflowInTest { workflow = workflow, scope = pausedTestScope, props = props, - runtimeConfig = runtimeConfig + runtimeConfig = runtimeConfig, + workflowTracer = null, ) {} assertEquals("props: foo", renderings.value.rendering) } @@ -112,7 +114,8 @@ class RenderWorkflowInTest { workflow, testScope, MutableStateFlow(Unit), - runtimeConfig = runtimeConfig + runtimeConfig = runtimeConfig, + workflowTracer = null, ) {} testScope.advanceUntilIdle() @@ -141,7 +144,8 @@ class RenderWorkflowInTest { workflow = workflow, scope = testScope, props = MutableStateFlow(Unit), - runtimeConfig = runtimeConfig + runtimeConfig = runtimeConfig, + workflowTracer = null, ) {} testScope.advanceUntilIdle() @@ -160,7 +164,8 @@ class RenderWorkflowInTest { workflow = workflow, scope = testScope, props = props, - runtimeConfig = runtimeConfig + runtimeConfig = runtimeConfig, + workflowTracer = null, ) {} assertEquals("props: foo", renderings.value.rendering) @@ -239,7 +244,8 @@ class RenderWorkflowInTest { workflow = workflow, scope = testScope, props = props, - runtimeConfig = runtimeConfig1 + runtimeConfig = runtimeConfig1, + workflowTracer = null, ) {} // Interact with the workflow to change the state. @@ -266,6 +272,7 @@ class RenderWorkflowInTest { scope = restoreScope, props = props, initialSnapshot = snapshot, + workflowTracer = null, runtimeConfig = runtimeConfig2 ) {} runtimeMatrixTestRunner.assertEquals( @@ -302,7 +309,8 @@ class RenderWorkflowInTest { workflow = workflow, scope = testScope, props = props, - runtimeConfig = runtimeConfig + runtimeConfig = runtimeConfig, + workflowTracer = null, ) {} val emitted = mutableListOf>() @@ -360,7 +368,8 @@ class RenderWorkflowInTest { workflow = workflow, scope = testScope, props = MutableStateFlow(Unit), - runtimeConfig = runtimeConfig + runtimeConfig = runtimeConfig, + workflowTracer = null, ) { receivedOutputs += it } @@ -402,7 +411,8 @@ class RenderWorkflowInTest { workflow = workflow, scope = testScope, props = MutableStateFlow(Unit), - runtimeConfig = runtimeConfig + runtimeConfig = runtimeConfig, + workflowTracer = null, ) { it: String -> receivedOutputs += it assertTrue(emittedRenderings.contains(it)) @@ -436,7 +446,8 @@ class RenderWorkflowInTest { workflow = workflow, scope = testScope, props = props, - runtimeConfig = runtimeConfig + runtimeConfig = runtimeConfig, + workflowTracer = null, ) { onOutputCalls++ } assertEquals(0, renderings.value.rendering) assertEquals(0, onOutputCalls) @@ -473,7 +484,8 @@ class RenderWorkflowInTest { workflow = workflow, scope = testScope, props = MutableStateFlow(Unit), - runtimeConfig = runtimeConfig + runtimeConfig = runtimeConfig, + workflowTracer = null, ) {} } assertTrue(testScope.isActive) @@ -499,7 +511,8 @@ class RenderWorkflowInTest { workflow = workflow, scope = testScope, props = MutableStateFlow(Unit), - runtimeConfig = runtimeConfig + runtimeConfig = runtimeConfig, + workflowTracer = null, ) {} } assertFalse(sideEffectWasRan) @@ -532,7 +545,8 @@ class RenderWorkflowInTest { workflow = workflow, scope = testScope, props = MutableStateFlow(Unit), - runtimeConfig = runtimeConfig + runtimeConfig = runtimeConfig, + workflowTracer = null, ) {} } assertTrue(sideEffectWasRan) @@ -565,7 +579,8 @@ class RenderWorkflowInTest { workflow = workflow, scope = testScope, props = MutableStateFlow(Unit), - runtimeConfig = runtimeConfig + runtimeConfig = runtimeConfig, + workflowTracer = null, ) {} } assertFalse(sideEffectWasRan) @@ -592,7 +607,8 @@ class RenderWorkflowInTest { workflow = workflow, scope = testScope, props = MutableStateFlow(Unit), - runtimeConfig = runtimeConfig + runtimeConfig = runtimeConfig, + workflowTracer = null, ) {} assertTrue(testScope.isActive) @@ -622,7 +638,8 @@ class RenderWorkflowInTest { workflow = workflow, scope = testScope, props = MutableStateFlow(Unit), - runtimeConfig = runtimeConfig + runtimeConfig = runtimeConfig, + workflowTracer = null, ) {} assertTrue(testScope.isActive) @@ -651,7 +668,8 @@ class RenderWorkflowInTest { workflow = workflow, scope = testScope, props = MutableStateFlow(Unit), - runtimeConfig = runtimeConfig + runtimeConfig = runtimeConfig, + workflowTracer = null, ) {} assertNull(cancellationException) assertTrue(testScope.isActive) @@ -681,7 +699,8 @@ class RenderWorkflowInTest { workflow = workflow, scope = testScope, props = MutableStateFlow(Unit), - runtimeConfig = runtimeConfig + runtimeConfig = runtimeConfig, + workflowTracer = null, ) {} assertTrue(testScope.isActive) assertTrue(renderCount == 1) @@ -714,7 +733,8 @@ class RenderWorkflowInTest { workflow = workflow, scope = testScope, props = MutableStateFlow(Unit), - runtimeConfig = runtimeConfig + runtimeConfig = runtimeConfig, + workflowTracer = null, ) {} assertNull(cancellationException) assertTrue(testScope.isActive) @@ -735,7 +755,8 @@ class RenderWorkflowInTest { workflow = workflow, scope = testScope, props = MutableStateFlow(Unit), - runtimeConfig = runtimeConfig + runtimeConfig = runtimeConfig, + workflowTracer = null, ) {} // Collect in separate scope so we actually test that the parent scope is failed when it's @@ -768,7 +789,8 @@ class RenderWorkflowInTest { workflow = workflow, scope = pausedTestScope, props = MutableStateFlow(Unit), - runtimeConfig = runtimeConfig + runtimeConfig = runtimeConfig, + workflowTracer = null, ) {} pausedTestScope.launch { @@ -796,7 +818,8 @@ class RenderWorkflowInTest { workflow = workflow, scope = pausedTestScope, props = MutableStateFlow(Unit), - runtimeConfig = runtimeConfig + runtimeConfig = runtimeConfig, + workflowTracer = null, ) { throw ExpectedException() } @@ -837,6 +860,7 @@ class RenderWorkflowInTest { scope = pausedTestScope, props = MutableStateFlow(Unit), runtimeConfig = runtimeConfig, + workflowTracer = null, onOutput = { events += "output($it)" } ) .onEach { events += "rendering(${it.rendering})" } @@ -883,7 +907,8 @@ class RenderWorkflowInTest { workflow = workflow, scope = testScope + exceptionHandler, props = props, - runtimeConfig = runtimeConfig + runtimeConfig = runtimeConfig, + workflowTracer = null, ) {} .value .snapshot @@ -921,7 +946,8 @@ class RenderWorkflowInTest { workflow = workflow, scope = testScope + exceptionHandler, props = props, - runtimeConfig = runtimeConfig + runtimeConfig = runtimeConfig, + workflowTracer = null, ) {} val renderings = ras.map { it.rendering } .produceIn(testScope) @@ -969,7 +995,8 @@ class RenderWorkflowInTest { workflow = workflow, scope = testScope + exceptionHandler, props = props, - runtimeConfig = runtimeConfig + runtimeConfig = runtimeConfig, + workflowTracer = null, ) {} val renderings = ras.map { it.rendering } .produceIn(testScope) @@ -1016,7 +1043,8 @@ class RenderWorkflowInTest { workflow = workflow, scope = testScope, props = props, - runtimeConfig = runtimeConfig + runtimeConfig = runtimeConfig, + workflowTracer = null, ) {} val emitted = mutableListOf>() @@ -1057,7 +1085,8 @@ class RenderWorkflowInTest { workflow = workflow, scope = testScope, props = props, - runtimeConfig = runtimeConfig + runtimeConfig = runtimeConfig, + workflowTracer = null, ) {} val emitted = mutableListOf>() diff --git a/workflow-runtime/src/commonTest/kotlin/com/squareup/workflow1/SimpleLoggingWorkflowInterceptorTest.kt b/workflow-runtime/src/commonTest/kotlin/com/squareup/workflow1/SimpleLoggingWorkflowInterceptorTest.kt index acebb5f8f..2af982fd3 100644 --- a/workflow-runtime/src/commonTest/kotlin/com/squareup/workflow1/SimpleLoggingWorkflowInterceptorTest.kt +++ b/workflow-runtime/src/commonTest/kotlin/com/squareup/workflow1/SimpleLoggingWorkflowInterceptorTest.kt @@ -88,11 +88,13 @@ internal class SimpleLoggingWorkflowInterceptorTest { override val sessionId: Long get() = 42 override val parent: WorkflowSession? get() = null override val runtimeConfig: RuntimeConfig = RuntimeConfigOptions.DEFAULT_CONFIG + override val workflowTracer: WorkflowTracer? = null } private object FakeRenderContext : BaseRenderContext { override val actionSink: Sink> get() = fail() + override val workflowTracer: WorkflowTracer? = null override fun renderChild( child: Workflow, diff --git a/workflow-runtime/src/commonTest/kotlin/com/squareup/workflow1/WorkflowInterceptorTest.kt b/workflow-runtime/src/commonTest/kotlin/com/squareup/workflow1/WorkflowInterceptorTest.kt index 4e7c26a21..5a233c00a 100644 --- a/workflow-runtime/src/commonTest/kotlin/com/squareup/workflow1/WorkflowInterceptorTest.kt +++ b/workflow-runtime/src/commonTest/kotlin/com/squareup/workflow1/WorkflowInterceptorTest.kt @@ -62,6 +62,7 @@ internal class WorkflowInterceptorTest { val intercepted = recorder.intercept(TestWorkflow, TestWorkflow.session) val fakeContext = object : BaseRenderContext { override val actionSink: Sink> get() = fail() + override val workflowTracer: WorkflowTracer? = null override fun renderChild( child: Workflow, @@ -103,6 +104,7 @@ internal class WorkflowInterceptorTest { val fakeContext = object : BaseRenderContext { override val actionSink: Sink> = Sink { value -> actions += value } + override val workflowTracer: WorkflowTracer? = null override fun renderChild( child: Workflow, @@ -136,6 +138,7 @@ internal class WorkflowInterceptorTest { val intercepted = recorder.intercept(workflow, workflow.session) val fakeContext = object : BaseRenderContext { override val actionSink: Sink> get() = fail() + override val workflowTracer: WorkflowTracer? = null override fun renderChild( child: Workflow, @@ -197,6 +200,7 @@ internal class WorkflowInterceptorTest { val intercepted = recorder.intercept(workflow, workflow.session) val fakeContext = object : BaseRenderContext { override val actionSink: Sink> get() = fail() + override val workflowTracer: WorkflowTracer? = null override fun renderChild( child: Workflow, @@ -224,6 +228,7 @@ internal class WorkflowInterceptorTest { override val sessionId: Long = 0 override val parent: WorkflowSession? = null override val runtimeConfig: RuntimeConfig = RuntimeConfigOptions.DEFAULT_CONFIG + override val workflowTracer: WorkflowTracer? = null } private object TestWorkflow : StatefulWorkflow() { diff --git a/workflow-runtime/src/commonTest/kotlin/com/squareup/workflow1/internal/ChainedWorkflowInterceptorTest.kt b/workflow-runtime/src/commonTest/kotlin/com/squareup/workflow1/internal/ChainedWorkflowInterceptorTest.kt index 6a35d557c..fb6d3fe5d 100644 --- a/workflow-runtime/src/commonTest/kotlin/com/squareup/workflow1/internal/ChainedWorkflowInterceptorTest.kt +++ b/workflow-runtime/src/commonTest/kotlin/com/squareup/workflow1/internal/ChainedWorkflowInterceptorTest.kt @@ -14,6 +14,7 @@ import com.squareup.workflow1.WorkflowIdentifier import com.squareup.workflow1.WorkflowInterceptor import com.squareup.workflow1.WorkflowInterceptor.RenderContextInterceptor import com.squareup.workflow1.WorkflowInterceptor.WorkflowSession +import com.squareup.workflow1.WorkflowTracer import com.squareup.workflow1.identifier import com.squareup.workflow1.parse import com.squareup.workflow1.rendering @@ -322,6 +323,7 @@ internal class ChainedWorkflowInterceptorTest { private object FakeRenderContext : BaseRenderContext { override val actionSink: Sink> get() = fail() + override val workflowTracer: WorkflowTracer? = null override fun renderChild( child: Workflow, @@ -348,5 +350,6 @@ internal class ChainedWorkflowInterceptorTest { override val sessionId: Long = 0 override val parent: WorkflowSession? = null override val runtimeConfig: RuntimeConfig = RuntimeConfigOptions.DEFAULT_CONFIG + override val workflowTracer: WorkflowTracer? = null } } diff --git a/workflow-runtime/src/commonTest/kotlin/com/squareup/workflow1/internal/RealRenderContextTest.kt b/workflow-runtime/src/commonTest/kotlin/com/squareup/workflow1/internal/RealRenderContextTest.kt index 4c58b9be2..35cdc97d4 100644 --- a/workflow-runtime/src/commonTest/kotlin/com/squareup/workflow1/internal/RealRenderContextTest.kt +++ b/workflow-runtime/src/commonTest/kotlin/com/squareup/workflow1/internal/RealRenderContextTest.kt @@ -393,11 +393,21 @@ internal class RealRenderContextTest { private fun createdPoisonedContext(): RealRenderContext { val workerRunner = PoisonRunner() - return RealRenderContext(PoisonRenderer(), workerRunner, eventActionsChannel) + return RealRenderContext( + PoisonRenderer(), + workerRunner, + eventActionsChannel, + workflowTracer = null + ) } private fun createTestContext(): RealRenderContext { val workerRunner = TestRunner() - return RealRenderContext(TestRenderer(), workerRunner, eventActionsChannel) + return RealRenderContext( + TestRenderer(), + workerRunner, + eventActionsChannel, + workflowTracer = null + ) } } diff --git a/workflow-runtime/src/commonTest/kotlin/com/squareup/workflow1/internal/SubtreeManagerTest.kt b/workflow-runtime/src/commonTest/kotlin/com/squareup/workflow1/internal/SubtreeManagerTest.kt index 9c37ef111..3653fb2d3 100644 --- a/workflow-runtime/src/commonTest/kotlin/com/squareup/workflow1/internal/SubtreeManagerTest.kt +++ b/workflow-runtime/src/commonTest/kotlin/com/squareup/workflow1/internal/SubtreeManagerTest.kt @@ -314,6 +314,7 @@ internal class SubtreeManagerTest { runtimeConfig = RuntimeConfigOptions.DEFAULT_CONFIG, emitActionToParent = { action, childResult -> ActionApplied(WorkflowOutput(action), childResult.stateChanged) - } + }, + workflowTracer = null ) } diff --git a/workflow-runtime/src/commonTest/kotlin/com/squareup/workflow1/internal/WorkflowNodeTest.kt b/workflow-runtime/src/commonTest/kotlin/com/squareup/workflow1/internal/WorkflowNodeTest.kt index 8ff1c4229..82f4bdc00 100644 --- a/workflow-runtime/src/commonTest/kotlin/com/squareup/workflow1/internal/WorkflowNodeTest.kt +++ b/workflow-runtime/src/commonTest/kotlin/com/squareup/workflow1/internal/WorkflowNodeTest.kt @@ -19,6 +19,7 @@ import com.squareup.workflow1.WorkflowInterceptor import com.squareup.workflow1.WorkflowInterceptor.RenderContextInterceptor import com.squareup.workflow1.WorkflowInterceptor.WorkflowSession import com.squareup.workflow1.WorkflowOutput +import com.squareup.workflow1.WorkflowTracer import com.squareup.workflow1.action import com.squareup.workflow1.contraMap import com.squareup.workflow1.identifier @@ -1339,5 +1340,6 @@ internal class WorkflowNodeTest { override val renderKey: String = "" override val parent: WorkflowSession? = null override val runtimeConfig: RuntimeConfig = RuntimeConfigOptions.DEFAULT_CONFIG + override val workflowTracer: WorkflowTracer? = null } } diff --git a/workflow-runtime/src/commonTest/kotlin/com/squareup/workflow1/internal/WorkflowRunnerTest.kt b/workflow-runtime/src/commonTest/kotlin/com/squareup/workflow1/internal/WorkflowRunnerTest.kt index df5e2d469..7efab5a76 100644 --- a/workflow-runtime/src/commonTest/kotlin/com/squareup/workflow1/internal/WorkflowRunnerTest.kt +++ b/workflow-runtime/src/commonTest/kotlin/com/squareup/workflow1/internal/WorkflowRunnerTest.kt @@ -344,6 +344,7 @@ internal class WorkflowRunnerTest { props, snapshot = null, interceptor = NoopWorkflowInterceptor, - runtimeConfig + runtimeConfig, + workflowTracer = null ) } diff --git a/workflow-testing/src/main/java/com/squareup/workflow1/testing/RealRenderTester.kt b/workflow-testing/src/main/java/com/squareup/workflow1/testing/RealRenderTester.kt index 067d1dca2..39f8b4a7d 100644 --- a/workflow-testing/src/main/java/com/squareup/workflow1/testing/RealRenderTester.kt +++ b/workflow-testing/src/main/java/com/squareup/workflow1/testing/RealRenderTester.kt @@ -13,6 +13,7 @@ import com.squareup.workflow1.WorkflowIdentifierType import com.squareup.workflow1.WorkflowIdentifierType.Snapshottable import com.squareup.workflow1.WorkflowIdentifierType.Unsnapshottable import com.squareup.workflow1.WorkflowOutput +import com.squareup.workflow1.WorkflowTracer import com.squareup.workflow1.applyTo import com.squareup.workflow1.identifier import com.squareup.workflow1.testing.RealRenderTester.Expectation @@ -107,6 +108,7 @@ internal class RealRenderTester( state to actionApplied.output } override val actionSink: Sink> get() = this + override val workflowTracer: WorkflowTracer? = null override fun expectWorkflow( description: String, diff --git a/workflow-ui/core-android/api/core-android.api b/workflow-ui/core-android/api/core-android.api index d0218530c..6e43b8975 100644 --- a/workflow-ui/core-android/api/core-android.api +++ b/workflow-ui/core-android/api/core-android.api @@ -1,11 +1,11 @@ public final class com/squareup/workflow1/ui/AndroidRenderWorkflowKt { public static final fun removeWorkflowState (Landroidx/lifecycle/SavedStateHandle;)V - public static final fun renderWorkflowIn (Lcom/squareup/workflow1/Workflow;Lkotlinx/coroutines/CoroutineScope;Landroidx/lifecycle/SavedStateHandle;Ljava/util/List;Ljava/util/Set;Lkotlin/jvm/functions/Function2;)Lkotlinx/coroutines/flow/StateFlow; - public static final fun renderWorkflowIn (Lcom/squareup/workflow1/Workflow;Lkotlinx/coroutines/CoroutineScope;Ljava/lang/Object;Landroidx/lifecycle/SavedStateHandle;Ljava/util/List;Ljava/util/Set;Lkotlin/jvm/functions/Function2;)Lkotlinx/coroutines/flow/StateFlow; - public static final fun renderWorkflowIn (Lcom/squareup/workflow1/Workflow;Lkotlinx/coroutines/CoroutineScope;Lkotlinx/coroutines/flow/StateFlow;Landroidx/lifecycle/SavedStateHandle;Ljava/util/List;Ljava/util/Set;Lkotlin/jvm/functions/Function2;)Lkotlinx/coroutines/flow/StateFlow; - public static synthetic fun renderWorkflowIn$default (Lcom/squareup/workflow1/Workflow;Lkotlinx/coroutines/CoroutineScope;Landroidx/lifecycle/SavedStateHandle;Ljava/util/List;Ljava/util/Set;Lkotlin/jvm/functions/Function2;ILjava/lang/Object;)Lkotlinx/coroutines/flow/StateFlow; - public static synthetic fun renderWorkflowIn$default (Lcom/squareup/workflow1/Workflow;Lkotlinx/coroutines/CoroutineScope;Ljava/lang/Object;Landroidx/lifecycle/SavedStateHandle;Ljava/util/List;Ljava/util/Set;Lkotlin/jvm/functions/Function2;ILjava/lang/Object;)Lkotlinx/coroutines/flow/StateFlow; - public static synthetic fun renderWorkflowIn$default (Lcom/squareup/workflow1/Workflow;Lkotlinx/coroutines/CoroutineScope;Lkotlinx/coroutines/flow/StateFlow;Landroidx/lifecycle/SavedStateHandle;Ljava/util/List;Ljava/util/Set;Lkotlin/jvm/functions/Function2;ILjava/lang/Object;)Lkotlinx/coroutines/flow/StateFlow; + public static final fun renderWorkflowIn (Lcom/squareup/workflow1/Workflow;Lkotlinx/coroutines/CoroutineScope;Landroidx/lifecycle/SavedStateHandle;Ljava/util/List;Ljava/util/Set;Lcom/squareup/workflow1/WorkflowTracer;Lkotlin/jvm/functions/Function2;)Lkotlinx/coroutines/flow/StateFlow; + public static final fun renderWorkflowIn (Lcom/squareup/workflow1/Workflow;Lkotlinx/coroutines/CoroutineScope;Ljava/lang/Object;Landroidx/lifecycle/SavedStateHandle;Ljava/util/List;Ljava/util/Set;Lcom/squareup/workflow1/WorkflowTracer;Lkotlin/jvm/functions/Function2;)Lkotlinx/coroutines/flow/StateFlow; + public static final fun renderWorkflowIn (Lcom/squareup/workflow1/Workflow;Lkotlinx/coroutines/CoroutineScope;Lkotlinx/coroutines/flow/StateFlow;Landroidx/lifecycle/SavedStateHandle;Ljava/util/List;Ljava/util/Set;Lcom/squareup/workflow1/WorkflowTracer;Lkotlin/jvm/functions/Function2;)Lkotlinx/coroutines/flow/StateFlow; + public static synthetic fun renderWorkflowIn$default (Lcom/squareup/workflow1/Workflow;Lkotlinx/coroutines/CoroutineScope;Landroidx/lifecycle/SavedStateHandle;Ljava/util/List;Ljava/util/Set;Lcom/squareup/workflow1/WorkflowTracer;Lkotlin/jvm/functions/Function2;ILjava/lang/Object;)Lkotlinx/coroutines/flow/StateFlow; + public static synthetic fun renderWorkflowIn$default (Lcom/squareup/workflow1/Workflow;Lkotlinx/coroutines/CoroutineScope;Ljava/lang/Object;Landroidx/lifecycle/SavedStateHandle;Ljava/util/List;Ljava/util/Set;Lcom/squareup/workflow1/WorkflowTracer;Lkotlin/jvm/functions/Function2;ILjava/lang/Object;)Lkotlinx/coroutines/flow/StateFlow; + public static synthetic fun renderWorkflowIn$default (Lcom/squareup/workflow1/Workflow;Lkotlinx/coroutines/CoroutineScope;Lkotlinx/coroutines/flow/StateFlow;Landroidx/lifecycle/SavedStateHandle;Ljava/util/List;Ljava/util/Set;Lcom/squareup/workflow1/WorkflowTracer;Lkotlin/jvm/functions/Function2;ILjava/lang/Object;)Lkotlinx/coroutines/flow/StateFlow; } public abstract interface class com/squareup/workflow1/ui/AndroidScreen : com/squareup/workflow1/ui/Screen { diff --git a/workflow-ui/core-android/src/main/java/com/squareup/workflow1/ui/AndroidRenderWorkflow.kt b/workflow-ui/core-android/src/main/java/com/squareup/workflow1/ui/AndroidRenderWorkflow.kt index 2ba4defea..1d0c19417 100644 --- a/workflow-ui/core-android/src/main/java/com/squareup/workflow1/ui/AndroidRenderWorkflow.kt +++ b/workflow-ui/core-android/src/main/java/com/squareup/workflow1/ui/AndroidRenderWorkflow.kt @@ -6,6 +6,7 @@ import com.squareup.workflow1.RuntimeConfig import com.squareup.workflow1.RuntimeConfigOptions import com.squareup.workflow1.Workflow import com.squareup.workflow1.WorkflowInterceptor +import com.squareup.workflow1.WorkflowTracer import com.squareup.workflow1.renderWorkflowIn import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.MutableStateFlow @@ -86,6 +87,7 @@ public fun renderWorkflowIn( savedStateHandle: SavedStateHandle? = null, interceptors: List = emptyList(), runtimeConfig: RuntimeConfig = RuntimeConfigOptions.DEFAULT_CONFIG, + workflowTracer: WorkflowTracer? = null, onOutput: suspend (OutputT) -> Unit = {} ): StateFlow { return renderWorkflowIn( @@ -95,6 +97,7 @@ public fun renderWorkflowIn( savedStateHandle = savedStateHandle, interceptors = interceptors, runtimeConfig = runtimeConfig, + workflowTracer = workflowTracer, onOutput = onOutput ) } @@ -172,6 +175,7 @@ public fun renderWorkflowIn( savedStateHandle: SavedStateHandle? = null, interceptors: List = emptyList(), runtimeConfig: RuntimeConfig = RuntimeConfigOptions.DEFAULT_CONFIG, + workflowTracer: WorkflowTracer? = null, onOutput: suspend (OutputT) -> Unit = {} ): StateFlow = renderWorkflowIn( workflow, @@ -180,6 +184,7 @@ public fun renderWorkflowIn( savedStateHandle, interceptors, runtimeConfig, + workflowTracer, onOutput ) @@ -272,6 +277,7 @@ public fun renderWorkflowIn( savedStateHandle: SavedStateHandle? = null, interceptors: List = emptyList(), runtimeConfig: RuntimeConfig = RuntimeConfigOptions.DEFAULT_CONFIG, + workflowTracer: WorkflowTracer? = null, onOutput: suspend (OutputT) -> Unit = {} ): StateFlow { val restoredSnap = savedStateHandle?.get(KEY)?.snapshot @@ -282,6 +288,7 @@ public fun renderWorkflowIn( restoredSnap, interceptors, runtimeConfig, + workflowTracer, onOutput )