otel P5: metrics aggregate-fold (per-route counts + p50/p95/p99)

otel/metrics folds spans → {:total-requests :routes}; each route carries a
request count and nearest-rank latency percentiles over its durations. Route
key is the http.route attr (falls back to span name). Includes a small
insertion sort (no sort primitive) and order-preserving distinct.
This commit is contained in:
2026-07-01 15:24:00 +00:00
parent 41c62f0c8c
commit c273467929
2 changed files with 96 additions and 0 deletions

View File

@@ -165,6 +165,43 @@
(host-ot-test "unknown trace still yields an svg"
(str (first (otel/waterfall "no-such-trace"))) "svg")
;; ── P5: metrics (aggregate-fold) ───────────────────────────────────
;; Fold recent spans → per-route counters + latency percentiles (nearest-rank).
;; Build spans with KNOWN durations so the percentiles are deterministic.
(otel/reset!)
(for-each
(fn (d)
(otel/record! {:trace "t" :span (str "s" d) :parent nil :name "GET /feed"
:t0 0 :t1 d :attrs {:http.route "/feed"} :events (list)}))
(list 30 10 50 20 40)) ;; unsorted on purpose — the fold must sort
(otel/record! {:trace "t" :span "h" :parent nil :name "GET /health"
:t0 0 :t1 5 :attrs {:http.route "/health"} :events (list)})
(define host-ot-m (otel/metrics (otel/recent)))
(host-ot-test "total requests" (get host-ot-m :total-requests) 6)
(host-ot-test "two routes" (len (get host-ot-m :routes)) 2)
(define host-ot-feed
(first (filter (fn (r) (= (get r :route) "/feed")) (get host-ot-m :routes))))
(host-ot-test "feed count" (get host-ot-feed :count) 5)
(host-ot-test "feed p50" (get host-ot-feed :p50) 30) ;; ceil(.5*5)=3 → sorted[2]=30
(host-ot-test "feed p95" (get host-ot-feed :p95) 50) ;; ceil(.95*5)=5 → sorted[4]=50
(host-ot-test "feed p99" (get host-ot-feed :p99) 50)
(define host-ot-health
(first (filter (fn (r) (= (get r :route) "/health")) (get host-ot-m :routes))))
(host-ot-test "health count" (get host-ot-health :count) 1)
(host-ot-test "health p50 of a single sample" (get host-ot-health :p50) 5)
;; the sort helper on its own
(host-ot-test "sort-nums ascending" (otel/-sort-nums (list 3 1 2 5 4)) (list 1 2 3 4 5))
;; empty ring → zeroed metrics, no routes
(otel/reset!)
(define host-ot-me (otel/metrics (otel/recent)))
(host-ot-test "empty metrics total 0" (get host-ot-me :total-requests) 0)
(host-ot-test "empty metrics no routes" (len (get host-ot-me :routes)) 0)
(define
host-ot-tests-run!
(fn