Skip to content

Add coarse-grained streaming-node tracing - #21309

Closed
rjzamora wants to merge 41 commits into
NVIDIA:mainfrom
rjzamora:structlog-profiling
Closed

Add coarse-grained streaming-node tracing#21309
rjzamora wants to merge 41 commits into
NVIDIA:mainfrom
rjzamora:structlog-profiling

Conversation

@rjzamora

@rjzamora rjzamora commented Feb 3, 2026

Copy link
Copy Markdown
Contributor

Description

cc @TomAugspurger (who suggested something along this general path) - This is hybrid between #21277 and the logging approach suggested offline:

When tracing is enabled (e.g. CUDF_POLARS_LOG_TRACES=1), we will collect coarse-grained information about streaming nodes, and log "Streaming Node" events. These events include ir_id information that should match the ir_id info added to the existing "Execute IR" events. When CUDF_POLARS_LOG_TRACES=0, we will still collect this information if/when TracingOptions are passed in by the user, but we will not emit the structlog events in that case.

My motivation for preserving the "in-memory" tracing infrastructure was simply personal convenience. I struggled a bit to get what I wanted out of a "structlog-only" approach.

Checklist

  • I am familiar with the Contributing Guidelines.
  • New or existing tests cover these changes.
  • The documentation is up to date with these changes.

@rjzamora rjzamora self-assigned this Feb 3, 2026
@rjzamora
rjzamora requested a review from a team as a code owner February 3, 2026 15:52
@rjzamora
rjzamora requested review from mroeschke and wence- February 3, 2026 15:52
@rjzamora rjzamora added 2 - In Progress Currently a work in progress improvement Improvement / enhancement to an existing function non-breaking Non-breaking change labels Feb 3, 2026
@github-actions github-actions Bot added Python Affects Python cuDF API. cudf-polars Issues specific to cudf-polars labels Feb 3, 2026
@GPUtester GPUtester moved this to In Progress in cuDF Python Feb 3, 2026
Comment thread python/cudf_polars/cudf_polars/experimental/rapidsmpf/tracing.py Outdated
Comment on lines +88 to +91
# Remap partitioning if schema has changed
partitioning = remap_partitioning(
metadata_in.partitioning, ir.children[0].schema, ir.schema
)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This matches up child columns 1-1 with our columns. Is this always valid if we say preserve_partitioning?

Comment thread python/cudf_polars/cudf_polars/experimental/rapidsmpf/utils.py Outdated
Comment on lines +145 to +146
except (IndexError, KeyError):
return None # Column missing in old or new schema

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Hmm, I think it should be impossible to get IndexError because if some column_index is not in old_names then something has gone catastrophically wrong.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

If we don't have the relevant old name in the new schema what does that mean? We've dropped some columns or (I think?) renamed some columns. Is it bad if we rename and don't notice?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

We get an index error if the column name we are "partitioned" on is not in our new schema. This can happen if we rename the column(s) we are partitioned on. Right now we just "forget" that we are partitioned in this case, but it won't be hard to handle this later.

"""Metadata payload for a channel."""
# If inter_rank partitioning was invalidated, the whole partitioning is invalid
if isinstance(partitioning.inter_rank, HashScheme) and new_inter_rank is None:
return None

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

OK, so if we were not previously inter-rank partitioned we can still potentially preserve local?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

This check isn't necessary, and should technically check the rank count - The node deciding to shuffle will check this kinds of detail anyway.

Comment thread python/cudf_polars/cudf_polars/experimental/rapidsmpf/utils.py Outdated
Comment on lines +201 to +236
def _repr_trace_tree(
ir: IR,
partition_info: MutableMapping[IR, PartitionInfo],
tracer: StreamingQueryTracer,
*,
offset: str = "",
) -> str:
"""Recursively build a tree representation with tracer data."""
header = _repr_ir(ir, offset=offset)
header = header.rstrip("\n")

# Get node tracer if it exists
if (node_tracer := tracer.node_tracers.get(ir)) is not None:
# Add actual row count if available
if node_tracer.row_count is not None:
header += f" rows={_fmt_row_count(node_tracer.row_count)}"

# Add decision if present
if node_tracer.decision is not None:
header += f" decision={node_tracer.decision}"

# Add actual chunk count
header += f" chunks={node_tracer.chunk_count}"

children_strs = [
_repr_trace_tree(child, partition_info, tracer, offset=offset + " ")
for child in ir.children
]

header += "\n"
return header + "".join(
f"{line}{offset} (repeated {count} times)\n"
if (count := sum(1 for _ in group)) > 1
else line
for line, group in groupby(children_strs)
)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Can we also have an option to dump this tree in a structured form (json would be fine)? That way automated tools have a chance of reading it rather than needing to parse.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I'm working on that and will have something later today.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

This PR will already dump the information in structlog form when CUDF_POLARS_LOG_TRACES=1. Tom is working on the code needed to post-process that output to provide something similar to the above. I suppose we could drop some of this logic if we feel like the dump + post-process workflow works well enough for multiple ranks.

from cudf_polars.dsl.ir import IR


def _stable_ir_id(ir_node: IR) -> int:

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This seems like a reasonable method to have on the IR class itself.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Yeah, that may make sense. This hash just uses the repr, so it isn't as "robust" as hash(), but it's useful for purposes like this.

Comment on lines +201 to +236
def _repr_trace_tree(
ir: IR,
partition_info: MutableMapping[IR, PartitionInfo],
tracer: StreamingQueryTracer,
*,
offset: str = "",
) -> str:
"""Recursively build a tree representation with tracer data."""
header = _repr_ir(ir, offset=offset)
header = header.rstrip("\n")

# Get node tracer if it exists
if (node_tracer := tracer.node_tracers.get(ir)) is not None:
# Add actual row count if available
if node_tracer.row_count is not None:
header += f" rows={_fmt_row_count(node_tracer.row_count)}"

# Add decision if present
if node_tracer.decision is not None:
header += f" decision={node_tracer.decision}"

# Add actual chunk count
header += f" chunks={node_tracer.chunk_count}"

children_strs = [
_repr_trace_tree(child, partition_info, tracer, offset=offset + " ")
for child in ir.children
]

header += "\n"
return header + "".join(
f"{line}{offset} (repeated {count} times)\n"
if (count := sum(1 for _ in group)) > 1
else line
for line, group in groupby(children_strs)
)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I'm working on that and will have something later today.

@rjzamora rjzamora mentioned this pull request Feb 3, 2026
3 tasks
@rjzamora rjzamora added 0 - Waiting on Author Waiting for author to respond to review and removed 2 - In Progress Currently a work in progress labels Feb 3, 2026
@rjzamora

rjzamora commented Feb 4, 2026

Copy link
Copy Markdown
Contributor Author

Closing in favor of #21316

@rjzamora rjzamora closed this Feb 4, 2026
@github-project-automation github-project-automation Bot moved this from In Progress to Done in cuDF Python Feb 4, 2026
rapids-bot Bot pushed a commit that referenced this pull request Feb 5, 2026
- Closes #21244
- **Another** Alternative to #21277 and #21309
- Part of #20482

Authors:
  - Richard (Rick) Zamora (https://github.com/rjzamora)

Approvers:
  - Tom Augspurger (https://github.com/TomAugspurger)

URL: #21316
@rjzamora
rjzamora deleted the structlog-profiling branch February 6, 2026 19:19
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

0 - Waiting on Author Waiting for author to respond to review cudf-polars Issues specific to cudf-polars improvement Improvement / enhancement to an existing function non-breaking Non-breaking change Python Affects Python cuDF API.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[FEA] Add IR-node profiling for the rapidsmpf runtime

4 participants