@@ -331,7 +331,7 @@ def _day_status(entries: list) -> str:
331331
332332
333333def 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