Skip to content

Commit b2e9f1e

Browse files
Add WorkflowTracer Through some Runtime Internals
1 parent 32cb7bb commit b2e9f1e

File tree

22 files changed

+219
-62
lines changed

22 files changed

+219
-62
lines changed

workflow-core/api/workflow-core.api

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ public abstract interface class com/squareup/workflow1/BaseRenderContext {
3535
public abstract fun eventHandler (Ljava/lang/String;Lkotlin/jvm/functions/Function8;)Lkotlin/jvm/functions/Function7;
3636
public abstract fun eventHandler (Ljava/lang/String;Lkotlin/jvm/functions/Function9;)Lkotlin/jvm/functions/Function8;
3737
public abstract fun getActionSink ()Lcom/squareup/workflow1/Sink;
38+
public abstract fun getWorkflowTracer ()Lcom/squareup/workflow1/WorkflowTracer;
3839
public abstract fun renderChild (Lcom/squareup/workflow1/Workflow;Ljava/lang/Object;Ljava/lang/String;Lkotlin/jvm/functions/Function1;)Ljava/lang/Object;
3940
public abstract fun runningSideEffect (Ljava/lang/String;Lkotlin/jvm/functions/Function2;)V
4041
}
@@ -167,6 +168,7 @@ public final class com/squareup/workflow1/StatefulWorkflow$RenderContext : com/s
167168
public fun eventHandler (Ljava/lang/String;Lkotlin/jvm/functions/Function8;)Lkotlin/jvm/functions/Function7;
168169
public fun eventHandler (Ljava/lang/String;Lkotlin/jvm/functions/Function9;)Lkotlin/jvm/functions/Function8;
169170
public fun getActionSink ()Lcom/squareup/workflow1/Sink;
171+
public fun getWorkflowTracer ()Lcom/squareup/workflow1/WorkflowTracer;
170172
public fun renderChild (Lcom/squareup/workflow1/Workflow;Ljava/lang/Object;Ljava/lang/String;Lkotlin/jvm/functions/Function1;)Ljava/lang/Object;
171173
public fun runningSideEffect (Ljava/lang/String;Lkotlin/jvm/functions/Function2;)V
172174
}
@@ -192,6 +194,7 @@ public final class com/squareup/workflow1/StatelessWorkflow$RenderContext : com/
192194
public fun eventHandler (Ljava/lang/String;Lkotlin/jvm/functions/Function8;)Lkotlin/jvm/functions/Function7;
193195
public fun eventHandler (Ljava/lang/String;Lkotlin/jvm/functions/Function9;)Lkotlin/jvm/functions/Function8;
194196
public fun getActionSink ()Lcom/squareup/workflow1/Sink;
197+
public fun getWorkflowTracer ()Lcom/squareup/workflow1/WorkflowTracer;
195198
public fun renderChild (Lcom/squareup/workflow1/Workflow;Ljava/lang/Object;Ljava/lang/String;Lkotlin/jvm/functions/Function1;)Ljava/lang/Object;
196199
public fun runningSideEffect (Ljava/lang/String;Lkotlin/jvm/functions/Function2;)V
197200
}
@@ -307,6 +310,16 @@ public final class com/squareup/workflow1/WorkflowOutput {
307310
public fun toString ()Ljava/lang/String;
308311
}
309312

313+
public abstract interface class com/squareup/workflow1/WorkflowTracer {
314+
public abstract fun beginSection (Ljava/lang/String;)V
315+
public abstract fun endSection ()V
316+
}
317+
318+
public final class com/squareup/workflow1/WorkflowTracerKt {
319+
public static final fun trace (Lcom/squareup/workflow1/WorkflowTracer;Ljava/lang/String;Lkotlin/jvm/functions/Function0;)Ljava/lang/Object;
320+
public static final fun trace (Lcom/squareup/workflow1/WorkflowTracer;Lkotlin/jvm/functions/Function0;Lkotlin/jvm/functions/Function0;)Ljava/lang/Object;
321+
}
322+
310323
public final class com/squareup/workflow1/Workflows {
311324
public static final fun RenderContext (Lcom/squareup/workflow1/BaseRenderContext;Lcom/squareup/workflow1/StatefulWorkflow;)Lcom/squareup/workflow1/StatefulWorkflow$RenderContext;
312325
public static final fun RenderContext (Lcom/squareup/workflow1/BaseRenderContext;Lcom/squareup/workflow1/StatelessWorkflow;)Lcom/squareup/workflow1/StatelessWorkflow$RenderContext;

workflow-core/src/commonMain/kotlin/com/squareup/workflow1/BaseRenderContext.kt

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,8 @@ public interface BaseRenderContext<out PropsT, StateT, in OutputT> {
5555
*/
5656
public val actionSink: Sink<WorkflowAction<PropsT, StateT, OutputT>>
5757

58+
public val workflowTracer: WorkflowTracer?
59+
5860
/**
5961
* Ensures [child] is running as a child of this workflow, and returns the result of its
6062
* `render` method.
@@ -437,6 +439,8 @@ internal fun <T, PropsT, StateT, OutputT>
437439
key: String = "",
438440
handler: (T) -> WorkflowAction<PropsT, StateT, OutputT>
439441
) {
440-
val workerWorkflow = WorkerWorkflow<T>(workerType, key)
442+
val workerWorkflow = workflowTracer.trace("CreateWorkerWorkflow") {
443+
WorkerWorkflow<T>(workerType, key)
444+
}
441445
renderChild(workerWorkflow, props = worker, key = key, handler = handler)
442446
}

workflow-core/src/commonMain/kotlin/com/squareup/workflow1/WorkerWorkflow.kt

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,11 +30,16 @@ import kotlin.reflect.KType
3030
*/
3131
internal class WorkerWorkflow<OutputT>(
3232
val workerType: KType,
33-
private val key: String
33+
private val key: String,
34+
workflowTracer: WorkflowTracer? = null
3435
) : StatefulWorkflow<Worker<OutputT>, Int, OutputT, Unit>(),
3536
ImpostorWorkflow {
3637

37-
override val realIdentifier: WorkflowIdentifier = unsnapshottableIdentifier(workerType)
38+
override val realIdentifier: WorkflowIdentifier =
39+
workflowTracer.trace("ComputeRealIdentifier" ) {
40+
unsnapshottableIdentifier(workerType)
41+
}
42+
3843
override fun describeRealIdentifier(): String = workerType.toString()
3944

4045
override fun initialState(
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
package com.squareup.workflow1
2+
3+
/**
4+
* This is a very simple tracing interface that can be passed into a workflow runtime in order
5+
* to inject span tracing throughout the workflow core and runtime internals.
6+
*/
7+
public interface WorkflowTracer {
8+
public fun beginSection(label: String): Unit
9+
public fun endSection(): Unit
10+
}
11+
12+
/**
13+
* Convenience function to wrap [block] with a trace span as defined by [WorkflowTracer].
14+
* Only calls [label] if there is an active [WorkflowTracer] use this for any label other than
15+
* a constant.
16+
*/
17+
public inline fun <T> WorkflowTracer?.trace(label: () -> String, block: () -> T): T {
18+
val optimizedLabel = if (this !== null) {
19+
label()
20+
} else {
21+
""
22+
}
23+
return trace(optimizedLabel, block)
24+
}
25+
26+
/**
27+
* Convenience function to wrap [block] with a trace span as defined by [WorkflowTracer].
28+
*/
29+
public inline fun <T> WorkflowTracer?.trace(label: String, block: () -> T): T {
30+
this?.beginSection(label)
31+
try {
32+
return block()
33+
} finally {
34+
this?.endSection()
35+
}
36+
}

workflow-runtime/api/workflow-runtime.api

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,8 @@ public final class com/squareup/workflow1/NoopWorkflowInterceptor : com/squareup
1010
}
1111

1212
public final class com/squareup/workflow1/RenderWorkflowKt {
13-
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;
14-
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;
13+
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;
14+
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;
1515
}
1616

1717
public final class com/squareup/workflow1/RenderingAndSnapshot {
@@ -104,6 +104,7 @@ public abstract interface class com/squareup/workflow1/WorkflowInterceptor$Workf
104104
public abstract fun getRenderKey ()Ljava/lang/String;
105105
public abstract fun getRuntimeConfig ()Ljava/util/Set;
106106
public abstract fun getSessionId ()J
107+
public abstract fun getWorkflowTracer ()Lcom/squareup/workflow1/WorkflowTracer;
107108
public abstract fun isRootWorkflow ()Z
108109
}
109110

workflow-runtime/src/commonMain/kotlin/com/squareup/workflow1/RenderWorkflow.kt

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -110,12 +110,21 @@ public fun <PropsT, OutputT, RenderingT> renderWorkflowIn(
110110
initialSnapshot: TreeSnapshot? = null,
111111
interceptors: List<WorkflowInterceptor> = emptyList(),
112112
runtimeConfig: RuntimeConfig = RuntimeConfigOptions.DEFAULT_CONFIG,
113+
workflowTracer: WorkflowTracer? = null,
113114
onOutput: suspend (OutputT) -> Unit
114115
): StateFlow<RenderingAndSnapshot<RenderingT>> {
115116
val chainedInterceptor = interceptors.chained()
116117

117118
val runner =
118-
WorkflowRunner(scope, workflow, props, initialSnapshot, chainedInterceptor, runtimeConfig)
119+
WorkflowRunner(
120+
scope,
121+
workflow,
122+
props,
123+
initialSnapshot,
124+
chainedInterceptor,
125+
runtimeConfig,
126+
workflowTracer
127+
)
119128

120129
// Rendering is synchronous, so we can run the first render pass before launching the runtime
121130
// coroutine to calculate the initial rendering.

workflow-runtime/src/commonMain/kotlin/com/squareup/workflow1/WorkflowInterceptor.kt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -160,6 +160,9 @@ public interface WorkflowInterceptor {
160160

161161
/** The [RuntimeConfig] of the runtime this session is executing in. */
162162
public val runtimeConfig: RuntimeConfig
163+
164+
/** The optional [WorkflowTracer] of the runtime this session is executing in. */
165+
public val workflowTracer: WorkflowTracer?
163166
}
164167

165168
/**
@@ -314,6 +317,7 @@ private class InterceptedRenderContext<P, S, O>(
314317
private val interceptor: RenderContextInterceptor<P, S, O>
315318
) : BaseRenderContext<P, S, O>, Sink<WorkflowAction<P, S, O>> {
316319
override val actionSink: Sink<WorkflowAction<P, S, O>> get() = this
320+
override val workflowTracer: WorkflowTracer? = baseRenderContext.workflowTracer
317321

318322
override fun send(value: WorkflowAction<P, S, O>) {
319323
interceptor.onActionSent(value) { interceptedAction ->

workflow-runtime/src/commonMain/kotlin/com/squareup/workflow1/internal/RealRenderContext.kt

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,15 @@ import com.squareup.workflow1.BaseRenderContext
44
import com.squareup.workflow1.Sink
55
import com.squareup.workflow1.Workflow
66
import com.squareup.workflow1.WorkflowAction
7+
import com.squareup.workflow1.WorkflowTracer
78
import kotlinx.coroutines.CoroutineScope
89
import kotlinx.coroutines.channels.SendChannel
910

1011
internal class RealRenderContext<out PropsT, StateT, OutputT>(
1112
private val renderer: Renderer<PropsT, StateT, OutputT>,
1213
private val sideEffectRunner: SideEffectRunner,
13-
private val eventActionsChannel: SendChannel<WorkflowAction<PropsT, StateT, OutputT>>
14+
private val eventActionsChannel: SendChannel<WorkflowAction<PropsT, StateT, OutputT>>,
15+
override val workflowTracer: WorkflowTracer?
1416
) : BaseRenderContext<PropsT, StateT, OutputT>, Sink<WorkflowAction<PropsT, StateT, OutputT>> {
1517

1618
interface Renderer<PropsT, StateT, OutputT> {

workflow-runtime/src/commonMain/kotlin/com/squareup/workflow1/internal/SubtreeManager.kt

Lines changed: 16 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,9 @@ import com.squareup.workflow1.Workflow
99
import com.squareup.workflow1.WorkflowAction
1010
import com.squareup.workflow1.WorkflowInterceptor
1111
import com.squareup.workflow1.WorkflowInterceptor.WorkflowSession
12+
import com.squareup.workflow1.WorkflowTracer
1213
import com.squareup.workflow1.identifier
14+
import com.squareup.workflow1.trace
1315
import kotlinx.coroutines.selects.SelectBuilder
1416
import kotlin.coroutines.CoroutineContext
1517

@@ -91,6 +93,7 @@ internal class SubtreeManager<PropsT, StateT, OutputT>(
9193
childResult: ActionApplied<*>
9294
) -> ActionProcessingResult,
9395
private val runtimeConfig: RuntimeConfig,
96+
private val workflowTracer: WorkflowTracer?,
9497
private val workflowSession: WorkflowSession? = null,
9598
private val interceptor: WorkflowInterceptor = NoopWorkflowInterceptor,
9699
private val idCounter: IdCounter? = null
@@ -121,17 +124,22 @@ internal class SubtreeManager<PropsT, StateT, OutputT>(
121124
handler: (ChildOutputT) -> WorkflowAction<PropsT, StateT, OutputT>
122125
): ChildRenderingT {
123126
// Prevent duplicate workflows with the same key.
124-
children.forEachStaging {
125-
require(!(it.matches(child, key))) {
126-
"Expected keys to be unique for ${child.identifier}: key=\"$key\""
127+
workflowTracer.trace("CheckingUniqueMatches") {
128+
children.forEachStaging {
129+
require(!(it.matches(child, key))) {
130+
"Expected keys to be unique for ${child.identifier}: key=\"$key\""
131+
}
127132
}
128133
}
129134

130135
// Start tracking this case so we can be ready to render it.
131-
val stagedChild = children.retainOrCreate(
132-
predicate = { it.matches(child, key) },
133-
create = { createChildNode(child, props, key, handler) }
134-
)
136+
val stagedChild =
137+
workflowTracer.trace("RetainingChildren") {
138+
children.retainOrCreate(
139+
predicate = { it.matches(child, key) },
140+
create = { createChildNode(child, props, key, handler) }
141+
)
142+
}
135143
stagedChild.setHandler(handler)
136144
return stagedChild.render(child.asStatefulWorkflow(), props)
137145
}
@@ -188,6 +196,7 @@ internal class SubtreeManager<PropsT, StateT, OutputT>(
188196
snapshot = childTreeSnapshots,
189197
baseContext = contextForChildren,
190198
runtimeConfig = runtimeConfig,
199+
workflowTracer = workflowTracer,
191200
emitAppliedActionToParent = ::acceptChildActionResult,
192201
parent = workflowSession,
193202
interceptor = interceptor,

workflow-runtime/src/commonMain/kotlin/com/squareup/workflow1/internal/WorkflowNode.kt

Lines changed: 19 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,11 @@ import com.squareup.workflow1.WorkflowExperimentalApi
1414
import com.squareup.workflow1.WorkflowIdentifier
1515
import com.squareup.workflow1.WorkflowInterceptor
1616
import com.squareup.workflow1.WorkflowInterceptor.WorkflowSession
17+
import com.squareup.workflow1.WorkflowTracer
1718
import com.squareup.workflow1.applyTo
1819
import com.squareup.workflow1.intercept
1920
import com.squareup.workflow1.internal.RealRenderContext.SideEffectRunner
21+
import com.squareup.workflow1.trace
2022
import kotlinx.coroutines.CancellationException
2123
import kotlinx.coroutines.CoroutineName
2224
import kotlinx.coroutines.CoroutineScope
@@ -51,6 +53,7 @@ internal class WorkflowNode<PropsT, StateT, OutputT, RenderingT>(
5153
baseContext: CoroutineContext,
5254
// Providing default value so we don't need to specify in test.
5355
override val runtimeConfig: RuntimeConfig = RuntimeConfigOptions.DEFAULT_CONFIG,
56+
override val workflowTracer: WorkflowTracer? = null,
5457
private val emitAppliedActionToParent: (ActionApplied<OutputT>) -> ActionProcessingResult =
5558
{ it },
5659
override val parent: WorkflowSession? = null,
@@ -74,6 +77,7 @@ internal class WorkflowNode<PropsT, StateT, OutputT, RenderingT>(
7477
contextForChildren = coroutineContext,
7578
emitActionToParent = ::applyAction,
7679
runtimeConfig = runtimeConfig,
80+
workflowTracer = workflowTracer,
7781
workflowSession = this,
7882
interceptor = interceptor,
7983
idCounter = idCounter
@@ -87,7 +91,8 @@ internal class WorkflowNode<PropsT, StateT, OutputT, RenderingT>(
8791
private val baseRenderContext = RealRenderContext(
8892
renderer = subtreeManager,
8993
sideEffectRunner = this,
90-
eventActionsChannel = eventActionsChannel
94+
eventActionsChannel = eventActionsChannel,
95+
workflowTracer = workflowTracer,
9196
)
9297
private val context = RenderContext(baseRenderContext, workflow)
9398

@@ -212,12 +217,14 @@ internal class WorkflowNode<PropsT, StateT, OutputT, RenderingT>(
212217
.render(props, state, context)
213218
baseRenderContext.freeze()
214219

215-
// Tear down workflows and workers that are obsolete.
216-
subtreeManager.commitRenderedChildren()
217-
// Side effect jobs are launched lazily, since they can send actions to the sink, and can only
218-
// be started after context is frozen.
219-
sideEffects.forEachStaging { it.job.start() }
220-
sideEffects.commitStaging { it.job.cancel() }
220+
workflowTracer.trace("UpdateRuntimeTree") {
221+
// Tear down workflows and workers that are obsolete.
222+
subtreeManager.commitRenderedChildren()
223+
// Side effect jobs are launched lazily, since they can send actions to the sink, and can only
224+
// be started after context is frozen.
225+
sideEffects.forEachStaging { it.job.start() }
226+
sideEffects.commitStaging { it.job.cancel() }
227+
}
221228

222229
return rendering
223230
}
@@ -261,8 +268,10 @@ internal class WorkflowNode<PropsT, StateT, OutputT, RenderingT>(
261268
key: String,
262269
sideEffect: suspend CoroutineScope.() -> Unit
263270
): SideEffectNode {
264-
val scope = this + CoroutineName("sideEffect[$key] for $id")
265-
val job = scope.launch(start = LAZY, block = sideEffect)
266-
return SideEffectNode(key, job)
271+
return workflowTracer.trace("CreateSideEffectNode") {
272+
val scope = this + CoroutineName("sideEffect[$key] for $id")
273+
val job = scope.launch(start = LAZY, block = sideEffect)
274+
SideEffectNode(key, job)
275+
}
267276
}
268277
}

0 commit comments

Comments
 (0)