otel: tick P7, log progress

This commit is contained in:
2026-07-01 15:55:03 +00:00
parent 84285d23e9
commit b478d0a8da

View File

@@ -56,7 +56,7 @@ type-block grammar + type-def editor). You are on branch `loops/otel` in
without reload. Reuse the reactive runtime (`sx/sx/reactive-runtime.sx`, `web/`) + Dream
SSE/streaming already in `lib/host`. Tests: the island SSRs; the SSE endpoint emits a span
event; the page lists recent traces.
- [ ] **P7 — OTLP-JSON export.** Serialize spans to the OTLP/JSON schema (resourceSpans →
- [x] **P7 — OTLP-JSON export.** Serialize spans to the OTLP/JSON schema (resourceSpans →
scopeSpans → spans with traceId/spanId/parentSpanId/name/startTimeUnixNano/endTimeUnixNano/
attributes). `otel/export-otlp traces` → the JSON; POST to an OTLP HTTP collector via an
**injected transport** (so it's testable without a live collector). Tests: OTLP shape matches
@@ -66,6 +66,7 @@ type-block grammar + type-def editor). You are on branch `loops/otel` in
+ an event). Tests: traceparent round-trips; an error thunk yields an error span.
## Progress log (newest first)
- 2026-07-01 — P7 done. `otel/export-otlp spans` folds → the OTLP/JSON envelope `{:resourceSpans [{:resource … :scopeSpans [{:scope … :spans […]}]}]}`; each span has hex `traceId`(32)/`spanId`(16)/`parentSpanId` (from `otel/-pad-hex` of the numeric id suffix via `string->number`+`number->string _ 16`), uint64-as-string `startTimeUnixNano`/`endTimeUnixNano`, typed `attributes` (number→`intValue`, else `stringValue`), and `kind` (2 SERVER if http.method, else 1 INTERNAL); root omits `parentSpanId`. `otel/export-otlp-json` → `dream-json-encode`. `otel/post-otlp endpoint spans transport` POSTs `{:method :url :headers :body}` through an INJECTED transport (tests pass a recorder; real deploy passes http POST). Suite 104/104 (26 new: nesting depth, hex widths+values, string timestamps, kinds, typed attrs, parentSpanId link, json+transport, empty envelope). All needed prims (`string->number`,`number->string`radix,`split`,`keys`,`assoc`,`has-key?`,`dream-json-encode`) are real (not server-env), so conformance-safe.
- 2026-07-01 — P6 done. `GET /otel` (`otel/dashboard-route`) SSRs `otel/dashboard`: metrics strip (table) + latest-trace waterfall `<svg>` + recent-traces `<ul>`, on a root carrying Datastar-style `data-on-load="@get('/otel/stream')"`. `GET /otel/stream` (`otel/stream-route`) emits an SSE frame `event: otel.span\ndata: <sxtp event>` — `otel/span-event` wraps a span as an SXTP `event` (the host's Datastar-borrowed wire format), `otel/-stream-body` frames the latest. Plus `otel/recent-traces` (newest-first {:trace :name :spans}) + `otel/latest-trace`. `otel/routes` mounts via make-app. Suite 78/78 (17 new: recent-traces order, SSR svg+strip+id+sub, SSE event-stream/framing/name, GET /otel via make-app, empty-ring placeholder). DECISION: SSR + declarative reactive attrs + SSE patches IS the reactive-island model here (sxtp = Datastar); SSRs via `render-to-html` (plain HTML tags, not `render-page` which is a server-env prim unavailable in conformance). Live client hydration = deploy concern, out of build+test scope.
- 2026-07-01 — P5 done. `otel/metrics spans` → `{:total-requests N :routes (…)}`; each route = `{:route :count :p50 :p95 :p99}`, route key = `:http.route` attr (falls back to span name). Nearest-rank percentiles (rank=ceil(p/100·N), 1-based) over per-route durations; needed a hand-rolled `otel/-insert`/`otel/-sort-nums` (no `sort` prim) + order-preserving `otel/-distinct`. `otel/metrics-recent` = over the ring. Suite 61/61 (11 new: total, 2 routes, feed count + p50=30/p95=50/p99=50 from [10..50], single-sample p50, sort helper, empty→zeroed). Note: `/` is float division here so `ceil(p/100·N)` is exact.
- 2026-07-01 — P4 done. `otel/waterfall-rects` folds a trace's spans → rect geometry (x ∝ t0trace.t0, width ∝ duration, y ∝ depth via `otel/-depth` parent-link ancestor count; zero-dur spans get a 1px sliver). `otel/waterfall` folds those into an inline `(svg … (g (rect …) (text …)) …)` — one rect+label per span — which `render-to-html` emits as real SVG (verified: nested `db` span at y=22 below its `GET /feed` root at y=4). Suite 50/50 (13 new: rect-per-span, depth 0/1/2, increasing-y with nesting, positive widths, svg head + rect/label counts via `otel/-tree-count`, empty-trace). GOTCHA: this evaluator's quasiquote splice symbol is `splice-unquote`, NOT `unquote-splicing` (plain `unquote` is fine) — the wrong name serialised literally and produced 0 rects.