fix(host_agent): SYSTIN and other board inputs past 100 °C don't set the temperature (#5759)
CI / lint (push) Successful in 2s
CI / integration (push) Successful in 1m21s
CI / unit (push) Successful in 1m26s
CI / publish (push) Successful in 17s

#5759 "VDNT-Util01 reads 114 °C: its "hottest sensor" is a Super-I/O input
with nothing wired to it". b3885c3 left out AUXTIN*, but Util01's SYSTIN
reads 114 °C too, so temp_c_max stayed at 114. A board's Super-I/O inputs
(SYSTIN, CPUTIN, AUXTIN*, PCH_*) now count only below 100 °C: a wired one
reads the board, far below that, and still counts on other boards.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-14 17:26:02 -04:00
co-authored by Claude Opus 5.5
parent b6c4ce510d
commit 82a2a3d41f
2 changed files with 26 additions and 7 deletions
+16 -6
View File
@@ -3,6 +3,7 @@ from __future__ import annotations
import asyncio import asyncio
import hashlib import hashlib
import re
import time import time
from datetime import datetime, timezone from datetime import datetime, timezone
from typing import Any from typing import Any
@@ -90,15 +91,24 @@ def pick_host_address(current: str | None, reported: str | None) -> str | None:
return None return None
# A board's Super-I/O inputs (Nuvoton/ITE: SYSTIN, CPUTIN, AUXTIN0-4, PCH_*)
# are thermistor pins that are often left unwired, and a floating one reads a
# junk constant. Wired, they read the board, which runs far below this.
_BOARD_INPUT = re.compile(r"^(SYSTIN|CPUTIN|AUXTIN\d*|PCH_)", re.IGNORECASE)
BOARD_INPUT_CEILING_C = 100
def counts_as_hottest(label: str, celsius: float) -> bool: def counts_as_hottest(label: str, celsius: float) -> bool:
"""Whether a sensor's reading may set the host's temperature (#5759). """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 VDNT-Util01's SYSTIN read 114 °C and AUXTIN0-4 105-112, flat at every
unconnected, and a floating one reads a junk constant (VDNT-Util01: 110 °C load, while its CPU package moved 49-87 and CPUTIN read 49: unwired
flat at every load while its CPU package moved 49-87). No running host's inputs. A board input at or past 100 °C is one of those; below it, it
sensor reads 0 or below, and chips shut down before 125. Each sensor's own counts. No running host's sensor reads 0 or below, and chips shut down
row is kept either way, so a wired AUXTIN can still be charted by name.""" before 125. Each sensor's own row is kept either way."""
return 0 < celsius < 125 and not label.upper().startswith("AUXTIN") if not 0 < celsius < 125:
return False
return not (_BOARD_INPUT.match(label) and celsius >= BOARD_INPUT_CEILING_C)
def _expand_sample_to_metrics( def _expand_sample_to_metrics(
@@ -85,7 +85,8 @@ def test_a_sensor_with_nothing_wired_to_it_does_not_set_the_host_s_temperature()
moved 49-87, so the host read critical all week. Every sensor keeps its 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.""" own row; only readings that can be real set the max."""
sample = {"ts": "2026-10-14T21:00:00+00:00", sample = {"ts": "2026-10-14T21:00:00+00:00",
"temps": [{"label": "AUXTIN0", "celsius": 110.0}, "temps": [{"label": "SYSTIN", "celsius": 114.0},
{"label": "AUXTIN0", "celsius": 110.0},
{"label": "PCH_CHIP_CPU_MAX_TEMP", "celsius": 0.0}, {"label": "PCH_CHIP_CPU_MAX_TEMP", "celsius": 0.0},
{"label": "AUXTIN3", "celsius": -1.0}, {"label": "AUXTIN3", "celsius": -1.0},
{"label": "Package id 0", "celsius": 60.0}]} {"label": "Package id 0", "celsius": 60.0}]}
@@ -104,3 +105,11 @@ def test_stalled_mounts_are_counted_and_have_no_figures():
assert idx[("mounts_stalled", "d3")] == 1 assert idx[("mounts_stalled", "d3")] == 1
assert idx[("disk_used_pct_worst", "d3")] == 50.0 assert idx[("disk_used_pct_worst", "d3")] == 50.0
assert ("disk_used_pct", "d3:/nfs/media") not in idx assert ("disk_used_pct", "d3:/nfs/media") not in idx
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
assert counts_as_hottest("SYSTIN", 38.0) and counts_as_hottest("AUXTIN1", 60.0)
assert not counts_as_hottest("SYSTIN", 114.0) and not counts_as_hottest("PCH_CHIP_TEMP", 0.0)
assert counts_as_hottest("Package id 0", 101.0) # a CPU past 100 is a hot CPU