feat(host_agent): the heat alert says which sensor set the temperature

"Host running hot" read the host's hottest sensor and didn't name it, so the
operator couldn't tell what hit 100 °C on VDNT-Util01. The alert email now
names the sensor and its reading, through the same names_behind a count uses.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-30 19:04:34 -04:00
co-authored by Claude Opus 5.5
parent e51d969c43
commit 4cce3f0d12
4 changed files with 36 additions and 3 deletions
+21
View File
@@ -122,6 +122,27 @@ def is_thermometer(host_name: str, label: str, celsius: float) -> bool:
return label not in sensors.unwired(host_name) and counts_as_hottest(label, celsius)
def hottest_sensor(host_name: str, temps: dict[str, float]) -> list[str]:
"""Which sensor sets a host's temperature, as an alert on it says:
["Package id 0 at 86°C"], or [] with no thermometer among `temps`."""
from steward.core.formatting import metric_value
real = {label: c for label, c in temps.items() if is_thermometer(host_name, label, c)}
if not real:
return []
label = max(real, key=real.get)
return [f"{label} at {metric_value('temp_c', real[label])}"]
async def name_hottest_sensor(session, resource: str) -> list[str]:
"""The sensor behind temp_c_max on host `resource` right now."""
from steward.models.metrics import PluginMetricLatest
prefix = f"{resource}:temp:"
rows = (await session.execute(select(PluginMetricLatest.resource_name, PluginMetricLatest.value).where(
PluginMetricLatest.source_module == "host_agent", PluginMetricLatest.metric_name == "temp_c",
PluginMetricLatest.resource_name.startswith(prefix, autoescape=True)))).all()
return hottest_sensor(resource, {name[len(prefix):]: value for name, value in rows})
def split_temps(host_name: str, temps: dict) -> tuple[dict, dict]:
"""(real, unwired): a host's sensors, hottest first, apart from the ones
is_thermometer leaves out. Unwired ones map to (reading, why); they read