Documentation
¶
Overview ¶
Package metrics owns the application's Prometheus instrumentation.
Rationale: kube-prometheus-stack already runs in the cluster and scrapes node/k8s/cnpg/argocd metrics out of the box. Adding a /metrics endpoint to rivolt with an explicit ServiceMonitor lights up per-handler latency, Rivian-result-class counters, and the other app-shaped signals the cluster-level scrapers can't see.
All metrics live on a private *prometheus.Registry rather than the global default. Reasons:
- Tests can spin up a fresh registry per test.
- We don't accidentally collide with go-collector / process-collector registrations from libraries that touch promauto.
- The handler is exported scoped to this registry, so /metrics output is exactly what we asked for.
Naming follows Prometheus conventions: snake_case, base unit suffix (_seconds, _bytes, _total).
Index ¶
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
This section is empty.
Types ¶
type Metrics ¶
type Metrics struct {
// HTTP server side.
HTTPRequestsTotal *prometheus.CounterVec
HTTPRequestDuration *prometheus.HistogramVec
// Rivian upstream classification (transient/outage/rate-limited/
// user-action/unknown — see internal/rivian/errclass.go). Counts
// outcomes per call class, not per error string, so cardinality
// stays bounded.
RivianResultsTotal *prometheus.CounterVec
// Subscription leases owned by this pod. The lease layer
// increments/decrements as it claims and releases.
SubscriptionLeases prometheus.Gauge
// Circuit breaker telemetry. State is a 0/1/2 gauge
// (closed/half_open/open) so Grafana can alert on
// max_over_time != 0. Trips is a counter labelled by reason
// ("rate_limited" | "outage") so we can answer "what's making
// it open" in one PromQL.
RivianBreakerState prometheus.Gauge
RivianBreakerTrips *prometheus.CounterVec
// Global token bucket gating. Counter increments every time
// the bucket says no, partitioned by class so we can answer
// "is priority being starved".
RivianRateLimitBlocked *prometheus.CounterVec
// AI provider spend per user is intentionally NOT exposed here.
// User-id labels would blow up cardinality at 1000 vehicles. The
// existing internal/ai/usage.go writes per-user totals to
// Postgres; surface those via a Grafana SQL data source if/when
// we want to chart them.
AIRequestsTotal *prometheus.CounterVec
// contains filtered or unexported fields
}
Metrics is the rivolt-specific instrumentation surface. One instance is built at startup and shared with anything that emits a metric. Keep it small — adding a metric here means it shows up in Grafana, so each one should answer a real question you'd want to ask at 3am.
func New ¶
func New() *Metrics
New constructs a Metrics with all collectors registered against a fresh registry. Pass the returned *Metrics into any package that needs to record a sample. Call Handler() to mount /metrics.
func (*Metrics) HTTPMiddleware ¶
HTTPMiddleware records request count + latency for every handler. Uses chi's RoutePattern (e.g. "/api/state/{vehicleID}") rather than the raw URL so cardinality stays bounded as vehicles scale.
Place AFTER chi route mounting (chi resolves the pattern when the handler runs, not before) — i.e. add it inside Router.Group / on individual subrouters that have routes mounted, OR rely on chi's behaviour of populating RouteContext as routing happens. We mount it at the root using a deferred capture: the recorder reads chi.RouteContext(r.Context()).RoutePattern() AFTER ServeHTTP, by which point chi has filled it in.
func (*Metrics) Handler ¶
Handler returns the http.Handler for /metrics scoped to this Metrics' private registry. Mount under /metrics with no auth — the Prometheus scraper inside the cluster reaches it via the pod IP, and the Ingress doesn't expose it externally (see deploy/helm/rivolt/templates/ingress.yaml).
func (*Metrics) Registry ¶
func (m *Metrics) Registry() *prometheus.Registry
Registry returns the underlying registry. Tests use this to gather expected samples without going through the HTTP handler.