diff --git a/.golangci.yml b/.golangci.yml index 2dfe8e5f0c..8548774b76 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -17,7 +17,8 @@ linters: - loggercheck - misspell - nilnesserr - - nolintlint + # TODO(bwplotka): Enable once https://github.com/golangci/golangci-lint/issues/3228 is fixed. + # - nolintlint - perfsprint - predeclared - revive diff --git a/CHANGELOG.md b/CHANGELOG.md index 7f0c8d91de..3fee14551d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,13 @@ ## unreleased +## 3.3.0-rc.1 / 2025-04-02 + +* [BUGFIX] Remote-Write: Reduce memory footprint during WAL replay. #16197 +* [BUGFIX] Scraping: Skip native histograms series when ingestion is disabled. #16218 +* [BUGFIX] UI: Display the correct value of Alerting rules' `keep_firing_for`. #16211 +* [BUGFIX] PromQL: return NaN from `irate()` if second-last sample is NaN. #16199 #15853 + ## 3.3.0-rc.0 / 2025-03-11 * [FEATURE] PromQL: Implement `idelta()` and `irate()` for native histograms. #15853 diff --git a/VERSION b/VERSION index be789e37d0..0726a21ca1 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -3.3.0-rc.0 +3.3.0-rc.1 diff --git a/promql/functions.go b/promql/functions.go index 3f977af56a..3c79684b0f 100644 --- a/promql/functions.go +++ b/promql/functions.go @@ -367,10 +367,11 @@ func instantValue(vals []parser.Value, args parser.Expressions, out Vector, isRa } switch { case ss[1].H == nil && ss[0].H == nil: - if !isRate || ss[1].F >= ss[0].F { - // Gauge or counter without reset. + if !isRate || !(ss[1].F < ss[0].F) { + // Gauge, or counter without reset, or counter with NaN value. resultSample.F = ss[1].F - ss[0].F } + // In case of a counter reset, we leave resultSample at // its current value, which is already ss[1]. case ss[1].H != nil && ss[0].H != nil: diff --git a/promql/promqltest/testdata/functions.test b/promql/promqltest/testdata/functions.test index 04d844bef9..fafe2dda40 100644 --- a/promql/promqltest/testdata/functions.test +++ b/promql/promqltest/testdata/functions.test @@ -218,6 +218,7 @@ clear load 5m http_requests_total{path="/foo"} 0+10x10 http_requests_total{path="/bar"} 0+10x5 0+10x5 + http_requests_nan{} 1 NaN NaN 5 11 http_requests_histogram{path="/a"} {{sum:2 count:2}}+{{sum:3 count:3}}x5 http_requests_histogram{path="/b"} 0 0 {{sum:1 count:1}} {{sum:4 count:4}} http_requests_histogram{path="/c"} 0 0 {{sum:1 count:1}} {{sum:4 count:4 counter_reset_hint:gauge}} @@ -235,6 +236,9 @@ eval instant at 30m irate(http_requests_total[50m]) {path="/foo"} .03333333333333333333 {path="/bar"} 0 +eval range from 0 to 20m step 5m irate(http_requests_nan[15m1s]) + {} _ NaN NaN NaN 0.02 + eval instant at 20m irate(http_requests_histogram{path="/a"}[20m]) {path="/a"} {{sum:0.01 count:0.01 counter_reset_hint:gauge}} @@ -288,6 +292,7 @@ clear load 5m http_requests{path="/foo"} 0 50 100 150 http_requests{path="/bar"} 0 50 100 50 + http_requests_nan{} 1 NaN NaN 5 11 http_requests_histogram{path="/a"} {{sum:2 count:2 counter_reset_hint:gauge}}+{{sum:1 count:3 counter_reset_hint:gauge}}x5 http_requests_histogram{path="/b"} 0 0 {{sum:1 count:1 counter_reset_hint:gauge}} {{sum:2 count:2 counter_reset_hint:gauge}} http_requests_histogram{path="/c"} 0 0 {{sum:1 count:1}} {{sum:2 count:2 counter_reset_hint:gauge}} @@ -300,6 +305,9 @@ eval instant at 20m idelta(http_requests[20m]) {path="/foo"} 50 {path="/bar"} -50 +eval range from 0 to 20m step 5m idelta(http_requests_nan[15m1s]) + {} _ NaN NaN NaN 6 + eval instant at 20m idelta(http_requests_histogram{path="/a"}[20m]) {path="/a"} {{sum:1 count:3 counter_reset_hint:gauge}} diff --git a/scrape/scrape.go b/scrape/scrape.go index 518103d345..14c442fc25 100644 --- a/scrape/scrape.go +++ b/scrape/scrape.go @@ -1700,7 +1700,7 @@ loop: t = *parsedTimestamp } - if sl.cache.getDropped(met) { + if sl.cache.getDropped(met) || isHistogram && !sl.enableNativeHistogramIngestion { continue } ce, seriesCached, seriesAlreadyScraped := sl.cache.get(met) @@ -1748,7 +1748,7 @@ loop: } else { if sl.enableCTZeroIngestion { if ctMs := p.CreatedTimestamp(); ctMs != 0 { - if isHistogram && sl.enableNativeHistogramIngestion { + if isHistogram { if h != nil { ref, err = app.AppendHistogramCTZeroSample(ref, lset, t, ctMs, h, nil) } else { @@ -1765,7 +1765,7 @@ loop: } } - if isHistogram && sl.enableNativeHistogramIngestion { + if isHistogram { if h != nil { ref, err = app.AppendHistogram(ref, lset, t, h, nil) } else { diff --git a/scrape/scrape_test.go b/scrape/scrape_test.go index 9725d688a5..e38a3fc4a8 100644 --- a/scrape/scrape_test.go +++ b/scrape/scrape_test.go @@ -2395,7 +2395,7 @@ metric_total{n="2"} 2 # {t="2"} 2.0 20000 }, }, { - title: "Native histogram with three exemplars", + title: "Native histogram with three exemplars from classic buckets", enableNativeHistogramsIngestion: true, scrapeText: `name: "test_histogram" @@ -2644,6 +2644,180 @@ metric: < {Labels: labels.FromStrings("dummyID", "58215"), Value: -0.00019, Ts: 1625851055146, HasTs: true}, }, }, + { + title: "Native histogram with exemplars and no classic buckets", + contentType: "application/vnd.google.protobuf", + enableNativeHistogramsIngestion: true, + scrapeText: `name: "test_histogram" +help: "Test histogram." +type: HISTOGRAM +metric: < + histogram: < + sample_count: 175 + sample_sum: 0.0008280461746287094 + schema: 3 + zero_threshold: 2.938735877055719e-39 + zero_count: 2 + negative_span: < + offset: -162 + length: 1 + > + negative_span: < + offset: 23 + length: 4 + > + negative_delta: 1 + negative_delta: 3 + negative_delta: -2 + negative_delta: -1 + negative_delta: 1 + positive_span: < + offset: -161 + length: 1 + > + positive_span: < + offset: 8 + length: 3 + > + positive_delta: 1 + positive_delta: 2 + positive_delta: -1 + positive_delta: -1 + exemplars: < + label: < + name: "dummyID" + value: "59732" + > + value: -0.00039 + timestamp: < + seconds: 1625851155 + nanos: 146848499 + > + > + exemplars: < + label: < + name: "dummyID" + value: "58242" + > + value: -0.00019 + timestamp: < + seconds: 1625851055 + nanos: 146848599 + > + > + exemplars: < + label: < + name: "dummyID" + value: "5617" + > + value: -0.00029 + > + > + timestamp_ms: 1234568 +> + +`, + histograms: []histogramSample{{ + t: 1234568, + metric: labels.FromStrings("__name__", "test_histogram"), + h: &histogram.Histogram{ + Count: 175, + ZeroCount: 2, + Sum: 0.0008280461746287094, + ZeroThreshold: 2.938735877055719e-39, + Schema: 3, + PositiveSpans: []histogram.Span{ + {Offset: -161, Length: 1}, + {Offset: 8, Length: 3}, + }, + NegativeSpans: []histogram.Span{ + {Offset: -162, Length: 1}, + {Offset: 23, Length: 4}, + }, + PositiveBuckets: []int64{1, 2, -1, -1}, + NegativeBuckets: []int64{1, 3, -2, -1, 1}, + }, + }}, + exemplars: []exemplar.Exemplar{ + // Exemplars with missing timestamps are dropped for native histograms. + {Labels: labels.FromStrings("dummyID", "58242"), Value: -0.00019, Ts: 1625851055146, HasTs: true}, + {Labels: labels.FromStrings("dummyID", "59732"), Value: -0.00039, Ts: 1625851155146, HasTs: true}, + }, + }, + { + title: "Native histogram with exemplars but ingestion disabled", + contentType: "application/vnd.google.protobuf", + enableNativeHistogramsIngestion: false, + scrapeText: `name: "test_histogram" +help: "Test histogram." +type: HISTOGRAM +metric: < + histogram: < + sample_count: 175 + sample_sum: 0.0008280461746287094 + schema: 3 + zero_threshold: 2.938735877055719e-39 + zero_count: 2 + negative_span: < + offset: -162 + length: 1 + > + negative_span: < + offset: 23 + length: 4 + > + negative_delta: 1 + negative_delta: 3 + negative_delta: -2 + negative_delta: -1 + negative_delta: 1 + positive_span: < + offset: -161 + length: 1 + > + positive_span: < + offset: 8 + length: 3 + > + positive_delta: 1 + positive_delta: 2 + positive_delta: -1 + positive_delta: -1 + exemplars: < + label: < + name: "dummyID" + value: "59732" + > + value: -0.00039 + timestamp: < + seconds: 1625851155 + nanos: 146848499 + > + > + exemplars: < + label: < + name: "dummyID" + value: "58242" + > + value: -0.00019 + timestamp: < + seconds: 1625851055 + nanos: 146848599 + > + > + exemplars: < + label: < + name: "dummyID" + value: "5617" + > + value: -0.00029 + > + > + timestamp_ms: 1234568 +> + +`, + }, } for _, test := range tests { diff --git a/tsdb/wlog/watcher.go b/tsdb/wlog/watcher.go index ca74a9ceaf..f171a8bdc1 100644 --- a/tsdb/wlog/watcher.go +++ b/tsdb/wlog/watcher.go @@ -491,12 +491,13 @@ func (w *Watcher) readSegment(r *LiveReader, segmentNum int, tail bool) error { metadata []record.RefMetadata ) for r.Next() && !isClosed(w.quit) { + var err error rec := r.Record() w.recordsReadMetric.WithLabelValues(dec.Type(rec).String()).Inc() switch dec.Type(rec) { case record.Series: - series, err := dec.Series(rec, series[:0]) + series, err = dec.Series(rec, series[:0]) if err != nil { w.recordDecodeFailsMetric.Inc() return err @@ -509,7 +510,7 @@ func (w *Watcher) readSegment(r *LiveReader, segmentNum int, tail bool) error { if !tail { break } - samples, err := dec.Samples(rec, samples[:0]) + samples, err = dec.Samples(rec, samples[:0]) if err != nil { w.recordDecodeFailsMetric.Inc() return err @@ -539,7 +540,7 @@ func (w *Watcher) readSegment(r *LiveReader, segmentNum int, tail bool) error { if !tail { break } - exemplars, err := dec.Exemplars(rec, exemplars[:0]) + exemplars, err = dec.Exemplars(rec, exemplars[:0]) if err != nil { w.recordDecodeFailsMetric.Inc() return err @@ -554,7 +555,7 @@ func (w *Watcher) readSegment(r *LiveReader, segmentNum int, tail bool) error { if !tail { break } - histograms, err := dec.HistogramSamples(rec, histograms[:0]) + histograms, err = dec.HistogramSamples(rec, histograms[:0]) if err != nil { w.recordDecodeFailsMetric.Inc() return err @@ -582,7 +583,7 @@ func (w *Watcher) readSegment(r *LiveReader, segmentNum int, tail bool) error { if !tail { break } - floatHistograms, err := dec.FloatHistogramSamples(rec, floatHistograms[:0]) + floatHistograms, err = dec.FloatHistogramSamples(rec, floatHistograms[:0]) if err != nil { w.recordDecodeFailsMetric.Inc() return err @@ -606,12 +607,12 @@ func (w *Watcher) readSegment(r *LiveReader, segmentNum int, tail bool) error { if !w.sendMetadata { break } - meta, err := dec.Metadata(rec, metadata[:0]) + metadata, err = dec.Metadata(rec, metadata[:0]) if err != nil { w.recordDecodeFailsMetric.Inc() return err } - w.writer.StoreMetadata(meta) + w.writer.StoreMetadata(metadata) case record.Unknown: // Could be corruption, or reading from a WAL from a newer Prometheus. diff --git a/web/ui/mantine-ui/package.json b/web/ui/mantine-ui/package.json index 0eda852213..cb7f886433 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.303.0-rc.0", + "version": "0.303.0-rc.1", "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.303.0-rc.0", + "@prometheus-io/codemirror-promql": "0.303.0-rc.1", "@reduxjs/toolkit": "^2.5.0", "@tabler/icons-react": "^3.28.1", "@tanstack/react-query": "^5.67.1", diff --git a/web/ui/mantine-ui/src/components/RuleDefinition.tsx b/web/ui/mantine-ui/src/components/RuleDefinition.tsx index f3c4c88485..f428df46a8 100644 --- a/web/ui/mantine-ui/src/components/RuleDefinition.tsx +++ b/web/ui/mantine-ui/src/components/RuleDefinition.tsx @@ -85,7 +85,7 @@ const RuleDefinition: FC<{ rule: Rule }> = ({ rule }) => { styles={{ label: { textTransform: "none" } }} leftSection={} > - keep_firing_for: {formatPrometheusDuration(rule.duration * 1000)} + keep_firing_for: {formatPrometheusDuration(rule.keepFiringFor * 1000)} )} diff --git a/web/ui/module/codemirror-promql/package.json b/web/ui/module/codemirror-promql/package.json index 8ccdb93341..dc01dd0014 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.303.0-rc.0", + "version": "0.303.0-rc.1", "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.303.0-rc.0", + "@prometheus-io/lezer-promql": "0.303.0-rc.1", "lru-cache": "^11.0.2" }, "devDependencies": { diff --git a/web/ui/module/lezer-promql/package.json b/web/ui/module/lezer-promql/package.json index 35b6be9b75..cc38148f01 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.303.0-rc.0", + "version": "0.303.0-rc.1", "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 b040d39895..4bf698586f 100644 --- a/web/ui/package-lock.json +++ b/web/ui/package-lock.json @@ -1,12 +1,12 @@ { "name": "prometheus-io", - "version": "0.303.0-rc.0", + "version": "0.303.0-rc.1", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "prometheus-io", - "version": "0.303.0-rc.0", + "version": "0.303.0-rc.1", "workspaces": [ "mantine-ui", "module/*" @@ -24,7 +24,7 @@ }, "mantine-ui": { "name": "@prometheus-io/mantine-ui", - "version": "0.303.0-rc.0", + "version": "0.303.0-rc.1", "dependencies": { "@codemirror/autocomplete": "^6.18.4", "@codemirror/language": "^6.10.8", @@ -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.303.0-rc.0", + "@prometheus-io/codemirror-promql": "0.303.0-rc.1", "@reduxjs/toolkit": "^2.5.0", "@tabler/icons-react": "^3.28.1", "@tanstack/react-query": "^5.67.1", @@ -156,10 +156,10 @@ }, "module/codemirror-promql": { "name": "@prometheus-io/codemirror-promql", - "version": "0.303.0-rc.0", + "version": "0.303.0-rc.1", "license": "Apache-2.0", "dependencies": { - "@prometheus-io/lezer-promql": "0.303.0-rc.0", + "@prometheus-io/lezer-promql": "0.303.0-rc.1", "lru-cache": "^11.0.2" }, "devDependencies": { @@ -189,7 +189,7 @@ }, "module/lezer-promql": { "name": "@prometheus-io/lezer-promql", - "version": "0.303.0-rc.0", + "version": "0.303.0-rc.1", "license": "Apache-2.0", "devDependencies": { "@lezer/generator": "^1.7.2", diff --git a/web/ui/package.json b/web/ui/package.json index aaacd5ef9d..ff42f000d1 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.303.0-rc.0", + "version": "0.303.0-rc.1", "private": true, "scripts": { "build": "bash build_ui.sh --all",