-
Notifications
You must be signed in to change notification settings - Fork 2
MetroMan Histogram
Bucketed distribution of observed values.
Reach for Histogram when you want a distribution that aggregates
cleanly across instances (the bucket counts add). For exact quantiles
on a single instance, use Summary — but those
don't aggregate.
Memory is fixed per series, not traffic-proportional. A Histogram series stores one counter per configured bucket —
buckets.lengthnumbers — regardless of how many observations it receives. Summary's per-series memory instead grows with observation rate (it retains raw samples for the wholewindow). Cardinality still multiplies either way: each label combination gets its own full bucket ladder, so 7 buckets × 100 distinct label combinations is 700 stored counters (and 700 rendered_bucketlines, plus+Inf/_sum/_countper series). See Labels for the cardinality-growth caveat that applies to every metric kind.
import { Histogram } from '@tundralibs/metro-man';
const latency = new Histogram({
name: 'http_request_seconds',
help: 'HTTP request latency',
buckets: [0.05, 0.1, 0.25, 0.5, 1, 2.5, 5],
});
latency.observe(0.083, { route: '/users' });
latency.observe(0.420, { route: '/users' });Parameters:
-
options.name— Required. Metric identifier. -
options.help— Optional. Human description. -
options.buckets— Optionalnumber[]. Defaults to[1, 1.5, 2, 5, 10]. Each bound must be a finite number; duplicate bounds are silently de-duplicated (see Bucket Semantics) and the result sorted ascending internally.
Throws:
-
InvalidMetricOptionsError— Whennameis missing, orbucketsis present but not an array of numbers, or contains a non-finite bound (NaN,±Infinity).
Record a single observation. For every bucket whose upper bound is
>= value, the bucket counter is incremented; value is added to
the series' running sum; and the series' total observation
count is incremented by 1 (independently of the buckets, so it
stays correct when observations exceed the largest finite bucket).
Throws:
-
InvalidMetricOptionsError— whenvalueis non-finite (NaN,±Infinity). -
InvalidLabelError— whenlabelscontainsle(reserved for Prometheus bucket labels), or a name outside[A-Za-z_][A-Za-z0-9_]*.
reset() drops every series; remove(labels?) drops a single series
and returns true if a series was actually removed. Both inherited from
BaseMetric.
Throws:
-
InvalidLabelError—remove(labels)rejects a label name outside[A-Za-z_][A-Za-z0-9_]*, the same validationobserveapplies.
Inherited from BaseMetric. toPrometheus() is overridden to emit
the _bucket{le="…"} / _sum / _count triple per series.
Buckets are cumulative — an observation of 0.3 lands in every
bucket whose upper bound is >= 0.3. The exposition format adds an
+Inf bucket at the end carrying the total observation count.
Observations above the largest finite bucket still increment
_count and the +Inf bucket — they simply don't appear in any
finite bucket.
Every output format lists buckets in ascending upper-bound order:
the JSON payload carries them as an ordered
Array<{ le: number; count: number }> (a numeric-keyed record
would list integer bounds before decimal ones).
Duplicate bounds are silently collapsed.
bucketsis de-duplicated withnew Set()before sorting, so[1, 1, 2]becomes two buckets, not three —0and-0also collapse to a single bound. A repeatedlevalue would otherwise render the same series twice, which a strict Prometheus scraper rejects outright. If your bucket list is concatenated from more than one config source, expect fewer buckets than the combined input length whenever they overlap.
For the Quick Start example above.
# HELP http_request_seconds HTTP request latency
# TYPE http_request_seconds histogram
http_request_seconds_bucket{route="/users",le="0.05"} 0
http_request_seconds_bucket{route="/users",le="0.1"} 1
http_request_seconds_bucket{route="/users",le="0.25"} 1
http_request_seconds_bucket{route="/users",le="0.5"} 2
…
http_request_seconds_bucket{route="/users",le="+Inf"} 2
http_request_seconds_sum{route="/users"} 0.503
http_request_seconds_count{route="/users"} 2
{
"name": "http_request_seconds",
"help": "HTTP request latency",
"type": "HISTOGRAM",
"labels": ["route"],
"data": {
"route=\"/users\"": {
"buckets": [
{ "le": 0.05, "count": 0 },
{ "le": 0.1, "count": 1 },
{ "le": 0.25, "count": 1 },
{ "le": 0.5, "count": 2 },
{ "le": 1, "count": 2 },
{ "le": 2.5, "count": 2 },
{ "le": 5, "count": 2 }
],
"sum": 0.503,
"count": 2
}
}
}