feat(prometheus): Fabled Archives template v2 against the live series; a state past its deadline is overdue (#5693)
CI / lint (push) Successful in 2s
CI / integration (push) Failing after 1m17s
CI / unit (push) Successful in 1m30s
CI / publish (push) Skipped

Read from what Steward stored for Archives on 2026-10-14: the nightly
Restic replication job had said "running" since the first scrape two days
earlier, with no good copy yet and past both its late and missing times.
Archives ranks "running" above lateness, so the widget called it healthy.

- states blocks take warn_past / crit_past: a deadline metric per row (a
  *_timestamp_seconds). Once it has passed, a healthy row reads as a
  warning or a problem: "FullNightly-Restic is running, 11h 53m overdue".
  Quiet and already-failing rows keep their own reading.
- The Archives template (version 2) uses the job's late_at / missing_at
  deadlines and shows its last good copy beside the next run. It says why
  section_up, the queue and the HTTP/audit counters are left out.
- tests/plugins/prometheus/archives_live.py is the series as captured
  live, and test_archives_template.py renders the template over them.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-14 09:43:38 -04:00
co-authored by Claude Opus 5.5
parent e42fde89f5
commit 41d9d00216
6 changed files with 195 additions and 10 deletions
+34 -1
View File
@@ -120,7 +120,7 @@ blocks:
"Line 2: `version` is the template's own version: a whole number from 1. "
"Raise it when you change a template you have shared.",
"Line 8: Block 1 (states) has no `colour`. It takes: title, metric, where, name, healthy, "
"quiet, warning, columns.",
"quiet, warning, warn_past, crit_past, columns.",
"Line 9: Block 2 is “gauge”; the kinds are: states, values, times, table, info.",
"Line 12: `metric` in an item of block 3 (values) should be a metric name as Steward stores it, "
"like fabledarchives_files (a counter is stored as <name>_per_sec).",
@@ -236,3 +236,36 @@ def test_all_well():
("", "fa_malware_held_files"): 0.0}
view = _view(healthy)
assert (view.tone, view.verdict) == ("ok", "All well.")
def test_a_healthy_state_past_its_deadline_is_overdue():
"""warn_past / crit_past (#5693): a row whose state is healthy but whose
deadline has passed reads as a warning or a problem; a quiet or already
failing row keeps its own reading."""
text = """\
name: Jobs
version: 1
blocks:
- states:
title: Jobs
metric: j_state
name: job
healthy: [ok, running]
quiet: [paused]
warn_past: j_late_at_timestamp_seconds
crit_past: j_missing_at_timestamp_seconds
"""
series = {}
for job, state, late, missing in (("A", "running", before(hours=2), before(hours=1) + 7200),
("B", "running", before(hours=5), before(hours=3)),
("C", "ok", before(hours=-1), before(hours=-2)),
("D", "paused", before(hours=9), before(hours=8))):
for s in ("ok", "running", "paused"):
series[(f"job={job},state={s}", "j_state")] = float(s == state)
series[(f"job={job}", "j_late_at_timestamp_seconds")] = late
series[(f"job={job}", "j_missing_at_timestamp_seconds")] = missing
rows = {r.name: r for r in render(check(text), T, latest(series), NOW).blocks[0].rows}
assert (rows["A"].tone, rows["A"].why) == ("warn", "is running, 2h overdue")
assert (rows["B"].tone, rows["B"].why) == ("crit", "is running, 3h overdue")
assert (rows["C"].tone, rows["C"].why) == ("ok", "")
assert rows["D"].tone == "dim"