fix(host_agent): sensors are judged from the raw week, not only its rollups (#5783)
Rollups exist only for hours older than the raw window (7 days by default), so the first deploy read an empty week and judged nothing. refresh now averages the raw samples by the hour and adds any rolled-up hours in the window. Sensors are named from plugin_metric_latest, so each series is read through its index instead of a pattern over every host_agent row. An integration test covers both halves against Postgres. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -164,3 +164,50 @@ def test_history_merges_hourly_and_raw(app):
|
||||
assert 42.0 in vals, "expected the rolled-up hourly point"
|
||||
assert 77.0 in vals, "expected the recent raw point"
|
||||
assert cpu == sorted(cpu, key=lambda p: p[0]), "series must be time-ordered"
|
||||
|
||||
|
||||
@_NEEDS_DB
|
||||
def test_sensors_are_judged_from_raw_hours_and_rollups_together(app):
|
||||
"""The live instance keeps raw samples for 7 days and rolls up only what
|
||||
is older, so a week of hourly rollups alone is empty: the first deploy of
|
||||
#5783 judged nothing. refresh reads both, by sensor name."""
|
||||
import math
|
||||
|
||||
from sqlalchemy import text
|
||||
from steward.models.metrics import PluginMetric, PluginMetricHourly, PluginMetricLatest
|
||||
from plugins.host_agent import sensors
|
||||
|
||||
now = datetime.now(timezone.utc).replace(minute=30, second=0, microsecond=0)
|
||||
host = "sensor-host-" + uuid.uuid4().hex[:8]
|
||||
|
||||
def cpu(i):
|
||||
return 65 + 15 * math.sin(i / 24 * 2 * math.pi)
|
||||
|
||||
series = {"Package id 0": cpu, "SYSTIN": lambda i: 114 - 0.1 * (cpu(i) - 65),
|
||||
"CPUTIN": lambda i: 40 + 0.3 * (cpu(i) - 65)}
|
||||
|
||||
async def _go():
|
||||
async with app.db_sessionmaker() as s:
|
||||
async with s.begin():
|
||||
for tbl in ("plugin_metrics", "plugin_metrics_hourly", "plugin_metric_latest"):
|
||||
await s.execute(text(f"DELETE FROM {tbl} WHERE resource_name LIKE :p"), {"p": host + "%"})
|
||||
for label, f in series.items():
|
||||
res = f"{host}:temp:{label}"
|
||||
for i in range(100): # 100 h back; the oldest 30 rolled up
|
||||
at = now - timedelta(hours=100 - i)
|
||||
if i < 30:
|
||||
s.add(PluginMetricHourly(source_module="host_agent", resource_name=res,
|
||||
metric_name="temp_c", bucket=at.replace(minute=0),
|
||||
value_avg=f(i), value_max=f(i), sample_count=1))
|
||||
else: # two samples an hour, averaged by refresh
|
||||
for m, d in ((5, -0.2), (35, 0.2)):
|
||||
s.add(PluginMetric(source_module="host_agent", resource_name=res,
|
||||
metric_name="temp_c", value=f(i) + d,
|
||||
recorded_at=at.replace(minute=m)))
|
||||
s.add(PluginMetricLatest(source_module="host_agent", resource_name=res,
|
||||
metric_name="temp_c", value=f(99), recorded_at=now))
|
||||
return await sensors.refresh(s, now)
|
||||
|
||||
got = asyncio.run(_go())
|
||||
assert list(got[host]) == ["SYSTIN"]
|
||||
assert got[host]["SYSTIN"].startswith("falls as the CPU warms (r -1.00 over 100 h")
|
||||
|
||||
Reference in New Issue
Block a user