rgoussu@goussu: ~/library/rust/toolchain
~/library/rust/toolchain cat profiling-and-debugging.md

Profiling & debugging Rust

# The profiling and debugging story — perf and cargo-flamegraph, samply, debug info in release builds, rust-gdb/rust-lldb, heap tools, and tokio-console for async.

Conceptsaved 2026-08-09 #rust#tooling#profiling#debugging#performance

Overview

Rust has no runtime, so it has no runtime-owned profiler — no JFR, no pprof endpoint. Compiled Rust is native code with DWARF debug info, and the observability story is the native one: perf and flamegraphs on Linux, Instruments on macOS, gdb/lldb for interactive debugging, valgrind-family and allocator hooks for the heap. The Rust-specific layer is thin but important: cargo wrappers that make the native tools one-command, name demangling, pretty-printers for stdlib types, and — because async erases the call stack — tokio-console as the async-native complement.

Key points

  • Profile release builds with debug info: the essential incantation is a profile with debug = true (or [profile.release] debug = "line-tables-only") — release optimization with symbols, otherwise flamegraphs are hex addresses. Consider strip = "none" overrides per-profile; see Cargo in depth.
  • cargo-flamegraph: cargo flamegraph --bin server wraps perf record (dtrace on macOS) and folds stacks into an SVG — the one-command CPU profile.
  • samply is the friendlier sampler: samply record ./target/release/server opens an interactive Firefox Profiler UI — flame graph, stack chart, thread timelines; the best default today.
  • Interactive debugging: rust-gdb/rust-lldb (shipped with the toolchain) load pretty-printers so Vec, String, Option render sanely; IDE debugging is these engines behind CodeLLDB/VS Code. Debugging optimized code has the usual inlined/optimized-out caveats.
  • Heap profiling: dhat-rs (in-process, precise allocation sites), heaptrack or bytehound (system-level), valgrind massif; leaks are rare by construction (RAII) but growth/fragmentation analysis still matters for long-running services.
  • Async needs its own lens: stack profilers see executor plumbing, not your async fns. tokio-console (via the console-subscriber crate) shows live tasks, poll times, and waker leaks; tracing spans are the async-aware substitute for stack context — see Async runtimes.
  • Microbenchmarks are a testing concern: criterion and divan, covered in Performance & load testing; use them to measure, the profilers here to explain.
  • cargo build --timings and -Zself-profile profile the build itself — often the performance problem that actually hurts daily.

Examples

[profile.profiling]           # a dedicated profile: release speed + symbols
inherits = "release"
debug = true
cargo flamegraph --profile profiling --bin api -- --port 8080
samply record ./target/profiling/api
rust-lldb ./target/debug/api           # pretty-printed interactive debugging

Related