otel P6: live dashboard — GET /otel SSR + /otel/stream SSE
otel/dashboard SSRs the metrics strip + latest-trace waterfall + recent-traces list as HTML carrying Datastar-style data-on-load subscribing to /otel/stream, the SSE feed of SXTP otel.span events. Routes otel/dashboard-route + otel/stream-route (otel/routes) mount via make-app. recent-traces/latest-trace + otel/span-event helpers.
This commit is contained in:
110
lib/host/otel.sx
110
lib/host/otel.sx
@@ -273,3 +273,113 @@
|
||||
|
||||
;; convenience: metrics over the current ring.
|
||||
(define otel/metrics-recent (fn () (otel/metrics (otel/recent))))
|
||||
|
||||
;; ── P6: live dashboard (SSR + SSE) ────────────────────────────────────
|
||||
;; GET /otel renders a dashboard — metrics strip + the latest trace's waterfall +
|
||||
;; a recent-traces list — as server-rendered HTML carrying Datastar-style reactive
|
||||
;; attributes that subscribe to GET /otel/stream, the SSE feed of new span events
|
||||
;; (SXTP events, the host's Datastar-borrowed wire format). SSR + declarative
|
||||
;; reactive attrs + SSE patches IS the reactive-island model here. (Live client
|
||||
;; hydration is a deploy concern; SSR, the event feed, and the data are tested.)
|
||||
|
||||
;; recent traces, newest-first: {:trace :name :spans}. root name = the parentless
|
||||
;; span (fallback: first recorded).
|
||||
(define otel/-trace-ids
|
||||
(fn () (otel/-distinct (map (fn (s) (get s :trace)) (otel/recent)))))
|
||||
(define otel/-trace-root-name
|
||||
(fn (trace-id)
|
||||
(let ((spans (otel/trace-spans trace-id)))
|
||||
(let ((roots (filter (fn (s) (nil? (get s :parent))) spans)))
|
||||
(cond
|
||||
((not (empty? roots)) (get (first roots) :name))
|
||||
((not (empty? spans)) (get (first spans) :name))
|
||||
(else ""))))))
|
||||
(define otel/trace-summary
|
||||
(fn (trace-id)
|
||||
{:trace trace-id
|
||||
:name (otel/-trace-root-name trace-id)
|
||||
:spans (len (otel/trace-spans trace-id))}))
|
||||
(define otel/recent-traces
|
||||
(fn () (reverse (map otel/trace-summary (otel/-trace-ids)))))
|
||||
(define otel/latest-trace
|
||||
(fn ()
|
||||
(let ((r (otel/recent)))
|
||||
(if (empty? r) nil (get (last r) :trace)))))
|
||||
|
||||
;; ── SSE span events (SXTP) ────────────────────────────────────────────
|
||||
(define otel/span-event
|
||||
(fn (s)
|
||||
(sxtp/event "otel.span"
|
||||
{:id (get s :span)
|
||||
:body {:trace (get s :trace) :span (get s :span) :parent (get s :parent)
|
||||
:name (get s :name) :t0 (get s :t0) :t1 (get s :t1)
|
||||
:attrs (get s :attrs)}
|
||||
:time (get s :t1)})))
|
||||
(define otel/latest-span-event
|
||||
(fn ()
|
||||
(let ((r (otel/recent)))
|
||||
(if (empty? r) nil (otel/span-event (last r))))))
|
||||
;; one SSE frame: `event: otel.span\n data: <sxtp event>\n\n`; "" when no spans.
|
||||
(define otel/-stream-body
|
||||
(fn ()
|
||||
(let ((e (otel/latest-span-event)))
|
||||
(if (nil? e)
|
||||
""
|
||||
(str "event: otel.span\ndata: " (sxtp/serialize e) "\n\n")))))
|
||||
|
||||
;; ── dashboard markup (plain HTML tags → render-to-html SSRs cleanly) ──
|
||||
(define otel/-metrics-strip
|
||||
(fn (m)
|
||||
(quasiquote
|
||||
(table :class "otel-metrics"
|
||||
(tr (th "route") (th "count") (th "p50") (th "p95") (th "p99"))
|
||||
(splice-unquote
|
||||
(map
|
||||
(fn (r)
|
||||
(quasiquote
|
||||
(tr (td (unquote (str (get r :route))))
|
||||
(td (unquote (str (get r :count))))
|
||||
(td (unquote (str (get r :p50))))
|
||||
(td (unquote (str (get r :p95))))
|
||||
(td (unquote (str (get r :p99)))))))
|
||||
(get m :routes)))))))
|
||||
|
||||
(define otel/-traces-list
|
||||
(fn (traces)
|
||||
(quasiquote
|
||||
(ul :class "otel-traces"
|
||||
(splice-unquote
|
||||
(map
|
||||
(fn (t)
|
||||
(quasiquote
|
||||
(li :data-trace (unquote (str (get t :trace)))
|
||||
(unquote (str (get t :name) " — " (get t :spans) " spans")))))
|
||||
traces))))))
|
||||
|
||||
(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')"
|
||||
(h1 "OpenTelemetry")
|
||||
(h2 "metrics")
|
||||
(unquote (otel/-metrics-strip m))
|
||||
(h2 "latest trace")
|
||||
(unquote
|
||||
(if (nil? lt)
|
||||
(quasiquote (p :class "otel-empty" "no traces yet"))
|
||||
(otel/waterfall lt)))
|
||||
(h2 "recent traces")
|
||||
(unquote (otel/-traces-list traces)))))))
|
||||
|
||||
;; ── routes ────────────────────────────────────────────────────────────
|
||||
(define otel/dashboard-route
|
||||
(dream-get "/otel"
|
||||
(fn (req) (dream-html (render-to-html (otel/dashboard) {})))))
|
||||
(define otel/stream-route
|
||||
(dream-get "/otel/stream"
|
||||
(fn (req)
|
||||
(dream-response 200 {:content-type "text/event-stream"} (otel/-stream-body)))))
|
||||
(define otel/routes (list otel/dashboard-route otel/stream-route))
|
||||
|
||||
Reference in New Issue
Block a user