Skip to main content

Module http_metrics

Module http_metrics 

Source
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} — counter
  • tensor_wasm_http_request_duration_seconds_bucket{route, method, status, le=...} — histogram
  • tensor_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 MatchedPath extension (one HashMap::get lookup),
  • one HashSet::contains probe against the allow-list,
  • two Family::get_or_create calls (one read-locked HashMap::get on the steady-state hot path; one write-locked insert on the cold path when a new label combination first appears),
  • one atomic fetch_add for the counter,
  • one atomic fetch_add + fetch_sub for the in-flight gauge,
  • one Histogram::observe which 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§

HttpMetricsLayerConfig
Bundle passed via request extensions to http_metrics_middleware.
RouteAllowList
Set of route templates that the metrics middleware is willing to emit as the route label value. Anything not in the set collapses to UNKNOWN_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.