metrics

package module
v0.1.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jun 7, 2026 License: MIT Imports: 10 Imported by: 0

README

metrics-go

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).

Light core. Backed by VictoriaMetrics/metrics — a tiny dep with Prometheus text exposition and built-in runtime/metrics collection. The heavy OpenTelemetry path (metrics + spans sharing one exporter) is quarantined in the import-gated otel/ leaf module, so a consumer that does not wire traces never pays for the OTel SDK weight (BOREALIS Law 6).

What & why

Ad-hoc per-handler instrumentation and bespoke metric names make metrics impossible to aggregate at fleet scale, and an OBS-13-compliant service could not be produced by construction without a named winner. This library gives every Go service one metrics shape:

  • Construct once with New (or FromConfig) — the lifecycle owner builds the registry; libraries and one-shot CLIs expose nothing (no scrape lifecycle).
  • RED from one middlewareRegistry.Middleware is a plain func(http.Handler) http.Handler decorator, the same stage server-go composes into its canonical chain. One instrumentation surface, not two.
  • Runtime metrics for free — Go runtime + process metrics come from runtime/metrics via the backend, never hand-rolled gauges.
  • Mounted by lifecycleRegistry.Handler is a plain http.Handler the lifecycle owner mounts on Registry.Path() (default /metrics); this package never mounts itself or imports lifecycle-go.

Surface (§3.5 canonical)

Symbol Purpose
New(opts ...Option) (*Registry, error) Canonical constructor; enabled / VictoriaMetrics / /metrics by default.
Config + FromConfig(cfg, opts…) (*Registry, error) Typed yaml config → registry (canonical config consumer; never calls shikumi.Load).
Registry.Handler() http.Handler Prometheus text exposition; mounted by lifecycle-go.
Registry.Path() string The endpoint to mount the Handler at (default /metrics).
Registry.Middleware(opts…) func(http.Handler) http.Handler RED instrumentation stage (Law 2).
Registry.Counter / Gauge / GaugeFunc / Histogram / Timer Typed instrument helpers.
Counter / Gauge / Histogram Backend-agnostic behaviour-carrier interfaces (Law 5).
Backend + WithBackend(Backend) The swappable store seam (VictoriaMetrics default; OTel via the gated leaf).
WithEnabled / WithNamespace / WithPath / WithBuckets Functional options.
L(name, value) Label Concise label constructor for call sites.
NewSummary(opts…) (*Summary, error) In-process distribution for short-lived CLIs — no scrape lifecycle, no /metrics.
Summary.Observe / ObserveDuration / Count Record observations (NaN-dropping, concurrency-safe).
Summary.Quantile(q) float64 Exact (type-7) percentile of the retained sample.
Summary.Stats() Stats Typed count/sum/min/max/mean + p50/p90/p95/p99 rollup, for borealis.Render.
WithReservoir(n) / WithReservoirSeed(seed) Bound memory via uniform reservoir sampling for an unbounded run.

Quick start

mx, err := metrics.New(metrics.WithNamespace("akeyless_gateway"))
if err != nil { /* … */ }

// RED on the service handler — the same chain server-go runs.
handler := mx.Middleware()(routes)

// lifecycle-go mounts this on mx.Path() later; here, by hand:
mux.Handle(mx.Path(), mx.Handler())

// typed instruments anywhere:
mx.Counter("secrets_fetched_total", metrics.L("backend", "akeyless")).Inc()
stop := mx.Timer("work_duration_seconds"); defer stop()
Config (shikumi sub-struct)
type Root struct {
    Metrics metrics.Config `yaml:"metrics"`
}
// after shikumi.For[Root](…).Load(ctx):
mx, _ := metrics.FromConfig(cfg.Metrics)
metrics:
  enabled: true          # OBS-13 default; absent ⇒ enabled
  namespace: akeyless_gw # prefixes every metric (joined with "_")
  path: /metrics         # exposition endpoint the lifecycle owner mounts
  buckets: []            # explicit Prometheus le bounds; empty ⇒ VM auto-ranging
OpenTelemetry (gated leaf)

Where traces are already wired and metrics + spans should share one exporter, install the gated OTel backend from the otel/ sub-module:

import metricsotel "github.com/pleme-io/metrics-go/otel"

be, _ := metricsotel.NewBackend(metricsotel.WithNamespace("akeyless_gw"))
mx, _ := metrics.New(metrics.WithBackend(be), metrics.WithNamespace("akeyless_gw"))
// be.MeterProvider() can be shared with an OTel trace pipeline.
In-process percentiles for a short-lived CLI (Summary)

A one-shot CLI has no scrape window, so a Registry//metrics is meaningless for it — but it still wants to report "p50/p95/p99 latency" before it exits. Summary is that non-server surface: pure stdlib (zero dependency weight), concurrency-safe, computing exact quantiles (the type-7 definition numpy / pandas default to), not a bucketed estimate.

sm, _ := metrics.NewSummary() // exact over every observation
for _, target := range targets {
    start := time.Now()
    probe(target)
    sm.ObserveDuration(start)
}
st := sm.Stats() // typed; render via borealis.Render(theme, st)
fmt.Printf("n=%d p50=%.3fs p95=%.3fs p99=%.3fs\n",
    st.Count, st.P["p50"], st.P["p95"], st.P["p99"])

For an unbounded or unknown observation count, cap memory with metrics.WithReservoir(n) (uniform reservoir sampling — quantiles stay exact over an unbiased sample of the stream). A bounded short-lived run leaves it off. Summary is independent of Registry by design (the long-lived scrape model vs the short-lived report model); a tool may use both without either importing the other.

Design notes (BOREALIS)

  • Law 2 — composition over framework. The RED stage is a func(http.Handler) http.Handler and the exposition is an http.Handler; both compose into server-go's chain and lifecycle's admin mux without this package owning either.
  • Law 5 — behaviour carriers + generics. Counter/Gauge/Histogram are small interfaces; consumers depend on them, not the VictoriaMetrics concrete types, so the OTel backend swaps in transparently.
  • Law 6 — weight import-gated. The core carries only VictoriaMetrics/metrics; the OTel SDK lives in the otel/ leaf module.
  • Law 8 — no core-core cycle. The core imports neither shikumi-go (the Config is a plain yaml-tagged struct read externally), nor borealis, nor lifecycle-go (the Handler is mounted by lifecycle, never the reverse).

Deferred integrations

These land when their in-flight sibling primitives are ready (each is a TODO, the core builds + tests green standalone):

  • lifecycle-go mountlifecycle.App mounts Registry.Handler() on Registry.Path(). This package only exposes the Handler and Path.
  • server-go shared chainRegistry.Middleware() becomes a stage in server-go's canonical middleware chain (one instrumentation surface).
  • logs↔metrics exemplars — a correlation_id exemplar link to logging-go (OBS-13/OBS-14).

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

View Source
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.

View Source
const (
	LabelMethod = "method"
	LabelRoute  = "route"
	LabelStatus = "status"
)

RED label names (declared schema).

View Source
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

func DefaultRoute(r *http.Request) string

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.

func NopGauge

func NopGauge() Gauge

NopGauge returns an inert Gauge (all methods no-op, Get reports 0).

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

type Label struct {
	Name  string
	Value string
}

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.

func L

func L(name, value string) Label

L is a concise Label constructor for call sites: metrics.L("method","GET").

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

func WithBackend(b Backend) Option

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

func WithBuckets(bounds ...float64) Option

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

func WithEnabled(enabled bool) Option

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

func WithNamespace(ns string) Option

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

func WithPath(path string) Option

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

func FromConfig(cfg Config, opts ...Option) (*Registry, error)

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

func New(opts ...Option) (*Registry, error)

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

func (r *Registry) Counter(name string, labels ...Label) 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

func (r *Registry) Enabled() bool

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

func (r *Registry) Gauge(name string, labels ...Label) 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

func (r *Registry) GaugeFunc(name string, f func() float64, labels ...Label) Gauge

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

func (r *Registry) Handler() http.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

func (r *Registry) Histogram(name string, labels ...Label) 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

func (r *Registry) Middleware(opts ...MiddlewareOption) func(http.Handler) http.Handler

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

func (r *Registry) Namespace() string

Namespace is the prefix applied to every instrument name (joined with "_"). Empty means unprefixed.

func (*Registry) Path

func (r *Registry) Path() string

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.

func (*Registry) Timer

func (r *Registry) Timer(name string, labels ...Label) func()

Timer starts a duration measurement against a labeled histogram and returns a stop func to defer: stop := mx.Timer("work_duration_seconds"); defer stop(). On a disabled Registry the stop func is a no-op.

type RouteFunc

type RouteFunc func(r *http.Request) string

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

func (s *Summary) Count() uint64

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

func (s *Summary) Observe(v float64)

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

func (s *Summary) ObserveDuration(start time.Time)

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

func (s *Summary) Quantile(q float64) float64

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

func (s *Summary) Stats() 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).

Directories

Path Synopsis
otel module

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL