From 322ff4f6912d9d14c8257ed5fe43478f63f0d993 Mon Sep 17 00:00:00 2001 From: giles Date: Wed, 1 Jul 2026 19:41:40 +0000 Subject: [PATCH] otel: funky dashboard (latency bar chart + status-colored waterfall) + boot self-warm Dashboard gains a per-route latency bar chart (nested p50/p95/p99 bars, tail visible) + status-colored waterfall with ms duration labels + a real 3s auto-refresh (replacing the non-functional data-on-load SSE attr). serve.sh self-warms the serving JIT over /dev/tcp so the first visitor after a restart gets ~78ms not the one-time ~2.5s compile. otel suite 125/125. --- lib/host/otel.sx | 71 +++++++++++++++++++++++++++++++++++++++--- lib/host/serve.sh | 21 +++++++++++++ lib/host/tests/otel.sx | 3 +- 3 files changed, 90 insertions(+), 5 deletions(-) diff --git a/lib/host/otel.sx b/lib/host/otel.sx index b42f6426..b264f93f 100644 --- a/lib/host/otel.sx +++ b/lib/host/otel.sx @@ -191,21 +191,36 @@ {:span (get s :span) :name (get s :name) :depth d + :dur dur + :status (get s :status) :x (+ otel/-pad (* (/ (- (get s :t0) t0) total) iw)) :y (+ otel/-pad (* d otel/-row-h)) :w (if (> raw-w 1) raw-w 1)}))) spans))))))) -;; one (bar + label) per rect. +;; ns → a compact "Nms" / "Nus" label. +(define otel/-ms + (fn (ns) + (if (>= ns 1000000) + (str (quotient ns 1000000) "ms") + (str (quotient ns 1000) "us")))) + +;; error spans render red, everything else teal. +(define otel/-bar-fill + (fn (status) (if (= status "error") "#e45756" "#4c9a8f"))) + +;; one (bar + name + duration) per rect. The bar is the only , so +;; count(rect) == count(span); axis chrome uses /. (define otel/-rect->g (fn (r) (quasiquote (g (rect :x (unquote (get r :x)) :y (unquote (get r :y)) :width (unquote (get r :w)) :height (unquote (- otel/-row-h 2)) - :fill "#4c78a8" :rx 2) + :fill (unquote (otel/-bar-fill (get r :status))) :rx 2) (text :x (unquote (+ (get r :x) 3)) :y (unquote (+ (get r :y) 12)) - :font-size 10 :fill "#ffffff" (unquote (get r :name))))))) + :font-size 10 :fill "#ffffff" + (unquote (str (get r :name) " " (otel/-ms (get r :dur))))))))) ;; max depth across rects (0 when empty) → svg height. (define otel/-max-depth @@ -370,14 +385,62 @@ (unquote (str (get t :name) " — " (get t :spans) " spans"))))) traces)))))) +;; a per-route latency bar chart: each row is a nested bar — teal p50 inside amber +;; p95 inside light-red p99 — so the tail (red beyond amber) is visible at a glance. +(define otel/-latency-chart + (fn (m) + (let ((routes (get m :routes))) + (if (empty? routes) + (quasiquote (p :class "otel-empty" "no metrics yet")) + (let ((maxp99 (reduce (fn (mx r) (max mx (get r :p99))) 1 routes)) + (label-w 150) + (bar-w 340) + (row-h 24)) + (quasiquote + (svg :width 560 :height (unquote (+ (* (len routes) row-h) 6)) + :xmlns "http://www.w3.org/2000/svg" :font-family "monospace" + (splice-unquote + (map-indexed + (fn (i r) + (let ((y (* i row-h))) + (let ((p99w (max 1 (* (/ (get r :p99) maxp99) bar-w))) + (p95w (max 1 (* (/ (get r :p95) maxp99) bar-w))) + (p50w (max 1 (* (/ (get r :p50) maxp99) bar-w)))) + (quasiquote + (g + (text :x 0 :y (unquote (+ y 15)) :font-size 11 :fill "#333" + (unquote (get r :route))) + (rect :x (unquote label-w) :y (unquote (+ y 5)) :width (unquote p99w) + :height 14 :fill "#f2c2c2" :rx 3) + (rect :x (unquote label-w) :y (unquote (+ y 5)) :width (unquote p95w) + :height 14 :fill "#f2a63b" :rx 3) + (rect :x (unquote label-w) :y (unquote (+ y 5)) :width (unquote p50w) + :height 14 :fill "#4c9a8f" :rx 3) + (text :x (unquote (+ label-w p99w 5)) :y (unquote (+ y 15)) + :font-size 9 :fill "#999" + (unquote (str "p99 " (otel/-ms (get r :p99)))))))))) + routes))))))))) + (define otel/dashboard (fn () (let ((m (otel/metrics (otel/recent))) (lt (otel/latest-trace)) (traces (otel/recent-traces))) (quasiquote - (div :id "otel-dashboard" :data-on-load "@get('/otel/stream')" + (div :id "otel-dashboard" + ;; The host serves single-body responses (no server-push SSE), so the + ;; dashboard stays live by reloading itself — every 3s it re-renders the + ;; latest metrics + trace. /otel/stream remains a snapshot of the newest + ;; span for any client that wants to poll it. + (meta :http-equiv "refresh" :content "3") (h1 "OpenTelemetry") + (p :style "font-size:0.8em;opacity:0.7" "live · auto-refreshes every 3s") + (h2 "latency by route") + (p :style "font-size:0.75em;opacity:0.7" + (span :style "color:#4c9a8f" "▉ p50") " " + (span :style "color:#f2a63b" "▉ p95") " " + (span :style "color:#f2c2c2" "▉ p99 (tail)")) + (unquote (otel/-latency-chart m)) (h2 "metrics") (unquote (otel/-metrics-strip m)) (h2 "latest trace") diff --git a/lib/host/serve.sh b/lib/host/serve.sh index 722fa152..6bb97cf0 100755 --- a/lib/host/serve.sh +++ b/lib/host/serve.sh @@ -28,6 +28,27 @@ fi PORT="${HOST_PORT:-8910}" +# Self-warm the SERVING JIT. The FIRST HTTP request after a restart pays a one-time +# ~2.5s cost: the shared request path (session middleware + router + instrumented +# handlers + blog render) JIT-compiles under the http-listen resolver, a context a +# boot-time render can't reach. So once the server is answering, we GET the hot +# pages ONCE over real HTTP to absorb that compile before a visitor does (~2.5s -> +# ~78ms warm). The container image has no curl/wget, so we speak HTTP over bash's +# /dev/tcp. Runs detached (survives the exec below); exits after warming. +_warm_serving_jit() { + local path + for _ in $(seq 1 180); do + case "$( { exec 3<>/dev/tcp/127.0.0.1/"$PORT" && printf 'GET /health HTTP/1.0\r\nHost: x\r\n\r\n' >&3 && head -1 <&3; exec 3>&- 3<&-; } 2>/dev/null )" in + *200*) break ;; + esac + sleep 0.5 + done + for path in / /welcome /nt-live-encore /otel; do + { exec 3<>/dev/tcp/127.0.0.1/"$PORT" && printf 'GET %s HTTP/1.0\r\nHost: x\r\n\r\n' "$path" >&3 && cat <&3 >/dev/null; exec 3>&- 3<&-; } 2>/dev/null + done +} +_warm_serving_jit & + # Modules: every load line from conformance.sh's MODULES list, minus the ledger # (not needed to serve). server.sx supplies host/serve. MODULES=( diff --git a/lib/host/tests/otel.sx b/lib/host/tests/otel.sx index 8d3db84d..49c8933d 100644 --- a/lib/host/tests/otel.sx +++ b/lib/host/tests/otel.sx @@ -227,7 +227,8 @@ (host-ot-test "dashboard has the root id" (contains? host-ot-dash "otel-dashboard") true) (host-ot-test "dashboard SSRs the waterfall svg" (contains? host-ot-dash "