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.
This commit is contained in:
2026-07-01 19:41:40 +00:00
parent fa1afd7b5d
commit 322ff4f691
3 changed files with 90 additions and 5 deletions

View File

@@ -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 <g> (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 <g> (bar + name + duration) per rect. The bar is the only <rect>, so
;; count(rect) == count(span); axis chrome uses <line>/<text>.
(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")

View File

@@ -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=(

View File

@@ -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 "<svg") true)
(host-ot-test "dashboard shows a route in the strip" (contains? host-ot-dash "/feed") true)
(host-ot-test "dashboard declares the SSE subscription" (contains? host-ot-dash "/otel/stream") true)
(host-ot-test "dashboard auto-refreshes (meta refresh, not fake SSE)" (contains? host-ot-dash "refresh") true)
(host-ot-test "dashboard shows the latency chart legend" (contains? host-ot-dash "p99 (tail)") true)
;; the SSE endpoint emits a span event, SSE-framed
(define host-ot-sse