Single-host disk diagnostics in your terminal. The terminal you open when the disk light won't stop blinking — before you reach for iostat, iotop, smartctl, lsblk, df, du, and a panic.
Sibling to NetWatch and SysWatch. Same chrome, same palette, same keys.
diskwatch --denseSix boxes, one screen, no chrome rows — every keybind, sort state and page counter lives in a box border.
| Box | Shows |
|---|---|
| io | Mirrored read/write throughput on a shared time axis. Peak, average, iops split by direction, mean request size, utilisation, await, p99. |
| devices | Per-device read/write, utilisation, size, type, 48s sparkline. Stacked devices (md, dm, LVM, LUKS) are listed but excluded from totals. |
| latency | IO completion histogram, seven buckets from <0.1ms to >50ms, with p50/p95/p99 and the share of ops past 10 ms. Bars are coloured by bucket, not by count, so the tail is visible before it fills. |
| volumes | Capacity meters with a days-to-full projection from observed growth. Silent when a volume is flat or shrinking. |
| smart | Health, wear, host writes, temperature, spare. |
| files | Busiest paths by event rate. Filter with /, sort with s. |
Falls back to a compact screen below 104×32, keeping the mirror and the percentiles.
| # | Tab | Shows | Replaces |
|---|---|---|---|
| 1 | Overview | KPI tiles, device summary, aggregate IO, capacity bar | — |
| 2 | Devices | model, firmware, serial, used %, SMART, per-device detail | lsblk, nvme list, diskutil list, hdparm -I |
| 3 | Volumes | APFS containers + roles; mdraid members, [UUUU] state, resync progress |
lvs, vgs, mdadm --detail, diskutil apfs list |
| 4 | FS | mounts with usage bars, thresholds, system/user/removable | df -h, df -i, mount, findmnt |
| 5 | IO | per-device throughput, 48s sparkline, p50/p99 read and write | iostat -x 1 |
| 6 | SMART | full NVMe/ATA attribute tables when smartctl is present |
smartctl -A, nvme smart-log |
| 7 | Hot Files | paths by event rate (FSEvents / inotify) | fatrace, fs_usage |
| 8 | Insights | capacity, SMART, wear, temperature, latency and hot-file anomalies | — |
diskwatch --lite80×24, six keys, no tabs: read and write throughput, a capacity line that answers how long
have I got, and the busiest files. Sized for a tmux split or an SSH session to a NAS. Same
grid and keys as netwatch --lite.
brew install diskwatch # macOS / Linux
nix-shell -p diskwatch # NixOS / Nix
paru -S diskwatch # Arch
cargo install diskwatch # anywhere with Rust
x eget use matthart1983/diskwatch # prebuilt release binaryOr a pre-built binary from Releases — Linux and macOS, x86_64 and aarch64, plus static musl builds.
No system dependencies on Linux. macOS uses the preinstalled ioreg, diskutil and
system_profiler. Optional: smartmontools for full SMART attribute tables; without it the
SMART tab falls back to the basic verified/failing flag.
Nix and Arch packages are maintained by community packagers — file packaging issues with them, diskwatch bugs here. Repology shows which are current.
From source
git clone https://github.com/matthart1983/diskwatch.git && cd diskwatch
cargo build --release
./target/release/diskwatchRust 1.75+.
| Key | Action |
|---|---|
1–8 |
Switch tabs |
V |
Cycle view: full → lite → dense |
L |
Jump straight to Lite |
↑ ↓ j k |
Move selection |
/ |
Filter files (Lite, Dense) |
s |
Cycle file sort (Dense) |
p |
Pause / resume |
, |
Settings — columns, temperature unit, SMART interval, theme, view |
r |
Force a SMART refresh |
? |
Help |
q / Esc |
Quit |
| Flag | |
|---|---|
--dense |
Start in Dense (--v2, --btop still work) |
--lite |
Start in Lite |
--view |
full, lite, dense |
--tab |
Start on a named tab |
--theme |
terminal (default), dark, light, ocean, solarized, dracula, nord |
--graph |
bars (default) or dots for btop-style braille |
--graph-fade |
btop's brightness gradient and dot grid — needs an RGB theme, so pair with --theme dark |
--no-graph-fade |
Turn the gradient off, against a config file that turns it on |
--watch PATH |
Watch this path in Hot Files instead of the defaults. Repeatable |
--watch-add PATH |
Watch this path in addition to the defaults. Repeatable |
--config PATH |
Read this config file instead of the default location |
--no-config |
Ignore any config file and run on built-in defaults |
--write-config |
Write a commented config file listing every setting, then exit |
--diag |
Print collected state and exit, no TUI |
diskwatch defers to your terminal's palette by default. It pins no colours of its
own: every slot resolves to an ANSI entry and foreground/background use Reset, so a
terminal profile, pywal, matugen or a system-wide rice carries straight through and
diskwatch sits beside your other tools instead of fighting them.
Pass --theme dark for diskwatch's own designed palette, or any of the other built-ins.
Those pin real RGB, which is what --graph-fade needs to fade through — a 16-colour
palette has no intermediate shades, so the gradient does nothing under the default.
Theme and graph style are also live in the settings overlay. Changes there last for the session; the config file below is what makes one stick.
diskwatch runs with zero config. If you want one anyway:
diskwatch --write-config
writes a commented file listing every setting at its default, to
$XDG_CONFIG_HOME/diskwatch/config.toml (~/.config/diskwatch/config.toml if that's
unset — macOS included). $DISKWATCH_CONFIG or --config PATH points elsewhere;
--no-config ignores it entirely.
theme = "nord"
view = "dense"
tab = "hot"
smart_interval_secs = 60
temp_unit = "fahrenheit"
columns = ["size", "free", "used_pct", "temp", "smart"]
# Hot Files roots. Replaces the defaults ($HOME plus the OS log and tmp dirs).
watch_paths = ["~/src", "/var/log"]
# ...or keep the defaults and add to them.
extra_watch_paths = ["/srv/data"]Precedence: CLI flag > environment variable > config file > built-in default. A flag you just typed always beats a file you wrote months ago.
Nothing in the file is fatal. An unknown key, an unparseable value, an unreadable file:
each becomes a warning on stderr, the rest of the file still applies, and diskwatch still
starts. --diag reprints the warnings, and the settings overlay says how many there were.
The format is a deliberate subset of TOML — flat key = value, # comments, no tables —
so diskwatch parses it without taking on a dependency that would move its MSRV. Every file
it accepts is also valid TOML.
The Hot Files tab watches $HOME plus the OS log and tmp directories by default. To point
it somewhere else, in descending precedence:
diskwatch --watch ~/src --watch /var/log # replace the defaults
diskwatch --watch-add /srv/data # keep them, add one
DISKWATCH_WATCH_PATHS=/srv:/var/log diskwatch
Each root is watched recursively, which on Linux costs one inotify watch per directory
underneath it. Point it at a large tree and you can exhaust
fs.inotify.max_user_watches — diskwatch names the path and says so rather than
reporting the kernel's bare "No space left on device", which sends people looking at
df. A root that doesn't exist is named too, and doesn't stop the others being watched.
| Metric | macOS | Linux |
|---|---|---|
| Device model / serial / firmware | ✅ system_profiler + IOKit |
✅ /sys/block/*/device/* |
| Per-device used bytes | ✅ via APFS container map | ✅ summed from mounts |
| Read/write byte rates, split | ✅ IOKit Statistics |
✅ /proc/diskstats 5/9 |
| Read/write iops, split | ✅ IOKit Operations |
✅ /proc/diskstats 4/8 |
| Avg per-op latency | ✅ Total Time / Operations |
✅ /proc/diskstats 6/10 |
| p50 / p99 latency | ✅ tick-averaged over 60s | ✅ tick-averaged over 60s |
| Latency histogram, 7 buckets | ✅ tick means weighted by ops | ✅ tick means weighted by ops |
| True per-op p99 | ❌ needs IOReport entitlement | ❌ needs eBPF biolatency |
| Device utilisation (%util) | ❌ IOKit has no busy-time counter | ✅ /proc/diskstats 13 |
| Requests in flight | ❌ not exposed by IOKit | ✅ /proc/diskstats 12 |
| SMART attributes | ✅ smartctl if installed |
✅ smartctl if installed |
| Volumes — APFS / mdraid | ✅ diskutil apfs list |
✅ /proc/mdstat |
| Volumes — ZFS, LVM | ⏳ deferred | ⏳ deferred |
| Hot files — paths | ✅ FSEvents | ✅ inotify |
| Hot files — bytes / pid | ❌ needs root or entitlement | ❌ needs eBPF biosnoop |
| Capacity growth + time-to-full | ✅ 10-min usage window | ✅ 10-min usage window |
Anything a platform can't measure renders -- and keeps its column, so the layout never
shifts between machines. Two consequences worth knowing:
- macOS reports no utilisation. IOKit counts service time, not time-with-IO-in-flight, which on a deep-queue NVMe exceeds wall clock. Dense sorts devices by throughput there and says so in its own border.
- The latency histogram samples. Each 200 ms tick contributes its op count to the bucket holding that tick's mean service time. It shows a sustained slow stretch; it smears a lone 50 ms outlier into whatever its tick averaged.
- Not multi-host. Use NetWatch Cloud for a fleet view.
- Not a daemon. No background collector, no persisted DB.
- Not a cleaner. It surfaces what's eating disk; it deletes nothing.
- Not a backup product. Snapshots are observed, not authored.
- Not a benchmark. It measures what's happening, not what's possible.
MIT.

