# 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][wba] for cargo.
```sh
cargo install cargo-treemap
cargo treemap --release --open
```

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.
[wba]: https://github.com/webpack-contrib/webpack-bundle-analyzer
## 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
```sh
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
```sh
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:

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.
| `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:
```sh
# 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:
```sh
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
```sh
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:
| 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.
[`object`]: https://crates.io/crates/object
[`cargo-zigbuild`]: https://crates.io/crates/cargo-zigbuild
[`cross`]: https://github.com/cross-rs/cross
## 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](LICENSE-MIT) or [Apache-2.0](LICENSE-APACHE), at your option.