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. Considerstrip = "none"overrides per-profile; see Cargo in depth. - cargo-flamegraph:
cargo flamegraph --bin serverwrapsperf record(dtrace on macOS) and folds stacks into an SVG — the one-command CPU profile. - samply is the friendlier sampler:
samply record ./target/release/serveropens 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 soVec,String,Optionrender 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 theconsole-subscribercrate) shows live tasks, poll times, and waker leaks;tracingspans 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 --timingsand-Zself-profileprofile 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
- The Rust toolchain — parent catalog.
- Performance & load testing — criterion/divan: measuring what these tools explain.
- Async runtimes — why async observability needs tokio-console and tracing.
- pprof and JFR & JMC — the runtime-integrated contrasts: Go and the JVM profile from inside the runtime, Rust from outside with native tools.