A board's spare thermistor inputs report whatever an open input reads, and the value rule from #5759 only catches the impossible ones. Once an hour, sensors.refresh judges each sensor against the host's CPU (Package id N, Tctl/Tdie/TccdN, PECI Agent N) over a week of hourly averages. A sensor is ruled out if it falls as the CPU warms (r <= -0.5) or holds within 0.5 °C while the CPU moved 10 °C or more. A judgment needs at least 72 shared hours. The host temperature leaves judged sensors out at ingest. The host page lists them under "Not counted", with each reason given once. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
75 lines
3.5 KiB
Python
75 lines
3.5 KiB
Python
"""Which temperature sensors are thermometers, judged from how each moves
|
||
against the host's CPU (#5783). VDNT-Util01's SYSTIN ran at r = −0.81
|
||
against its CPU package over a week; CPUTIN beside it at +0.83 (#5782)."""
|
||
|
||
import math
|
||
from datetime import datetime, timedelta, timezone
|
||
|
||
from plugins.host_agent import sensors as s
|
||
|
||
T0 = datetime(2026, 10, 8, tzinfo=timezone.utc)
|
||
|
||
|
||
def _hours(n, f):
|
||
"""{hour: f(i)} for n hourly buckets."""
|
||
return {T0 + timedelta(hours=i): f(i) for i in range(n)}
|
||
|
||
|
||
def cpu(i): # a CPU that swings 50-80 °C through the day
|
||
return 65 + 15 * math.sin(i / 24 * 2 * math.pi)
|
||
|
||
|
||
def test_a_sensor_that_falls_as_the_cpu_warms_is_not_a_thermometer():
|
||
ref = _hours(96, cpu)
|
||
systin = _hours(96, lambda i: 114 - 0.1 * (cpu(i) - 65)) # Util01's shape
|
||
why = s.judge(ref, systin)
|
||
assert why.startswith("falls as the CPU warms (r -1.00 over 96 h")
|
||
|
||
|
||
def test_a_sensor_that_holds_still_is_not_a_thermometer():
|
||
why = s.judge(_hours(96, cpu), _hours(96, lambda i: 110.0 + (0.1 if i % 2 else 0.0)))
|
||
assert why == "holds at 110 °C while the CPU moved 50–80 °C"
|
||
|
||
|
||
def test_a_wired_sensor_is_left_alone_even_when_it_barely_follows_the_cpu():
|
||
ref = _hours(96, cpu)
|
||
assert s.judge(ref, _hours(96, lambda i: 40 + 0.3 * (cpu(i) - 65))) is None # a board sensor
|
||
assert s.judge(ref, _hours(96, lambda i: 45 + 3 * math.sin(i))) is None # a drive, its own rhythm
|
||
|
||
|
||
def test_nothing_is_judged_without_enough_history_or_a_cpu_that_moved():
|
||
falls = lambda i: 114 - 0.1 * (cpu(i) - 65) # noqa: E731
|
||
assert s.judge(_hours(48, cpu), _hours(48, falls)) is None # 48 h < MIN_HOURS
|
||
idle = _hours(96, lambda i: 50 + (i % 3)) # moved 2 °C
|
||
assert s.judge(idle, _hours(96, lambda i: 110 - (i % 3))) is None
|
||
|
||
|
||
def test_the_cpu_reference_is_found_by_its_driver_names():
|
||
assert s.reference_label(["SYSTIN", "Core 0", "Package id 1", "Package id 0"]) == "Package id 0"
|
||
assert s.reference_label(["Tccd1", "Tctl", "Composite"]) == "Tctl"
|
||
assert s.reference_label(["PECI Agent 0", "SYSTIN"]) == "PECI Agent 0"
|
||
assert s.reference_label(["Composite", "temp1"]) is None
|
||
|
||
|
||
def test_hosts_are_judged_from_hourly_rows_and_a_host_without_a_cpu_reading_is_skipped():
|
||
rows = []
|
||
for i in range(96):
|
||
h = T0 + timedelta(hours=i)
|
||
rows += [("util:temp:Package id 0", h, cpu(i)),
|
||
("util:temp:SYSTIN", h, 114 - 0.1 * (cpu(i) - 65)),
|
||
("util:temp:CPUTIN", h, 40 + 0.3 * (cpu(i) - 65)),
|
||
("nas:temp:temp1", h, 110 - 0.1 * (cpu(i) - 65)),
|
||
("util", h, 1.0)] # not a sensor row
|
||
got = s.judge_hosts(rows)
|
||
assert list(got) == ["util"] and list(got["util"]) == ["SYSTIN"]
|
||
|
||
|
||
def test_a_judged_sensor_is_left_out_of_the_host_temperature_and_listed_with_why(monkeypatch):
|
||
from plugins.host_agent.routes import is_thermometer, split_temps
|
||
monkeypatch.setattr(s, "_unwired", {"util": {"temp3": "falls as the CPU warms (r -0.81 over 168 h)"}})
|
||
assert not is_thermometer("util", "temp3", 62.0) # a plausible value, still not a thermometer
|
||
assert is_thermometer("other", "temp3", 62.0)
|
||
real, unwired = split_temps("util", {"temp3": 62.0, "Package id 0": 55.0})
|
||
assert list(real) == ["Package id 0"]
|
||
assert unwired == {"temp3": (62.0, "falls as the CPU warms (r -0.81 over 168 h)")}
|