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
@@ -146,3 +146,19 @@ def test_build_sample_first_call_has_no_rates_but_keeps_state(monkeypatch):
assert s1["net"] == [] and s1["diskio"] == [] # no prior counters yet
assert state["mono"] is not None
assert state["net"] == {"eth0": (1000, 500)}
def test_collect_temps_says_which_chip_each_sensor_is_on(tmp_path, monkeypatch):
nvme = tmp_path / "hwmon1"
nvme.mkdir()
(nvme / "name").write_text("nvme\n")
(nvme / "temp3_input").write_text("49850\n")
(nvme / "temp3_label").write_text("Sensor 2\n")
bare = tmp_path / "hwmon2"
bare.mkdir()
(bare / "name").write_text("acpitz\n")
(bare / "temp1_input").write_text("27800\n")
monkeypatch.setattr(agent, "HWMON_DIR", str(tmp_path))
got = sorted(agent.collect_temps(), key=lambda t: t["label"])
assert got == [{"label": "Sensor 2", "celsius": 49.9, "chip": "nvme"},
{"label": "acpitz_temp1", "celsius": 27.8, "chip": "acpitz"}]
@@ -114,6 +114,24 @@ def test_the_heat_alert_names_the_sensor_that_set_it():
assert hottest_sensor("box", {"AUXTIN0": 110.0}) == []
def test_the_hottest_sensor_is_named_by_its_chip_once_the_agent_says_it():
"""An NVMe drive's "Sensor 2" alone doesn't say it's the drive."""
from plugins.host_agent.routes import hottest_sensor
sample = {"ts": "2026-06-16T00:00:00+00:00",
"temps": [{"label": "Sensor 2", "celsius": 49.0, "chip": "nvme"},
{"label": "Package id 0", "celsius": 41.0, "chip": "coretemp"},
{"label": "nct6798_temp9", "celsius": 30.0, "chip": "nct6798"},
{"label": "fan_ic", "celsius": 35.0, "chip": "mystery9"}]}
idx = _index(_expand_sample_to_metrics(sample, "chipbox", datetime.now(timezone.utc)))
assert idx[("temp_c", "chipbox:temp:Sensor 2")] == 49.0 # keyed by label alone
temps = {"Sensor 2": 49.0, "Package id 0": 41.0}
assert hottest_sensor("chipbox", temps) == ["NVMe · Sensor 2 at 49°C"]
assert hottest_sensor("chipbox", {"Package id 0": 41.0}) == ["CPU · Package id 0 at 41°C"]
assert hottest_sensor("chipbox", {"nct6798_temp9": 30.0}) == ["nct6798_temp9 at 30°C"]
assert hottest_sensor("chipbox", {"fan_ic": 35.0}) == ["mystery9 · fan_ic at 35°C"]
assert hottest_sensor("otherbox", temps) == ["Sensor 2 at 49°C"] # an agent that never said
def test_a_wired_board_input_still_counts():
"""#5759: below 100 °C a board input is a real reading."""
from plugins.host_agent.routes import counts_as_hottest