From d357a5a7b93f12ea773cae6333341591a6042602 Mon Sep 17 00:00:00 2001 From: giles Date: Wed, 1 Jul 2026 19:00:12 +0000 Subject: [PATCH 1/3] otel: mount otel/routes before the blog /:slug catch-all in serve.sh The blog post-detail route /:slug matches any single segment, so /otel was being served as a missing blog slug (404). Order otel/routes ahead of the blog routes so the literal /otel + /otel/stream match first. --- lib/host/serve.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/host/serve.sh b/lib/host/serve.sh index dc82404c..63d6aa29 100755 --- a/lib/host/serve.sh +++ b/lib/host/serve.sh @@ -195,5 +195,5 @@ EPOCH=1 # middleware, so a browser logs in then publishes. The bearer resolver is a stub # (no API tokens configured) — browser session is the live auth path for now. # blog-routes LAST — its GET /:slug catch-all must not shadow the rest. - echo "(eval \"(host/serve $PORT (list host/static-routes host/feed-routes host/relations-routes (host/blog-write-routes (fn (tok) nil)) host/blog-routes otel/routes))\")" + echo "(eval \"(host/serve $PORT (list host/static-routes otel/routes host/feed-routes host/relations-routes (host/blog-write-routes (fn (tok) nil)) host/blog-routes))\")" } | exec "$SX_SERVER" From 6868d984a054b0f612c7b93c1b98b9b801c8c44e Mon Sep 17 00:00:00 2001 From: giles Date: Wed, 1 Jul 2026 19:24:09 +0000 Subject: [PATCH 2/3] otel/perf: JIT-warm the hot pages at boot to kill the cold-start p99 tail MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The blog render path (comp-fold + relations + typed-block) JIT-compiles on first call, so the first visitor after a restart paid ~2.5s (vs ~78ms warm) — that was the /:slug p99 tail. Define the route groups once, render / + welcome + nt-live-encore + /otel through a throwaway app at boot to force compilation, then reset the otel ring so warmup spans don't skew live metrics. --- lib/host/serve.sh | 25 +++++++++++++++++-------- 1 file changed, 17 insertions(+), 8 deletions(-) diff --git a/lib/host/serve.sh b/lib/host/serve.sh index 63d6aa29..6afde9d5 100755 --- a/lib/host/serve.sh +++ b/lib/host/serve.sh @@ -187,13 +187,22 @@ EPOCH=1 echo "(epoch $EPOCH)" echo "(eval \"(host/static-build-sxh-index!)\")" EPOCH=$((EPOCH+1)) + # Route groups defined ONCE so warmup + serve use the exact same list. otel early + # so /otel isn't swallowed by /:slug; blog-routes LAST — its GET /:slug catch-all + # must not shadow the rest. The write routes (POST /new, POST/PUT/DELETE /posts) + # are guarded by host/require-user (session login OR Bearer) + ACL; make-app + # auto-mounts /login + /logout and wraps all of it in the signed-session middleware. echo "(epoch $EPOCH)" - # Anonymous reads (feed timeline + relations container reads + blog post detail) - # plus the GUARDED blog write routes: POST /new (editor form ingest), POST/PUT/ - # DELETE /posts behind host/require-user (session login OR Bearer) + ACL. make-app - # auto-mounts /login + /logout and wraps everything in the signed-session - # middleware, so a browser logs in then publishes. The bearer resolver is a stub - # (no API tokens configured) — browser session is the live auth path for now. - # blog-routes LAST — its GET /:slug catch-all must not shadow the rest. - echo "(eval \"(host/serve $PORT (list host/static-routes otel/routes host/feed-routes host/relations-routes (host/blog-write-routes (fn (tok) nil)) host/blog-routes))\")" + 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" From 0fda26f1d54dc74689080dc20a73198e4ca23dfa Mon Sep 17 00:00:00 2001 From: giles Date: Wed, 1 Jul 2026 19:32:36 +0000 Subject: [PATCH 3/3] otel: real auto-refresh dashboard + HTTP self-warm (kills cold p99) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Dashboard: drop the non-functional data-on-load SSE attr; add 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. --- lib/host/otel.sx | 8 +++++++- lib/host/serve.sh | 30 ++++++++++++++++++++++-------- 2 files changed, 29 insertions(+), 9 deletions(-) diff --git a/lib/host/otel.sx b/lib/host/otel.sx index b42f6426..f8db6aca 100644 --- a/lib/host/otel.sx +++ b/lib/host/otel.sx @@ -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") diff --git a/lib/host/serve.sh b/lib/host/serve.sh index 6afde9d5..300d5503 100755 --- a/lib/host/serve.sh +++ b/lib/host/serve.sh @@ -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"