Skip to content

Commit 8ae65c7

Browse files
author
Tom Lasswell
committed
feat(readme): add response-time sparkline to uptime graph
Plots per-check mean latency (open_ms/app_ms, already recorded) over the last 72 checks with an 'avg N ms' stat, color-graded green/amber/red.
1 parent b2210b0 commit 8ae65c7

1 file changed

Lines changed: 37 additions & 3 deletions

File tree

scripts/status_badges.py

Lines changed: 37 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -331,7 +331,7 @@ def _day_status(entries: list) -> str:
331331

332332

333333
def render_uptime_svg(history: list) -> str:
334-
w, h = 480, 150
334+
w, h = 480, 192
335335
s = card_open(w, h)
336336
pad = 20
337337

@@ -366,8 +366,8 @@ def render_uptime_svg(history: list) -> str:
366366
s.append(txt(w - pad, 40, f"{pct:.1f}%", 26, TEXT, weight=700, anchor="end"))
367367
s.append(txt(w - pad, 58, f"uptime / {len(days)}d", 10.5, MUTED, anchor="end"))
368368

369-
# daily bars
370-
bx0, by0, bw, bh = pad, 84, w - 2 * pad, 34
369+
# daily status bars
370+
bx0, by0, bw, bh = pad, 80, w - 2 * pad, 24
371371
n = max(len(days), 1)
372372
gap = 3
373373
bar_w = max((bw - gap * (n - 1)) / n, 2)
@@ -379,6 +379,40 @@ def render_uptime_svg(history: list) -> str:
379379
if not days:
380380
s.append(txt(w / 2, by0 + bh / 2 + 4, "collecting history…", 12, MUTED, anchor="middle"))
381381

382+
# response-time sparkline (per-check mean of the two hosts we already record)
383+
ms_series = []
384+
for e in history[-72:]:
385+
vals = [v for v in (e.get("open_ms"), e.get("app_ms")) if v is not None]
386+
if vals:
387+
ms_series.append(sum(vals) / len(vals))
388+
avg_ms = int(sum(ms_series) / len(ms_series)) if ms_series else None
389+
lat_col = GREEN if (avg_ms or 0) < 350 else AMBER if (avg_ms or 0) < 900 else RED
390+
391+
s.append(txt(pad, 130, "RESPONSE TIME", 10, MUTED, weight=600, spacing="1.2"))
392+
if avg_ms is not None:
393+
s.append(txt(w - pad, 130, f"avg {avg_ms} ms · last {len(ms_series)} checks", 10.5, MUTED, anchor="end"))
394+
gx0, gy0, gw, gh = pad, 138, w - 2 * pad, 32
395+
if len(ms_series) >= 2:
396+
lo, hi = min(ms_series), max(ms_series)
397+
rng = (hi - lo) or 1
398+
pts = []
399+
for i, m in enumerate(ms_series):
400+
px = gx0 + gw * i / (len(ms_series) - 1)
401+
py = gy0 + gh - (gh - 4) * (m - lo) / rng
402+
pts.append((px, py))
403+
line = " ".join(f"{x:.1f},{y:.1f}" for x, y in pts)
404+
area = f"M{gx0},{gy0 + gh} " + " ".join(f"L{x:.1f},{y:.1f}" for x, y in pts) + f" L{pts[-1][0]:.1f},{gy0 + gh} Z"
405+
s.append(
406+
f'<defs><linearGradient id="lg" x1="0" x2="0" y1="0" y2="1">'
407+
f'<stop offset="0" stop-color="{lat_col}" stop-opacity="0.35"/>'
408+
f'<stop offset="1" stop-color="{lat_col}" stop-opacity="0"/></linearGradient></defs>'
409+
)
410+
s.append(f'<path d="{area}" fill="url(#lg)"/>')
411+
s.append(f'<polyline points="{line}" fill="none" stroke="{lat_col}" stroke-width="1.8" stroke-linejoin="round"/>')
412+
s.append(f'<circle cx="{pts[-1][0]:.1f}" cy="{pts[-1][1]:.1f}" r="3" fill="{lat_col}"/>')
413+
else:
414+
s.append(txt(w / 2, gy0 + gh / 2 + 4, "collecting latency…", 11, MUTED, anchor="middle"))
415+
382416
s.append(txt(pad, h - 12, "openapi + app2 · hourly", 10.5, MUTED))
383417
s.append(txt(w - pad, h - 12, stamp(), 10.5, MUTED, anchor="end"))
384418
s.append("</svg>")

0 commit comments

Comments
 (0)