Skip to content

Reject repeated histogram bucket bounds - #1214

Open
chrikrah wants to merge 2 commits into
prometheus:masterfrom
chrikrah:chrikrah/histogram-strictly-increasing-buckets
Open

chrikrah wants to merge 2 commits into
prometheus:masterfrom
chrikrah:chrikrah/histogram-strictly-increasing-buckets

Conversation

@chrikrah

@chrikrah chrikrah commented Oct 4, 2026

Copy link
Copy Markdown
Contributor

A NaN bucket bound passes Histogram._prepare_buckets (prometheus_client/metrics.py:642), because buckets != sorted(buckets) never fires on it. The histogram then exposes an le="NaN" series that no observation can land in. This change requires strictly increasing bounds: [0.1, nan, 1.0] and a repeated bound such as [0.1, 0.5, 0.5, 1.0] now raise ValueError at construction. No issue exists for this.

Before, at 9cd073c:

# Python 3.12.3; buckets=[0.1, float('nan'), 1.0], observe(0.05), observe(0.7)
$ PYTHONPATH=. python3 nan_bucket.py | grep _bucket
h_bucket{le="0.1"} 1.0
h_bucket{le="NaN"} 1.0
h_bucket{le="1.0"} 2.0
h_bucket{le="+Inf"} 2.0

The repeated bound is the part to decide. Prometheus ingests it today, since both le="0.5" series always carry the same value. Raising there buys consistency with client_golang ("histogram buckets must be in increasing order", prometheus/histogram.go:590), not ingestion safety. Code that passes one today gets a ValueError with this change. A lone [nan] still passes, as the check compares pairs.

Verification

# Python 3.14.7
$ python -m pytest -q tests
428 passed, 1 skipped in 10.21s
# base 9cd073c: 428 passed, 1 skipped (the new cases are assertions inside test_setting_buckets)
$ git stash -- prometheus_client/metrics.py && python -m pytest -q tests/test_core.py   # fix reverted, test kept
FAILED tests/test_core.py::TestHistogram::test_setting_buckets - AssertionError: ValueError not raised by Histogram
1 failed, 113 passed in 3.73s
$ flake8 prometheus_client/ tests/ && isort --check prometheus_client/ tests/   # flake8 7.4.1, isort 5.10.1

This touches the same function as #1104. I will rebase onto whichever lands first.

@csmarchbanks, should a repeated bound raise as here, or be dropped silently as client_java does?

_prepare_buckets accepted any non-decreasing list, so buckets=[0.1, 0.5,
0.5, 1.0] exposed two series with le="0.5" and a NaN bound passed too.
Require strictly increasing bounds, as client_golang does.

Signed-off-by: Christopher Krah <github@christopher-krah.com>

@csmarchbanks csmarchbanks left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks! I think we need a test case for just [float("nan")] as well. I prefer raising as it probably indicates a typo or something, it doesn't make sense to duplicate them.

A single NaN bound skipped the pairwise order check, so buckets=[float("nan")]
was accepted and exposed a le="NaN" series.

Signed-off-by: Christopher Krah <48338417+chrikrah@users.noreply.github.com>

@chrikrah chrikrah left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@csmarchbanks Added. It closes the lone-NaN case the description flagged: a one-element list has no pairs, so 5739cf9 still accepted [float("nan")] and exposed a le="NaN" series. The new commit rejects any NaN bound up front, before the order check.

$ PYTHONPATH=. python -m pytest tests/test_core.py -q -p no:cacheprovider   # Python 3.12.3, pytest 9.1.1
new head:                                  114 passed
same tests, metrics.py from 5739cf9:       1 failed, 113 passed  (test_setting_buckets, the [nan] case)
$ flake8 prometheus_client/ tests/   # flake8 7.3, via tox env deps
$ isort --check prometheus_client/ tests/   # isort 5.10.1
both clean

Kept raising for repeated bounds, as you prefer. If a separate NaN message reads as noise next to the order error, I'd still keep it: "not in strictly increasing order" would send someone hunting for a sort problem that isn't there. Anything else you want in this one before it merges?

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants