feat(prometheus): fold state sets on the target page; forget series an endpoint drops (#5689)
#5689 "Prometheus target page: fold state sets, show epoch times, size bytes", step 1 of milestone 508 "Prometheus widget templates". - A state set (name ending in _state, a state label, every series 0 or 1; or the OpenMetrics form, the label named after the metric) reads as one row per thing it describes, naming the current state, the others on hover. fabledarchives_jobs{state} is a count and stays as it is. - label_pairs / state_label / fold_states in service.py, for the widget templates to reuse. - After a good scrape, series it no longer has lose their newest value (forget_gone): Archives' old build_info row read as current beside the new one. History stays; a failed scrape forgets nothing; alert rules keep their own last-sample time. - Epoch times and _bytes sizes were already read by shown_value / metric_value. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -8,8 +8,8 @@ import pytest
|
||||
from plugins.prometheus.models import PrometheusTarget
|
||||
from plugins.prometheus.scheduler import is_due
|
||||
from plugins.prometheus.scraper import ScrapeError, scrape
|
||||
from plugins.prometheus.service import (FormError, map_families, parse_form, patterns,
|
||||
rate_name, resource_for, target_state, wanted)
|
||||
from plugins.prometheus.service import (FormError, fold_states, label_pairs, map_families, parse_form,
|
||||
patterns, rate_name, resource_for, target_state, wanted)
|
||||
from steward.core import prometheus
|
||||
from steward.core.counter_rates import CounterRates
|
||||
|
||||
@@ -75,6 +75,38 @@ def test_names():
|
||||
assert patterns("a_*, b_*\n\n a_* \nc") == ["a_*", "b_*", "c"]
|
||||
|
||||
|
||||
def test_labels_read_back_as_stored_even_with_a_comma_in_a_value():
|
||||
labels = resource_for("A", (("job", "Nightly"), ("schedule", "0 3 * * 1,3,5"), ("state", "ok")))
|
||||
assert label_pairs(labels[2:]) == [("job", "Nightly"), ("schedule", "0 3 * * 1,3,5"), ("state", "ok")]
|
||||
assert label_pairs("") == []
|
||||
|
||||
|
||||
def _rows(metric_rows):
|
||||
return [{"labels": labels, "value": value, "resource": f"A:{labels}"} for labels, value in metric_rows]
|
||||
|
||||
|
||||
def test_a_state_set_folds_to_its_current_state_per_thing():
|
||||
rows = _rows([("target=S,state=late", 0), ("target=S,state=fresh", 1), ("target=S,state=missing", 0),
|
||||
("target=T,state=fresh", 0), ("target=T,state=late", 1), ("target=T,state=missing", 1),
|
||||
("target=U,state=fresh", 0), ("target=U,state=late", 0)])
|
||||
folded = fold_states("fa_backup_target_state", rows)
|
||||
assert [(f["labels"], f["state"]) for f in folded] == [
|
||||
("target=S", "fresh"), ("target=T", "late, missing"), ("target=U", "")]
|
||||
assert folded[0]["states"] == ["fresh", "late", "missing"]
|
||||
assert folded[0]["resource"] == "A:target=S,state=fresh" # the current one's chart
|
||||
assert folded[2]["resource"] == "A:target=U,state=fresh" # none current: the first
|
||||
# OpenMetrics names a stateset's label after the metric itself.
|
||||
assert fold_states("fa_mode", _rows([("fa_mode=a", 1), ("fa_mode=b", 0)]))[0]["state"] == "a"
|
||||
|
||||
|
||||
def test_only_a_state_set_is_folded():
|
||||
# A count split by a state label reads 0 or 1 often enough; its name says it is a count.
|
||||
assert fold_states("fa_jobs", _rows([("kind=scan,state=waiting", 1), ("kind=scan,state=running", 0)])) is None
|
||||
assert fold_states("fa_state", _rows([("state=a", 1), ("state=b", 2)])) is None # not 0/1
|
||||
assert fold_states("fa_state", _rows([("state=a", 1), ("target=S", 0)])) is None # a row without one
|
||||
assert fold_states("fa_state", []) is None
|
||||
|
||||
|
||||
def test_a_counter_becomes_a_rate_and_a_reset_is_skipped():
|
||||
rates = CounterRates()
|
||||
t0 = datetime(2026, 10, 12, 12, 0, tzinfo=timezone.utc)
|
||||
|
||||
Reference in New Issue
Block a user