Skip to content

feat(df-otel): post-hoc ExecutionPlan -> OTel operator span crate (RFC 0040 slice 1) - #632

Merged
jensholdgaard merged 4 commits into
mainfrom
rfc0040-slice1-df-otel-crate
Jul 25, 2026
Merged

feat(df-otel): post-hoc ExecutionPlan -> OTel operator span crate (RFC 0040 slice 1)#632
jensholdgaard merged 4 commits into
mainfrom
rfc0040-slice1-df-otel-crate

Conversation

@jensholdgaard

@jensholdgaard jensholdgaard commented Jul 25, 2026

Copy link
Copy Markdown
Owner

Summary

  • New crates/ourios-df-otel crate: record_plan_spans walks a finished DataFusion physical plan and emits one child OTel span per BaselineMetrics-backed ExecutionPlan node, using the node's real StartTimestamp/EndTimestamp wall-clock bounds (RFC 0040 §3.1–§3.3). Deliberately datafusion + opentelemetry-only, no ourios-* deps, so it lifts cleanly to a standalone datafusion-contrib crate later.
  • Before writing this, both candidate approaches were prototyped and measured against real Ourios query plans (throwaway spikes, not included in this PR): the post-hoc design here, and adopting datafusion-contrib/datafusion-tracing (a live-span PhysicalOptimizerRule). The latter has a production-blocking bug on Ourios's real multi-partition plans — RepartitionExec's internal tokio spawns aren't covered by its join-set tracer hook (DataFusion 54.0.0), so every operator span is silently dropped unless partitioning is disabled — and its attributes are pretty-printed strings, not the normative typed schema this RFC specifies. RFC 0040 §7 is updated with these findings.
  • docs/rfcs/0040-datafusion-operator-instrumentation.md: fixed the §3.2 record_plan_spans signature to the where T::Span: Send + Sync + 'static bound the Context API actually requires (found while implementing — the bare T: Tracer sketch doesn't compile), settled both remaining §7 open questions (crate-name/extraction, querier OTel deps), and added a short note on why backdated timestamps + SpanKind::Internal are OTel-spec-sanctioned (verified via the OTel MCP), not a workaround.

Related

RFC: docs/rfcs/0040-datafusion-operator-instrumentation.md (slice 1 of the implementation; querier wiring + acceptance-criteria tests land in later slices, mirroring RFC 0039's 4-slice split)

Checklist

  • cargo fmt -p ourios-df-otel --check clean
  • cargo clippy -p ourios-df-otel --all-targets --all-features -- -D warnings clean
  • Tests added (cargo test -p ourios-df-otel --all-features, 3/3 pass)
  • Docs / CHANGELOG.md updated (this is a design/doc correction to an already-specified RFC, not a user-facing change yet)
  • RFC linked

Note: this crate isn't wired into ourios-querier yet — that's slice 3, once the weaver registry entries for datafusion.operator.* (slice 2) exist. No behavior change to the running binary in this PR.

Summary by CodeRabbit

  • New Features

    • Added OpenTelemetry instrumentation for DataFusion execution plans.
    • Captures operator timing, output metrics, compute time, and row-group statistics in trace spans.
    • Skips untimed operations and preserves accurate parent-child span relationships.
  • Documentation

    • Updated the instrumentation RFC with API requirements, OpenTelemetry guidance, and resolved design questions.
  • Chores

    • Added the instrumentation component to the workspace and updated third-party license records.

…C 0040 slice 1)

Prototyped and measured both post-hoc reconstruction and adopting
datafusion-contrib/datafusion-tracing before building: the latter drops every
operator span on Ourios's real multi-partition plans (RepartitionExec's
internal spawns aren't covered by its join-set tracer hook) and emits
string-typed attributes, not the normative datafusion.operator.* table.
record_plan_spans walks a finished physical plan and emits one child span per
BaselineMetrics-backed node using its real StartTimestamp/EndTimestamp wall
clock, skipping untimed nodes and re-parenting their children instead of
inventing a timeline.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Qtny6z6cA74xPZa4qRhk4F
Signed-off-by: Jens Holdgaard Pedersen <Jens@holdgaard.org>
@jensholdgaard
jensholdgaard requested a review from Copilot July 25, 2026 08:53
@coderabbitai

coderabbitai Bot commented Jul 25, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@jensholdgaard, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 52 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: c075115c-5f42-47de-923b-0f4c9cba5aba

📥 Commits

Reviewing files that changed from the base of the PR and between 2328487 and c221483.

📒 Files selected for processing (1)
  • crates/ourios-df-otel/src/lib.rs
📝 Walkthrough

Walkthrough

The PR adds the ourios-df-otel workspace crate, which reconstructs timed DataFusion execution-plan nodes as OpenTelemetry spans, maps execution metrics to attributes, skips untimed nodes, and documents the API and RFC decisions.

Changes

OpenTelemetry instrumentation

Layer / File(s) Summary
Crate integration and dependency setup
crates/ourios-df-otel/Cargo.toml, Cargo.toml, THIRD-PARTY-LICENSES.md
Registers the new crate, configures DataFusion and OpenTelemetry dependencies, and updates the generated Apache-2.0 inventory.
Plan span recording and validation
crates/ourios-df-otel/src/lib.rs
Adds record_plan_spans, metric attribute mapping, timestamp reduction, recursive parent handling, and tests for timed and untimed execution-plan nodes.
RFC API and behavior alignment
docs/rfcs/0040-datafusion-operator-instrumentation.md
Documents the span bound, timestamp and span-kind behavior, and resolved crate and dependency decisions.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant ParentContext
  participant record_plan_spans
  participant ExecutionPlan
  participant OpenTelemetryTracer
  ParentContext->>record_plan_spans: provide sampled parent context
  record_plan_spans->>ExecutionPlan: read metrics and children
  record_plan_spans->>OpenTelemetryTracer: create timed operator span
  record_plan_spans->>ExecutionPlan: recurse with updated parent context
  OpenTelemetryTracer-->>ParentContext: record reconstructed span tree
Loading

Possibly related PRs

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly reflects the new DataFusion-to-OpenTelemetry span crate and RFC slice.
Description check ✅ Passed The description matches the template with Summary, Related, and Checklist sections and includes the key implementation details.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch rfc0040-slice1-df-otel-crate

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

cargo about's attribution list is keyed off workspace members; ourios-df-otel
(RFC 0040 slice 1) wasn't in it yet.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Qtny6z6cA74xPZa4qRhk4F
Signed-off-by: Jens Holdgaard Pedersen <Jens@holdgaard.org>

Copilot AI left a comment

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.

Pull request overview

Adds a new, dependency-light crate to emit OpenTelemetry operator spans from a completed DataFusion physical plan (post-hoc), and updates RFC 0040 to match the implemented API constraints and settled design decisions.

Changes:

  • Introduce crates/ourios-df-otel with record_plan_spans that walks an executed ExecutionPlan and emits one span per timed (BaselineMetrics) node using Start/EndTimestamp wall-clock bounds.
  • Update RFC 0040 to correct the record_plan_spans signature and document the settled approach vs. datafusion-contrib/datafusion-tracing.
  • Register the new crate in the workspace (and lockfile).

Reviewed changes

Copilot reviewed 5 out of 6 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
docs/rfcs/0040-datafusion-operator-instrumentation.md Updates the RFC signature bounds and documents findings/decisions from prototype evaluation.
crates/ourios-df-otel/src/lib.rs Implements post-hoc plan-walk span emission plus unit tests around metrics mapping and untimed-wrapper behavior.
crates/ourios-df-otel/Cargo.toml Adds the new crate manifest with minimal deps (DataFusion + OTel) and test-only SDK deps.
Cargo.toml Adds crates/ourios-df-otel to workspace members.
Cargo.lock Records the new workspace crate and its dependencies.
Comments suppressed due to low confidence (2)

crates/ourios-df-otel/src/lib.rs:113

  • These as i64 casts can wrap on overflow, which would record negative values into span attributes. Even if it’s unlikely in practice, a wrapped negative is always wrong telemetry; prefer i64::try_from(...) and omit the attribute if it doesn’t fit.
            MetricValue::OutputRows(count) => {
                attrs.push(KeyValue::new(ATTR_OUTPUT_ROWS, count.value() as i64));
            }
            MetricValue::ElapsedCompute(time) => {
                attrs.push(KeyValue::new(ATTR_ELAPSED_COMPUTE, time.value() as i64));

crates/ourios-df-otel/src/lib.rs:122

  • Like the other metric mappings, pruned()/matched() are cast with as i64 and can wrap to negative values on overflow. Use i64::try_from and skip attributes that don’t fit to avoid incorrect telemetry.
            MetricValue::PruningMetrics {
                name,
                pruning_metrics,
            } if name == ROW_GROUPS_PRUNED_STATISTICS => {
                attrs.push(KeyValue::new(

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread crates/ourios-df-otel/src/lib.rs
Comment thread crates/ourios-df-otel/src/lib.rs Outdated

Copilot AI left a comment

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.

Pull request overview

Copilot reviewed 5 out of 6 changed files in this pull request and generated 3 comments.

Comments suppressed due to low confidence (1)

crates/ourios-df-otel/src/lib.rs:112

  • Direct as i64 casts from usize can wrap on overflow and yield negative OpenTelemetry attributes. Using i64::try_from avoids silent corruption and also makes the cast_possible_wrap allowance unnecessary once all casts are removed.
        match metric.value() {
            MetricValue::OutputRows(count) => {
                attrs.push(KeyValue::new(ATTR_OUTPUT_ROWS, count.value() as i64));
            }
            MetricValue::ElapsedCompute(time) => {

Comment thread crates/ourios-df-otel/src/lib.rs
Comment thread crates/ourios-df-otel/src/lib.rs
Comment thread crates/ourios-df-otel/src/lib.rs
…casts

Copilot review on RFC 0040 slice 1 (PR #632):
- timed_bounds now explicitly reduces via earliest-start/latest-end and
  treats an inverted end < start as untimed (skip + re-parent), rather than
  trusting aggregate_by_name to have already collapsed to one instance and
  overwriting on each iteration.
- node_attributes converts usize -> i64 via try_from, omitting the attribute
  on overflow instead of wrapping to a silently negative value; the
  cast_possible_wrap allow is gone because there's no wrapping cast left.
- The skip/reparent test pins an explicit AlwaysOn sampler instead of relying
  on the SDK default, since record_plan_spans gates on sampling.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Qtny6z6cA74xPZa4qRhk4F
Signed-off-by: Jens Holdgaard Pedersen <Jens@holdgaard.org>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@crates/ourios-df-otel/src/lib.rs`:
- Around line 90-128: Add unit tests covering the invalid-data policies in
timed_bounds and attr: verify timed_bounds returns None when the end timestamp
precedes the start timestamp, and verify attr returns None for a usize value
exceeding i64::MAX. Use the existing test conventions and target these symbols
directly without changing their production behavior.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: ec61352a-1159-4e91-a750-1a4954da1918

📥 Commits

Reviewing files that changed from the base of the PR and between 7e89677 and 2328487.

⛔ Files ignored due to path filters (1)
  • Cargo.lock is excluded by !**/*.lock
📒 Files selected for processing (5)
  • Cargo.toml
  • THIRD-PARTY-LICENSES.md
  • crates/ourios-df-otel/Cargo.toml
  • crates/ourios-df-otel/src/lib.rs
  • docs/rfcs/0040-datafusion-operator-instrumentation.md

Comment thread crates/ourios-df-otel/src/lib.rs

Copilot AI left a comment

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.

Pull request overview

Copilot reviewed 5 out of 6 changed files in this pull request and generated no new comments.

CodeRabbit review on PR #632: the two invalid-data policies added in the
prior fix (end < start treated as untimed; usize -> i64 overflow omits the
attribute rather than wrapping) had no dedicated tests protecting them from
regression.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Qtny6z6cA74xPZa4qRhk4F
Signed-off-by: Jens Holdgaard Pedersen <Jens@holdgaard.org>

Copilot AI left a comment

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.

Pull request overview

Copilot reviewed 5 out of 6 changed files in this pull request and generated no new comments.

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.

2 participants