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

@@ -214,3 +214,62 @@
(let ((self (if (and (not (empty? tree)) (= (str (first tree)) head)) 1 0)))
(reduce (fn (acc n) (+ acc (otel/-tree-count n head))) self tree))
0)))
;; ── P5: metrics (aggregate-fold) ──────────────────────────────────────
;; Fold the recent spans into per-route request counts + a latency histogram
;; (p50/p95/p99 from durations). No `sort` primitive here, so percentiles ride a
;; tiny insertion sort; nearest-rank keeps the maths exact for the tests.
(define otel/-insert
(fn (x sorted)
(if (empty? sorted)
(list x)
(if (<= x (first sorted))
(cons x sorted)
(cons (first sorted) (otel/-insert x (rest sorted)))))))
(define otel/-sort-nums
(fn (lst) (reduce (fn (acc x) (otel/-insert x acc)) (list) lst)))
;; nearest-rank percentile of an ASCENDING list: rank = ceil(p/100 · N), 1-based.
(define otel/-percentile
(fn (sorted p)
(if (empty? sorted)
0
(let ((n (len sorted)))
(let ((idx (- (ceil (* (/ p 100) n)) 1)))
(let ((i (if (< idx 0) 0 (if (>= idx n) (- n 1) idx))))
(nth sorted i)))))))
;; a span's route label: the http.route attr, else the span name.
(define otel/-span-route
(fn (s) (or (get (get s :attrs) :http.route) (get s :name))))
(define otel/-span-dur (fn (s) (- (get s :t1) (get s :t0))))
;; distinct values, order-preserving.
(define otel/-distinct
(fn (lst)
(reduce
(fn (acc x) (if (some (fn (y) (= y x)) acc) acc (append acc (list x))))
(list)
lst)))
;; the aggregate for one route: count + latency percentiles over its durations.
(define otel/route-metrics
(fn (spans route)
(let ((rs (filter (fn (s) (= (otel/-span-route s) route)) spans)))
(let ((durs (otel/-sort-nums (map otel/-span-dur rs))))
{:route route
:count (len rs)
:p50 (otel/-percentile durs 50)
:p95 (otel/-percentile durs 95)
:p99 (otel/-percentile durs 99)}))))
;; fold spans → {:total-requests N :routes (per-route metrics …)}.
(define otel/metrics
(fn (spans)
(let ((routes (otel/-distinct (map otel/-span-route spans))))
{:total-requests (len spans)
:routes (map (fn (r) (otel/route-metrics spans r)) routes)})))
;; convenience: metrics over the current ring.
(define otel/metrics-recent (fn () (otel/metrics (otel/recent))))