cargo-treemap 0.1.0

Interactive treemap analyzer for Rust binary size and dependency API usage — like webpack-bundle-analyzer, for cargo.
cargo-treemap-0.1.0 is not a library.

cargo-treemap

An interactive treemap of what's in your Rust binary, and how much of each dependency you use. It's webpack-bundle-analyzer for cargo.

cargo install cargo-treemap
cargo treemap --release --open

treemap of ripgrep

Every rectangle is a symbol, nested under its module and crate and sized by its bytes in the compiled artifact. Click a cell to zoom in, switch the weighting between .text, .data, and gzip, search, and toggle crates in the sidebar. The report is a single self-contained HTML file; it needs no server or network.

Why

cargo-bloat tells you which crates are big. cargo-treemap adds two things. The first is a zoomable treemap of the binary down to the symbol. The second is a measure of how much of each dependency's public API you call: a 400 KB dependency you use three functions of is a different decision from a 400 KB dependency you lean on heavily, and cargo-treemap tells them apart and flags the ones that are large but barely used.

Usage

cargo treemap                        # analyze the default target, write cargo-treemap.html
cargo treemap --release --open       # release profile, open the report in a browser
cargo treemap --format table         # ranked crate breakdown in the terminal
cargo treemap --format json -o out.json   # machine-readable, stable enough to diff in CI

# workspaces & selection
cargo treemap --workspace
cargo treemap -p my-crate --bin my-bin
cargo treemap --features foo,bar --no-default-features

# other targets (see "Targets" below)
cargo treemap --target wasm32-unknown-unknown
cargo treemap --artifact ./path/to/prebuilt-binary     # analyze a binary directly, no build

# attribute code by its source (definition site) instead of by symbol name
cargo treemap --release --dwarf

# add per-crate compile time (does a full from-scratch timed build)
cargo treemap --release --compile-time

Source-based attribution (--dwarf)

By default a symbol is credited to the crate whose name heads it, which for a generic function is the crate that instantiated it. --dwarf reads debug info and credits code to the crate whose source defines it. That is usually what you want for "which dependency is bloating me": a heavily-generic dependency like serde_json, whose code gets monomorphized all over your own crates, shows up under serde_json instead of scattered across its callers. It forces a debug build and can move hundreds of KB between crates. It only refines code; anonymous rodata has no line info and stays in [runtime].

Dependency API usage

cargo treemap --release --api-usage             # add the "how dependent are you" analysis
cargo treemap --release --api-usage --accurate  # slower, but the ratios are trustworthy

--api-usage adds a dependencies panel (button in the top bar, or open the report at #deps) and a section to the terminal and JSON output:

dependency API-usage panel

For each direct dependency it shows the size of its public API (surface), how many of those functions you use (used), the ratio, its size in your binary, and a verdict that flags crates that are large but barely used.

Read --accurate before trusting the numbers. Without it, the "used" count comes from the optimized binary, where the compiler has inlined most cross-crate calls out of existence, so it is a lower bound and can wrongly flag a crate as underused. --accurate does a second opt-level-0 build (-C symbol-mangling-version=v0) where those calls survive as symbols, giving true ratios. The report labels which mode produced it.

Metrics

The treemap can be weighted by any of these. Every node carries all of them, so switching is instant.

Metric What it is
text Bytes in executable-code sections (.text). The default, and the closest analog to webpack's parsed size.
data Bytes in data and read-only-data sections.
gzip Gzipped size of each symbol's bytes.
mono Number of symbols and monomorphizations, showing where generic instantiation piles up.
stat Source size per crate (the webpack "stat" analog).

A crates panel (button in the top bar, or open the report at #crates) holds a per-crate table: binary size, source LOC and bytes, unsafe count, monomorphizations, dependency kind, and, with --compile-time, how long each crate took to build. Crates whose code runs only at compile time (proc-macros and their closure) are labeled compile-only: they cost build time but ship zero bytes. syn is often your largest crate by source, takes over a second to compile, and contributes nothing to the binary. A color-by-dep-kind toggle recolors the treemap to match.

The dependency panel adds the API-usage ratio and value-to-size verdict per dependency.

--compile-time reads per-crate build time from cargo build --timings. It does a full from-scratch build in a separate target dir, so it's slow, but it shows which crates (often proc-macros with no runtime footprint) dominate your build.

Comparing builds

Save a baseline and diff against it later:

# on the baseline commit
cargo treemap --release --format json -o baseline.json

# after your change
cargo treemap --release --compare baseline.json            # size delta in the terminal
cargo treemap --release --compare baseline.json --open     # HTML treemap of the changes

Or compare straight against a git ref, which builds it in a throwaway worktree so your working tree is untouched:

cargo treemap --release --compare-ref main            # current build vs the main branch
cargo treemap --release --compare-ref v1.2.0 --open   # vs a tag, as an HTML treemap

The diff aligns the two trees by crate, module, and symbol and reports the signed size change. The HTML view shows only what changed, sized by the magnitude of the change and colored red where it grew and green where it shrank. The JSON delta is easy to gate on in CI.

Feature size attribution

cargo treemap --release --attribute-features

This builds the package once with --no-default-features, then once per feature with that feature added, and reports the marginal binary size each feature costs. Features can enable one another, so the numbers are relative to the no-default baseline and do not sum.

Targets

The size layer reads the compiled artifact with the object crate, so it is format-agnostic and analyzes a foreign binary regardless of your host:

Target format Status
Mach-O (macOS) full
ELF (Linux) full
WASM (wasm32-*) size is honest; per-symbol coverage is partial (a dedicated extractor is planned)

Building a foreign target still needs a working cross-linker such as cargo-zigbuild or cross. If you already have the binary, --artifact <path> skips the build and analyzes it directly, which is handy for CI artifacts and cross-compiled outputs.

How attribution works, and where it's approximate

By default each symbol is attributed to a crate in this order: an exact match against the dependency and std rlib symbol tables (which blames a monomorphized generic on the crate that instantiated it), then the first crate name in its demangled path. Symbols that name no crate (compiler-outlined functions, _main, anonymous constants) go to [runtime]; per-section slack that belongs to no symbol goes to [unattributed]. --dwarf switches code attribution to the source-definition site instead.

Known approximations, all shown in the output:

  • Anonymous read-only data (Unicode tables, regex DFAs, string literals) often has no crate-attributable symbol name and pools in [runtime]. --dwarf does not fix this, because .debug_line only covers code addresses.
  • Inlining and LTO erase symbols, so .text under-counts by whatever got inlined.
  • API usage is measured by function, so trait and derive crates like serde, and re-export facades like clap (which re-exports clap_builder), read as "no measurable fns". Matching is by function name, which can over-count slightly on collisions.

Output formats

  • --format html (default): the interactive self-contained report.
  • --format table: a ranked terminal breakdown, like cargo-bloat.
  • --format json: the full node tree plus the dependency and per-crate analysis, with a stable schema you can diff between runs to catch size regressions.

License

Dual-licensed under MIT or Apache-2.0, at your option.