otel: waterfall time ruler + recent-traces show actual target path & duration
This commit is contained in:
@@ -131,9 +131,12 @@
|
|||||||
(handler (dream-route-handler r)))
|
(handler (dream-route-handler r)))
|
||||||
(dream-route method path
|
(dream-route method path
|
||||||
(fn (req)
|
(fn (req)
|
||||||
|
;; :http.route is the PATTERN (groups metrics: all posts under /:slug);
|
||||||
|
;; :http.target is the ACTUAL path this request hit (e.g. /welcome), so
|
||||||
|
;; the trace view can show which concrete resource was served.
|
||||||
(otel/-timed
|
(otel/-timed
|
||||||
(str method " " path)
|
(str method " " path)
|
||||||
{:http.method method :http.route path}
|
{:http.method method :http.route path :http.target (dream-path req)}
|
||||||
(fn (resp) {:http.status (dream-status (dream-coerce-response resp))})
|
(fn (resp) {:http.status (dream-status (dream-coerce-response resp))})
|
||||||
(fn () (handler req))))))))
|
(fn () (handler req))))))))
|
||||||
|
|
||||||
@@ -226,15 +229,54 @@
|
|||||||
(define otel/-max-depth
|
(define otel/-max-depth
|
||||||
(fn (rects) (reduce (fn (m r) (max m (get r :depth))) 0 rects)))
|
(fn (rects) (reduce (fn (m r) (max m (get r :depth))) 0 rects)))
|
||||||
|
|
||||||
;; the trace as an inline <svg> waterfall.
|
;; (0 1 … n-1)
|
||||||
|
(define otel/-range
|
||||||
|
(fn (n) (if (<= n 0) (list) (append (otel/-range (- n 1)) (list (- n 1))))))
|
||||||
|
|
||||||
|
;; a trace's wall span (ns): latest end − earliest start.
|
||||||
|
(define otel/-trace-dur
|
||||||
|
(fn (trace-id)
|
||||||
|
(let ((spans (otel/trace-spans trace-id)))
|
||||||
|
(if (empty? spans) 0 (- (otel/-max-t1 spans) (otel/-min-t0 spans))))))
|
||||||
|
|
||||||
|
;; a time ruler across the top: N vertical gridlines spanning the body, each with a
|
||||||
|
;; "<t>ms" label showing the offset from the trace start. Chrome is <line>/<text> so
|
||||||
|
;; the per-span <rect> count is unaffected.
|
||||||
|
(define otel/-ruler
|
||||||
|
(fn (dur ruler-h total-h)
|
||||||
|
(let ((inner (- otel/-svg-w (* 2 otel/-pad)))
|
||||||
|
(n 6))
|
||||||
|
(quasiquote
|
||||||
|
(g
|
||||||
|
(line :x1 (unquote otel/-pad) :y1 (unquote (- ruler-h 3))
|
||||||
|
:x2 (unquote (- otel/-svg-w otel/-pad)) :y2 (unquote (- ruler-h 3))
|
||||||
|
:stroke "#ccc" :stroke-width 1)
|
||||||
|
(splice-unquote
|
||||||
|
(map
|
||||||
|
(fn (i)
|
||||||
|
(let ((x (+ otel/-pad (quotient (* inner i) (- n 1))))
|
||||||
|
(ms (quotient (* dur i) (* (- n 1) 1000000))))
|
||||||
|
(quasiquote
|
||||||
|
(g
|
||||||
|
(line :x1 (unquote x) :y1 (unquote (- ruler-h 3)) :x2 (unquote x)
|
||||||
|
:y2 (unquote total-h) :stroke "#eee" :stroke-width 1)
|
||||||
|
(text :x (unquote (+ x 2)) :y 10 :font-size 9 :fill "#999"
|
||||||
|
(unquote (str ms "ms")))))))
|
||||||
|
(otel/-range n))))))))
|
||||||
|
|
||||||
|
;; the trace as an inline <svg> waterfall, with a time ruler above the bars.
|
||||||
(define otel/waterfall
|
(define otel/waterfall
|
||||||
(fn (trace-id)
|
(fn (trace-id)
|
||||||
(let ((rects (otel/waterfall-rects trace-id)))
|
(let ((rects (otel/waterfall-rects trace-id))
|
||||||
(let ((h (+ (* 2 otel/-pad) (* (+ (otel/-max-depth rects) 1) otel/-row-h))))
|
(ruler-h 16))
|
||||||
(quasiquote
|
(let ((body-h (* (+ (otel/-max-depth rects) 1) otel/-row-h)))
|
||||||
(svg :width (unquote otel/-svg-w) :height (unquote h)
|
(let ((h (+ (* 2 otel/-pad) ruler-h body-h)))
|
||||||
:xmlns "http://www.w3.org/2000/svg"
|
(quasiquote
|
||||||
(splice-unquote (map otel/-rect->g rects))))))))
|
(svg :width (unquote otel/-svg-w) :height (unquote h)
|
||||||
|
:xmlns "http://www.w3.org/2000/svg" :font-family "monospace"
|
||||||
|
(unquote (otel/-ruler (otel/-trace-dur trace-id) ruler-h h))
|
||||||
|
(g :transform (unquote (str "translate(0," (+ otel/-pad ruler-h) ")"))
|
||||||
|
(splice-unquote (map otel/-rect->g rects))))))))))
|
||||||
|
|
||||||
;; count nodes whose head symbol prints as `head` — a small SVG-tree assertion aid.
|
;; count nodes whose head symbol prints as `head` — a small SVG-tree assertion aid.
|
||||||
(define otel/-tree-count
|
(define otel/-tree-count
|
||||||
@@ -323,10 +365,17 @@
|
|||||||
((not (empty? roots)) (get (first roots) :name))
|
((not (empty? roots)) (get (first roots) :name))
|
||||||
((not (empty? spans)) (get (first spans) :name))
|
((not (empty? spans)) (get (first spans) :name))
|
||||||
(else ""))))))
|
(else ""))))))
|
||||||
|
;; the actual target path of a trace's root request (nil if not an http trace).
|
||||||
|
(define otel/-trace-target
|
||||||
|
(fn (trace-id)
|
||||||
|
(let ((roots (filter (fn (s) (nil? (get s :parent))) (otel/trace-spans trace-id))))
|
||||||
|
(if (empty? roots) nil (get (get (first roots) :attrs) :http.target)))))
|
||||||
(define otel/trace-summary
|
(define otel/trace-summary
|
||||||
(fn (trace-id)
|
(fn (trace-id)
|
||||||
{:trace trace-id
|
{:trace trace-id
|
||||||
:name (otel/-trace-root-name trace-id)
|
:name (otel/-trace-root-name trace-id)
|
||||||
|
:target (otel/-trace-target trace-id)
|
||||||
|
:dur (otel/-trace-dur trace-id)
|
||||||
:spans (len (otel/trace-spans trace-id))}))
|
:spans (len (otel/trace-spans trace-id))}))
|
||||||
(define otel/recent-traces
|
(define otel/recent-traces
|
||||||
(fn () (reverse (map otel/trace-summary (otel/-trace-ids)))))
|
(fn () (reverse (map otel/trace-summary (otel/-trace-ids)))))
|
||||||
@@ -382,7 +431,11 @@
|
|||||||
(fn (t)
|
(fn (t)
|
||||||
(quasiquote
|
(quasiquote
|
||||||
(li :data-trace (unquote (str (get t :trace)))
|
(li :data-trace (unquote (str (get t :trace)))
|
||||||
(unquote (str (get t :name) " — " (get t :spans) " spans")))))
|
;; show the concrete target (/welcome) if known, else the span
|
||||||
|
;; name, then the wall duration and span count.
|
||||||
|
(span :style "font-weight:600"
|
||||||
|
(unquote (or (get t :target) (get t :name))))
|
||||||
|
(unquote (str " · " (otel/-ms (get t :dur)) " · " (get t :spans) " spans")))))
|
||||||
traces))))))
|
traces))))))
|
||||||
|
|
||||||
;; a per-route latency bar chart: each row is a nested bar — teal p50 inside amber
|
;; a per-route latency bar chart: each row is a nested bar — teal p50 inside amber
|
||||||
|
|||||||
@@ -158,7 +158,8 @@
|
|||||||
(define host-ot-svg (otel/waterfall host-ot-tid))
|
(define host-ot-svg (otel/waterfall host-ot-tid))
|
||||||
(host-ot-test "waterfall head is svg" (str (first host-ot-svg)) "svg")
|
(host-ot-test "waterfall head is svg" (str (first host-ot-svg)) "svg")
|
||||||
(host-ot-test "svg has one rect per span" (otel/-tree-count host-ot-svg "rect") 4)
|
(host-ot-test "svg has one rect per span" (otel/-tree-count host-ot-svg "rect") 4)
|
||||||
(host-ot-test "svg has one label per span" (otel/-tree-count host-ot-svg "text") 4)
|
(host-ot-test "svg has at least one label per span" (>= (otel/-tree-count host-ot-svg "text") 4) true)
|
||||||
|
(host-ot-test "waterfall has a time ruler (gridlines)" (> (otel/-tree-count host-ot-svg "line") 0) true)
|
||||||
|
|
||||||
;; unknown trace → empty waterfall, still a valid svg
|
;; unknown trace → empty waterfall, still a valid svg
|
||||||
(host-ot-test "unknown trace has no rects" (len (otel/waterfall-rects "no-such-trace")) 0)
|
(host-ot-test "unknown trace has no rects" (len (otel/waterfall-rects "no-such-trace")) 0)
|
||||||
@@ -218,6 +219,12 @@
|
|||||||
(host-ot-test "recent-traces lists both" (len (otel/recent-traces)) 2)
|
(host-ot-test "recent-traces lists both" (len (otel/recent-traces)) 2)
|
||||||
(host-ot-test "recent-traces newest first"
|
(host-ot-test "recent-traces newest first"
|
||||||
(get (first (otel/recent-traces)) :name) "GET /health")
|
(get (first (otel/recent-traces)) :name) "GET /health")
|
||||||
|
(host-ot-test "recent-traces carries the actual target path"
|
||||||
|
(get (first (otel/recent-traces)) :target) "/health")
|
||||||
|
(host-ot-test "recent-traces carries a wall duration"
|
||||||
|
(>= (get (first (otel/recent-traces)) :dur) 0) true)
|
||||||
|
(host-ot-test "request span records http.target"
|
||||||
|
(get (get (last (otel/recent)) :attrs) :http.target) "/health")
|
||||||
(host-ot-test "latest-trace is the /health trace"
|
(host-ot-test "latest-trace is the /health trace"
|
||||||
(otel/-trace-root-name (otel/latest-trace)) "GET /health")
|
(otel/-trace-root-name (otel/latest-trace)) "GET /health")
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user