From fe1c85f35f075d6dc5207a2cd0847e36325d8a68 Mon Sep 17 00:00:00 2001 From: Colin Kelley Date: Sat, 11 Jul 2026 11:12:03 -0700 Subject: [PATCH 1/3] Remove xrate/xincrease/xdelta and their REPLACE_RATE_FUNCS modes. Keep only the yrate family and REPLACE_RATE_FUNCS=2/y/Y for swapping standard rate/increase/delta. Co-authored-by: Cursor --- promql/extended_xrate_funcs.go | 106 ------------------ promql/parser/functions.go | 20 +--- promql/promqltest/testdata/functions.test | 126 ++-------------------- promql/replace_rate_funcs.go | 48 ++------- promql/replace_rate_funcs_test.go | 2 - 5 files changed, 17 insertions(+), 285 deletions(-) delete mode 100644 promql/extended_xrate_funcs.go diff --git a/promql/extended_xrate_funcs.go b/promql/extended_xrate_funcs.go deleted file mode 100644 index 620fff6fb0c..00000000000 --- a/promql/extended_xrate_funcs.go +++ /dev/null @@ -1,106 +0,0 @@ -// Copyright 2015 The Prometheus Authors -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package promql - -import ( - "github.com/prometheus/prometheus/promql/parser" - "github.com/prometheus/prometheus/util/annotations" -) - -// extendedXRate is a utility function for xrate/xincrease/xdelta. -// It calculates the rate (allowing for counter resets if isCounter is true), -// taking into account the last sample before the range start, and returns -// the result as either per-second (if isRate is true) or overall. -// -// On the 2.55.1 line this was named extendedRate. Renamed here to avoid -// collision with extendedRate() in functions.go, which implements upstream -// Prometheus 3.x anchored/smoothed selectors. -func extendedXRate(matrixVals Matrix, args parser.Expressions, enh *EvalNodeHelper, isCounter, isRate bool) (Vector, annotations.Annotations) { - ms := args[0].(*parser.MatrixSelector) - vs := ms.VectorSelector.(*parser.VectorSelector) - - var ( - samples = matrixVals[0] - rangeStart = enh.Ts - durationMilliseconds(ms.Range+vs.Offset) - rangeEnd = enh.Ts - durationMilliseconds(vs.Offset) - ) - - points := samples.Floats - if len(points) < 2 { - return enh.Out, nil - } - sampledRange := float64(points[len(points)-1].T - points[0].T) - averageInterval := sampledRange / float64(len(points)-1) - - firstPoint := 0 - // If the point before the range is too far from rangeStart, drop it. - if float64(rangeStart-points[0].T) > averageInterval { - if len(points) < 3 { - return enh.Out, nil - } - firstPoint = 1 - sampledRange = float64(points[len(points)-1].T - points[firstPoint].T) - averageInterval = sampledRange / float64(len(points)-2) - } - - var ( - counterCorrection float64 - lastValue float64 - ) - if isCounter { - for i := firstPoint; i < len(points); i++ { - sample := points[i] - if sample.F < lastValue { - counterCorrection += lastValue - } - lastValue = sample.F - } - } - resultValue := points[len(points)-1].F - points[firstPoint].F + counterCorrection - - // Duration between last sample and boundary of range. - durationToEnd := float64(rangeEnd - points[len(points)-1].T) - - // If the points cover the whole range (i.e. they start just before the - // range start and end just before the range end) adjust the value from - // the sampled range to the requested range. - if points[firstPoint].T <= rangeStart && durationToEnd < averageInterval { - adjustToRange := float64(durationMilliseconds(ms.Range)) - resultValue *= (adjustToRange / sampledRange) - } - - if isRate { - resultValue /= ms.Range.Seconds() - } - - return append(enh.Out, Sample{F: resultValue}), nil -} - -func funcXdelta(_ []Vector, matrixVals Matrix, args parser.Expressions, enh *EvalNodeHelper) (Vector, annotations.Annotations) { - return extendedXRate(matrixVals, args, enh, false, false) -} - -func funcXrate(_ []Vector, matrixVals Matrix, args parser.Expressions, enh *EvalNodeHelper) (Vector, annotations.Annotations) { - return extendedXRate(matrixVals, args, enh, true, true) -} - -func funcXincrease(_ []Vector, matrixVals Matrix, args parser.Expressions, enh *EvalNodeHelper) (Vector, annotations.Annotations) { - return extendedXRate(matrixVals, args, enh, true, false) -} - -func init() { - FunctionCalls["xdelta"] = funcXdelta - FunctionCalls["xincrease"] = funcXincrease - FunctionCalls["xrate"] = funcXrate -} diff --git a/promql/parser/functions.go b/promql/parser/functions.go index e5e1c3da2ff..6003cb16191 100644 --- a/promql/parser/functions.go +++ b/promql/parser/functions.go @@ -22,7 +22,7 @@ type Function struct { ReturnType ValueType Experimental bool // ExtRange marks functions that need one extra sample before the range - // start (xrate/yrate family). + // start (yrate family). ExtRange bool } @@ -498,24 +498,6 @@ var Functions = map[string]*Function{ ArgTypes: []ValueType{ValueTypeScalar}, ReturnType: ValueTypeVector, }, - "xdelta": { - Name: "xdelta", - ArgTypes: []ValueType{ValueTypeMatrix}, - ReturnType: ValueTypeVector, - ExtRange: true, - }, - "xincrease": { - Name: "xincrease", - ArgTypes: []ValueType{ValueTypeMatrix}, - ReturnType: ValueTypeVector, - ExtRange: true, - }, - "xrate": { - Name: "xrate", - ArgTypes: []ValueType{ValueTypeMatrix}, - ReturnType: ValueTypeVector, - ExtRange: true, - }, "ydelta": { Name: "ydelta", ArgTypes: []ValueType{ValueTypeMatrix}, diff --git a/promql/promqltest/testdata/functions.test b/promql/promqltest/testdata/functions.test index 316e460bf6b..953d9ac88dd 100644 --- a/promql/promqltest/testdata/functions.test +++ b/promql/promqltest/testdata/functions.test @@ -1,4 +1,4 @@ -# Comparison of rate vs xrate vs yrate. +# Comparison of rate vs yrate. # # /bar is an 11-point counter whose per-scrape delta grows by 1 (+1, +2, …, +10). # It starts at 11 — one above the largest in-range step — so the "unwritten origin" @@ -15,13 +15,9 @@ load 5s # # 1. Reference eval, aligned with collection. -# Upstream rate() expectations vary by Prometheus major version; xrate/yrate +# Upstream rate() expectations vary by Prometheus major version; yrate # behaviour is asserted below. -eval instant at 25s xrate(http_requests[50s]) - {path="/foo"} .02 - {path="/bar"} 0.3 - eval instant at 25s yrate(http_requests[50s]) {path="/foo"} 0.04 {path="/bar"} 0.52 @@ -30,10 +26,6 @@ eval instant at 25s yrate(http_requests[50s]) # * path="/foo" rate should be same or fractionally higher ("shorter" sample, same actual increase); # * path="/bar" rate should be same or fractionally lower (80% the increase, 80/96% range covered by sample). # XXX Seeing ~20% jump for path="/foo" -eval instant at 24s xrate(http_requests[50s]) - {path="/foo"} .02 - {path="/bar"} 0.2 - eval instant at 24s yrate(http_requests[50s]) {path="/foo"} 0.04 {path="/bar"} 0.42 @@ -42,10 +34,6 @@ eval instant at 24s yrate(http_requests[50s]) # * path="/foo" rate should be same or fractionally lower ("longer" sample, same actual increase). # * path="/bar" rate should be same or fractionally lower ("longer" sample, same actual increase). # XXX Higher instead of lower for both. -eval instant at 26s xrate(http_requests[50s]) - {path="/foo"} .02 - {path="/bar"} 0.3 - eval instant at 26s yrate(http_requests[50s]) {path="/foo"} 0.04 {path="/bar"} 0.52 @@ -56,10 +44,6 @@ eval instant at 26s yrate(http_requests[50s]) # # 4. Reference eval, aligned with collection. -eval instant at 75s xrate(http_requests[50s]) - {path="/foo"} .02 - {path="/bar"} 0.8 - eval instant at 75s yrate(http_requests[50s]) {path="/foo"} 0.02 {path="/bar"} 0.8 @@ -68,10 +52,6 @@ eval instant at 75s yrate(http_requests[50s]) # * path="/foo" rate should be same or fractionally lower ("longer" sample, same actual increase). # * path="/bar" rate should be same or fractionally lower ("longer" sample, same actual increase). # XXX Higher instead of lower for both. -eval instant at 74s xrate(http_requests[50s]) - {path="/foo"} .02 - {path="/bar"} 0.9 - eval instant at 74s yrate(http_requests[50s]) {path="/foo"} 0.02 {path="/bar"} 0.9 @@ -80,46 +60,17 @@ eval instant at 74s yrate(http_requests[50s]) # * path="/foo" rate should be same or fractionally higher ("shorter" sample, same actual increase); # * path="/bar" rate should be same or fractionally lower (80% the increase, 80/96% range covered by sample). # XXX Seeing ~20% jump for path="/foo", decrease instead of increase for path="/bar". -eval instant at 76s xrate(http_requests[50s]) - {path="/foo"} .02 - {path="/bar"} 0.8 - eval instant at 76s yrate(http_requests[50s]) {path="/foo"} 0.02 {path="/bar"} 0.8 -# -# Evaluation of 10 second rate every 10 seconds, not aligned with collection. -# - -eval instant at 9s xrate(http_requests[10s]) - {path="/foo"} 0 - {path="/bar"} 0.1 - -eval instant at 19s xrate(http_requests[10s]) - {path="/foo"} 0.1 - {path="/bar"} 0.5 - -eval instant at 29s xrate(http_requests[10s]) - {path="/foo"} 0 - {path="/bar"} 0.9 - -eval instant at 39s xrate(http_requests[10s]) - {path="/foo"} 0 - {path="/bar"} 1.3 - -# XXX Sees the increase in path="/foo" between timestamps 35 and 40. -eval instant at 49s xrate(http_requests[10s]) - {path="/foo"} .1 - {path="/bar"} 1.7 - clear -# Tests for increase()/xincrease()/yincrease()/xrate()/yrate(). +# Tests for increase()/yincrease()/yrate(). # # The counters start at 1000/2000 so yincrease/yrate (which treat every # pre-origin value as 0) return significantly different results from the -# xrate / rate family (which only consider deltas inside the range). +# rate family (which only consider deltas inside the range). # # Eval times are 49s/48s rather than 50s/47s so that sample timestamps # land strictly inside the range (start, end] rather than at its @@ -138,23 +89,6 @@ eval instant at 49s increase(http_requests[100s]) {path="/foo"} 103 {path="/bar"} 97.27777777777777 -# Tests for xincrease(). -eval instant at 49s xincrease(http_requests[50s]) - {path="/foo"} 90 - {path="/bar"} 85 - -eval instant at 49s xincrease(http_requests[100s]) - {path="/foo"} 90 - {path="/bar"} 85 - -eval instant at 49s xincrease(http_requests[5s]) - {path="/foo"} 10 - {path="/bar"} 10 - -eval instant at 49s xincrease(http_requests[3s]) - -eval instant at 48s xincrease(http_requests[3s]) - # Tests for yincrease(). yrate always compares to a pre-origin of 0, # so yincrease sees the full 1000/2000 offset in the first range. eval instant at 49s yincrease(http_requests[50s]) @@ -173,23 +107,6 @@ eval instant at 49s yincrease(http_requests[3s]) {path="/foo"} 0 {path="/bar"} 0 -# Tests for xrate(). -eval instant at 49s xrate(http_requests[50s]) - {path="/foo"} 1.8 - {path="/bar"} 1.7 - -eval instant at 49s xrate(http_requests[100s]) - {path="/foo"} 0.9 - {path="/bar"} 0.85 - -eval instant at 49s xrate(http_requests[5s]) - {path="/foo"} 2 - {path="/bar"} 2 - -eval instant at 49s xrate(http_requests[3s]) - -eval instant at 48s xrate(http_requests[3s]) - # Tests for yrate(). eval instant at 49s yrate(http_requests[50s]) {path="/foo"} 21.8 @@ -209,7 +126,7 @@ eval instant at 49s yrate(http_requests[3s]) clear -# Test for increase()/xincrease()/yincrease() with counter reset. +# Test for increase()/yincrease() with counter reset. # When the counter is reset, it always starts at 0. # So the sequence 1006 4 (decreasing counter = reset) is interpreted the # same as 1006 0 1 2 3 4. Prometheus assumes it missed the intermediate @@ -220,40 +137,25 @@ load 5m eval instant at 29m increase(http_requests[30m]) {path="/foo"} 18 -eval instant at 29m xincrease(http_requests[30m]) - {path="/foo"} 15 - eval instant at 29m yincrease(http_requests[30m]) {path="/foo"} 1015 # Test counter reset inside the range, not spanning the range boundary. -eval instant at 19m xincrease(http_requests[5m]) - {path="/foo"} 3 - eval instant at 19m yincrease(http_requests[5m]) {path="/foo"} 3 -eval instant at 19m xincrease(http_requests[10m]) - {path="/foo"} 5 - eval instant at 19m yincrease(http_requests[10m]) {path="/foo"} 5 -eval instant at 24m xincrease(http_requests[5m]) - {path="/foo"} 4 - eval instant at 24m yincrease(http_requests[5m]) {path="/foo"} 4 -eval instant at 24m xincrease(http_requests[10m]) - {path="/foo"} 7 - eval instant at 24m yincrease(http_requests[10m]) {path="/foo"} 7 clear -# Tests for delta()/xdelta(). +# Tests for delta() (standard Prometheus, for reference). load 5m http_requests{path="/foo"} 0 50 300 150 200 http_requests{path="/bar"} 200 150 300 50 0 @@ -262,18 +164,6 @@ eval instant at 20m delta(http_requests[20m]) {path="/foo"} 200 {path="/bar"} -200 -eval instant at 20m xdelta(http_requests[20m]) - {path="/foo"} 200 - {path="/bar"} -200 - -eval instant at 20m xdelta(http_requests[19m]) - {path="/foo"} 190 - {path="/bar"} -190 - -eval instant at 20m xdelta(http_requests[1m]) - {path="/foo"} 10 - {path="/bar"} -10 - clear # Tests for ydelta(). @@ -292,10 +182,6 @@ eval instant at 29m delta(http_requests[30m]) {path="/foo"} 18 {path="/bar"} -12 -eval instant at 29m xdelta(http_requests[30m]) - {path="/foo"} 15 - {path="/bar"} -10 - eval instant at 29m ydelta(http_requests[30m]) {path="/foo"} 15 {path="/bar"} -10 diff --git a/promql/replace_rate_funcs.go b/promql/replace_rate_funcs.go index cb5d7fb79ab..e05f1e9ec72 100644 --- a/promql/replace_rate_funcs.go +++ b/promql/replace_rate_funcs.go @@ -21,56 +21,28 @@ import ( "github.com/prometheus/prometheus/promql/parser" ) -// initReplaceRateFuncs swaps built-in rate/increase/delta for the x* or y* family -// when REPLACE_RATE_FUNCS is set. Called from yrate_funcs init after x* and y* +// initReplaceRateFuncs swaps built-in rate/increase/delta for the yrate family +// when REPLACE_RATE_FUNCS is set. Called from yrate_funcs init after y* // FunctionCalls are registered. func initReplaceRateFuncs() { // REPLACE_RATE_FUNCS lets operators swap the built-in rate extrapolation - // functions with the xrate or yrate family at process start, so - // Grafana auto-completion, Prometheus tooling, Thanos, etc. continue to - // work against queries that call the standard rate/increase/delta names. + // functions with the yrate family at process start, so Grafana + // auto-completion, Prometheus tooling, Thanos, etc. continue to work + // against queries that call the standard rate/increase/delta names. // // Values: - // "1" - replace rate/increase/delta with xrate/xincrease/xdelta - // AND remove the x* names (legacy behaviour). - // "x", "X" - point rate/increase/delta at xrate/xincrease/xdelta; - // keep the x* names; preserve upstream implementations as - // _rate/_increase/_delta. - // "2", - point rate/increase/delta at yrate/yincrease/ydelta; - // "y", "Y" keep the y* (and x*) names; preserve upstream - // implementations as _rate/_increase/_delta. + // "2", "y", "Y" - point rate/increase/delta at yrate/yincrease/ydelta; + // keep the y* names; preserve upstream implementations as + // _rate/_increase/_delta. switch os.Getenv("REPLACE_RATE_FUNCS") { - case "1": - FunctionCalls["delta"] = FunctionCalls["xdelta"] - FunctionCalls["increase"] = FunctionCalls["xincrease"] - FunctionCalls["rate"] = FunctionCalls["xrate"] - delete(FunctionCalls, "xdelta") - delete(FunctionCalls, "xincrease") - delete(FunctionCalls, "xrate") - - parser.Functions["delta"] = parser.Functions["xdelta"] - parser.Functions["increase"] = parser.Functions["xincrease"] - parser.Functions["rate"] = parser.Functions["xrate"] - parser.Functions["delta"].Name = "delta" - parser.Functions["increase"].Name = "increase" - parser.Functions["rate"].Name = "rate" - delete(parser.Functions, "xdelta") - delete(parser.Functions, "xincrease") - delete(parser.Functions, "xrate") - fmt.Println("Successfully replaced rate & friends with xrate & friends (and removed xrate & friends function keys).") - - case "x", "X": - replaceStandardRateFuncs("x") - fmt.Println("Successfully replaced rate/increase/delta with xrate/xincrease/xdelta; originals available as _rate/_increase/_delta; x* names also available.") - case "2", "y", "Y": replaceStandardRateFuncs("y") - fmt.Println("Successfully replaced rate/increase/delta with yrate/yincrease/ydelta; originals available as _rate/_increase/_delta; y* and x* names also available.") + fmt.Println("Successfully replaced rate/increase/delta with yrate/yincrease/ydelta; originals available as _rate/_increase/_delta; y* names also available.") } } // replaceStandardRateFuncs preserves upstream delta/increase/rate as -// _delta/_increase/_rate and repoints the standard names at the x* or y* family +// _delta/_increase/_rate and repoints the standard names at the y* family // (per replacementPrefix). func replaceStandardRateFuncs(replacementPrefix string) { for _, name := range []string{"delta", "increase", "rate"} { diff --git a/promql/replace_rate_funcs_test.go b/promql/replace_rate_funcs_test.go index 85f5bc7f2ea..6155b5bc13a 100644 --- a/promql/replace_rate_funcs_test.go +++ b/promql/replace_rate_funcs_test.go @@ -38,11 +38,9 @@ func TestReplaceRateFuncs2(t *testing.T) { require.NotNil(t, parser.Functions["rate"]) require.NotNil(t, parser.Functions["yrate"]) require.NotNil(t, parser.Functions["_rate"]) - require.NotNil(t, parser.Functions["xrate"]) require.Equal(t, "rate", parser.Functions["rate"].Name) require.Equal(t, "yrate", parser.Functions["yrate"].Name) require.Equal(t, "_rate", parser.Functions["_rate"].Name) require.True(t, rateFuncPointersEqual(FunctionCalls["rate"], FunctionCalls["yrate"])) require.False(t, rateFuncPointersEqual(FunctionCalls["_rate"], FunctionCalls["rate"])) - require.False(t, rateFuncPointersEqual(FunctionCalls["rate"], FunctionCalls["xrate"])) } From 8638872d91f5859b64f65e7dc8311de91903cc06 Mon Sep 17 00:00:00 2001 From: Colin Kelley Date: Sat, 11 Jul 2026 12:27:51 -0700 Subject: [PATCH 2/3] Regenerate PromQL UI function signatures for the yrate family. make check-generated-promql-functions requires functionSignatures.ts to match parser.Functions after dropping xrate. Co-authored-by: Cursor --- web/ui/mantine-ui/src/promql/functionSignatures.ts | 3 --- 1 file changed, 3 deletions(-) diff --git a/web/ui/mantine-ui/src/promql/functionSignatures.ts b/web/ui/mantine-ui/src/promql/functionSignatures.ts index 6769196f341..d8c21502b31 100644 --- a/web/ui/mantine-ui/src/promql/functionSignatures.ts +++ b/web/ui/mantine-ui/src/promql/functionSignatures.ts @@ -204,9 +204,6 @@ export const functionSignatures: Record = { returnType: valueType.vector, }, vector: { name: "vector", argTypes: [valueType.scalar], variadic: 0, returnType: valueType.vector }, - xdelta: { name: "xdelta", argTypes: [valueType.matrix], variadic: 0, returnType: valueType.vector }, - xincrease: { name: "xincrease", argTypes: [valueType.matrix], variadic: 0, returnType: valueType.vector }, - xrate: { name: "xrate", argTypes: [valueType.matrix], variadic: 0, returnType: valueType.vector }, ydelta: { name: "ydelta", argTypes: [valueType.matrix], variadic: 0, returnType: valueType.vector }, year: { name: "year", argTypes: [valueType.vector], variadic: 1, returnType: valueType.vector }, yincrease: { name: "yincrease", argTypes: [valueType.matrix], variadic: 0, returnType: valueType.vector }, From 9899175a156deb84ba748f2599ee64b874155666 Mon Sep 17 00:00:00 2001 From: Colin Kelley Date: Sat, 11 Jul 2026 13:41:41 -0700 Subject: [PATCH 3/3] Drop xrate family from features.json golden file. Co-authored-by: Cursor --- cmd/prometheus/testdata/features.json | 3 --- 1 file changed, 3 deletions(-) diff --git a/cmd/prometheus/testdata/features.json b/cmd/prometheus/testdata/features.json index 3e6d319fe4c..f636a1f4b26 100644 --- a/cmd/prometheus/testdata/features.json +++ b/cmd/prometheus/testdata/features.json @@ -138,9 +138,6 @@ "ts_of_max_over_time": false, "ts_of_min_over_time": false, "vector": true, - "xdelta": true, - "xincrease": true, - "xrate": true, "ydelta": true, "year": true, "yincrease": true,