-
Notifications
You must be signed in to change notification settings - Fork 102
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add WorkflowTracer Through some Runtime Internals #1240
Conversation
fb26293
to
cdd7c76
Compare
@@ -437,6 +439,8 @@ internal fun <T, PropsT, StateT, OutputT> | |||
key: String = "", | |||
handler: (T) -> WorkflowAction<PropsT, StateT, OutputT> | |||
) { | |||
val workerWorkflow = WorkerWorkflow<T>(workerType, key) | |||
val workerWorkflow = workflowTracer.trace({ "Create WorkerWorkflow($workerType,$key)" }) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
since we're trying to trace otherwise very fast operations, I would suggest to stick to static strings and avoid any string concat to avoid accidentally increasing time. Especially since workerType and key aren't strings already.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
cdd7c76
to
7c43871
Compare
|
e071968
to
18312f1
Compare
*/ | ||
public inline fun <T> WorkflowTracer?.trace(label: String, block: () -> T): T { | ||
this?.beginSection(label) | ||
try { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
How much overhead is there to adding a no-op try/finally? I realize the only other alternative is duplicating the block
here, which is probably also bad but in different ways.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
hmm good question. I'd have to look at the decompiled byte code I guess
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
given that its inline
if I branch it to only use the try / finally
when the tracer is non-null, what is the other concern you referred to? I think this would be fine as it would be inlined and then the unused branch shaken out of the code?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
then the unused branch shaken out of the code?
There's no shaking out at compile time as whether WorkflowTracer is null or not is determined at runtime.
I'm not aware of any try / finally cost, however the resulting bytecode here shows we're checking whether WorkflowTracer is null twice, which also means we're reaching into the workflow tracer field twice.
This avoids the double null check:
public inline fun <T> WorkflowTracer?.trace(label: String, block: () -> T): T {
return if (this == null) {
block()
} else {
beginSection(label)
try {
block()
} finally {
endSection()
}
}
}
Hopefully we're never running into a case where a workflow tracer ref changes in between a begin and end, but if it did this code here is more correct (symmetry)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
onOutput: suspend (OutputT) -> Unit | ||
): StateFlow<RenderingAndSnapshot<RenderingT>> { | ||
val chainedInterceptor = interceptors.chained() | ||
|
||
val runner = | ||
WorkflowRunner(scope, workflow, props, initialSnapshot, chainedInterceptor, runtimeConfig) | ||
WorkflowRunner( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit: WorkflowRunner(
can move up to previous line now that it's wrapped.
* Only calls [label] if there is an active [WorkflowTracer] use this for any label other than | ||
* a constant. | ||
*/ | ||
public inline fun <T> WorkflowTracer?.trace(label: () -> String, block: () -> T): T { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
- Remove this function and update the documentation on the other one to call out that we shouldn't use interpolated strings for label, so keep costs low.
children.forEachStaging { | ||
require(!(it.matches(child, key, workflowTracer))) { | ||
"Expected keys to be unique for ${child.identifier}: key=\"$key\"" | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should these checks be debug only? Or toggeable off to delete their runtime cost?
I guess we'll see them show up in traces if they're a problem, so good job adding this
18312f1
to
8a18121
Compare
8a18121
to
4a0b76f
Compare
Just waiting for verification using our internal test suite. |
This can allow users to inject some tracing into the workflow runtime internals that are not reachable by the
WorkflowInterceptor
via a cheap and easy API.