sensors: raise only what the hardware says is failing, and say a hung watch
mesh/merge-gate pass: builds new: modules/sensors, sent nowhere; no bus step; every machine composes with the change as it did without (4 of 4 compose)
mesh/repo-check pass: its merge-check.sh passed
mesh/delivery superseded: a newer head of the same pull request

The review of #129 found false alarms and a silent failure: the NVMe temperature bit, old-age
attributes, a fan's minimum or latched alarm, an unrelated hot sensor and a board chip's limits could
raise a condition, while a hung smartctl stopped the fans' looks and an old readings file passed as
healthy. Read spinning drives hourly (smartd too) so a read never keeps one from spinning down, and skip
a drive that cannot say whether it sleeps.
This commit is contained in:
jochen
2026-10-08 14:35:16 +02:00
parent 73610f8e3c
commit 66a32b196d
13 changed files with 689 additions and 157 deletions
+60 -29
View File
@@ -28,13 +28,29 @@ seat's verbs. The full reasoning is in ADR 0256.
|---|---|---|
| `smartmontools` | the package: `smartctl` and `smartd` | |
| `/etc/smartd.conf` | every drive (`DEVICESCAN`), every check, never waking a drive in standby, no mail | written whole |
| `smartd.service` drop-in | runs on a virtual machine too, and stays up with no drive to watch (`-q never`) | written whole |
| `smartd.service` drop-in | runs on a virtual machine too, stays up with no drive to watch (`-q never`), and checks each drive hourly (`-i 3600`) | written whole |
| `smartd` | watches every drive between looks, and logs what it finds to the journal | root, the package's unit |
| `watch` (`sensors watch`) | every 10 min each drive's health with `smartctl --json -a -n standby`; every 30 s which fans turn; one file, `/run/mesh-sensors/readings.json` (0644) | root, the node-engine's process |
| the tools | read `/sys`, `/proc` and the watch's file | the operator account, in the tool runner |
| `watch` (`sensors watch`) | each drive's health with `/usr/bin/smartctl --json -a -n standby,2,3`: a spinning drive hourly, any other every 10 min; every 30 s which fans turn; one file, `/run/mesh-sensors/readings.json` (0644, in a 0755 directory) | root, the node-engine's process |
| the tools | read `/sys`, `/proc` and the watch's file, once per call | the operator account, in the tool runner |
The tools need no root, because the watch has already read what only root may read. A drive in standby
is never woken. The watch keeps its last reading of that drive and says that it was asleep.
The tools need no root, because the watch has already read what only root may read.
**The drives never hold up the fans.** The watch reads the drives beside its looks at the fans, one pass
at a time. Each smartctl is bounded by 20 s, plus 2 s for a child that keeps its output open. The file is
written every 30 s whatever a drive does, so a file older than five minutes means the watch is hung or
gone, and the health check says so.
**A drive is never woken, and is read seldom enough to sleep.**
- smartctl's `-n standby,2,3` stops with status 2 and the drive's power mode when the drive is in standby
or asleep. The watch then keeps the reading before and says the drive was asleep. A drive is asleep only
on status 2 with a parsed power mode (or smartctl's own "STANDBY (OS)").
- It stops with status 3 when the drive cannot say (a USB bridge that does not pass CHECK POWER MODE on).
Such a drive is not read, so that reading it never wakes it, and it is said as not read.
- Reading SMART from an awake drive can restart its standby timer, so a drive read more often than its
timer is set for never spins down. No mesh tool reads a drive's standby timer, so whether the servers'
drives spin down is not known. A spinning drive is therefore read hourly, by the watch and by smartd.
A drive whose timer is set longer than an hour may still be kept awake.
- smartd has no per-drive equivalent of status 3. A drive behind such a bridge may be woken by smartd.
## Tools
@@ -55,20 +71,26 @@ All six only read.
|---|---|
| temperature HIGH | at or above its own maximum, or a zone's `hot` trip (without one, its lowest `passive` trip) |
| temperature CRITICAL | at or above its own critical or emergency value, or a zone's `critical` trip |
| drive FAILING | SMART's overall verdict failed, an attribute is failing now, or an NVMe critical warning bit is set |
| drive HIGH | reallocated, pending or unreadable sectors; an attribute that failed in the past; NVMe media errors or 90 % of rated endurance used; errors in the error log; a last self-test that did not pass |
| fan STOPPED | its chip's alarm, or under its own minimum, once it has been seen turning; or it ran since boot and reads 0 while a temperature is at or above its own maximum |
| drive FAILING | SMART's overall verdict failed (SATA), a pre-failure attribute is failing now, or an NVMe critical warning bit other than temperature is set |
| drive HIGH | an old-age attribute failing now; an attribute that failed in the past; the NVMe temperature bit alone; reallocated, pending or unreadable sectors; NVMe media errors or 90 % of rated endurance used; errors in the error log; a last self-test that did not pass |
| fan STOPPED | it ran since boot, reads 0 now, and the CPU, the board or a sensor on its own chip is at or above its own maximum |
| fan note | under its chip's minimum, or its chip's alarm bit set: said, never flagged |
| memory HIGH / CRITICAL | corrected / uncorrectable errors since boot |
| PCIe HIGH | fatal or non-fatal errors since boot |
**What is no threshold.** A threshold given as the format's largest value is no threshold. The laptop's
NVMe drive gives 65261.85 °C as the maximum of two of its sensors. A threshold of 0 is no threshold
either, and neither is anything outside −40 to 200 °C. The reading is then said without a flag. A CPU
sensor that gives no threshold, such as AMD's `Tctl`, is never flagged.
**What is no threshold, and no reading.**
- A threshold given as the format's largest value is no threshold. The laptop's NVMe drive gives
65261.85 °C as the maximum of two of its sensors.
- A threshold of 0 is no threshold either, and neither is anything outside −40 to 200 °C. The reading
is then said without a flag.
- A CPU sensor that gives no threshold, such as AMD's `Tctl`, is never flagged.
- 0 °C is a reading. 127 and 127.5 °C are not: they are what a board chip's input with nothing on it
answers (a phantom AUXTIN). Such a sensor is said as not read.
**Why a fan at 0 is not enough.** Laptop fans and GPU fans in zero-fan mode stop on purpose when the
machine is cool. A board header with nothing on it reads 0, and may carry an alarm or a minimum that the
firmware set. So a fan is judged only once it has been seen turning.
**Why a fan at 0, a minimum or an alarm is not enough.** Laptop fans and GPU fans in zero-fan mode stop on
purpose when the machine is cool, under the minimum their chip gives. A board chip latches an alarm bit,
and a header with nothing on it reads 0. Only a fan that ran and stopped while what it cools is hot is a
fault. A hot drive or network adapter says nothing of a fan.
## Health
@@ -80,15 +102,18 @@ The module declares its health under ADR 0240:
`sensors_health` is unhealthy when:
- a temperature is at or above its own critical value;
- a drive says it is failing;
- a fan that ran has stopped while the machine is hot;
- memory reports an uncorrectable error.
- a CPU, GPU, drive or zone temperature is at or above its own critical value. A board chip's (Super I/O)
critical value is shown CRITICAL by the tools and never makes the module unhealthy: its limits are
whatever the firmware left;
- a drive says it is failing (the FAILING row above);
- a fan is STOPPED (the row above);
- memory reports an uncorrectable error;
- the watch has written no readings since the machine started, or none for five minutes.
Its `why` is one plain sentence per finding, for the operator. For example: "The drive sdb (WDC
WUH722222ALE6L4) says it is failing: its overall SMART health check failed." HIGH readings raise nothing.
A drive whose health was not read is said in `why` and does not make the check unhealthy. The watch not
running is caught by the watch's own liveness.
WUH722222ALE6L4) says it is failing: its overall SMART health check failed." HIGH readings and fan notes
raise nothing. A drive whose health was not read (asleep since boot, or behind a bridge) is said in `why`
and does not make the check unhealthy.
The condition this raises is `module.sensors.<machine>.unhealthy`. ADR 0256 records what this costs:
@@ -100,7 +125,7 @@ The machine judging its own hardware, as it judges its network, is the follow-up
## Tests
`go test ./...`. The tests read a tree standing in for `/sys`, `/proc` and `/run`, and smartctl answers
recorded as smartmontools 7 writes them. There are three machines:
as smartmontools 7.5 writes them (its standby answer as its source sets it). There are three machines:
- a laptop, as the laptop showed itself on 2026-10-08;
- a server with ECC memory, three SATA drives read by drivetemp and a board chip with an empty fan header;
@@ -110,14 +135,20 @@ They cover:
- thresholds taken from the hardware only, and a zone's hwmon copy left out;
- HIGH at the maximum and CRITICAL at the critical value; a zone's passive trip as its high;
- a sensor that gives no reading said as not read;
- 0 °C as a reading, a phantom 127 °C as not read, and a board chip's critical value raising nothing;
- every class not present on the virtual machine, which is healthy;
- a failing SATA drive, an NVMe critical warning, and early signs that are HIGH and raise nothing;
- a missing, stale or earlier-boot reading said as such;
- a fan at rest, a fan that stopped while hot, an empty header, and a fan under its minimum;
- a failing SATA drive; an NVMe critical warning; the NVMe temperature bit alone as HIGH; NVMe without
`smart_status`; an old-age attribute failing now and a past failure as HIGH;
- a missing, stale or earlier-boot reading, and the watch's staleness making the module unhealthy;
- a fan at rest, a fan that stopped while the board is hot, a hot drive saying nothing of a fan, a hot
sensor on the fan's own chip, an empty header, a zero-fan GPU under its minimum, and a latched alarm;
- memory and PCIe errors, and CPU throttling;
- the watch reading as root without waking a drive, keeping a reading for a drive asleep, reading drives
only every 10 min, and keeping what it saw across a restart;
- the watch reading as root without waking a drive: smartctl's real standby answer, an open failure
that is not sleep, and a bridge without a power mode;
- a spinning drive read hourly and a solid-state one every 10 min;
- a smartctl that hangs with a child holding its output, bounded, and the fans looked at and written
while a drive hangs;
- a restart keeping what the last watch read; the readings directory given its mode;
- a watch that is not root asking through `sudo -n`, and saying what stops it;
- the health answer in the shape the node-engine reads;
- the manifest: the tools listed are the tools served, all read-only, no seat claimed, no machine named,