feat(hosts): the hottest sensor is named with its chip, and the host-reading cutoffs are read and set over MCP (#6806)
CI / lint (push) Successful in 1s
CI / unit (push) Successful in 1m39s
CI / integration (push) Successful in 2m2s
CI / publish (push) Successful in 1m12s

Agent 1.27.0 sends each temperature sensor's hwmon chip. Steward keeps the
chip per host and label beside the reading, so a sensor's metric key stays
its label, and the strip and the heat alert name it as "NVMe · Sensor 2"
rather than a bare "Sensor 2".

get_thresholds (viewer) and set_thresholds (admin, audited) read and change
the warn/crit cutoffs CPU, memory, disk, load and temperature are graded by,
as Settings → Thresholds does.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-11-03 17:39:31 -05:00
co-authored by Claude Opus 5.5
parent 6eeb4039d7
commit 38ae99fb8e
9 changed files with 273 additions and 9 deletions
+45
View File
@@ -53,6 +53,51 @@ def unwired(host_name: str) -> dict[str, str]:
return _unwired.get(host_name, {})
# {host name: {sensor label: hwmon chip}}, as the host's latest sample said.
# A sensor's metric is keyed by its label alone; the chip only names it.
_chips: dict[str, dict[str, str]] = {}
# What each hwmon driver measures, in the words a page uses. A driver not
# listed is shown by its own name.
_CHIP_WORDS = (
(re.compile(r"^(coretemp|k10temp|zenpower|cpu_thermal)$"), "CPU"),
(re.compile(r"^nvme$"), "NVMe"),
(re.compile(r"^drivetemp$"), "Drive"),
(re.compile(r"^(nct\d+|it\d+|w83\w+|f71\w+)$"), "Board"),
(re.compile(r"^pch_\w+$"), "Chipset"),
(re.compile(r"^(amdgpu|radeon|nouveau|i915|xe)$"), "GPU"),
(re.compile(r"^(spd5118|jc42)$"), "Memory"),
(re.compile(r"^iwlwifi"), "Wi-Fi"),
(re.compile(r"^acpitz$"), "ACPI"),
)
def note_chips(host_name: str, temps) -> None:
"""Remember which chip each of a host's sensors is on, from a sample's
temps. An agent that doesn't say leaves what is known as it is."""
seen = {str(t["label"]): str(t["chip"]) for t in temps or [] if t.get("chip")}
if seen:
_chips[host_name] = seen
def chip_word(chip: str) -> str:
"""What a hwmon chip measures ("nvme" → "NVMe"), or its own name."""
for pattern, word in _CHIP_WORDS:
if pattern.match(chip):
return word
return chip
def sensor_name(host_name: str, label: str) -> str:
"""A sensor as a page names it: its chip, then its label, as in
"NVMe · Sensor 2". The label alone when the chip isn't known or the
label already carries it ("nvme_temp1")."""
chip = _chips.get(host_name, {}).get(label)
if not chip or label.lower().startswith(chip.lower()):
return label
return f"{chip_word(chip)} · {label}"
def reference_label(labels) -> str | None:
"""The sensor that reads the host's CPU, or None when it has none."""
labels = sorted(labels)