Skip to content

histogram_fraction returns NaN for classic histograms when lower falls within the first bucket #17367

@MohammadAlavi1986

Description

@MohammadAlavi1986

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


Metadata

Metadata

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions