How to Convert Rust pprof Profiles for Code-Graph-RAG: A Complete Guide
Rust pprof profiles are converted for Code-Graph-RAG by reusing the generic Go pprof decoder with Rust-specific symbol demangling and repository filtering applied via convert_rust_pprof in codebase_rag/trace/rust_pprof.py.
The vitali87/code-graph-rag repository ingests dynamic execution data from CPU profiles to enrich its retrieval-augmented generation (RAG) pipeline. Because the Rust pprof-rs crate emits protobuf profiles identical to Go's pprof format on the wire, the conversion leverages a shared decoder with language-specific customizations for symbol normalization and build artifact filtering.
Understanding the Rust pprof Profile Format
The pprof Rust crate (commonly used via cargo-flamegraph) generates gzipped protobuf files containing sample, function, location, and string tables. These match the Google pprof protocol exactly.
This structural compatibility means Code-Graph-RAG doesn't need a separate parser. Instead, it delegates to the language-agnostic decoder in codebase_rag/trace/pprof.py and injects Rust-specific behavior through callback functions.
Rust-Specific Conversion Logic
Three customizations differentiate Rust profile conversion from Go profiles:
- Symbol demangling and frame normalization — handled by
_bare_name()incodebase_rag/trace/rust_pprof.py - Repository-scope filtering — implemented in
_build_frame()to excludetarget/directories and external files - Language tagging — applied through constants from
codebase_rag/constants.py
Frame Name Normalization with _bare_name
Rust symbols require aggressive cleaning before they can match static analysis results. The _bare_name() function applies a multi-stage pipeline:
- Strip legacy hash suffixes — removes
::h<hex>compiler-inserted hashes - Remove generic arguments — eliminates
<...>turbofish syntax and type parameters - Discard function arguments — strips
(...)parameter lists - Split qualified paths — respects nesting depth of brackets and parentheses
- Collapse closures — maps anonymous closure frames to
cs.TRACE_QUALNAME_ANONYMOUS
The helper _split_top_level() performs depth-aware path splitting, ensuring that nested angle brackets in generic types don't break the module hierarchy.
Repository Filtering in _build_frame
The _build_frame() function constructs FramePoint objects identical to the Go path, but with two Rust-specific exclusions:
- Files outside the
repo_rootare skipped - Files inside
target/directories (Cargo's build output) are ignored
This prevents build artifacts and standard library internals from polluting the dynamic call graph.
The Conversion Pipeline
The convert_rust_pprof() function orchestrates the full workflow:
def convert_rust_pprof(profile_path, repo_root, output, workload=None):
# Delegate to generic pipeline with Rust-specific callbacks
return convert_pprof_profile(
profile_path=profile_path,
build_frame=_build_frame, # Rust frame construction
language=cs.TRACE_LANGUAGE_RUST, # "rust"
tracer_name=cs.TRACE_TOOL_NAME_RUST_PPROF, # "rust-pprof"
output_path=output,
workload=workload,
)
The convert_pprof_profile() function in pprof.py handles:
- Reading and decompressing the gzipped protobuf
- Parsing profile tables into in-memory structures
- Iterating samples and converting stacks to call-edge records
- Writing JSON-Lines output with language-specific metadata
Practical Code Examples
Python API Conversion
Convert a Rust profile programmatically:
from pathlib import Path
from codebase_rag.trace.rust_pprof import convert_rust_pprof
profile = Path("target/debug/cpu.pb") # Output from pprof-rs / cargo-flamegraph
repo_root = Path("/path/to/your/project") # Repository root for filtering
output = Path("trace_edges.jsonl") # Trace interchange format
edges_written = convert_rust_pprof(
profile_path=profile,
repo_root=repo_root,
output=output,
workload="my-rust-binary", # Optional workload identifier
)
print(f"Converted {edges_written} call edges")
CLI Conversion
The cgr trace command auto-detects Rust profiles:
cgr trace pull \
--language rust \
--profile /path/to/cpu.pb \
--repo-root /path/to/project \
--output edges.jsonl
The CLI dispatcher in codebase_rag/trace/cli.py (lines 86–96) routes --language rust to convert_rust_pprof().
Key Source Files
| File | Purpose |
|---|---|
codebase_rag/trace/rust_pprof.py |
Core conversion logic, symbol demangling (_bare_name, _split_top_level, _strip_generics_and_args), and frame building (_build_frame) |
codebase_rag/trace/pprof.py |
Language-agnostic protobuf decoder and convert_pprof_profile() pipeline |
codebase_rag/trace/cli.py |
Command-line interface, dispatches to Rust converter |
codebase_rag/trace/records.py |
FramePoint and trace record definitions |
codebase_rag/constants.py |
Language tags (TRACE_LANGUAGE_RUST, TRACE_TOOL_NAME_RUST_PPROF) and anonymous closure sentinel |
Summary
- Rust pprof profiles use the same protobuf format as Go, enabling decoder reuse
- Symbol demangling is Rust-specific: hashes, generics, and closures are normalized via
_bare_name() target/and external files are filtered to keep only project-relevant frames- The
convert_rust_pprof()function wraps the generic pipeline with Rust callbacks and constants - Both Python API and CLI interfaces are available for production workflows
Frequently Asked Questions
Why does Rust use the same pprof decoder as Go?
The pprof-rs crate implements the Google pprof protocol identically on the wire. Both Go's runtime profiler and pprof-rs output gzip-compressed protobuf files with matching table structures for samples, functions, locations, and strings. Code-Graph-RAG exploits this compatibility by factoring the parsing logic into pprof.py and injecting language-specific behavior through the build_frame callback.
How does symbol demangling handle Rust's complex generics?
The _bare_name() function uses _strip_generics_and_args() to recursively remove <...> turbofish blocks and (...) argument lists while tracking nesting depth. The _split_top_level() helper then decomposes the remaining path into module components without breaking on internal brackets. This produces clean module::Type::method signatures that align with static analysis results.
What files are excluded during Rust profile conversion?
The _build_frame() function in rust_pprof.py filters out two categories: files outside the repository root (to exclude standard library and dependency code) and files inside target/ directories (to exclude Cargo build artifacts). Only source files within your project's actual codebase contribute to the dynamic call graph.
Can I use this with profiling tools other than cargo-flamegraph?
Yes. Any tool that produces pprof-compatible protobuf output works, including direct pprof-rs integration, tokio-console, or custom instrumentation. The key requirement is the standard pprof wire format; the converter handles all Rust-specific symbol processing regardless of how the profile was collected.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →