-
Notifications
You must be signed in to change notification settings - Fork 10.2k
Description
What did you do?
histogram_fraction returns NaN when the lower argument falls within the first bucket of a classic histogram. For example, for a classic histogram with the following buckets, histogram_fraction(0.0, 1.5, metric_name) returns NaN, instead of the expected 0.02. The same query on a native histogram with custom buckets (NHCB) returns the correct result.
{
name: "positive buckets, lower falls in the first bucket",
lower: 0.0,
upper: 1.5,
buckets: Buckets{
{UpperBound: 1.0, Count: 1.0},
{UpperBound: 2.0, Count: 3.0},
{UpperBound: 3.0, Count: 6.0},
{UpperBound: math.Inf(1), Count: 100.0},
},
// - Bucket [0, 1]: contributes 1.0 observation (full bucket)
// - Bucket (1, 2]: contributes (1.5-1)/(2-1) * (3-1) = 0.5 * 2 = 1.0 observations
// Total: (1.0 + 1.0) / 100.0 = 0.02
expected: 0.02,
actual: math.NaN(),
},
BucketFraction, which calculates fractions for classic histograms, always assumes -Inf as the lower bound of the first bucket, then tries to interpolate the number of observations within that bucket. This leads to dividing infinity by infinity and thus yields NaN. In contrast, HistogramFraction, which calculates fractions for native histograms, assumes a lower bound of 0 when the histogram has only positive buckets and produces the correct fraction.
Similarly, for histogram with negative buckets where the first bucket’s lower bound is -Inf, histogram_fraction returns NaN when the lower argument falls within that first bucket. For example, for a classic histogram with the buckets below, histogram_fraction(-4.0, -2.0, metric_name) returns NaN instead of the expected 0.02.
{
name: "negative buckets, lower falls in the first bucket",
lower: -4.0,
upper: -2.0,
buckets: Buckets{
{UpperBound: -3.0, Count: 10.0},
{UpperBound: -2.0, Count: 12.0},
{UpperBound: -1.0, Count: 15.0},
{UpperBound: math.Inf(1), Count: 100.0},
},
// - Bucket (-Inf, -3]: contributes zero observations (no interpolation with infinite width bucket)
// - Bucket (-3, -2]: contributes 12-10 = 2.0 observations (full bucket)
// Total: 2.0 / 100.0 = 0.02
expected: 0.02,
actual: math.NaN(),
},
HistogramFraction also returns NaN in this case. However, I would expect the first bucket (-Inf lower bound) to be treated similarly to the last bucket (+Inf upper bound) and contribute no observations. For the last bucket (+Inf upper bound), interpolation effectively yields 0 because the denominator is infinite while the numerator is finite:
bucket.Count * (value - bucket.LowerBound) / (bucket.UpperBound - bucket.LowerBound)
However, if the first bucket has a-Inf lower bound, interpolation becomes ∞/∞, which evaluates to NaN, so interpolation should be skipped in this case.
I've explored this issue and think I have a solution. If the maintainers agree this is indeed a bug, I'd be happy to open a PR.
What did you expect to see?
For histograms with only positive buckets, assume the first bucket's lower bound is 0. For histograms with negative buckets, no interpolation should be used for the first bucket (-Inf lower bound).
What did you see instead? Under which circumstances?
I saw NaN instead.
System information
Darwin 24.6.0 arm64
Prometheus version
prometheus, version 3.5.0 (branch: HEAD, revision: 8be3a9560fbdd18a94dedec4b747c35178177202)
build user: root@c1d5ac160d3d
build date: 20250714-16:18:31
go version: go1.24.5
platform: darwin/arm64
tags: netgo,builtinassets
Prometheus configuration file
Alertmanager version
Alertmanager configuration file
Logs