Expand description
HTTP request metrics middleware.
Emits the three series referenced by docs/SLO.md
and the reference Grafana dashboard
(docs/dashboards/README.md):
tensor_wasm_http_requests_total{route, method, status}— countertensor_wasm_http_request_duration_seconds_bucket{route, method, status, le=...}— histogramtensor_wasm_http_requests_in_flight{route, method}— gauge
All three families live on the shared
tensor_wasm_core::metrics::TensorWasmMetrics registry so a single
GET /metrics scrape sees the HTTP series next to the kernel-latency and
tenant gauges. The middleware itself is a thin
axum::middleware::from_fn body — the production stack composes it via
http_metrics_middleware in crate::server::build_router, placed
outside bearer_auth so the counter increments for 401 Unauthorized
responses as well (per the SLO doc’s availability_http SLI, which counts
every HTTP response).
§Route-template extraction
Axum exposes the matched route template via
axum::extract::MatchedPath, which the routing layer inserts into the
request extensions before invoking any per-route layer. Our middleware is
installed via Router::layer, which wraps each route’s terminal endpoint
— at the point our middleware runs, MatchedPath is therefore already
present in the extensions whenever the request matched a route. For
requests that did not match a route (axum’s built-in 404 path), the
extension is absent and the middleware labels the metric as "unknown"
to avoid leaking arbitrary path strings into the label space.
§Cardinality control
Label cardinality is bounded by a runtime allow-list of route templates
initialised at startup (RouteAllowList). Templates not in the
allow-list collapse to "unknown". This choice over a compile-time list
is deliberate: the router is built dynamically (build_router /
build_router_with_audit / build_router_with_full_config), so a
compile-time list would either duplicate the route registry (drift risk)
or require a const-fn router builder (not what axum offers in 0.7). The
allow-list is constructed from the same string literals the router uses
— see DEFAULT_ROUTE_ALLOWLIST — and any new route added to
build_router_with_audit MUST also be added to that constant. Failing
to do so is not a security or correctness issue (the metric still
emits, under "unknown"), but it does lose per-route resolution in
dashboards. CI does not enforce parity today; the orchestrator should
flag any router change that is not accompanied by an
DEFAULT_ROUTE_ALLOWLIST update.
§Performance budget
Target overhead is < 1 µs per request. The middleware:
- reads the
MatchedPathextension (oneHashMap::getlookup), - one
HashSet::containsprobe against the allow-list, - two
Family::get_or_createcalls (one read-lockedHashMap::geton the steady-state hot path; one write-locked insert on the cold path when a new label combination first appears), - one atomic
fetch_addfor the counter, - one atomic
fetch_add+fetch_subfor the in-flight gauge, - one
Histogram::observewhich is a single bucket-array sweep.
§In-flight gauge balancing
The in-flight gauge is managed by an RAII InFlightGuard: construction
increments the gauge and the guard’s Drop decrements it. Because the
guard lives on the middleware future’s stack across next.run(req).await,
the gauge is decremented on every exit path — normal completion, an
early return, a panic, and crucially future cancellation (a client
disconnect or an outer tower::timeout layer dropping the metrics future
mid-await). The timeout/load-shed layers in build_router sit outside
this middleware, so cancellation is the realistic leak path; the guard
closes it. This replaces the previous manual inc()/dec() pair, which
leaked tensor_wasm_http_requests_in_flight{route,method} permanently
whenever the future was dropped between the two calls.
No heap allocations on the steady-state path beyond what
prometheus-client itself does on a cold-cache get_or_create. The
String label clones are unavoidable today — prometheus-client 0.24
does not yet support borrowed label values in the
prometheus_client::metrics::family::Family API; this is a known
cost we accept in exchange for the type-safe label set.
Structs§
- Http
Metrics Layer Config - Bundle passed via request extensions to
http_metrics_middleware. - Route
Allow List - Set of route templates that the metrics middleware is willing to emit as
the
routelabel value. Anything not in the set collapses toUNKNOWN_ROUTE.
Constants§
- DEFAULT_
ROUTE_ ALLOWLIST - Default allow-list of axum route templates emitted by
crate::server::build_router_with_audit. Kept in sync by hand with the.route(...)calls in that function; CI does not enforce parity (see module docs for the cardinality-control rationale). - UNKNOWN_
ROUTE - Label substituted when a request did not match any route template (axum’s built-in 404 fallback) or when the matched template is not on the configured allow-list.
Functions§
- http_
metrics_ middleware - Tower middleware that emits the three HTTP-metric families per request.
- route_
label - Resolve the route-template label for a request.