What it shows
| # | Tab | Replaces |
|---|---|---|
| 1 | Overview | one screen across capacity, IO, health, hot files |
| 2 | Devices | lsblk, nvme list, diskutil list, hdparm -I |
| 3 | Volumes | lvs + vgs, mdadm --detail, diskutil apfs list |
| 4 | FS | df -h, df -i, mount, findmnt |
| 5 | IO | iostat -x 1, biolatency-style averages |
| 6 | SMART | smartctl -A, nvme smart-log |
| 7 | Hot Files | fanotify/fseventsd watcher (paths, not bytes) |
| 8 | Insights | plain-English anomaly summaries |
Plus two single-screen views that are not tabs: 2.0 (--v2, six btop-style boxes) and
Lite (--lite, 80×24, six keys).
Where lsblk shows you which disks exist, DiskWatch shows you what's happening on them — capacity trending, IO throughput, p99 latency, SMART health, and the files being written right now — and tells you why in plain English when something's anomalous.
2.0
Six boxes tile the terminal with zero chrome rows — no header bar, no menu bar, no status bar. Identity, uptime, sort state, paging and every keybind live inside the box borders, which a box spends anyway.
╭┤1├─┤ io ├─┤ 4 physical · 6 volumes ├────────────┤ diskwatch 0.2.0 nas-01 up 4d 02:18 ├─╮
│ r 128 MB/s read peak 412M avg 96M iops 5.0k avg req 36KB · 4 disks summed │
│ 500M┤ ⣠⣴⣶⣿⣿⣷⣦⣄ │
│ 0┤ ⣀⣠⣤⣶⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿ │
│ 2m┤ ────────────────────────────┤ 60s ├───────────────────────────────────────┤ now ├ │
│ 0┤ ⠛⠛⠿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿ │
│ 160M┤ ⠉⠛⠿⠟⠋ │
│ w 14 MB/s write peak 38M avg 11M iops 4.3k │
│ vitals util ■■■■■■■■········ 52% await 0.35ms iops 9.3k p99 4.25ms · headroom 48% │
╰─┤ V view , settings ├────────────────────────────┤ inflight 2 · 52% util ├─────────────╯
The mirror is earned here. In a CPU monitor a mirrored graph is decoration, because compute has no opposing direction. Disk read/write is two directions of one flow, so it takes the full width: a restore is a cliff above the axis, a backup a cliff below it, and a database at work is roughly symmetric about it.
The latency histogram is the disk-specific part. Seven buckets from <0.1ms to
>50ms, drawn as horizontal bars. Bar length comes from the count, but colour comes from
the bucket's position — so the right-hand buckets are red even when nearly empty, and you
learn where the tail lives before it grows. A mean of 5ms can hide a p99 of 60, and the tail
is what you actually feel.
Below 104×32 it falls back to a compact screen — the mirror and the percentiles survive,
because they are the identity of the tool; devices, volumes and smart collapse to
summary lines.
V cycles full → lite → 2.0 → full from any view, the same convention as
netwatch, and the View row in the , settings
overlay walks the same list for anyone who finds it there first. Unlike Lite, the 2.0 view
carries that overlay: it replaces the 8-tab view rather than deliberately reducing it, so
the dials stay reachable without leaving. Every key it binds is printed in a box border —
and nothing is printed that it doesn't bind.
Graphs are braille: two samples per character column at four vertical levels per row, with the fill coloured by its height in the graph rather than by which series it belongs to. Axis ceilings come off each series' own measured peak, on a ladder whose rungs are at most 25% apart — so a peak always lands in the top fifth of the axis instead of leaving the top braille row unused.
Lite
One screen at 80×24, six keys, no tabs. Read and write throughput, a capacity line that answers how long have I got rather than how full am I, and the busiest files — sized to run in a tmux split or an SSH session to a NAS.
diskwatch nas-01 · 4 devices ● 2.6 / 3.8 TB
r 128 MB/s read peak 412 MB/s avg 96 MB/s
▁▂▃▅█▇▅▃▂▁▁▂▄▆█▇▄▂▁▁▁▂▃▅▇█▆▄▂▁▁▁▂▃▄▆█▇▅▃▂▁▁▂▃▅▇█▆▄▂▁▁▁▂▃▄▅▇█▆▄▂▁▁▂▃▄▆▇█▅▃▂▁▁▂
w 14 MB/s write peak 38 MB/s avg 11 MB/s
▁▁▂▁▁▂▃▂▁▁▁▂▁▁▂▂▁▁▁▂▃▂▁▁▁▁▂▁▁▂▂▁▁▁▂▁▁▁▂▂▁▁▁▂▃▂▁▁▁▂▁▁▂▂▁▁▁▂▁▁▂▃▂▁▁▁▂▁▁▂▂▁▁▂▁▁▂
78s ago ─────────────────────────────────────────────────────────── now
/ 68% /var 94% health 4/4 growth +8.2 GB/day /var 94% · 11 days left
It is not the full tool with tabs hidden — it is a different view for one machine and one
question. L jumps straight here and back, V cycles through all three views; the full 8-tab TUI stays the default
at every terminal size. Below 80×24 Lite shows a notice rather than a clipped grid.
Same grid, keys and palette as netwatch --lite
— only the subject changes.
The capacity projection needs about a minute of observation before it will commit to a number, and stays silent when usage is flat or shrinking. The file table reports events, not bytes: FSEvents and inotify say that a path changed, not who wrote it or how much (see What's real, what's deferred).
Install
Or grab a pre-built binary from Releases.
The Nix and Arch packages are maintained by community packagers — thank you. File packaging issues with them; file diskwatch bugs here. The Repology page shows which packages are current.
| Platform | Download |
|---|---|
| Linux (x86_64) | diskwatch-linux-x86_64.tar.gz |
| Linux (aarch64) | diskwatch-linux-aarch64.tar.gz |
| Linux (x86_64, static) | diskwatch-linux-x86_64-static.tar.gz |
| Linux (aarch64, static) | diskwatch-linux-aarch64-static.tar.gz |
| macOS (Intel) | diskwatch-macos-x86_64.tar.gz |
| macOS (Apple Silicon) | diskwatch-macos-aarch64.tar.gz |
From source:
&&
Prerequisites: Rust 1.75+ (only if building from source). No system dependencies on Linux. macOS calls the standard ioreg, diskutil, and system_profiler binaries — all preinstalled. Optional: smartmontools (brew install smartmontools / apt install smartmontools) for full SMART attribute tables — without it, the SMART tab falls back to the basic verified/failing flag from diskutil.
Keys
| Key | Action |
|---|---|
1–8 |
Switch tabs |
↑ / ↓ / j / k |
Move selection (Devices, FS) |
p |
Pause / resume sampling |
, |
Settings (columns, temperature unit, SMART interval, theme, view) |
V |
Cycle view: full → lite → 2.0 → full |
L |
Jump straight to Lite |
s |
Cycle the file sort (2.0 only) |
/ |
Filter files by name or path (Lite, 2.0) |
? |
Help |
q / Esc |
Quit |
--lite |
Start in the minimal single-screen view |
--v2 |
Start in the 2.0 six-box screen (alias --btop) |
--view |
Start in a named view: full, lite, v2 |
--graph |
bars (default) or dots for btop-style braille |
--graph-fade |
btop's brightness gradient + dot grid |
--diag |
Print collected state and exit (no TUI) |
--theme |
dark (default), light, ocean, solarized, dracula, nord, terminal |
Themes
Seven built-in themes, matching syswatch's set and cycle order:
dark (default) · light · ocean · solarized · dracula · nord · terminal
terminal pins no colors of its own — every slot resolves to an ANSI palette entry, and
foreground and background use your terminal's own defaults. If you theme your whole desktop
with pywal, matugen, or a terminal profile, this is the one that follows along.
Switch it live from the settings overlay (, → Theme → Space), or start with it:
The choice isn't persisted between runs — like the other settings, it resets to dark on
restart. Use the flag to make it stick.
Graph style
Every chart in the app — the Overview aggregate, the IO tab's per-device panels, and both Lite charts — draws through one renderer, so a single setting changes them all.
bars is the stacked eighth-block look diskwatch shipped with — eight levels per row. dots packs four braille pixel rows into each cell, so a one-row sparkline resolves four distinct heights instead of reading as on/off. On the tall charts the difference is subtler; on Lite's 9-column row sparklines it's the difference between a shape and a blob.
--graph-fade is btop's other half: each chart runs bright at now and dims toward the
left edge, over a faint dot grid. It's a separate switch because the gradient reads fine
under bars too — and because it interpolates in RGB, so it's ignored under
--theme terminal, which exists precisely to pin no RGB. The grid only draws on charts at
least 16×4, which means the IO and Overview panels get it and Lite's 3- and 2-row charts
don't.
Both are live in the settings overlay (, → Graph style / Graph fade → Space).
Lite has no settings overlay of its own — press L, change it, press L back.
Tabs in detail
[1] Overview — 5 KPI tiles (capacity, IO, p99 latency, health, insights), per-device summary, aggregate IO sparkline, top insights, segmented capacity bar.
[2] Devices — block-device table with model, firmware, serial, used %, SMART status. Detail panel for the selected device.
[3] Volumes — APFS containers (macOS) with nested volumes, role, mount, FileVault. mdraid arrays (Linux) with members, slot state [UUUU], resync/recovery progress.
[4] FS — mounted filesystems with inline usage bars, threshold colors, system/user/removable classification.
[5] IO — per-device read / write throughput, 48s sparkline, p50 + p99 latency (read and write) over a 60s rolling window.
[6] SMART — full NVMe / ATA attribute tables when smartctl is on PATH; degraded banner with install instructions when not. Always shows the basic verified/failing flag.
[7] Hot Files — paths by event rate via FSEvents (macOS) / inotify (Linux). Honest footer: this tab can't show bytes/sec or process attribution without root (fs_usage) / Endpoint Security entitlement / eBPF biosnoop.
[8] Insights — anomaly cards over the collected state: capacity warnings, SMART failures, NVMe wear, drive temperature, p99 latency outliers, IO-dominant devices, hot-file runaway, removable drives.
What's real, what's deferred
| Metric | macOS | Linux |
|---|---|---|
| Device model / serial / firmware | ✅ system_profiler + IOKit |
✅ /sys/block/*/device/{model,serial,firmware_rev} |
| Per-device used bytes | ✅ via APFS container map | ✅ summed from sysinfo mounts |
| Read/write byte rates (split) | ✅ IOKit Statistics |
✅ /proc/diskstats cols 5/9 |
| Avg per-op latency | ✅ Total Time / Operations |
✅ /proc/diskstats cols 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 (histogram) | ❌ needs IOReport entitlement | ❌ needs eBPF biolatency (CAP_BPF) |
| Read/write iops, split | ✅ IOKit Operations |
✅ /proc/diskstats cols 4/8 |
| Device utilisation (%util) | ❌ IOKit counts service time, not busy time | ✅ /proc/diskstats col 13 (io_ticks) |
| SMART attributes | ✅ smartctl if installed |
✅ smartctl if installed |
| Volumes — APFS | ✅ diskutil apfs list |
n/a |
| Volumes — mdraid | n/a | ✅ /proc/mdstat |
| Volumes — ZFS, LVM | ⏳ deferred | ⏳ deferred |
| Hot files (paths) | ✅ FSEvents | ✅ inotify |
| Hot files — bytes / pid | ❌ needs root fs_usage / entitlement |
❌ needs eBPF biosnoop |
| Capacity growth + time-to-full | ✅ 10-min usage window | ✅ 10-min usage window |
| Requests in flight | ❌ not exposed by IOKit | ✅ /proc/diskstats col 12 |
The 2.0 view renders -- for anything the platform can't measure and keeps the column, so
the layout never shifts between machines. Utilisation is the one that matters: macOS has no
time-with-IO-in-flight counter, only summed service time, which on a deep-queue NVMe exceeds
wall clock and would read as a permanent 100%. Rather than print that, the devices table
sorts by throughput there — and says sort ↓ throughput in its own border, so the indicator
can never advertise a sort that didn't happen.
Its latency histogram is honest about its resolution too: each 200ms sample contributes its
whole op count to the bucket containing that sample's mean service time. It will show a
sustained slow stretch; it will smear a single 50ms outlier into whatever its tick averaged.
That is why the box is subtitled io completion · sampled.
Design
Inherits the *Watch family chrome — #0c1418 background, terminal-green accent, JetBrains Mono, 130×36 character grid with responsive reflow ≥ 110×30. The same character-grid mockups that drive NetWatch and SysWatch drive DiskWatch.
Anti-goals
- Not multi-host. Use NetWatch Cloud if you need a fleet view.
- Not a daemon. No long-running collector, no persisted DB.
- Not a deduper / cleaner. We surface what's eating disk; we don't delete anything. Mutation is a different tool.
- Not a backup product. Snapshots are observed, not authored.
- Not a benchmark. We measure what's happening, not what's possible.
License
MIT.