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:
@@ -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")
|
||||
|
||||
@@ -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=(
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user