feat(traefik): the page leads with its verdict and the request chart (#5710)
The Traefik page now reads state first: a verdict naming each router that needs a look, why, and when it started (from its history buckets over the page's range), then the request chart following the page's time range, then the problem cards (which say since when too), the proxy's own figures, and the healthy rest. The verdict is filled out of band by /rows each poll; the "Needs attention" and "Healthy routers" tiles it replaces are gone. The request chart takes ?range= from the page; past a day its times carry the day, and its clock is 24-hour (hourCycle h23). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -137,6 +137,23 @@ def gone_routers(routers: list[dict], scrape_seconds: int) -> tuple[list[dict],
|
||||
return current, gone
|
||||
|
||||
|
||||
def problem_since(history: list, thresholds: dict) -> tuple[datetime | None, bool]:
|
||||
"""(start, before_range) of a router's current problem, read from its
|
||||
history buckets (router_history's, oldest first): the start of the
|
||||
trailing run of buckets that grade as a problem. (None, False) when the
|
||||
newest bucket reads ok, as for a problem too recent or too brief for a
|
||||
bucket to show. `before_range` says the run reaches back to the first
|
||||
bucket, so the problem began before the range shown."""
|
||||
start = None
|
||||
for point in reversed(history):
|
||||
if router_state(point, thresholds)[0] == "ok":
|
||||
break
|
||||
start = point
|
||||
if start is None:
|
||||
return None, False
|
||||
return start.bucket, start is history[0]
|
||||
|
||||
|
||||
def split_routers(routers: list[dict]) -> tuple[list[dict], list[dict]]:
|
||||
"""(problems, healthy): failing routers before slow or noisy ones, each
|
||||
group by name; healthy routers by name."""
|
||||
@@ -259,6 +276,8 @@ async def rows():
|
||||
phases.mark("window")
|
||||
for r in current:
|
||||
points = history[r["name"]]
|
||||
r["since"], r["since_before"] = (problem_since(points, thresholds) if r["state"] != "ok"
|
||||
else (None, False))
|
||||
r["sparkline_req"] = _sparkline([p.request_rate for p in points])
|
||||
r["sparkline_bytes"] = _sparkline([p.response_bytes_rate for p in points])
|
||||
r["sparkline_p95"] = _sparkline([p.latency_p95_ms for p in points])
|
||||
@@ -309,6 +328,7 @@ async def rows():
|
||||
problems=problems,
|
||||
healthy=healthy,
|
||||
gone=gone,
|
||||
current_range=range_key,
|
||||
error_4xx=error_4xx,
|
||||
error_5xx=error_5xx,
|
||||
global_stat=global_stat,
|
||||
@@ -524,11 +544,15 @@ async def widget_request_chart():
|
||||
share of them answered 5xx, over the last few hours (#5256). Each scrape
|
||||
is summed over routers first (the total), then averaged per bucket; the
|
||||
5xx share is weighted by each router's traffic, not averaged per router."""
|
||||
hours = max(1, min(24, int(request.args.get("hours", 6) or 6)))
|
||||
widget_id = request.args.get("wid", "0")
|
||||
|
||||
since = datetime.now(timezone.utc) - timedelta(hours=hours)
|
||||
b_secs = max(60, (hours * 3600) // 80) # ~80 buckets
|
||||
if request.args.get("range"):
|
||||
# The Traefik page: the page's own time range (#5710).
|
||||
since, span = parse_range(request.args.get("range"))
|
||||
b_secs = bucket_seconds(since)
|
||||
else:
|
||||
hours = max(1, min(24, int(request.args.get("hours", 6) or 6)))
|
||||
since, span = datetime.now(timezone.utc) - timedelta(hours=hours), f"{hours}h"
|
||||
b_secs = max(60, (hours * 3600) // 80) # ~80 buckets
|
||||
|
||||
async with current_app.db_sessionmaker() as db:
|
||||
history = await request_history(db, since, b_secs)
|
||||
@@ -543,7 +567,8 @@ async def widget_request_chart():
|
||||
req_rate_json=json.dumps(req_rates),
|
||||
error_rate_json=json.dumps(err_pcts),
|
||||
widget_id=widget_id,
|
||||
hours=hours,
|
||||
span=span,
|
||||
days=(datetime.now(timezone.utc) - since) > timedelta(hours=24),
|
||||
now_req=req_rates[-1] if req_rates else None,
|
||||
peak_req=max(req_rates) if req_rates else None,
|
||||
peak_err=max(err_pcts) if err_pcts else None,
|
||||
|
||||
Reference in New Issue
Block a user