Documentation
¶
Overview ¶
Package metrics is the Go representation of pleme-io's mandated runtime metrics surface (GSDS OBS-13): every service exposes a Prometheus-compatible /metrics endpoint carrying RED signals (request Rate, Error rate, request Duration) plus Go runtime metrics, scraped by the platform Vector pipeline into VictoriaMetrics (homelab) / Datadog (SaaS).
The mandate, like logging-go's: no ad-hoc per-handler instrumentation, no bespoke metric names. Construct one Registry with New (or the canonical config consumer FromConfig), wrap the service's HTTP handler with the RED Registry.Middleware (the same middleware chain server-go runs — one instrumentation surface, not two), and mount Registry.Handler on /metrics. The lifecycle owner mounts it; libraries and one-shot CLIs expose nothing (no scrape lifecycle, OBS-13) but still emit structured logs.
mx, _ := metrics.New(metrics.WithNamespace("akeyless_foo"))
srv := &http.Server{Handler: mx.Middleware()(routes)}
mux.Handle(mx.Path(), mx.Handler()) // mounted by lifecycle-go later
Backend ¶
The core is backed by github.com/VictoriaMetrics/metrics — a tiny dep with Prometheus text exposition and built-in runtime/metrics collection. The surface is abstracted behind Backend so an OpenTelemetry-backed registry (metrics + spans sharing one exporter) can swap in via the import-gated metrics-go/otel leaf (Law 6 / Law 8). The default backend is offline and allocation-light.
Laws ¶
Composition over framework (Law 2): the RED stage is a plain func(http.Handler) http.Handler decorator and the exposition is a plain http.Handler — both compose into server-go's canonical chain and lifecycle's admin mux without this package owning either. Weight is import-gated (Law 6): the OTel path is a leaf sub-module; the core carries only VictoriaMetrics/ metrics. No core-core cycle (Law 8): the core imports neither shikumi-go nor borealis nor lifecycle-go — Config is a plain yaml-tagged struct read by shikumi externally, and the Handler is mounted by lifecycle, not the reverse.
Index ¶
- Constants
- func DefaultRoute(r *http.Request) string
- type Backend
- type Config
- type Counter
- type Gauge
- type Histogram
- type Label
- type MiddlewareOption
- type Option
- type Registry
- func (r *Registry) Counter(name string, labels ...Label) Counter
- func (r *Registry) Enabled() bool
- func (r *Registry) Gauge(name string, labels ...Label) Gauge
- func (r *Registry) GaugeFunc(name string, f func() float64, labels ...Label) Gauge
- func (r *Registry) Handler() http.Handler
- func (r *Registry) Histogram(name string, labels ...Label) Histogram
- func (r *Registry) Middleware(opts ...MiddlewareOption) func(http.Handler) http.Handler
- func (r *Registry) Namespace() string
- func (r *Registry) Path() string
- func (r *Registry) Timer(name string, labels ...Label) func()
- type RouteFunc
- type Stats
- type Summary
- type SummaryOption
- type VictoriaBackend
- type VictoriaOption
Constants ¶
const ( // MetricRequestsTotal is the RED Rate + Errors signal: total HTTP requests, // labeled by method, route, and status code. The error rate is derived by // the scraper as the ratio of 5xx (and optionally 4xx) to the whole. MetricRequestsTotal = "http_requests_total" // MetricRequestDuration is the RED Duration signal: request latency in // seconds, labeled by method and route. MetricRequestDuration = "http_request_duration_seconds" // MetricRequestsInFlight is the concurrent-request gauge — a useful adjunct // to RED for saturation. MetricRequestsInFlight = "http_requests_in_flight" )
RED metric base names (declared schema, OBS-13 — not ad-hoc). They follow the Prometheus/OpenMetrics convention (`_total` for counters, `_seconds` for duration histograms). The Registry namespace is prefixed on top.
const ( LabelMethod = "method" LabelRoute = "route" LabelStatus = "status" )
RED label names (declared schema).
const DefaultPath = "/metrics"
DefaultPath is the conventional exposition endpoint (OBS-13). It is the default for Config.Path and Registry.Path when none is configured.
Variables ¶
This section is empty.
Functions ¶
func DefaultRoute ¶
DefaultRoute returns the matched ServeMux pattern (Go 1.22+), or "<unmatched>" when the request was not matched by a pattern-bearing mux. This keeps the route label bounded by the number of registered routes, never by traffic.
Types ¶
type Backend ¶
type Backend interface {
// Counter returns a monotonically-increasing counter for the fully-rendered
// metric name (already namespaced + labeled by the Registry). Repeated calls
// with the same name return the same instrument (get-or-create).
Counter(name string) Counter
// Gauge returns a gauge whose value is produced on scrape by f. A nil f
// yields a settable gauge (f defaults to reporting the last Set value).
Gauge(name string, f func() float64) Gauge
// Histogram returns a duration/value histogram. When buckets is non-nil the
// backend SHOULD use those explicit Prometheus le upper bounds; when nil it
// uses its native bucketing (VictoriaMetrics vmrange auto-buckets).
Histogram(name string, buckets []float64) Histogram
// WritePrometheus writes the full Prometheus text exposition for every
// instrument in this backend, plus Go runtime + process metrics where the
// backend collects them (OBS-13 runtime/metrics, never hand-rolled gauges).
WritePrometheus(w io.Writer)
}
Backend is the swappable instrument store behind a Registry (Law 2 / Law 6: the surface is abstracted so an OTel-backed store can replace VictoriaMetrics without changing the public Registry API). It is a small, allocation-light seam: the four instrument constructors plus Prometheus text exposition.
The default is VictoriaBackend. The import-gated metrics-go/otel leaf supplies an OTel-backed implementation (metrics + spans sharing one exporter) installed via WithBackend, so a consumer that does not need OTel never pays for the SDK weight.
Implementations MUST be safe for concurrent use: instruments are created at construction time but updated from request goroutines.
type Config ¶
type Config struct {
// Enabled toggles metrics collection. The pointer distinguishes an absent
// field (nil → enabled, the OBS-13 default) from an explicit `enabled: false`
// in yaml. A disabled Registry is a no-op but still exposes an (empty) mount
// point.
Enabled *bool `yaml:"enabled" json:"enabled"`
// Namespace prefixes every instrument name (joined with "_"). Convention: the
// service name, e.g. "akeyless_gateway". Empty means unprefixed.
Namespace string `yaml:"namespace" json:"namespace"`
// Path is the exposition endpoint the lifecycle owner mounts on. Empty means
// [DefaultPath] ("/metrics").
Path string `yaml:"path" json:"path"`
// Buckets sets explicit Prometheus le upper bounds for the RED duration
// histogram. Must be strictly increasing; an invalid set fails [FromConfig].
// Empty (recommended) uses the VictoriaMetrics-native auto-ranging histogram.
Buckets []float64 `yaml:"buckets" json:"buckets"`
}
Config is the typed, yaml-tagged knob surface for a Registry (Law 3). It is the sub-struct a caller's shikumi-loaded root config embeds:
type Root struct {
Metrics metrics.Config `yaml:"metrics"`
// …
}
and hands to FromConfig. Per §3.5, FromConfig consumes this already-loaded struct and MUST NOT itself call shikumi.Load — config loading happens once, at main, via shikumi.For[Root]; every primitive only consumes its sub-struct. Precedence (args > env > file) is resolved by shikumi during the load, not here.
The zero value is a valid, ENABLED config: an unprefixed registry exposed at "/metrics" using the VictoriaMetrics-native auto-ranging duration histogram — the OBS-13 default. (Enabled defaults true because OBS-13 mandates the surface for services; a one-shot CLI / library simply never constructs a Registry.)
type Counter ¶
type Counter interface {
// Inc adds one.
Inc()
// Add adds delta (delta should be non-negative for a counter).
Add(delta int)
// Get returns the current value.
Get() uint64
}
Counter is a monotonically-increasing cumulative value (request totals, errors, bytes). The behaviour-carrier shape (Law 5): consumers depend on this interface, not the VictoriaMetrics concrete type, so the OTel backend swaps in transparently.
func NopCounter ¶
func NopCounter() Counter
NopCounter returns an inert Counter (all methods no-op, Get reports 0). It is the safe fallback a Backend returns when an instrument cannot be created (e.g. an OTel SDK error in the gated leaf), so a backend failure degrades to inert metrics rather than a panic.
type Gauge ¶
type Gauge interface {
// Set replaces the current value (no-op for callback-backed gauges).
Set(v float64)
// Inc adds one (no-op for callback-backed gauges).
Inc()
// Dec subtracts one (no-op for callback-backed gauges).
Dec()
// Add adds delta, which may be negative (no-op for callback-backed gauges).
Add(delta float64)
// Get returns the current value (callback value for callback gauges).
Get() float64
}
Gauge is an instantaneous value that can rise and fall (in-flight requests, queue depth, connection-pool size). A gauge created with a callback reports the callback's value on scrape; a settable gauge reports its last Gauge.Set.
type Histogram ¶
type Histogram interface {
// Update records a single observation.
Update(v float64)
// UpdateDuration records the seconds elapsed since start (the RED duration
// idiom: defer h.UpdateDuration(time.Now())).
UpdateDuration(start time.Time)
}
Histogram samples a distribution of observations (request durations, payload sizes). For RED durations prefer Histogram.UpdateDuration/Registry.Timer.
func NopHistogram ¶
func NopHistogram() Histogram
NopHistogram returns an inert Histogram (all methods no-op).
type Label ¶
Label is a single name=value dimension applied to an instrument. Labels are part of a metric's declared schema (OBS-13: a declared, not ad-hoc, label set); keep cardinality bounded — never put unbounded values (request IDs, raw paths) in a label. Use the route template, not the concrete path.
type MiddlewareOption ¶
type MiddlewareOption func(*middlewareConfig)
MiddlewareOption tunes one Registry.Middleware instantiation.
func WithRoute ¶
func WithRoute(f RouteFunc) MiddlewareOption
WithRoute overrides the route-label deriver (default DefaultRoute). Supply a router-specific function so the route label stays bounded (the chi/gorilla route template, not the raw path).
type Option ¶
type Option func(*config)
Option configures New using the functional-options pattern, matching the house style across the pleme-io Go libraries (logging-go, errors-go).
func WithBackend ¶
WithBackend installs a custom Backend, overriding the default VictoriaMetrics backend. This is how the import-gated metrics-go/otel leaf supplies an OTel-backed registry (metrics + spans sharing one exporter) without the core importing the OTel SDK (Law 6 / Law 8). A nil backend keeps the default.
func WithBuckets ¶
WithBuckets sets explicit Prometheus le upper bounds for the RED duration histogram (and any Registry.Histogram without its own bounds). Bounds must be strictly increasing; an invalid set fails New/FromConfig (never panics). With no buckets the VictoriaMetrics-native auto-ranging histogram is used — the recommended default, since it needs no a-priori bucket choice and still scrapes as Prometheus. Use this only to match a fixed downstream dashboard's le buckets.
func WithEnabled ¶
WithEnabled toggles the Registry. A disabled Registry is a no-op: inert instruments, pass-through middleware, empty exposition — so an OBS-13 mount point exists unconditionally while a disabled service pays nothing. Enabled by default.
func WithNamespace ¶
WithNamespace sets the prefix applied to every instrument name (joined with "_"). Convention: the service name, e.g. "akeyless_gateway". Empty (default) means unprefixed.
func WithPath ¶
WithPath sets the exposition endpoint the lifecycle owner mounts the Handler at (default DefaultPath = "/metrics"). An empty value keeps the default.
type Registry ¶
type Registry struct {
// contains filtered or unexported fields
}
Registry is a typed, owned metrics registry: a metric/label namespace, a set of declared instruments (RED + caller-defined counters/gauges/histograms), the RED HTTP middleware that feeds them, and an http.Handler that renders Prometheus text exposition. It is constructed once (by the lifecycle owner of a service) via New/FromConfig and never per-handler.
A Registry wraps a backend Backend (VictoriaMetrics by default; OTel via the gated leaf). Multiple Registry values coexist — each owns its own backend set, so tests and embedded servers never collide on the process-global default set.
The zero value is not usable; use New. When Config.Enabled is false (or the disabled option is applied), the Registry is a no-op: instruments are inert, the middleware is pass-through, and the Handler returns 200 with an empty body — so an OBS-13 mount point exists unconditionally while a disabled service pays nothing.
func FromConfig ¶
FromConfig builds a *Registry from an already-loaded Config (§3.5). It is the canonical config-consuming constructor: it takes the sub-struct and MUST NOT call shikumi.Load. Extra opts are applied after the config-derived ones, so a caller can layer non-yaml knobs (a custom Backend from the OTel leaf, a different bucket set) on top:
mx, err := metrics.FromConfig(cfg.Metrics, metrics.WithBackend(otelBackend))
It returns an error only for an invalid bucket set (non-increasing bounds); all other fields have safe fallbacks, so the zero Config yields the New default (enabled, unprefixed, /metrics, auto-ranging histogram).
func New ¶
New constructs a Registry from functional options (§3.5 canonical constructor). With no options it is enabled, backed by VictoriaMetrics, with no namespace prefix, exposed at DefaultPath, using the VictoriaMetrics-native auto-ranging duration histogram (no fixed buckets). RED instruments are registered eagerly so the first request never races their creation.
It returns an error only for an invalid configuration (e.g. non-increasing custom duration buckets via WithBuckets); the option-free path never errors.
func (*Registry) Counter ¶
Counter declares (or fetches) a labeled counter under the Registry namespace. Repeated calls with the same name+labels return the same instrument. On a disabled Registry it returns an inert no-op counter.
func (*Registry) Enabled ¶
Enabled reports whether metrics collection is active. A disabled Registry is a no-op (inert instruments, pass-through middleware, empty exposition) so an OBS-13 mount point always exists.
func (*Registry) Gauge ¶
Gauge declares (or fetches) a settable labeled gauge under the Registry namespace. Use Registry.GaugeFunc for a callback-backed gauge. On a disabled Registry it returns an inert no-op gauge.
func (*Registry) GaugeFunc ¶
GaugeFunc declares a callback-backed labeled gauge whose value f is sampled on every scrape — the right shape for derived values (pool size, build info). On a disabled Registry it returns an inert no-op gauge.
func (*Registry) Handler ¶
Handler returns the Prometheus text-exposition http.Handler for /metrics (Law 2: a plain http.Handler the lifecycle owner mounts; this package never mounts itself). It writes the backend's instruments plus, for the VictoriaMetrics backend, Go runtime + process metrics from runtime/metrics — never hand-rolled gauges (OBS-13). A disabled Registry serves 200 with an empty body.
func (*Registry) Histogram ¶
Histogram declares (or fetches) a labeled histogram under the Registry namespace, using the Registry's configured duration buckets (or native auto-bucketing when none configured). On a disabled Registry it returns an inert no-op histogram.
func (*Registry) Middleware ¶
Middleware returns the RED instrumentation stage as a plain func(http.Handler) http.Handler decorator (Law 2) — the *same* middleware server-go composes into its canonical chain, so RED is derived from one instrumentation surface, not a second. It records, per request:
- http_requests_total{method,route,status} — Rate + Errors
- http_request_duration_seconds{method,route} — Duration
- http_requests_in_flight — saturation adjunct
On a disabled Registry it is pass-through (returns next unchanged), so the chain shape is identical whether or not metrics are enabled.
The route label is derived by DefaultRoute unless overridden with WithRoute. Status labels are the numeric code; group into classes at query time (status=~"5..").
func (*Registry) Namespace ¶
Namespace is the prefix applied to every instrument name (joined with "_"). Empty means unprefixed.
func (*Registry) Path ¶
Path is the exposition endpoint this Registry should be mounted at (OBS-13). lifecycle-go reads it to mount Registry.Handler; it is never hard-coded by the caller.
type RouteFunc ¶
RouteFunc derives the low-cardinality route label for a request. The default (Registry.Middleware with no override) uses DefaultRoute, which returns the request's pattern from Go 1.22+ http.Request.Pattern when the request was matched by an http.ServeMux, falling back to "<unmatched>". Supplying a router-specific RouteFunc (chi/gorilla/echo) keeps the route label bounded — never the raw r.URL.Path, which is unbounded cardinality.
type Stats ¶
type Stats struct {
// Count is the total observations seen.
Count uint64 `json:"count" yaml:"count"`
// Sum is the total of all retained observations.
Sum float64 `json:"sum" yaml:"sum"`
// Min is the smallest retained observation (0 when empty).
Min float64 `json:"min" yaml:"min"`
// Max is the largest retained observation (0 when empty).
Max float64 `json:"max" yaml:"max"`
// Mean is the arithmetic mean of retained observations (0 when empty).
Mean float64 `json:"mean" yaml:"mean"`
// P holds the conventional percentiles keyed "p50"/"p90"/"p95"/"p99".
P map[string]float64 `json:"percentiles" yaml:"percentiles"`
}
Stats is the typed point-in-time rollup of a Summary, returned by Summary.Stats for uniform rendering. It is the data behind borealis.Render(theme, sm.Stats()) (BOREALIS §3.5 / Law 4) — Summary deliberately exposes no bespoke string formatter; the borealis render adapter would live in a leaf sub-package so the core never imports borealis (Law 8).
The P map carries the conventional latency percentiles (p50/p90/p95/p99) so a caller need not name them; use Summary.Quantile for an arbitrary q.
type Summary ¶
type Summary struct {
// contains filtered or unexported fields
}
Summary is a non-server, in-process distribution: a thread-safe accumulator of observations exposing exact quantiles (percentiles), count, sum, min, max and mean — with NO scrape lifecycle and NO /metrics endpoint.
It is the surface a short-lived CLI uses (BOREALIS §2.5a: "libraries and one-shot CLIs expose no /metrics … but still emit structured logs"). A batch tool that fans out N requests and wants to print "p50/p95/p99 latency" at the end has no scrape window for a Registry to be sampled in — Prometheus exposition is meaningless for a process that exits in seconds. Summary fills that gap: collect durations with Summary.Observe, read percentiles with Summary.Quantile before exit, render the typed Stats the uniform way.
Summary is pure stdlib (no VictoriaMetrics, no backend), so it adds zero dependency weight (Law 6) and is offline-testable. It is independent of Registry by design: a Registry is for the long-lived scrape model; a Summary is for the short-lived report model. A tool may use both — Registry while it serves, Summary for a one-shot subcommand — without either importing the other.
Quantiles are EXACT (computed over the retained sample with linear interpolation, the NIST/Excel "type-7" definition that pandas/numpy default to), not the bucketed estimate a Prometheus histogram yields. Exactness is the right tradeoff for a bounded short-lived run; a WithReservoir cap bounds memory for an unexpectedly long run by uniform random sampling (Vitter's algorithm R), trading exactness for a fixed footprint.
The zero value is not usable; construct with NewSummary.
func NewSummary ¶
func NewSummary(opts ...SummaryOption) (*Summary, error)
NewSummary constructs an in-process Summary (§3.5 canonical constructor). With no options it retains every observation and computes exact quantiles — the right default for a short-lived CLI. It never returns an error; the signature returns one only for §3.5 uniformity (`New(required…, opts…) (*T, error)`), and to leave room for a future validating option.
func (*Summary) Count ¶
Count returns the total number of observations seen (including those evicted by a reservoir), so a percentile report can state the true sample size.
func (*Summary) Observe ¶
Observe records a single observation (a latency in seconds, a payload size, any real value). It is safe for concurrent callers. NaN observations are dropped (they would corrupt the sort-based quantile). Under a WithReservoir cap, an observation past capacity replaces a uniformly-chosen retained sample with the correct probability so the reservoir stays an unbiased sample.
func (*Summary) ObserveDuration ¶
ObserveDuration records the seconds elapsed since start — the duration idiom (defer sm.ObserveDuration(time.Now())) mirroring Histogram.UpdateDuration, so a CLI times work the same way it would on the server path.
func (*Summary) Quantile ¶
Quantile returns the q-quantile (0 ≤ q ≤ 1) of the observations, computed exactly over the retained sample with linear interpolation between order statistics (the NIST "type-7" definition used by numpy/pandas). Quantile(0.5) is the median, Quantile(0.95) the p95, Quantile(0.99) the p99. q is clamped to [0,1]. With no observations it returns 0 (a percentile of nothing is reported as zero, never a panic or NaN). It is safe for concurrent callers.
func (*Summary) Stats ¶
Stats returns a typed Stats snapshot — count, sum, min, max, mean, and the p50/p90/p95/p99 percentiles — for a CLI to print before exit. It computes the percentiles exactly (one sort), so prefer it over four separate Summary.Quantile calls when a full report is wanted. With no observations every field is zero.
type SummaryOption ¶
type SummaryOption func(*summaryConfig)
SummaryOption configures a Summary at construction (the functional-options half of the canonical `New(required…, opts…)` shape, BOREALIS §3.5).
func WithReservoir ¶
func WithReservoir(n int) SummaryOption
WithReservoir caps the Summary at n retained samples using uniform random reservoir sampling (Vitter algorithm R): once n observations are retained, each further observation replaces a uniformly-chosen retained sample with probability n/seen. Quantiles are then exact over the reservoir (an unbiased sample of the full stream) rather than the full stream. Use it only when the observation count is unbounded or unknown; a bounded short-lived CLI should leave it off (the default, unbounded, exact over every observation). A non-positive n keeps the default (unbounded).
func WithReservoirSeed ¶
func WithReservoirSeed(seed uint64) SummaryOption
WithReservoirSeed sets the deterministic seed for reservoir sampling so a test (or a reproducible CLI run) sees the same retained sample. Ignored when no reservoir is configured. The default seed is non-zero and fixed.
type VictoriaBackend ¶
type VictoriaBackend struct {
// contains filtered or unexported fields
}
VictoriaBackend is the default Backend, backed by an owned github.com/VictoriaMetrics/metrics Set. Each VictoriaBackend owns its own Set, so multiple registries (and tests) never collide on the process-global default set. Runtime + process metrics are appended on exposition.
func NewVictoriaBackend ¶
func NewVictoriaBackend(opts ...VictoriaOption) *VictoriaBackend
NewVictoriaBackend constructs a VictoriaMetrics-backed backend with runtime and process metrics enabled on exposition (the OBS-13 default).
func (*VictoriaBackend) Counter ¶
func (b *VictoriaBackend) Counter(name string) Counter
Counter implements Backend.
func (*VictoriaBackend) Gauge ¶
func (b *VictoriaBackend) Gauge(name string, f func() float64) Gauge
Gauge implements Backend. A nil f produces a settable gauge whose Set value is reported on scrape (VictoriaMetrics gauges are pull-based callbacks, so a settable gauge is modelled by a callback over an owned atomic value).
func (*VictoriaBackend) Histogram ¶
func (b *VictoriaBackend) Histogram(name string, buckets []float64) Histogram
Histogram implements Backend. With explicit buckets it uses a classic Prometheus le-bucketed histogram; with nil buckets it uses the VictoriaMetrics native auto-ranging (vmrange) histogram — both scrape cleanly in Prometheus exposition.
func (*VictoriaBackend) WritePrometheus ¶
func (b *VictoriaBackend) WritePrometheus(w io.Writer)
WritePrometheus implements Backend: this backend's instruments plus Go runtime + process metrics (from runtime/metrics, never hand-rolled).
type VictoriaOption ¶
type VictoriaOption func(*VictoriaBackend)
VictoriaOption tunes a VictoriaBackend at construction.
func WithoutProcessMetrics ¶
func WithoutProcessMetrics() VictoriaOption
WithoutProcessMetrics disables process-level metrics (FDs, RSS, CPU) for this backend.
func WithoutRuntimeMetrics ¶
func WithoutRuntimeMetrics() VictoriaOption
WithoutRuntimeMetrics disables Go runtime/metrics exposition for this backend. Runtime metrics are on by default (OBS-13); disable only for a backend whose runtime signal is collected elsewhere (e.g. a sidecar).