# pdu turns a disk usage scan into a chart you can read at a glance

> parallel-disk-usage is a Rust command line program that renders a graphical chart of directory sizes instead of a list of numbers, with a library crate and a JSON mode for integration. It ignores reflinks, does not follow symbolic links, and optimises runtime at the cost of a larger binary.

**KSXGitHub/parallel-disk-usage** — Highly parallelized, blazing fast directory tree analyzer

- Repository: https://github.com/KSXGitHub/parallel-disk-usage
- Website: https://crates.io/crates/parallel-disk-usage
- Stars: 739 · Forks: 25
- Language: Rust
- License: Apache-2.0
- Published: 2026-10-21 · Updated: 2026-10-21 · Language: en
- Canonical page: https://hysenlabs.com/projects/ksxgithub-parallel-disk-usage

## A chart, not a column of numbers

du prints a list of sizes, one per path, and reading it means sorting it mentally. pdu takes the same walk over a directory tree and renders the result as a graphical chart sized to the terminal. The repository describes it as a highly parallelized directory tree analyzer, and the README is explicit that it is a CLI program that renders a graphical chart for disk usage of files and directories, listed as an alternative to dust and dutree.

That positioning is about the shape of the answer, not only the speed. A chart makes the single large directory visible immediately, and it also makes relative comparison of separate files a first class feature. If you pass two paths, the chart shows how the two relate rather than giving you two unrelated totals you have to compare yourself.

The implementation is Rust, distributed as a crate named parallel-disk-usage with the binary called pdu, under the Apache-2.0 licence. The crate is published on crates.io and the project is also packaged for Arch Linux. The README points to USAGE.md for the full help text, which is where the flag reference lives.

The audience is anyone who has ever run du on a machine they did not maintain and had to work out which directory to delete. It also fits the routine habit of checking a build machine or a container image before it fills up.

## Hardlinks are counted once, reflinks are not counted at all

Two counting decisions define what pdu reports, and the README is clear about both.

On hardlinks, pdu is unbiased: all hardlinks are treated as equally real. There is optional hardlink detection and deduplication, and the README says plainly that enabling it would make pdu proportionally slower. So the default behaviour counts every hardlinked path, which means a file with a hundred links contributes a hundred times its size, and the deduplicating mode is opt in.

On reflinks, the limitation is different. The README states that pdu is ignorant of reflinks from copy-on-write filesystems such as BTRFS and ZFS. A reflink is a copy-on-write clone that shares the data blocks but has its own path, and a BTRFS or ZFS tree can therefore report far more apparent size than actual space used. pdu will report the apparent size. If your disk is one of those filesystems, the number you see is not what df will tell you.

Two more limitations are listed. pdu does not follow symbolic links, which avoids the classic symlink loop and double counting problem, at the cost of missing whatever a link points at. And the runtime is optimised at the expense of binary size, which is the trade the author has chosen, and the reason a pdu binary is bigger than you might expect for a program that walks a filesystem.

The progress report is on the same list of trade-offs. It is optional and the README says it would make pdu slightly slower, so the default output is quiet.

## The JSON mode, and the (total) root that trips people up

The command can emit and consume JSON, which is how it integrates with other tools. The README describes the --json-output flag as converting disk usage data into JSON and the --json-input flag as turning that JSON back into visualization, and it presents the pair as a way to follow the UNIX philosophy and pipe between CLI tools.

There is a detail in the JSON format that will bite you if you write a parser against it. The structure of the JSON tree differs depending on how many file or directory names you provided as CLI arguments. With only zero or one name, the root of the tree is a real path, either `.` or the name you gave. With two or more, the root is named `(total)`, which is not a real path, and the names you provided become the children of that root.

The consequence is that a script reading the root key cannot assume it is a directory. The same program produces two different shapes depending on the argument count, which is convenient for humans reading a chart and awkward for tooling.

The library crate is the other integration path. The parallel-disk-usage crate is both a binary crate and a library crate, and the README is candid about the condition for using it: if you want features that pdu itself lacks, that is after you have asked the maintainers for the features and they refused. In other words, the library is the escape hatch for what the CLI will not do, not the first thing to reach for. The library documentation lives on docs.rs.

## Building it: cargo build, two scripts and a matrix of environment flags

The prerequisite for working on the project is cargo, and the build is a plain Cargo build of the binary:

```bash
cargo build --bin pdu
```

That produces target/debug/pdu. The release build adds the release flag and lands in target/release/pdu:

```bash
cargo build --bin pdu --release
```

Around that, the repository ships helper scripts. Running the tests is one command that does both a debug and a release pass, since the difference matters for a program with a parallel runtime:

```bash
./test.sh && ./test.sh --release
```

The script is controlled by environment variables, and the README documents the whole set. FMT, LINT and BUILD default to true, DOC defaults to false, and TEST defaults to true. BUILD_FLAGS and TEST_FLAGS are space-separated lists of flags for cargo build and cargo test, and TEST_SKIP is a list of test names to skip. Turning off FMT and LINT is the normal way to run a focused subset locally.

Two more scripts are worth knowing. The run script launches the binary with the arguments you give it, and generate-completions.sh updates the shell completion files, which is the step to run after changing the CLI definition.

The repository also carries an AGENTS.md, a CLAUDE.md, a dylint.toml and a rust-toolchain file, plus a .devcontainer directory and a ci directory, which tells you the project has a defined contributor environment and a pinned toolchain rather than an ad hoc setup.

## Three installation routes, and one of them carries a signature

On any desktop operating system the README lists three routes.

The simplest is the GitHub Release Page, where you download a binary. The release notes add something unusual for a small Rust CLI: starting with version 0.23.0, every executable published to GitHub Releases ships with a build provenance attestation, so you can cryptographically verify that the binary was produced by this repository's deployment workflow rather than uploaded by hand. The README points to a section explaining the verification command.

The second route is crates.io, which requires cargo and installs the binary from the published crate:

```bash
cargo install parallel-disk-usage --bin pdu
```

The third is the Arch Linux package from the official repository:

```bash
sudo pacman -S parallel-disk-usage
```

The attestation note is the part worth reading carefully if you install the binary rather than compiling it. A source build is verifiable by construction, since you run the compiler yourself. A downloaded binary is verifiable only if the project publishes a signed statement about how it was built, and this one now does, from version 0.23.0 onwards. Older release binaries carry no attestation, so if you are writing a policy about which artefacts to allow in a build pipeline, the version boundary matters.

## Cargo features decide which binaries and formats exist at all

The Cargo manifest is where the project's shape is visible, and it is unusually deliberate about it. The package declares version 0.24.0 on edition 2024, with a library target at src/lib.rs and a binary named pdu at cli/main.rs. That binary is not unconditional. It requires the cli feature:

```toml
[features]
default = ["cli"]
json = ["serde/derive", "serde_json"]
cli = ["clap/derive", "clap_complete", "clap-utilities", "json"]
```

The dependency chain explains the design. The cli feature pulls in json, which pulls in serde and serde_json, and json is what makes the --json-output and --json-input modes exist. clap and clap_complete provide the argument parser and the shell completions. So a build that disables the cli feature yields the library without the binary and without the JSON format, which is exactly what you want if you are embedding the library.

Four more binaries are declared behind their own features: pdu-completions, pdu-man-page, pdu-usage-md and pdu-ai-instructions. The last one is unusual. It exists to generate AI instructions for the tool, which suggests the author has thought about how the program should be described to an assistant rather than only to a person at a terminal. The other three generate the completions, the man page and the usage markdown, which is how USAGE.md stays in step with the CLI definition.

For the parallel behaviour itself, the dependency list names rayon 1.12.0 for work distribution and dashmap 6.2.1 for concurrent maps, alongside itertools and derive_more. The concurrency primitives are the point of the crate, and the choice of a concurrent hash map over a locked one is visible in the manifest.

## Where pdu is the wrong tool

Three cases stand out, and the first is copy-on-write filesystems. If your data lives on BTRFS or ZFS and you use reflinks or snapshots, pdu reports apparent size rather than the space actually consumed, and the README lists this as a limitation rather than a bug. If the number is going into a capacity report, that gap matters and no flag fixes it.

The second is symbolic links. pdu does not follow them, so a tree that routes through symlinked directories will under-report, and a symlink farm will not appear as the paths you expect. This is a defensible design choice, but it means the output describes the physical tree, not the logical one.

The third is interactive exploration. The comparison list in the README includes ncdu, and pdu's own description is a program that renders a chart or emits JSON; the README does not describe an interactive browser, so browsing a tree in place is not what this tool offers. If your workflow is to descend into a directory and re-measure, a chart is the wrong interaction model.

There is also the benchmark caveat. The comparison in the README was generated by a GitHub Workflow at version 0.20.0 and covers pdu, dust 1.2.1, dua 2.30.1, ncdu, gdu 5.31.0 and du, with lower being better, on charts for block size and for hardlink deduplication. It is a reproducible, workflow generated comparison rather than an anecdotal one, and it measures version 0.20.0 rather than the current 0.24.0.

## Maintenance, releases and the size of the binary

The repository is not archived, and the last push was on 2026-09-14. Recent releases are 0.24.0 published on 2026-05-28, 0.23.0 on 2026-04-08, and a 0.22.1 release candidate on 2026-04-07, so the release candidate shipped alongside a stable release in the same window. The README has no changelog section, so the release notes on GitHub are the record of what changed.

The licence is Apache-2.0, stated in the Cargo manifest and in the LICENSE file, and it applies to both the binary and the library. Anyone embedding parallel-disk-usage in a larger tool inherits those terms, and the library split means a downstream project can take the analysis engine without taking the terminal chart.

The upgrade cost for users is small in effort and real in size. The binary is deliberately large because the runtime was optimised, and each release replaces the previous binary wholesale rather than patching anything in place. For a tool you run occasionally, that trade is usually fine. For a fleet of constrained machines, it is the thing to measure before you roll it out, since the README flags binary size as a known consequence of the performance work rather than an accident.

## Conclusion

Adopt pdu if you want a quick visual answer to what is filling a disk, on a machine where scanning speed matters, and if you can live with its three documented blind spots: reflinks are ignored, symbolic links are not followed, and the binary is large because the runtime was optimised. Do not adopt it as an interactive browser or as an accounting tool for copy-on-write filesystems. Before you rely on a scan, check whether your filesystem is BTRFS or ZFS, since the README lists ignorance of reflinks as a limitation, and use the --json-output mode if you intend to feed the result into another tool, since the JSON root is named (total) when you pass two or more paths.

## FAQ

### How do I install parallel-disk-usage?

You can download a binary from the GitHub Release Page, install it from crates.io with cargo install parallel-disk-usage --bin pdu, which needs cargo, or use the Arch Linux package with sudo pacman -S parallel-disk-usage. From version 0.23.0 the GitHub binaries ship with a build provenance attestation.

### How does pdu handle hardlinks and reflinks?

All hardlinks are treated as equally real by default, and optional hardlink detection and deduplication is available but makes pdu proportionally slower. Reflinks from copy-on-write filesystems such as BTRFS and ZFS are ignored, so apparent size can exceed real usage on those filesystems.

### What do the --json-output and --json-input flags do?

The --json-output flag converts disk usage data into JSON and the --json-input flag turns that JSON into visualization, so pdu can be piped into other CLI tools. Note that the JSON tree root is a real path when you pass zero or one path, and is named (total) when you pass two or more.

### Can I use parallel-disk-usage as a library in my own Rust tool?

Yes, the crate is both a binary crate and a library crate, with library documentation on docs.rs. The README frames it as the route for features pdu itself lacks, after asking the maintainers for those features first. The cli feature, and therefore the binary and JSON support, can be disabled in a library-only build.

### How do I run the tests for parallel-disk-usage?

Run ./test.sh && ./test.sh --release from the repository root, which covers both a debug and a release pass. The script is driven by environment variables such as FMT, LINT, DOC, BUILD, TEST, BUILD_FLAGS, TEST_FLAGS and TEST_SKIP, and cargo is the only prerequisite.

## Sources

- [KSXGitHub/parallel-disk-usage on GitHub](https://github.com/KSXGitHub/parallel-disk-usage)
- [License: Apache-2.0](https://github.com/KSXGitHub/parallel-disk-usage/blob/master/LICENSE)
- [Project website](https://crates.io/crates/parallel-disk-usage)
- [README](https://github.com/KSXGitHub/parallel-disk-usage/blob/master/README.md)
- [Releases](https://github.com/KSXGitHub/parallel-disk-usage/releases)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/ksxgithub-parallel-disk-usage
