Skip to content
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

Merged
merged 2 commits into from
Dec 11, 2024

Conversation

steve-the-edwards
Copy link
Contributor

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.

@@ -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)" }) {
Copy link
Member

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.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

@steve-the-edwards
Copy link
Contributor Author

steve-the-edwards commented Dec 5, 2024

  • Add tests for tracer

@steve-the-edwards steve-the-edwards force-pushed the sedwards/tracer-config branch 3 times, most recently from e071968 to 18312f1 Compare December 6, 2024 17:00
*/
public inline fun <T> WorkflowTracer?.trace(label: String, block: () -> T): T {
this?.beginSection(label)
try {
Copy link
Collaborator

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.

Copy link
Contributor Author

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

Copy link
Contributor Author

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?

Copy link
Member

@pyricau pyricau Dec 9, 2024

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)

Copy link
Contributor Author

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(
Copy link
Collaborator

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 {
Copy link
Member

@pyricau pyricau Dec 9, 2024

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\""
}
Copy link
Member

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

@steve-the-edwards
Copy link
Contributor Author

Just waiting for verification using our internal test suite.

@steve-the-edwards steve-the-edwards merged commit 0a219dc into main Dec 11, 2024
30 checks passed
@steve-the-edwards steve-the-edwards deleted the sedwards/tracer-config branch December 11, 2024 16:26
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants