otel: real auto-refresh dashboard + HTTP self-warm (kills cold p99)

Dashboard: drop the non-functional data-on-load SSE attr; add <meta refresh 3s>
so it genuinely live-updates (the host serves single-body responses, no
server-push SSE). /otel/stream stays a snapshot for pollers.

serve.sh: replace the ineffective boot-time make-app warmup (wrong JIT context)
with a backgrounded self-warmer that GETs the hot pages over real HTTP (bash
/dev/tcp — no curl in the image) once /health is up, so the first real visitor
after a restart gets ~78ms instead of the one-time ~2.5s serving-JIT compile.
This commit is contained in:
2026-07-01 19:32:36 +00:00
parent 6868d984a0
commit 0fda26f1d5
2 changed files with 29 additions and 9 deletions

View File

@@ -376,8 +376,14 @@
(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 "metrics")
(unquote (otel/-metrics-strip m))
(h2 "latest trace")

View File

@@ -28,6 +28,28 @@ 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
# wait until /health answers 200 (the port opens only after all modules load)
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=(
@@ -195,14 +217,6 @@ EPOCH=1
echo "(epoch $EPOCH)"
echo "(eval \"(define host/-serve-groups (list host/static-routes otel/routes host/feed-routes host/relations-routes (host/blog-write-routes (fn (tok) nil)) host/blog-routes))\")"
EPOCH=$((EPOCH+1))
# JIT warmup: render the hot pages once through a throwaway app so the FIRST real
# visitor after a restart doesn't eat the cold-compile cost. The blog render path
# (comp-fold + relations + typed-block) JIT-compiles on first call — that was the
# ~2.5s p99 on /:slug, vs ~78ms warm. Reset the otel ring after so warmup spans
# don't skew the live metrics.
echo "(epoch $EPOCH)"
echo "(eval \"(let ((warm (host/make-app host/-serve-groups))) (begin (warm (dream-request \\\"GET\\\" \\\"/\\\" {} \\\"\\\")) (warm (dream-request \\\"GET\\\" \\\"/welcome\\\" {} \\\"\\\")) (warm (dream-request \\\"GET\\\" \\\"/nt-live-encore\\\" {} \\\"\\\")) (warm (dream-request \\\"GET\\\" \\\"/otel\\\" {} \\\"\\\")) (otel/reset!) nil))\")"
EPOCH=$((EPOCH+1))
echo "(epoch $EPOCH)"
echo "(eval \"(host/serve $PORT host/-serve-groups)\")"
} | exec "$SX_SERVER"