diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index ed4cfbf356..e4c2fbce18 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -19,7 +19,7 @@ jobs: - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 with: persist-credentials: false - - uses: prometheus/promci@443c7fc2397e946bc9f5029e313a9c3441b9b86d # v0.4.7 + - uses: prometheus/promci@c0916f0a41f13444612a8f0f5e700ea34edd7c19 # v0.5.3 - uses: ./.github/promci/actions/setup_environment with: enable_npm: true @@ -37,7 +37,7 @@ jobs: - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 with: persist-credentials: false - - uses: prometheus/promci@443c7fc2397e946bc9f5029e313a9c3441b9b86d # v0.4.7 + - uses: prometheus/promci@c0916f0a41f13444612a8f0f5e700ea34edd7c19 # v0.5.3 - uses: ./.github/promci/actions/setup_environment - run: go test --tags=dedupelabels ./... - run: go test --tags=slicelabels -race ./cmd/prometheus ./model/textparse ./prompb/... @@ -81,7 +81,7 @@ jobs: - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 with: persist-credentials: false - - uses: prometheus/promci@443c7fc2397e946bc9f5029e313a9c3441b9b86d # v0.4.7 + - uses: prometheus/promci@c0916f0a41f13444612a8f0f5e700ea34edd7c19 # v0.5.3 - uses: ./.github/promci/actions/setup_environment with: enable_go: false @@ -146,7 +146,7 @@ jobs: - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 with: persist-credentials: false - - uses: prometheus/promci@443c7fc2397e946bc9f5029e313a9c3441b9b86d # v0.4.7 + - uses: prometheus/promci@c0916f0a41f13444612a8f0f5e700ea34edd7c19 # v0.5.3 - uses: ./.github/promci/actions/build with: promu_opts: "-p linux/amd64 -p windows/amd64 -p linux/arm64 -p darwin/amd64 -p darwin/arm64 -p linux/386" @@ -173,7 +173,7 @@ jobs: - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 with: persist-credentials: false - - uses: prometheus/promci@443c7fc2397e946bc9f5029e313a9c3441b9b86d # v0.4.7 + - uses: prometheus/promci@c0916f0a41f13444612a8f0f5e700ea34edd7c19 # v0.5.3 - uses: ./.github/promci/actions/build with: parallelism: 12 @@ -268,7 +268,7 @@ jobs: - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 with: persist-credentials: false - - uses: prometheus/promci@443c7fc2397e946bc9f5029e313a9c3441b9b86d # v0.4.7 + - uses: prometheus/promci@c0916f0a41f13444612a8f0f5e700ea34edd7c19 # v0.5.3 - uses: ./.github/promci/actions/publish_main with: docker_hub_login: ${{ secrets.docker_hub_login }} @@ -287,7 +287,7 @@ jobs: - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 with: persist-credentials: false - - uses: prometheus/promci@443c7fc2397e946bc9f5029e313a9c3441b9b86d # v0.4.7 + - uses: prometheus/promci@c0916f0a41f13444612a8f0f5e700ea34edd7c19 # v0.5.3 - uses: ./.github/promci/actions/publish_release with: docker_hub_login: ${{ secrets.docker_hub_login }} @@ -304,7 +304,7 @@ jobs: uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 with: persist-credentials: false - - uses: prometheus/promci@443c7fc2397e946bc9f5029e313a9c3441b9b86d # v0.4.7 + - uses: prometheus/promci@c0916f0a41f13444612a8f0f5e700ea34edd7c19 # v0.5.3 - name: Install nodejs uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v6.0.0 with: diff --git a/CHANGELOG.md b/CHANGELOG.md index 01da079725..15b620bfa6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,42 @@ * [FEATURE] Templates: Add urlQueryEscape to template functions. #17403 * [BUGFIX] TSDB: Register `prometheus_tsdb_sample_ooo_delta` metric properly. #17477 +## 3.8.0 / 2025-11-28 + +* [CHANGE] Remote-write 2 (receiving): Update to [2.0-rc.4 spec](https://github.com/prometheus/docs/blob/60c24e450010df38cfcb4f65df874f6f9b26dbcb/docs/specs/prw/remote_write_spec_2_0.md). "created timestamp" (CT) is now called "start timestamp" (ST). #17411 +* [CHANGE] TSDB: Native Histogram Custom Bounds with a NaN threshold are now rejected. #17287 +* [FEATURE] OAuth2: support jwt-bearer grant-type (RFC7523 3.1). #17592 +* [FEATURE] Dockerfile: Add OpenContainers spec labels to Dockerfile. #16483 +* [FEATURE] SD: Add unified AWS service discovery for ec2, lightsail and ecs services. #17046 +* [FEATURE] Native histograms are now a stable, but optional feature, use the `scrape_native_histogram` config setting. #17232 #17315 +* [FEATURE] UI: Support anchored and smoothed keyword in promql editor. #17239 +* [FEATURE] UI: Show detailed relabeling steps for each discovered target. #17337 +* [FEATURE] Alerting: Add urlQueryEscape to template functions. #17403 +* [FEATURE] Promtool: Add Remote-Write 2.0 support to `promtool push metrics` via the `--protobuf_message` flag. #17417 +* [ENHANCEMENT] Clarify the docs about handling negative native histograms. #17249 +* [ENHANCEMENT] Mixin: Add static UID to the remote-write dashboard. #17256 +* [ENHANCEMENT] PromQL: Reconcile mismatched NHCB bounds in `Add` and `Sub`. #17278 +* [ENHANCEMENT] Alerting: Add "unknown" state for alerting rules that haven't been evaluated yet. #17282 +* [ENHANCEMENT] Scrape: Allow simultaneous use of classic histogram → NHCB conversion and zero-timestamp ingestion. #17305 +* [ENHANCEMENT] UI: Add smoothed/anchored in explain. #17334 +* [ENHANCEMENT] OTLP: De-duplicate any `target_info` samples with the same timestamp for the same series. #17400 +* [ENHANCEMENT] Document `use_fips_sts_endpoint` in `sigv4` config sections. #17304 +* [ENHANCEMENT] Document Prometheus Agent. #14519 +* [PERF] PromQL: Speed up parsing of variadic functions. #17316 +* [PERF] UI: Speed up alerts/rules/... pages by not rendering collapsed content. #17485 +* [PERF] UI: Performance improvement when getting label name and values in promql editor. #17194 +* [PERF] UI: Speed up /alerts for many firing alerts via virtual scrolling. #17254 +* [BUGFIX] PromQL: Fix slice indexing bug in info function on churning series. #17199 +* [BUGFIX] API: Reduce lock contention on `/api/v1/targets`. #17306 +* [BUGFIX] PromQL: Consistent handling of gauge vs. counter histograms in aggregations. #17312 +* [BUGFIX] TSDB: Allow NHCB with -Inf as the first custom value. #17320 +* [BUGFIX] UI: Fix duplicate loading of data from the API speed up rendering of some pages. #17357 +* [BUGFIX] Old UI: Fix createExpressionLink to correctly build /graph URLs so links from Alerts/Rules work again. #17365 +* [BUGFIX] PromQL: Avoid panic when parsing malformed `info` call. #17379 +* [BUGFIX] PromQL: Include histograms when enforcing sample_limit. #17390 +* [BUGFIX] Config: Fix panic if TLS CA file is absent. #17418 +* [BUGFIX] PromQL: Fix `histogram_fraction` for classic histograms and NHCB if lower bound is in the first bucket. #17424 + ## 3.7.3 / 2025-10-29 * [BUGFIX] UI: Revert changed (and breaking) redirect behavior for `-web.external-url` if `-web.route-prefix` is configured, which was introduced in #17240. #17389 diff --git a/VERSION b/VERSION index c1e43e6d45..19811903a7 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -3.7.3 +3.8.0 diff --git a/cmd/prometheus/main_test.go b/cmd/prometheus/main_test.go index 607e422868..2a1c9816b8 100644 --- a/cmd/prometheus/main_test.go +++ b/cmd/prometheus/main_test.go @@ -979,6 +979,7 @@ remote_write: // | dataPending | 0 | 1228.8 | // | desiredShards | 0.6 | 369.2 |. func TestRemoteWrite_ReshardingWithoutDeadlock(t *testing.T) { + t.Skip("flaky test, see https://github.com/prometheus/prometheus/issues/17489") t.Parallel() tmpDir := t.TempDir() diff --git a/go.mod b/go.mod index 55b8d2ce1f..9cf136eb39 100644 --- a/go.mod +++ b/go.mod @@ -58,7 +58,7 @@ require ( github.com/prometheus/client_golang v1.23.2 github.com/prometheus/client_golang/exp v0.0.0-20250914183048-a974e0d45e0a github.com/prometheus/client_model v0.6.2 - github.com/prometheus/common v0.67.2 + github.com/prometheus/common v0.67.4 github.com/prometheus/common/assets v0.2.0 github.com/prometheus/exporter-toolkit v0.15.0 github.com/prometheus/sigv4 v0.3.0 diff --git a/go.sum b/go.sum index 2c0042edbb..579e86ca58 100644 --- a/go.sum +++ b/go.sum @@ -447,8 +447,8 @@ github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNw github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE= github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= github.com/prometheus/common v0.9.1/go.mod h1:yhUN8i9wzaXS3w1O07YhxHEBxD+W35wd8bs7vj7HSQ4= -github.com/prometheus/common v0.67.2 h1:PcBAckGFTIHt2+L3I33uNRTlKTplNzFctXcWhPyAEN8= -github.com/prometheus/common v0.67.2/go.mod h1:63W3KZb1JOKgcjlIr64WW/LvFGAqKPj0atm+knVGEko= +github.com/prometheus/common v0.67.4 h1:yR3NqWO1/UyO1w2PhUvXlGQs/PtFmoveVO0KZ4+Lvsc= +github.com/prometheus/common v0.67.4/go.mod h1:gP0fq6YjjNCLssJCQp0yk4M8W6ikLURwkdd/YKtTbyI= github.com/prometheus/common/assets v0.2.0 h1:0P5OrzoHrYBOSM1OigWL3mY8ZvV2N4zIE/5AahrSrfM= github.com/prometheus/common/assets v0.2.0/go.mod h1:D17UVUE12bHbim7HzwUvtqm6gwBEaDQ0F+hIGbFbccI= github.com/prometheus/exporter-toolkit v0.15.0 h1:Pcle5sSViwR1x0gdPd0wtYrPQENBieQAM7TmT0qtb2U= diff --git a/promql/promqltest/testdata/histograms.test b/promql/promqltest/testdata/histograms.test index 84a467a314..436390ee41 100644 --- a/promql/promqltest/testdata/histograms.test +++ b/promql/promqltest/testdata/histograms.test @@ -158,6 +158,383 @@ eval instant at 50m histogram_fraction(0, 0.2, rate(testhistogram3_bucket[10m])) {start="positive"} 0.6363636363636364 {start="negative"} 0 +# Positive buckets, lower falls in the first bucket. +load_with_nhcb 5m + positive_buckets_lower_falls_in_the_first_bucket_bucket{le="1"} 1+0x10 + positive_buckets_lower_falls_in_the_first_bucket_bucket{le="2"} 3+0x10 + positive_buckets_lower_falls_in_the_first_bucket_bucket{le="3"} 6+0x10 + positive_buckets_lower_falls_in_the_first_bucket_bucket{le="+Inf"} 100+0x10 + +# - 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 + +eval instant at 50m histogram_fraction(0, 1.5, positive_buckets_lower_falls_in_the_first_bucket_bucket) + expect no_warn + {} 0.02 + +eval instant at 50m histogram_fraction(0, 1.5, positive_buckets_lower_falls_in_the_first_bucket) + expect no_warn + {} 0.02 + +# Negative buckets, lower falls in the first bucket. +load_with_nhcb 5m + negative_buckets_lower_falls_in_the_first_bucket_bucket{le="-3"} 10+0x10 + negative_buckets_lower_falls_in_the_first_bucket_bucket{le="-2"} 12+0x10 + negative_buckets_lower_falls_in_the_first_bucket_bucket{le="-1"} 15+0x10 + negative_buckets_lower_falls_in_the_first_bucket_bucket{le="+Inf"} 100+0x10 + +# - 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 + +eval instant at 50m histogram_fraction(-4, -2, negative_buckets_lower_falls_in_the_first_bucket_bucket) + expect no_warn + {} 0.02 + +eval instant at 50m histogram_fraction(-4, -2, negative_buckets_lower_falls_in_the_first_bucket) + expect no_warn + {} 0.02 + +# Lower is -Inf. +load_with_nhcb 5m + lower_is_negative_Inf_bucket{le="-3"} 10+0x10 + lower_is_negative_Inf_bucket{le="-2"} 12+0x10 + lower_is_negative_Inf_bucket{le="-1"} 15+0x10 + lower_is_negative_Inf_bucket{le="+Inf"} 100+0x10 + +# - Bucket [-Inf, -3]: contributes 10.0 observations (full bucket). +# - Bucket [-3, -2]: contributes 12-10 = 2.0 observations (full bucket). +# - Bucket [-2, -1]: contributes (-1.5-(-2))/(-1-(-2)) * (15-12) = 0.5 * 3 = 1.5 observations. +# Total: (10.0 + 2.0 + 1.5) / 100.0 = 0.135 + +eval instant at 50m histogram_fraction(-Inf, -1.5, lower_is_negative_Inf_bucket) + expect no_warn + {} 0.135 + +eval instant at 50m histogram_fraction(-Inf, -1.5, lower_is_negative_Inf) + expect no_warn + {} 0.135 + +# Lower is -Inf and upper is +Inf (positive buckets). +load_with_nhcb 5m + lower_is_negative_Inf_and_upper_is_positive_Inf__positive_buckets__bucket{le="1"} 1+0x10 + lower_is_negative_Inf_and_upper_is_positive_Inf__positive_buckets__bucket{le="2"} 3+0x10 + lower_is_negative_Inf_and_upper_is_positive_Inf__positive_buckets__bucket{le="3"} 6+0x10 + lower_is_negative_Inf_and_upper_is_positive_Inf__positive_buckets__bucket{le="+Inf"} 100+0x10 + +# Range [-Inf, +Inf] captures all observations. + +eval instant at 50m histogram_fraction(-Inf, +Inf, lower_is_negative_Inf_and_upper_is_positive_Inf__positive_buckets__bucket) + expect no_warn + {} 1.0 + +eval instant at 50m histogram_fraction(-Inf, +Inf, lower_is_negative_Inf_and_upper_is_positive_Inf__positive_buckets_) + expect no_warn + {} 1.0 + +# Lower is -Inf and upper is +Inf (negative buckets). +load_with_nhcb 5m + lower_is_negative_Inf_and_upper_is_positive_Inf__negative_buckets__bucket{le="-3"} 10+0x10 + lower_is_negative_Inf_and_upper_is_positive_Inf__negative_buckets__bucket{le="-2"} 12+0x10 + lower_is_negative_Inf_and_upper_is_positive_Inf__negative_buckets__bucket{le="-1"} 15+0x10 + lower_is_negative_Inf_and_upper_is_positive_Inf__negative_buckets__bucket{le="+Inf"} 100+0x10 + +# Range [-Inf, +Inf] captures all observations. + +eval instant at 50m histogram_fraction(-Inf, +Inf, lower_is_negative_Inf_and_upper_is_positive_Inf__negative_buckets__bucket) + expect no_warn + {} 1.0 + +eval instant at 50m histogram_fraction(-Inf, +Inf, lower_is_negative_Inf_and_upper_is_positive_Inf__negative_buckets_) + expect no_warn + {} 1.0 + +# Lower and upper fall in last bucket (positive buckets). +load_with_nhcb 5m + lower_and_upper_fall_in_last_bucket__positive_buckets__bucket{le="1"} 1+0x10 + lower_and_upper_fall_in_last_bucket__positive_buckets__bucket{le="2"} 3+0x10 + lower_and_upper_fall_in_last_bucket__positive_buckets__bucket{le="3"} 6+0x10 + lower_and_upper_fall_in_last_bucket__positive_buckets__bucket{le="+Inf"} 100+0x10 + +# - Bucket [3, +Inf]: contributes zero observations (no interpolation with infinite width bucket). +# Total: 0.0 / 100.0 = 0.0 + +eval instant at 50m histogram_fraction(4, 5, lower_and_upper_fall_in_last_bucket__positive_buckets__bucket) + expect no_warn + {} 0.0 + +eval instant at 50m histogram_fraction(4, 5, lower_and_upper_fall_in_last_bucket__positive_buckets_) + expect no_warn + {} 0.0 + +# Lower and upper fall in last bucket (negative buckets). +load_with_nhcb 5m + lower_and_upper_fall_in_last_bucket__negative_buckets__bucket{le="-3"} 10+0x10 + lower_and_upper_fall_in_last_bucket__negative_buckets__bucket{le="-2"} 12+0x10 + lower_and_upper_fall_in_last_bucket__negative_buckets__bucket{le="-1"} 15+0x10 + lower_and_upper_fall_in_last_bucket__negative_buckets__bucket{le="+Inf"} 100+0x10 + +# - Bucket [-1, +Inf]: contributes zero observations (no interpolation with infinite width bucket). +# Total: 0.0 / 100.0 = 0.0 + +eval instant at 50m histogram_fraction(0, 1, lower_and_upper_fall_in_last_bucket__negative_buckets__bucket) + expect no_warn + {} 0.0 + +eval instant at 50m histogram_fraction(0, 1, lower_and_upper_fall_in_last_bucket__negative_buckets_) + expect no_warn + {} 0.0 + +# Upper falls in last bucket. +load_with_nhcb 5m + upper_falls_in_last_bucket_bucket{le="1"} 1+0x10 + upper_falls_in_last_bucket_bucket{le="2"} 3+0x10 + upper_falls_in_last_bucket_bucket{le="3"} 6+0x10 + upper_falls_in_last_bucket_bucket{le="+Inf"} 100+0x10 + +# - Bucket [2, 3]: 6-3 = 3.0 observations (full bucket). +# - Bucket [3, +Inf]: contributes zero observations (no interpolation with infinite width bucket). +# Total: 3.0 / 100.0 = 0.03 + +eval instant at 50m histogram_fraction(2, 5, upper_falls_in_last_bucket_bucket) + expect no_warn + {} 0.03 + +eval instant at 50m histogram_fraction(2, 5, upper_falls_in_last_bucket) + expect no_warn + {} 0.03 + +# Upper is +Inf. +load_with_nhcb 5m + upper_is_positive_Inf_bucket{le="1"} 1+0x10 + upper_is_positive_Inf_bucket{le="2"} 3+0x10 + upper_is_positive_Inf_bucket{le="3"} 6+0x10 + upper_is_positive_Inf_bucket{le="+Inf"} 100+0x10 + +# All observations in +Inf bucket: 100-6 = 94.0 observations. +# Total: 94.0 / 100.0 = 0.94 + +eval instant at 50m histogram_fraction(400, +Inf, upper_is_positive_Inf_bucket) + expect no_warn + {} 0.94 + +eval instant at 50m histogram_fraction(400, +Inf, upper_is_positive_Inf) + expect no_warn + {} 0.94 + +# Lower equals upper. +load_with_nhcb 5m + lower_equals_upper_bucket{le="1"} 1+0x10 + lower_equals_upper_bucket{le="2"} 3+0x10 + lower_equals_upper_bucket{le="3"} 6+0x10 + lower_equals_upper_bucket{le="+Inf"} 100+0x10 + +# No observations can be captured in a zero-width range. + +eval instant at 50m histogram_fraction(2, 2, lower_equals_upper_bucket) + expect no_warn + {} 0.0 + +eval instant at 50m histogram_fraction(2, 2, lower_equals_upper) + expect no_warn + {} 0.0 + +# Lower greater than upper. +load_with_nhcb 5m + lower_greater_than_upper_bucket{le="1"} 1+0x10 + lower_greater_than_upper_bucket{le="2"} 3+0x10 + lower_greater_than_upper_bucket{le="3"} 6+0x10 + lower_greater_than_upper_bucket{le="+Inf"} 100+0x10 + +eval instant at 50m histogram_fraction(3, 2, lower_greater_than_upper_bucket) + expect no_warn + {} 0.0 + +eval instant at 50m histogram_fraction(3, 2, lower_greater_than_upper) + expect no_warn + {} 0.0 + +# Single bucket. +load_with_nhcb 5m + single_bucket_bucket{le="+Inf"} 100+0x10 + +# - Bucket [0, +Inf]: contributes zero observations (no interpolation with infinite width bucket). +# Total: 0.0 / 100.0 = 0.0 + +eval instant at 50m histogram_fraction(0, 1, single_bucket_bucket) + expect no_warn + {} 0.0 + +eval instant at 50m histogram_fraction(0, 1, single_bucket) + expect no_warn + {} 0.0 + +# All zero counts. +load_with_nhcb 5m + all_zero_counts_bucket{le="1"} 0+0x10 + all_zero_counts_bucket{le="2"} 0+0x10 + all_zero_counts_bucket{le="3"} 0+0x10 + all_zero_counts_bucket{le="+Inf"} 0+0x10 + +eval instant at 50m histogram_fraction(0, 5, all_zero_counts_bucket) + expect no_warn + {} NaN + +eval instant at 50m histogram_fraction(0, 5, all_zero_counts) + expect no_warn + {} NaN + +# Lower exactly on bucket boundary. +load_with_nhcb 5m + lower_exactly_on_bucket_boundary_bucket{le="1"} 1+0x10 + lower_exactly_on_bucket_boundary_bucket{le="2"} 3+0x10 + lower_exactly_on_bucket_boundary_bucket{le="3"} 6+0x10 + lower_exactly_on_bucket_boundary_bucket{le="+Inf"} 100+0x10 + +# - Bucket [2, 3]: 6-3 = 3.0 observations (full bucket). +# - Bucket [3, +Inf]: contributes zero observations (no interpolation with infinite width bucket). +# Total: 3.0 / 100.0 = 0.03 + +eval instant at 50m histogram_fraction(2, 3.5, lower_exactly_on_bucket_boundary_bucket) + expect no_warn + {} 0.03 + +eval instant at 50m histogram_fraction(2, 3.5, lower_exactly_on_bucket_boundary) + expect no_warn + {} 0.03 + +# Upper exactly on bucket boundary. +load_with_nhcb 5m + upper_exactly_on_bucket_boundary_bucket{le="1"} 1+0x10 + upper_exactly_on_bucket_boundary_bucket{le="2"} 3+0x10 + upper_exactly_on_bucket_boundary_bucket{le="3"} 6+0x10 + upper_exactly_on_bucket_boundary_bucket{le="+Inf"} 100+0x10 + +# - Bucket [0, 1]: (1.0-0.5)/(1.0-0.0) * 1.0 = 0.5 * 1.0 = 0.5 observations. +# - Bucket [1, 2]: 3-1 = 2.0 observations (full bucket). +# Total: (0.5 + 2.0) / 100.0 = 0.025 + +eval instant at 50m histogram_fraction(0.5, 2, upper_exactly_on_bucket_boundary_bucket) + expect no_warn + {} 0.025 + +eval instant at 50m histogram_fraction(0.5, 2, upper_exactly_on_bucket_boundary) + expect no_warn + {} 0.025 + +# Both bounds exactly on bucket boundaries. +load_with_nhcb 5m + both_bounds_exactly_on_bucket_boundaries_bucket{le="1"} 1+0x10 + both_bounds_exactly_on_bucket_boundaries_bucket{le="2"} 3+0x10 + both_bounds_exactly_on_bucket_boundaries_bucket{le="3"} 6+0x10 + both_bounds_exactly_on_bucket_boundaries_bucket{le="+Inf"} 100+0x10 + +# - Bucket [1, 2]: 3-1 = 2.0 observations (full bucket). +# - Bucket [2, 3]: 6-3 = 3.0 observations (full bucket). +# Total: (2.0 + 3.0) / 100.0 = 0.05 + +eval instant at 50m histogram_fraction(1, 3, both_bounds_exactly_on_bucket_boundaries_bucket) + expect no_warn + {} 0.05 + +eval instant at 50m histogram_fraction(1, 3, both_bounds_exactly_on_bucket_boundaries) + expect no_warn + {} 0.05 + +# Fractional bucket bounds. +load_with_nhcb 5m + fractional_bucket_bounds_bucket{le="0.5"} 2.5+0x10 + fractional_bucket_bounds_bucket{le="1"} 7.5+0x10 + fractional_bucket_bounds_bucket{le="+Inf"} 100+0x10 + +# - Bucket [0, 0.5]: (0.5-0.1)/(0.5-0.0) * 2.5 = 0.8 * 2.5 = 2.0 observations. +# - Bucket [0.5, 1.0]: (0.75-0.5)/(1.0-0.5) * (7.5-2.5) = 0.5 * 5.0 = 2.5 observations. +# Total: (2.0 + 2.5) / 100.0 = 0.045 + +eval instant at 50m histogram_fraction(0.1, 0.75, fractional_bucket_bounds_bucket) + expect no_warn + {} 0.045 + +eval instant at 50m histogram_fraction(0.1, 0.75, fractional_bucket_bounds) + expect no_warn + {} 0.045 + +# Range crosses zero. +load_with_nhcb 5m + range_crosses_zero_bucket{le="-2"} 5+0x10 + range_crosses_zero_bucket{le="-1"} 10+0x10 + range_crosses_zero_bucket{le="0"} 15+0x10 + range_crosses_zero_bucket{le="1"} 20+0x10 + range_crosses_zero_bucket{le="+Inf"} 100+0x10 + +# - Bucket [-1, 0]: 15-10 = 5.0 observations (full bucket). +# - Bucket [0, 1]: 20-15 = 5.0 observations (full bucket). +# Total: (5.0 + 5.0) / 100.0 = 0.1 + +eval instant at 50m histogram_fraction(-1, 1, range_crosses_zero_bucket) + expect no_warn + {} 0.1 + +eval instant at 50m histogram_fraction(-1, 1, range_crosses_zero) + expect no_warn + {} 0.1 + +# Lower is NaN. +load_with_nhcb 5m + lower_is_NaN_bucket{le="1"} 1+0x10 + lower_is_NaN_bucket{le="+Inf"} 100+0x10 + +eval instant at 50m histogram_fraction(NaN, 1, lower_is_NaN_bucket) + expect no_warn + {} NaN + +eval instant at 50m histogram_fraction(NaN, 1, lower_is_NaN) + expect no_warn + {} NaN + +# Upper is NaN. +load_with_nhcb 5m + upper_is_NaN_bucket{le="1"} 1+0x10 + upper_is_NaN_bucket{le="+Inf"} 100+0x10 + +eval instant at 50m histogram_fraction(0, NaN, upper_is_NaN_bucket) + expect no_warn + {} NaN + +eval instant at 50m histogram_fraction(0, NaN, upper_is_NaN) + expect no_warn + {} NaN + +# Range entirely below all buckets. +load_with_nhcb 5m + range_entirely_below_all_buckets_bucket{le="1"} 1+0x10 + range_entirely_below_all_buckets_bucket{le="2"} 3+0x10 + range_entirely_below_all_buckets_bucket{le="+Inf"} 10+0x10 + +eval instant at 50m histogram_fraction(-10, -5, range_entirely_below_all_buckets_bucket) + expect no_warn + {} 0.0 + +eval instant at 50m histogram_fraction(-10, -5, range_entirely_below_all_buckets) + expect no_warn + {} 0.0 + +# Range entirely above all buckets. +load_with_nhcb 5m + range_entirely_above_all_buckets_bucket{le="1"} 1+0x10 + range_entirely_above_all_buckets_bucket{le="2"} 3+0x10 + range_entirely_above_all_buckets_bucket{le="+Inf"} 10+0x10 + +eval instant at 50m histogram_fraction(5, 10, range_entirely_above_all_buckets_bucket) + expect no_warn + {} 0.0 + +eval instant at 50m histogram_fraction(5, 10, range_entirely_above_all_buckets) + expect no_warn + {} 0.0 + + # In the classic histogram, we can access the corresponding bucket (if # it exists) and divide by the count to get the same result. diff --git a/promql/quantile.go b/promql/quantile.go index 1454974107..78df925c51 100644 --- a/promql/quantile.go +++ b/promql/quantile.go @@ -406,6 +406,18 @@ func HistogramFraction(lower, upper float64, h *histogram.FloatHistogram, metric // consistent with the linear interpolation known from classic // histograms. It is also used for the zero bucket. interpolateLinearly := func(v float64) float64 { + // Note: `v` is a finite value. + // For buckets with infinite bounds, we cannot interpolate meaningfully. + // For +Inf upper bound, interpolation returns the cumulative count of the previous bucket + // as the second term in the interpolation formula yields 0 (finite/Inf). + // In other words, no observations from the last bucket are considered in the fraction calculation. + // For -Inf lower bound, however, the second term would be (v-(-Inf))/(upperBound-(-Inf)) = Inf/Inf = NaN. + // To achieve the same effect of no contribution as the +Inf bucket, handle the -Inf case by returning + // the cumulative count at the first bucket (which equals the bucket's count). + // In both cases, we effectively skip interpolation within the infinite-width bucket. + if b.Lower == math.Inf(-1) { + return b.Count + } return rank + b.Count*(v-b.Lower)/(b.Upper-b.Lower) } @@ -531,14 +543,34 @@ func BucketFraction(lower, upper float64, buckets Buckets) float64 { rank, lowerRank, upperRank float64 lowerSet, upperSet bool ) + + // If the upper bound of the first bucket is greater than 0, we assume + // we are dealing with positive buckets only and lowerBound for the + // first bucket is set to 0; otherwise it is set to -Inf. + lowerBound := 0.0 + if buckets[0].UpperBound <= 0 { + lowerBound = math.Inf(-1) + } + for i, b := range buckets { - lowerBound := math.Inf(-1) if i > 0 { lowerBound = buckets[i-1].UpperBound } upperBound := b.UpperBound interpolateLinearly := func(v float64) float64 { + // Note: `v` is a finite value. + // For buckets with infinite bounds, we cannot interpolate meaningfully. + // For +Inf upper bound, interpolation returns the cumulative count of the previous bucket + // as the second term in the interpolation formula yields 0 (finite/Inf). + // In other words, no observations from the last bucket are considered in the fraction calculation. + // For -Inf lower bound, however, the second term would be (v-(-Inf))/(upperBound-(-Inf)) = Inf/Inf = NaN. + // To achieve the same effect of no contribution as the +Inf bucket, handle the -Inf case by returning + // the cumulative count at the first bucket. + // In both cases, we effectively skip interpolation within the infinite-width bucket. + if lowerBound == math.Inf(-1) { + return b.Count + } return rank + (b.Count-rank)*(v-lowerBound)/(upperBound-lowerBound) } diff --git a/web/ui/mantine-ui/package.json b/web/ui/mantine-ui/package.json index 219d357f0d..7ec13b1b8d 100644 --- a/web/ui/mantine-ui/package.json +++ b/web/ui/mantine-ui/package.json @@ -1,7 +1,7 @@ { "name": "@prometheus-io/mantine-ui", "private": true, - "version": "0.307.3", + "version": "0.308.0", "type": "module", "scripts": { "start": "vite", @@ -28,7 +28,7 @@ "@microsoft/fetch-event-source": "^2.0.1", "@nexucis/fuzzy": "^0.5.1", "@nexucis/kvsearch": "^0.9.1", - "@prometheus-io/codemirror-promql": "0.307.3", + "@prometheus-io/codemirror-promql": "0.308.0", "@reduxjs/toolkit": "^2.10.1", "@tabler/icons-react": "^3.35.0", "@tanstack/react-query": "^5.90.7", diff --git a/web/ui/module/codemirror-promql/package.json b/web/ui/module/codemirror-promql/package.json index f850342728..ee7bcc045f 100644 --- a/web/ui/module/codemirror-promql/package.json +++ b/web/ui/module/codemirror-promql/package.json @@ -1,6 +1,6 @@ { "name": "@prometheus-io/codemirror-promql", - "version": "0.307.3", + "version": "0.308.0", "description": "a CodeMirror mode for the PromQL language", "types": "dist/esm/index.d.ts", "module": "dist/esm/index.js", @@ -29,7 +29,7 @@ }, "homepage": "https://github.com/prometheus/prometheus/blob/main/web/ui/module/codemirror-promql/README.md", "dependencies": { - "@prometheus-io/lezer-promql": "0.307.3", + "@prometheus-io/lezer-promql": "0.308.0", "lru-cache": "^11.2.2" }, "devDependencies": { diff --git a/web/ui/module/lezer-promql/package.json b/web/ui/module/lezer-promql/package.json index 05511c2b89..034ead9741 100644 --- a/web/ui/module/lezer-promql/package.json +++ b/web/ui/module/lezer-promql/package.json @@ -1,6 +1,6 @@ { "name": "@prometheus-io/lezer-promql", - "version": "0.307.3", + "version": "0.308.0", "description": "lezer-based PromQL grammar", "main": "dist/index.cjs", "type": "module", diff --git a/web/ui/package-lock.json b/web/ui/package-lock.json index 2631802e53..7f2961784b 100644 --- a/web/ui/package-lock.json +++ b/web/ui/package-lock.json @@ -1,12 +1,12 @@ { "name": "prometheus-io", - "version": "0.307.3", + "version": "0.308.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "prometheus-io", - "version": "0.307.3", + "version": "0.308.0", "workspaces": [ "mantine-ui", "module/*" @@ -24,7 +24,7 @@ }, "mantine-ui": { "name": "@prometheus-io/mantine-ui", - "version": "0.307.3", + "version": "0.308.0", "dependencies": { "@codemirror/autocomplete": "^6.19.1", "@codemirror/language": "^6.11.3", @@ -42,7 +42,7 @@ "@microsoft/fetch-event-source": "^2.0.1", "@nexucis/fuzzy": "^0.5.1", "@nexucis/kvsearch": "^0.9.1", - "@prometheus-io/codemirror-promql": "0.307.3", + "@prometheus-io/codemirror-promql": "0.308.0", "@reduxjs/toolkit": "^2.10.1", "@tabler/icons-react": "^3.35.0", "@tanstack/react-query": "^5.90.7", @@ -88,10 +88,10 @@ }, "module/codemirror-promql": { "name": "@prometheus-io/codemirror-promql", - "version": "0.307.3", + "version": "0.308.0", "license": "Apache-2.0", "dependencies": { - "@prometheus-io/lezer-promql": "0.307.3", + "@prometheus-io/lezer-promql": "0.308.0", "lru-cache": "^11.2.2" }, "devDependencies": { @@ -121,7 +121,7 @@ }, "module/lezer-promql": { "name": "@prometheus-io/lezer-promql", - "version": "0.307.3", + "version": "0.308.0", "license": "Apache-2.0", "devDependencies": { "@lezer/generator": "^1.8.0", diff --git a/web/ui/package.json b/web/ui/package.json index e237294df8..5023d1d21b 100644 --- a/web/ui/package.json +++ b/web/ui/package.json @@ -1,7 +1,7 @@ { "name": "prometheus-io", "description": "Monorepo for the Prometheus UI", - "version": "0.307.3", + "version": "0.308.0", "private": true, "scripts": { "build": "bash build_ui.sh --all",