Files
mesh-catalog/modules/sensors
jochen 73610f8e3c
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
sensors: read the machine's hardware condition on every machine (hq ADR 0256)
Temperatures against the hardware's own thresholds, fans, each drive's health and the kernel's error
counts, read-only. A root watch reads SMART so the tools need no root and never wake a sleeping drive;
smartd and the watch carry the health that raises a condition when the hardware says it is failing.
2026-10-08 13:48:53 +02:00
..

sensors

The condition of a machine's own hardware: temperatures with the thresholds the hardware gives for itself, fans, each drive's health, and the kernel's counts of memory and PCIe errors (novox/hq ADR 0256). It reads, and changes nothing on the hardware. Every flag is measured against the hardware's own threshold, never one the mesh chose. A class the machine does not have is said as not present, never as an error, so the module can be assigned to every machine: a laptop, a desktop, a server, a virtual machine.

Why it exists, and why here

The operator set the boundary on 2026-10-08. What hardware a machine has is a node-engine fact. A module holding a generic role reads and provides it. A vendor module adds only vendor-specific extras.

Before this module, only the laptop's vendor module read a temperature, and no module read a drive's health. The machines' 20 drives, 15 of them spinning, were watched by nothing. power keeps the battery and the power draw. disk-load keeps how busy the disks are. This module keeps everything else the kernel says about the hardware's condition.

It holds no seat. A seat is a role that callers reach without knowing the module, and nothing asks for this role yet. A new seat would also be a controller change first (ADR 0246). When a caller needs to ask by role, such as the bar's temperature blocks or a vendor module's fan policy, these tools become the seat's verbs. The full reasoning is in ADR 0256.

What runs

resource what as
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 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

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.

Tools

All six only read.

tool what replaces
sensors_summary one line per finding, flagged CRITICAL, HIGH, FAILING or STOPPED; then one line per class: temperatures with the hottest, fans, drives, memory and PCIe errors sensors, smartctl -H
sensors_temperatures every thermal zone with its trip points, and every hwmon temperature with its label, its kind (cpu, gpu, drive, board, network), the drive it sits in, and its own maximum and critical values sensors
sensors_fans every fan's RPM, its chip's minimum and alarm, and whether the watch saw it turn since boot sensors
sensors_drives each drive: model, serial, size, whether it spins, the SMART verdict; for NVMe the critical warning bits, spare, endurance used and media errors; for SATA the reallocated, pending and unreadable sectors, the error log and the last self-test smartctl -a, nvme smart-log
sensors_faults EDAC memory errors per controller and module, PCIe AER errors per device, and how often an Intel CPU was slowed for heat edac-util
sensors_health the module's health check: healthy or not, with why

The flags

flag when
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
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.

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.

Health

The module declares its health under ADR 0240:

  • smartd has the unit kind: the unit's own readiness, which the module does not judge itself.
  • The watch has the tool kind: sensors_health, every 60 s, unhealthy after 2 looks in a row, with a 60 s grace.

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.

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.

The condition this raises is module.sensors.<machine>.unhealthy. ADR 0256 records what this costs:

  • the condition names the module, not the machine;
  • a standing hardware fault holds this module's own new builds at their gate on that machine.

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:

  • 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;
  • a virtual machine with no sensor at all.

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;
  • 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;
  • 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;
  • 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, and the two health checks.

Follow-ups

  • The machine's hardware judged by the node-engine, raising a machine condition, instead of this module's health (ADR 0256).
  • The module-health condition carrying the tool check's why as its plain words (ADR 0253).
  • Temperature blocks for the bar, once there is a generic way for a module to provide bar blocks.
  • The laptop vendor module's zephyrus_thermals keeps only what is the vendor's.