Skip to content

feat: add Rust dynamic call-graph tracer (dyn dispatch capture) - #1274

Closed
vitali87 wants to merge 2 commits into
feat/trace-sampled-flagfrom
feat/trace-rust-tracer
Closed

vitali87 wants to merge 2 commits into
feat/trace-sampled-flagfrom
feat/trace-rust-tracer

Conversation

@vitali87

Copy link
Copy Markdown
Owner

What

Adds a dynamic call-graph tracer for Rust, the last runtime in the epic (#1244) with no tracer. pprof-rs / cargo flamegraph write gzipped protobuf CPU profiles in the same wire format as Go, so this reuses the Go pprof decoder and accumulation and adds only Rust-specific symbol demangling and scoping.

Why

Static analysis already resolves monomorphised Rust calls, so the dynamic payoff is narrower but real: dyn Trait dispatch, function pointers, and closures routed across boundaries. This closes #1251.

Approach

  • codebase_rag/trace/rust_pprof.py: convert_rust_pprof reuses _decompress / _decode_profile / _accumulate_edges from pprof.py; a Rust _bare_name strips the legacy ::h<hash>, splits paths at angle/paren depth zero so <Dog as Animal>::speak yields speak, drops trailing turbofish (fn::<u32> -> fn), collapses generic instantiations, and marks {{closure}} anonymous. Frames outside the repo and under target/ are seen through to the nearest project frame. The header is tagged sampled=True (pprof-rs samples).
  • Resolution reuses JsFrameResolver (span-first, bare name + declaration line), so monomorphised instances resolve to their single generic source definition; ingest.py adds rust to that dispatch tuple.
  • CLI: Rust pprof is gzipped protobuf indistinguishable from Go's by magic bytes, so cgr trace convert --language rust selects the Rust demangler. Unknown --language values are rejected.
  • Docs: supported-runtimes list plus a "Recording a Rust trace" section (pprof-rs recipe and caveats).

Tests

  • Converter: sample-weighted edges, the dyn Trait dispatch edge captured through an intervening std frame, target/+stdlib frames out of scope, closures/generics normalised, _bare_name demangling table, workload labelling, malformed-profile rejection.
  • Ingest: a Rust trace resolves the dyn-dispatch edge to the graph node and flags it dynamic_sampled + static_missed.
  • CLI: --language rust routes to the Rust converter (and needs --repo-path); unknown language rejected. Plus the TRACE_LANGUAGE_RUST constant assertion.

Stacked on #1273 (the sampled interchange flag it depends on). Closes #1251. Refs #1244.

@coderabbitai

coderabbitai Bot commented Aug 15, 2026 •

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: f79a2f08-181c-4cd5-953b-1ed4f43868f3

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

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.

@greptile-apps

greptile-apps Bot commented Aug 15, 2026 •

Copy link
Copy Markdown
Contributor

Greptile Summary

This change adds Rust pprof tracing documentation alongside Rust trace conversion support. Two documented Rust recording behaviors were reproduced: the profile-writing snippet does not compile without importing the protobuf Message trait, and enabling release debug information does not reduce compiler inlining. The recording guide needs both corrections before users can reliably generate complete Rust call graphs.

Confidence Score: 4/5

The Rust tracing implementation should not be considered ready to merge until the recording documentation is corrected, because the advertised copy-and-run workflow fails to compile.

The two documented behaviors were reproduced with minimal Rust/Cargo harnesses: one demonstrated the exact compiler failure and its required import, and the other compared optimized release builds while changing only debug-information generation.

Files Needing Attention: docs/guide/dynamic-tracing.md needs the missing protobuf trait import near the profile writer and corrected guidance about release-build inlining.

T-Rex T-Rex Logs

What T-Rex did

  • T-Rex produced a proof for a posted P1 finding and validated it by showing that importing the Message trait fixes the Rust recipe, enabling a successful compile and protobuf output.
  • T-Rex produced a proof for a posted P2 finding and demonstrated a reproducible release profile by running a minimal Rust workload and compiling release builds with and without debug information.
  • A second P1 finding and a second P2 finding proof were posted.

View all artifacts

T-Rex Ran code and verified through T-Rex

Comments Outside Diff (2)

  1. General comment

    P1 Rust dynamic tracing recipe omits the protobuf Message trait import

    • Bug
      • At docs/guide/dynamic-tracing.md:273, write_to_writer is called on the pprof protobuf Profile without importing the trait that supplies that method. The copied recipe fails to compile with E0599 under pprof 0.13.0 and the documented protobuf-codec feature.
    • Cause
      • write_to_writer is provided by pprof::protos::Message, and Rust requires that extension trait to be in scope for method resolution.
    • Fix
      • Add use pprof::protos::Message; to the Rust snippet before calling write_to_writer.

    T-Rex Ran code and verified through T-Rex

  2. General comment

    P2 debug = true is incorrectly described as reducing release inlining

    • Bug
      • docs/guide/dynamic-tracing.md:286 recommends debug = true as a way to build with reduced inlining. In the executed opt-level = 3 release workload, changing only debug from false to true left the ordinary candidate function inlined: it had no emitted symbol in either build, while the #[inline(never)] control remained emitted. The two variants also returned the same runtime value.
    • Cause
      • Cargo's release debug profile setting controls debug-information generation; it does not change the selected release optimization level or reliably inhibit LLVM/Rust inlining.
    • Fix
      • Remove debug = true from the reduced-inlining alternatives. Keep debug = true only as guidance for retaining symbols/line tables, and recommend an optimization-setting change such as opt-level = 0 when reduced inlining is required.

    T-Rex Ran code and verified through T-Rex

Prompt To Fix All With AI
### Issue 1
docs/guide/dynamic-tracing.md:273
**Rust profile writer lacks its trait import**

The recording snippet calls `write_to_writer` without bringing the protobuf `Message` trait into scope. Copying the documented `pprof = { version = "0.13", features = ["protobuf-codec"] }` recipe fails with E0599 before it can write a profile. Add `use pprof::protos::Message;` to the snippet.

### Issue 2
docs/guide/dynamic-tracing.md:286
**Debug information does not reduce release inlining**

`debug = true` retains symbols and line tables, but it does not reduce or disable inlining in an optimized release build. Presenting it as an alternative to `opt-level = 0` can leave profiled callees inlined and absent from the sampled call graph. Keep `debug = true` as symbol and line-table guidance, and recommend an optimization-level change when reduced inlining is required.

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

Reviews (1): Last reviewed commit: "feat: add Rust dynamic call-graph tracer..." | Re-trigger Greptile

Comment thread docs/guide/dynamic-tracing.md Outdated
run_the_workload();
if let Ok(report) = guard.report().build() {
let mut file = std::fs::File::create("cpu.pb.gz").unwrap();
report.pprof().unwrap().write_to_writer(&mut file).unwrap();

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.

P1 Rust profile writer lacks its trait import

The recording snippet calls write_to_writer without bringing the protobuf Message trait into scope. Copying the documented pprof = { version = "0.13", features = ["protobuf-codec"] } recipe fails with E0599 before it can write a profile. Add use pprof::protos::Message; to the snippet.

Artifacts

Copied Rust recipe without the Message trait import

  • Minimal source copied from the documented recipe and compiled unchanged, showing the missing import condition; takeaway: this is the failing input.

Corrected Rust recipe with the Message trait import

  • The same minimal source with only `use pprof::protos::Message;` added, used for the successful comparison; takeaway: this is the required correction.

Cargo manifest pinning pprof 0.13 with protobuf-codec

  • Cargo manifest used by both runs pins the documented pprof dependency and feature; takeaway: the reproduction matches the stated dependency scope.

Executable before and after Cargo harness

  • Shell harness that compiles the copied recipe first and then the trait-imported variant, capturing both command outputs; takeaway: the comparison is repeatable.

Cargo compile failure for the copied documentation recipe

  • Captured `cargo run --release` output exits 101 with E0599 and recommends importing `pprof::protos::Message`; takeaway: the documented snippet does not compile.

Cargo compile success after importing Message

  • Captured `cargo run --release` output exits 0 after adding the required trait import; takeaway: the one-line import fixes compilation.

Corrected pprof harness runtime output

  • Captured release execution exits 0 and confirms nonempty output beginning with protobuf bytes `0a 04 08`; takeaway: the corrected recipe runs and writes profiling data.

View artifacts

T-Rex Ran code and verified through T-Rex

Prompt To Fix With AI
This is a comment left during a code review.
Path: docs/guide/dynamic-tracing.md
Line: 273

Comment:
**Rust profile writer lacks its trait import**

The recording snippet calls `write_to_writer` without bringing the protobuf `Message` trait into scope. Copying the documented `pprof = { version = "0.13", features = ["protobuf-codec"] }` recipe fails with E0599 before it can write a profile. Add `use pprof::protos::Message;` to the snippet.

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

Comment thread docs/guide/dynamic-tracing.md Outdated

Sampled stacks make `dyn Trait` dispatch and calls through function pointers
visible; counts are sample counts, so give the workload enough CPU time, and
build with reduced inlining (`opt-level = 0` or `debug = true`) or inlined

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.

P2 Debug information does not reduce release inlining

debug = true retains symbols and line tables, but it does not reduce or disable inlining in an optimized release build. Presenting it as an alternative to opt-level = 0 can leave profiled callees inlined and absent from the sampled call graph. Keep debug = true as symbol and line-table guidance, and recommend an optimization-level change when reduced inlining is required.

Artifacts

Minimal Rust release workload source

  • Defines an ordinary candidate call and an inline-never control used to compare release compilation with debug information disabled and enabled, with the takeaway that symbol presence and call output directly expose the inlining outcome.

Reproducible Cargo release profile runner

  • Builds the same opt-level 3 Cargo release workload with only debug=false or debug=true changed, runs it, and inspects symbols and workload disassembly, with the takeaway that the validation can be repeated exactly.

Release build output with debug information disabled

  • Captured command output for the debug=false release build shows the runtime result, no candidate symbol, and the inline-never control symbol, with the takeaway that the baseline optimized build inlines the candidate.

Release build output with debug information enabled

  • Captured command output for the debug=true release build shows the same runtime result, no candidate symbol, and the inline-never control symbol, with the takeaway that enabling debug information did not reduce inlining.

View artifacts

T-Rex Ran code and verified through T-Rex

Prompt To Fix With AI
This is a comment left during a code review.
Path: docs/guide/dynamic-tracing.md
Line: 286

Comment:
**Debug information does not reduce release inlining**

`debug = true` retains symbols and line tables, but it does not reduce or disable inlining in an optimized release build. Presenting it as an alternative to `opt-level = 0` can leave profiled callees inlined and absent from the sampled call graph. Keep `debug = true` as symbol and line-table guidance, and recommend an optimization-level change when reduced inlining is required.

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

@vitali87

Copy link
Copy Markdown
Owner Author

@greptile-apps Both fixed in eeac19d (docs-only):

  • P1 (Message trait import): added use pprof::protos::Message; to the recording snippet so write_to_writer resolves.
  • P2 (debug vs inlining): reworded the guidance. The recipe now traces the dev profile (cargo test, opt-level = 0) so callees are not inlined away, and explicitly states debug = true only preserves symbols/line tables and does not reduce inlining in an optimized --release build.

Also switched the snippet to write cpu.pb (uncompressed protobuf, which is what write_to_writer produces) and noted the converter reads the profile gzipped or not.

@greptile-apps

greptile-apps Bot commented Aug 15, 2026

Copy link
Copy Markdown
Contributor

Both fixes are confirmed in eeac19db and the docs are now accurate:

  1. use pprof::protos::Message; — present in the snippet with the explanatory comment. write_to_writer will resolve correctly.

  2. Inlining guidance — the recipe now correctly distinguishes the two knobs: trace the dev profile (cargo test, opt-level = 0) to prevent inlining, and debug = true is accurately characterised as symbol/line-table preservation only, with an explicit warning that it does not suppress inlining in --release. That's the right mental model to give users.

  3. cpu.pb (uncompressed) — matches what write_to_writer actually produces, and the note that the converter handles gzipped or plain is a nice UX touch.

LGTM on the docs fix. No further concerns.

@vitali87

Copy link
Copy Markdown
Owner Author

Superseded by #1275. This PR was stacked on #1273's branch and GitHub auto-closed it when that base branch was deleted on merge. #1275 is the same commits rebased onto main (the sampled flag it depends on is now merged via #1273).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

1 participant