NVMe and SATA/ATA SSD/HDD diagnostics and root-cause analysis for Linux and macOS.
NVMe Doctor combines drive health data with the host-side evidence that is actually available on the current platform. It is designed to answer not only “what do the counters say?”, but also “what is the strongest explanation supported by those counters, the transport, and the host path?”
NVMe Doctor is GPL-3.0-or-later software from KernelSoft.
1.1.21
Current scope:
- native NVMe on Linux and macOS;
- native SATA/ATA SSDs and HDDs;
- USB-to-NVMe where the OS/bridge exposes NVMe health, plus direct Realtek RTL9210 read-only access on macOS;
- USB-to-SATA through smartctl automatic/SAT pass-through, with a conservative direct read-only SAT fallback on macOS;
- protocol-aware hardware topology;
- one-tree topology for all physical drives;
- quick non-disruptive health in
list; - doctor-style assessment, JSON reports, before/after diff, and change watching.
The repository contains the maintainable source under src/ and a pre-built standalone ./nvme-doctor executable. The standalone uses Python's standard library and can be copied directly to another Linux or macOS machine with Python 3.9+.
No installation is required:
./nvme-doctor --version
./nvme-doctor list
sudo ./nvme-doctor check nvme0
sudo ./nvme-doctor check sda
sudo ./nvme-doctor topologyDevice names may be given with or without /dev/, for example nvme0, /dev/nvme0n1, sda, /dev/sda, or disk4 on macOS.
If exactly one supported physical drive is discoverable, check may omit the device argument.
nvme-doctor list
nvme-doctor list --jsonHuman output is intentionally compact:
Controller Health Proto Transport Size Model Firmware Serial
nvme0 GOOD NVMe pcie 4.00 TB ... ... ...
sda GOOD ATA SATA 1.92 TB ... ... ...
disk4 - ATA USB -> SATA 2.00 TB ... ... ...
Quick health values are:
GOOD— a cheap, non-disruptive health check passed;WARN— a warning signal was available in the quick health data;FAIL— the quick health source reported failure;-— trustworthy health is not available quickly/non-disruptively.
list never ejects or captures a macOS USB device just to populate Health.
sudo nvme-doctor check nvme0
sudo nvme-doctor check /dev/sda
sudo nvme-doctor check disk4
nvme-doctor check nvme0 --json
nvme-doctor check nvme0 --debugNormal human checks print the version immediately and show an in-place Checking... spinner while evidence is collected. --debug replaces the spinner with timed collection stages. JSON output remains machine-readable.
The assessment separates three concepts:
- Verdict — current health state:
HEALTHY,DEGRADED,AT RISK,CRITICAL, orINCOMPLETE; - Near-term risk —
LOW,ELEVATED,HIGH, orUNKNOWNbased on current evidence, not a time-to-failure prediction; - Trend — a single check reports
UNKNOWN; trend requires comparison data.
Unavailable evidence is never treated as clean evidence. For example, a USB bridge that hides SMART produces INCOMPLETE, not HEALTHY.
A nonexistent device is rejected before collection:
nvme-doctor: device not found: /dev/sdb
Show all physical drives in one merged tree:
sudo nvme-doctor topology
sudo nvme-doctor topology --debug
nvme-doctor topology --jsonOr inspect one drive:
nvme-doctor topology nvme0
nvme-doctor topology /dev/sda
nvme-doctor topology disk4On Linux, common NUMA/PCIe/controller branches are merged. Native SATA disks behind the same AHCI controller are separated by their libata/SCSI attachment, so two disks do not appear to occupy the same physical path.
Example SATA shape:
NUMA node 0
└─ PCI domain 0000
└─ ... PCIe bridge / port
└─ ... SATA/AHCI controller
host PCIe Gen5 x16
├─ libata port ata3 — host3
│ └─ SCSI address 3:0:0:0
│ └─ SATA link — 6.0 Gb/s
│ └─ /dev/sda
└─ libata port ata4 — host4
└─ SCSI address 4:0:0:0
└─ SATA link — 6.0 Gb/s
└─ /dev/sdb
For SATA, a PCIe generation shown at the AHCI controller is the host-controller PCIe interconnect, not the SATA drive link. The actual drive-side link is reported separately as SATA speed.
On macOS, parameterless topology uses a fast non-disruptive physical-disk inventory and shows Collecting topology... while it runs. It does not perform SMART health collection or repeatedly invoke slow native-NVMe inventory just to draw the all-drive tree.
Save a human or JSON report:
sudo nvme-doctor report nvme0 -o report.txt
sudo nvme-doctor report nvme0 --json -o report.jsonCompare two saved JSON reports:
sudo nvme-doctor report nvme0 --json -o before.json
# perform one controlled reboot / power / workload event
sudo nvme-doctor report nvme0 --json -o after.json
nvme-doctor diff before.json after.jsonThis is the preferred way to establish counter changes or a real trend. Lifetime counters such as NVMe unsafe shutdowns do not contain per-event cause or timestamp information by themselves.
sudo nvme-doctor watch nvme0
sudo nvme-doctor watch nvme0 --interval 10 --count 12
sudo nvme-doctor watch nvme0 --json-lineswatch reports meaningful changes in collected counters/findings; it does not perform remediation.
Where supported by the OS/tooling:
- NVMe SMART/Health critical warnings;
- available spare and threshold;
- Percentage Used/endurance;
- media/data-integrity errors;
- temperature and thermal history;
- unsafe shutdowns, power cycles and power-on hours;
- data read/written and host command counters;
- controller busy time;
- NVMe error-information history.
Linux can additionally expose:
- PCIe current/max generation and width;
- endpoint/upstream topology and down-training;
- NUMA locality;
- PCIe AER counters;
- target-scoped reset/timeout/controller-down events;
- ASPM/APST context as a hypothesis, never as proof by itself.
NVMe Doctor uses high-signal ATA SMART evidence without assigning universal meaning to every vendor-specific attribute:
- SMART overall-health and threshold failures;
- reallocated, pending and offline-uncorrectable sectors;
- reported uncorrectable errors;
- command timeouts;
- UDMA CRC errors as cable/connector/backplane/transport evidence;
- temperature, power-on hours and power cycles;
- SMART error log and self-test log;
- SATA revision and current/max interface speed when exposed;
- explicit reserved-space attributes and their thresholds;
- vendor-normalized media-wear indicators when their meaning is sufficiently established.
For ATA, smartctl's synthesized top-level spare_available is not blindly treated as literal spare NAND remaining. Explicit reserve-space SMART attributes take precedence, and SMART 5 uses its RAW reallocated-sector count rather than its vendor-normalized VALUE.
USB is treated as a separate failure domain from the underlying SSD/HDD.
NVMe Doctor records the USB/SCSI path where available, including enclosure path, UAS versus usb-storage, SCSI attachment, and target-correlated USB/SCSI reset or I/O events. Native SSD PCIe ancestry hidden behind a USB bridge is reported as unavailable rather than invented.
Probe order is conservative and non-disruptive first:
- smartctl automatic device detection;
- explicit SAT mode;
- direct read-only USB SAT only as a last resort when explicitly/interaction-safely permitted.
Some bridges work with plain smartctl /dev/diskN but fail when -d sat is forced; automatic detection therefore comes first.
When smartctl cannot reach NVMe SMART through the enclosure, NVMe Doctor can use a direct read-only libusb backend for RTL9210. Interactive root checks may temporarily unmount/eject/capture the enclosure when safe; mounted media requires confirmation. Non-interactive direct access requires --direct-usb.
Optional slower RTL9210 Error Information/Firmware Slot reads require --usb-extra-logs.
Direct USB access is operationally disruptive even though the drive commands are read-only: close applications using the disk first. See docs/MACOS.md for the exact safety model and limitations.
| Capability | Linux | macOS |
|---|---|---|
| Native NVMe SMART | nvme-cli, fallback/supplemental smartctl |
smartctl when exposed |
| ATA/SATA SMART | smartctl |
smartctl |
| PCIe generation/width | sysfs | partial for native NVMe where exposed |
| PCIe AER | yes | unavailable |
| PCIe/NUMA ancestry | yes | limited |
| SATA link speed | smartctl when exposed |
smartctl when exposed |
| USB-to-SATA health | smartctl/SAT where supported | smartctl auto → SAT → conservative direct SAT fallback |
| USB-to-NVMe health | smartctl/SNT where supported | smartctl where exposed; RTL9210 direct backend available |
| Target-scoped OS events | kernel log | only when a stable target identity can be matched |
Capability disclosure is part of the diagnosis. Missing platform data does not become a zero/clean reading.
- Python 3.9+
The pre-built standalone itself has no pip runtime dependencies.
Depending on the drive/path:
# Debian/Ubuntu
sudo apt install nvme-cli smartmontools pciutilsUseful built-ins include sysfs, journalctl/dmesg, and udev metadata.
brew install smartmontoolsFor optional direct USB backends:
brew install libusbBuilt-in diskutil and, for native NVMe detail paths, system_profiler are used where appropriate.
The root nvme-doctor executable is pre-built.
Rebuild from src/:
./build.sh
# or
make buildInstall the existing pre-built artifact:
sudo ./install.sh install
# or
sudo make installBuild and install are intentionally separate. install.sh never rebuilds the program; if the root executable is missing, run ./build.sh first.
Default install location is /usr/local/bin/nvme-doctor; override with PREFIX/DESTDIR if required.
0— collection completed without warning/critical findings;1— warning/degraded result;2— critical result;3— evidence incomplete;4— command/input/collection error.
The assessment's human verdict (HEALTHY, DEGRADED, AT RISK, CRITICAL, INCOMPLETE) is intentionally richer than the process-status category used for exit codes.
The diagnostic commands do not format, sanitize, flash firmware, reset controllers, modify namespaces, change ASPM/APST settings, or write user data.
macOS direct USB backends may temporarily unmount/eject/capture an enclosure to issue read-only drive commands and then restore the original mount state. This is a deliberate exception to “non-disruptive collection” and requires appropriate permission.
Reports can contain drive serial numbers, hostnames, PCI addresses, USB paths, and log fragments. Review them before publishing.
See SECURITY.md for the concise security policy.
python3 -m venv .venv
. .venv/bin/activate
python3 -m pip install -e . pytest
python3 -m pytest
./build.sh
./nvme-doctor --versionAdditional design notes:
GPL-3.0-or-later. See LICENSE.