fix(host_agent): an unwired sensor no longer sets a host's temperature (#5759)

#5759 "VDNT-Util01 reads 114 °C: its "hottest sensor" is a Super-I/O input
with nothing wired to it". AUXTIN0/1 on the board's Nuvoton chip read
110-112 °C flat at every load while the CPU package moved 49-87, so
temp_c_max read critical all week. The max now counts only readings that
can be real: not an AUXTIN input, above 0 and below 125 °C. Each sensor
keeps its own temp_c row.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-14 17:09:48 -04:00
co-authored by Claude Opus 5.5
parent c7ff5bc556
commit b3885c3b81
2 changed files with 29 additions and 2 deletions
+14 -2
View File
@@ -90,6 +90,17 @@ def pick_host_address(current: str | None, reported: str | None) -> str | None:
return None return None
def counts_as_hottest(label: str, celsius: float) -> bool:
"""Whether a sensor's reading may set the host's temperature (#5759).
A Super-I/O chip's AUXTIN inputs are spare thermistor pins, usually left
unconnected, and a floating one reads a junk constant (VDNT-Util01: 110 °C
flat at every load while its CPU package moved 49-87). No running host's
sensor reads 0 or below, and chips shut down before 125. Each sensor's own
row is kept either way, so a wired AUXTIN can still be charted by name."""
return 0 < celsius < 125 and not label.upper().startswith("AUXTIN")
def _expand_sample_to_metrics( def _expand_sample_to_metrics(
sample: dict, host_name: str, recorded_at: datetime sample: dict, host_name: str, recorded_at: datetime
) -> list[PluginMetric]: ) -> list[PluginMetric]:
@@ -186,14 +197,15 @@ def _expand_sample_to_metrics(
row("disk_read_bps", host_name, dr_total) row("disk_read_bps", host_name, dr_total)
row("disk_write_bps", host_name, dw_total) row("disk_write_bps", host_name, dw_total)
# Temperatures — per sensor plus the host max for at-a-glance. # Temperatures — per sensor plus the host max for at-a-glance. The max
# counts only readings that can be real (#5759).
max_temp: float | None = None max_temp: float | None = None
for t in sample.get("temps") or []: for t in sample.get("temps") or []:
c = t.get("celsius") c = t.get("celsius")
if c is None: if c is None:
continue continue
row("temp_c", f"{host_name}:temp:{t['label']}", c) row("temp_c", f"{host_name}:temp:{t['label']}", c)
if max_temp is None or c > max_temp: if counts_as_hottest(str(t["label"]), c) and (max_temp is None or c > max_temp):
max_temp = c max_temp = c
if max_temp is not None: if max_temp is not None:
row("temp_c_max", host_name, max_temp) row("temp_c_max", host_name, max_temp)
@@ -78,3 +78,18 @@ def test_every_resource_is_the_host_or_a_host_colon_part():
resources = {r.resource_name for r in _expand_sample_to_metrics(sample, "web01", datetime.now(timezone.utc))} resources = {r.resource_name for r in _expand_sample_to_metrics(sample, "web01", datetime.now(timezone.utc))}
assert len(resources) > 5 assert len(resources) > 5
assert {r for r in resources if r != "web01" and not r.startswith("web01" + sep)} == set() assert {r for r in resources if r != "web01" and not r.startswith("web01" + sep)} == set()
def test_a_sensor_with_nothing_wired_to_it_does_not_set_the_host_s_temperature():
"""#5759: VDNT-Util01's AUXTIN0 read 110 °C flat while its CPU package
moved 49-87, so the host read critical all week. Every sensor keeps its
own row; only readings that can be real set the max."""
sample = {"ts": "2026-10-14T21:00:00+00:00",
"temps": [{"label": "AUXTIN0", "celsius": 110.0},
{"label": "PCH_CHIP_CPU_MAX_TEMP", "celsius": 0.0},
{"label": "AUXTIN3", "celsius": -1.0},
{"label": "Package id 0", "celsius": 60.0}]}
idx = _index(_expand_sample_to_metrics(sample, "util", datetime.now(timezone.utc)))
assert idx[("temp_c_max", "util")] == 60.0
assert idx[("temp_c", "util:temp:AUXTIN0")] == 110.0
assert idx[("temp_c", "util:temp:PCH_CHIP_CPU_MAX_TEMP")] == 0.0