fix(traefik): grade routers on their last 15 minutes; the Config tile reads Traefik's reload time (#5355)
CI / lint (push) Failing after 2s
CI / integration (push) Successful in 54s
CI / unit (push) Successful in 1m24s
CI / publish (push) Skipped

The operator: problem cards "can disappear so quickly". Each router was
graded on its latest scrape, so a 4xx share near the 1% cutoff crossed it
on one scrape and fell back on the next. recent_window reads every
router's last 15 minutes in one query, the error shares weighted by
traffic; grade_routers uses it on the page and the dashboard widget, and
the why says "over the last 15 min".

traefik_config_last_reload_success is the Unix time of the last good
reload, not a 0/1 flag, so the tile said "Reload failed" on every scrape.
traefik_005 stores it as config_last_reload_at, and the tile reads
"Config reloaded 3m ago". The router index also carries the 4xx share.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-08 08:26:10 -04:00
co-authored by Claude Opus 5.5
parent 7a444da871
commit 7ce61a2099
7 changed files with 194 additions and 24 deletions
+10 -6
View File
@@ -152,21 +152,21 @@ def compute_router_metrics(
def compute_global_stats(
current: ParsedMetrics,
previous: ParsedMetrics | None,
) -> dict[str, float | None]:
) -> dict[str, float | datetime | None]:
"""Extract process-level and config health metrics from a scrape.
Returns:
{
"open_conns_total": float | None,
"config_reloads_total": float | None,
"config_last_reload_success": float | None, # 1.0 = success, 0.0 = failure
"config_last_reload_at": datetime | None, # last successful reload
"process_memory_bytes": float | None,
}
"""
stats: dict[str, float | None] = {
stats: dict[str, float | datetime | None] = {
"open_conns_total": None,
"config_reloads_total": None,
"config_last_reload_success": None,
"config_last_reload_at": None,
"process_memory_bytes": None,
}
@@ -180,11 +180,15 @@ def compute_global_stats(
if reloads:
stats["config_reloads_total"] = sum(reloads.values())
# Last reload success gauge
# The moment of the last successful reload, as a Unix timestamp: Traefik
# names it "last_reload_success", but it is a time, not a 0/1 flag. Read
# as a flag, every scrape said "Reload failed" (#5355).
last_success = current.get("traefik_config_last_reload_success", {})
if last_success:
# Usually a single sample with no labels
stats["config_last_reload_success"] = next(iter(last_success.values()))
ts = next(iter(last_success.values()))
if ts > 0:
stats["config_last_reload_at"] = datetime.fromtimestamp(ts, timezone.utc)
# Process RSS memory
mem = current.get("process_resident_memory_bytes", {})