4.3 KiB
6. Collector acquisition path: smartctl counters, sysfs identity
Status
Accepted — resolves Choose the collector's NVMe acquisition path on the Wayfinder map.
Context
The collector (ADR 0003) must acquire SMART/Health counters, thermal evidence, and controller identity each run. The controller-identity research fixed the identity key to the normalized, kernel-exposed subsystem NQN and warned that normalization must be specified once and applied at write time — or a collector implementation change can split a drive's own history. ADR 0004 pins exact Python dependencies in a dedicated venv, and the segment-metadata decision froze nullable vid/ssvid/transport alongside the identity fields. Three first-party paths were candidates: the official libnvme Python bindings (SWIG; sysfs-backed attribute getters delivering normalized values), nvme CLI JSON output, and the incumbent smartctl -j plus sysfs reads.
Decision
- Pin. Every collection run acquires counters and thermal evidence solely from
smartctl -a -j <device>and controller identity (subnqn,sn,mn,fr,transport) solely from sysfs (/sys/class/nvme/<ctrl>/). No other acquisition path exists anywhere in the codebase. - Hard pin, no fallback. Any acquisition failure — missing binary, nonzero exit, malformed JSON, unreadable sysfs attribute — fails the whole collection run; ADR 0005's flat retry and freshness grading absorb the miss. A partial sample (identity without counters, or counters without identity) is never written: a transient read failure must not push a healthy drive down the degraded-identity path.
- Normalization once, at write time. One collector-side function normalizes every identity field: trailing spaces and newlines stripped, no case folding, empty-after-strip stored blank.
smartctlcounter and thermal fields are consumed as-is (smartmontools already trims the strings it copies). Padded and unpadded renderings of the same field therefore yield byte-identical stored values. - Segment metadata sourcing.
transportcomes from the NVMe class sysfs directory;vid/ssvidfrom the PCI node (/sys/class/nvme/<ctrl>/device/{vendor,subsystem_vendor}) when present, null otherwise — metadata only, never key components. - Prerequisites.
make installverifiessmartctlis present and fails cleanly otherwise. The acquisition path adds no Python dependency and no OS package beyond smartmontools; the ADR 0004 lockfile is untouched.
Considered options
- libnvme Python bindings — the purest API and natively-normalized getters, but the SWIG module is not on PyPI: entering the venv requires the distro's
python3-libnvmethrough--system-site-packagesor a from-source build, coupling the exact-lockfile venv to the system Python and the distro's shipping choices. Rejected on dependency weight for one privileged five-minute oneshot. nvmeCLI JSON — one binary covers counters and identity, but it adds an OS package for what smartmontools already provides, emits untrimmed strings, and reportssubnqnfrom Identify data rather than the kernel: when a controller reports an empty NQN the kernel synthesizes one for sysfs whileid-ctrlJSON omits the field, so the identity ladder would drop a rung depending on the drive. Rejected on packaging and identity-key consistency.
Consequences
- The venv stays pure-Python; the two acquisition channels per run (subprocess JSON plus sysfs reads) hide behind one acquisition function, gated by acceptance criteria AC-1–AC-5.
- Identity is read from exactly the source the identity key names; libnvme's getters wrap the same sysfs attributes, so the values agree byte-for-byte where both exist.
- Switching acquisition path later is history-sensitive: a future path must deliver byte-identical normalized identity values, or the change itself forces a controller-segment boundary.