Skip to content

Commit 0a219dc

Browse files
Merge pull request #1240 from square/sedwards/tracer-config
Add WorkflowTracer Through some Runtime Internals
2 parents 32cb7bb + 4a0b76f commit 0a219dc

File tree

23 files changed

+360
-102
lines changed

23 files changed

+360
-102
lines changed

workflow-core/api/workflow-core.api

+12
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,15 @@ 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+
}
321+
310322
public final class com/squareup/workflow1/Workflows {
311323
public static final fun RenderContext (Lcom/squareup/workflow1/BaseRenderContext;Lcom/squareup/workflow1/StatefulWorkflow;)Lcom/squareup/workflow1/StatefulWorkflow$RenderContext;
312324
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

+5-1
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

+7-2
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(
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
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]. This
14+
* wraps very frequently evaluated code and we should only use constants for [label], with no
15+
* interpolation.
16+
*/
17+
public inline fun <T> WorkflowTracer?.trace(
18+
label: String,
19+
block: () -> T
20+
): T {
21+
return if (this == null) {
22+
block()
23+
} else {
24+
beginSection(label)
25+
try {
26+
return block()
27+
} finally {
28+
endSection()
29+
}
30+
}
31+
}

workflow-runtime/api/workflow-runtime.api

+3-2
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

+10-2
Original file line numberDiff line numberDiff line change
@@ -110,12 +110,20 @@ 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

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

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

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

+4
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

+3-1
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

+16-7
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, workflowTracer))) {
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, workflowTracer) },
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/WorkflowChildNode.kt

+5-2
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,9 @@ package com.squareup.workflow1.internal
33
import com.squareup.workflow1.StatefulWorkflow
44
import com.squareup.workflow1.Workflow
55
import com.squareup.workflow1.WorkflowAction
6+
import com.squareup.workflow1.WorkflowTracer
67
import com.squareup.workflow1.internal.InlineLinkedList.InlineListNode
8+
import com.squareup.workflow1.trace
79

810
/**
911
* Representation of a child workflow that has been rendered by another workflow.
@@ -32,8 +34,9 @@ internal class WorkflowChildNode<
3234
*/
3335
fun matches(
3436
otherWorkflow: Workflow<*, *, *>,
35-
key: String
36-
): Boolean = id.matches(otherWorkflow, key)
37+
key: String,
38+
workflowTracer: WorkflowTracer?
39+
): Boolean = workflowTracer.trace("matches") { id.matches(otherWorkflow, key) }
3740

3841
/**
3942
* Updates the handler function that will be invoked by [acceptChildOutput].

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

+19-10
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
}

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

+4-1
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import com.squareup.workflow1.TreeSnapshot
1010
import com.squareup.workflow1.Workflow
1111
import com.squareup.workflow1.WorkflowExperimentalRuntime
1212
import com.squareup.workflow1.WorkflowInterceptor
13+
import com.squareup.workflow1.WorkflowTracer
1314
import kotlinx.coroutines.CancellationException
1415
import kotlinx.coroutines.CoroutineScope
1516
import kotlinx.coroutines.DelicateCoroutinesApi
@@ -28,7 +29,8 @@ internal class WorkflowRunner<PropsT, OutputT, RenderingT>(
2829
props: StateFlow<PropsT>,
2930
snapshot: TreeSnapshot?,
3031
private val interceptor: WorkflowInterceptor,
31-
private val runtimeConfig: RuntimeConfig
32+
private val runtimeConfig: RuntimeConfig,
33+
private val workflowTracer: WorkflowTracer?
3234
) {
3335
private val workflow = protoWorkflow.asStatefulWorkflow()
3436
private val idCounter = IdCounter()
@@ -55,6 +57,7 @@ internal class WorkflowRunner<PropsT, OutputT, RenderingT>(
5557
snapshot = snapshot,
5658
baseContext = scope.coroutineContext,
5759
runtimeConfig = runtimeConfig,
60+
workflowTracer = workflowTracer,
5861
interceptor = interceptor,
5962
idCounter = idCounter
6063
)

0 commit comments

Comments
 (0)