Files
Fenris/docs/adr/0006-collector-acquisition-path.md
T

4.3 KiB
Raw Blame History

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

  1. 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.
  2. 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.
  3. 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. smartctl counter 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.
  4. Segment metadata sourcing. transport comes from the NVMe class sysfs directory; vid/ssvid from the PCI node (/sys/class/nvme/<ctrl>/device/{vendor,subsystem_vendor}) when present, null otherwise — metadata only, never key components.
  5. Prerequisites. make install verifies smartctl is 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-libnvme through --system-site-packages or 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.
  • nvme CLI JSON — one binary covers counters and identity, but it adds an OS package for what smartmontools already provides, emits untrimmed strings, and reports subnqn from Identify data rather than the kernel: when a controller reports an empty NQN the kernel synthesizes one for sysfs while id-ctrl JSON 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.