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

Lines changed: 12 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,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

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: 31 additions & 0 deletions
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

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 & 2 deletions
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

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, 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

Lines changed: 5 additions & 2 deletions
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].

0 commit comments

Comments
 (0)