From 9fd2f35adc285b0964baad1182df74e077733acd Mon Sep 17 00:00:00 2001 From: Colin Kelley Date: Tue, 7 Jul 2026 21:12:26 -0700 Subject: [PATCH 1/2] Preserve upstream rate funcs as _rate when REPLACE_RATE_FUNCS is set When REPLACE_RATE_FUNCS=x or =2, copy the original rate/increase/delta parser and evaluator entries to _rate/_increase/_delta before repointing the standard names at the xrate or yrate family. Fix repointParserFunctions to copy the target struct so the source entry is not aliased. Add TestReplaceRateFuncs2 to verify the registration layout in a subprocess (REPLACE_RATE_FUNCS runs once at init). Co-authored-by: Cursor --- promql/functions.go | 48 ++++++++++++++++++++++++------- promql/replace_rate_funcs_test.go | 36 +++++++++++++++++++++++ 2 files changed, 74 insertions(+), 10 deletions(-) create mode 100644 promql/replace_rate_funcs_test.go diff --git a/promql/functions.go b/promql/functions.go index 43feab1ec12..4eb50a466ff 100644 --- a/promql/functions.go +++ b/promql/functions.go @@ -1908,10 +1908,12 @@ func init() { // 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 but - // keep the x* names available. - // "2", - point rate/increase/delta at yrate/yincrease/ydelta but - // "y", "Y" keep the y* names available. + // "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. switch os.Getenv("REPLACE_RATE_FUNCS") { case "1": FunctionCalls["delta"] = FunctionCalls["xdelta"] @@ -1933,30 +1935,56 @@ func init() { fmt.Println("Successfully replaced rate & friends with xrate & friends (and removed xrate & friends function keys).") case "x", "X": + preserveOriginalRateFuncs() repointParserFunctions("delta", "xdelta") repointParserFunctions("increase", "xincrease") repointParserFunctions("rate", "xrate") repointFunction("delta", "xdelta") repointFunction("increase", "xincrease") repointFunction("rate", "xrate") - fmt.Println("Successfully replaced rate/increase/delta with xrate/xincrease/xdelta (and left the x* names available as well).") + 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": + preserveOriginalRateFuncs() repointParserFunctions("delta", "ydelta") repointParserFunctions("increase", "yincrease") repointParserFunctions("rate", "yrate") repointFunction("delta", "ydelta") repointFunction("increase", "yincrease") repointFunction("rate", "yrate") - fmt.Println("Successfully replaced rate/increase/delta with yrate/yincrease/ydelta (and left the y* names available as well).") + fmt.Println("Successfully replaced rate/increase/delta with yrate/yincrease/ydelta; originals available as _rate/_increase/_delta; y* and x* names also available.") } } -// repointParserFunctions makes the parser entry for name resolve to the -// entry currently registered under newName (e.g. "rate" -> "xrate"). -// It leaves the newName entry in place. +// preserveOriginalRateFuncs copies the upstream rate/increase/delta parser and +// evaluator entries to _rate/_increase/_delta before repointing the standard +// names at the xrate or yrate family. +func preserveOriginalRateFuncs() { + copyParserFunction("delta", "_delta") + copyParserFunction("increase", "_increase") + copyParserFunction("rate", "_rate") + copyFunctionCall("delta", "_delta") + copyFunctionCall("increase", "_increase") + copyFunctionCall("rate", "_rate") +} + +func copyParserFunction(fromName, toName string) { + result := *parser.Functions[fromName] + result.Name = toName + parser.Functions[toName] = &result +} + +func copyFunctionCall(fromName, toName string) { + FunctionCalls[toName] = FunctionCalls[fromName] +} + +// repointParserFunctions makes name resolve to newName's implementation while +// keeping name as the displayed/parser function name. A copy is made so the +// newName entry is not mutated. func repointParserFunctions(name, newName string) { - parser.Functions[name] = parser.Functions[newName] + result := *parser.Functions[newName] + result.Name = name + parser.Functions[name] = &result } // repointFunction makes the FunctionCalls entry for name dispatch to the diff --git a/promql/replace_rate_funcs_test.go b/promql/replace_rate_funcs_test.go new file mode 100644 index 00000000000..4161aa0ed5b --- /dev/null +++ b/promql/replace_rate_funcs_test.go @@ -0,0 +1,36 @@ +package promql + +import ( + "os" + "os/exec" + "reflect" + "testing" + + "github.com/stretchr/testify/require" + + "github.com/prometheus/prometheus/promql/parser" +) + +func TestReplaceRateFuncs2(t *testing.T) { + if os.Getenv("PROMQL_TEST_REPLACE_RATE_FUNCS") != "1" { + cmd := exec.Command(os.Args[0], "-test.run=^TestReplaceRateFuncs2$") + cmd.Env = append(os.Environ(), + "REPLACE_RATE_FUNCS=2", + "PROMQL_TEST_REPLACE_RATE_FUNCS=1", + ) + out, err := cmd.CombinedOutput() + require.NoError(t, err, "subprocess failed:\n%s", out) + return + } + + 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.Equal(t, reflect.ValueOf(FunctionCalls["rate"]).Pointer(), reflect.ValueOf(FunctionCalls["yrate"]).Pointer()) + require.NotEqual(t, reflect.ValueOf(FunctionCalls["_rate"]).Pointer(), reflect.ValueOf(FunctionCalls["rate"]).Pointer()) + require.NotEqual(t, reflect.ValueOf(FunctionCalls["rate"]).Pointer(), reflect.ValueOf(FunctionCalls["xrate"]).Pointer()) +} From 66de80a80bea2a9427168e72d584c30bee5c5aaa Mon Sep 17 00:00:00 2001 From: Colin Kelley Date: Thu, 9 Jul 2026 17:29:06 -0700 Subject: [PATCH 2/2] Consolidate REPLACE_RATE_FUNCS parser/function repoint helpers Unify copyParserFunction/copyFunctionCall with repointParserFunctions/ repointFunction via setParserFunctionFrom and setFunctionCallFrom, then call those helpers directly from init(). Co-authored-by: Cursor --- promql/functions.go | 68 +++++++++++++++------------------------------ 1 file changed, 22 insertions(+), 46 deletions(-) diff --git a/promql/functions.go b/promql/functions.go index 4eb50a466ff..1f074796824 100644 --- a/promql/functions.go +++ b/promql/functions.go @@ -1935,63 +1935,39 @@ func init() { fmt.Println("Successfully replaced rate & friends with xrate & friends (and removed xrate & friends function keys).") case "x", "X": - preserveOriginalRateFuncs() - repointParserFunctions("delta", "xdelta") - repointParserFunctions("increase", "xincrease") - repointParserFunctions("rate", "xrate") - repointFunction("delta", "xdelta") - repointFunction("increase", "xincrease") - repointFunction("rate", "xrate") + 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": - preserveOriginalRateFuncs() - repointParserFunctions("delta", "ydelta") - repointParserFunctions("increase", "yincrease") - repointParserFunctions("rate", "yrate") - repointFunction("delta", "ydelta") - repointFunction("increase", "yincrease") - repointFunction("rate", "yrate") + 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.") } } -// preserveOriginalRateFuncs copies the upstream rate/increase/delta parser and -// evaluator entries to _rate/_increase/_delta before repointing the standard -// names at the xrate or yrate family. -func preserveOriginalRateFuncs() { - copyParserFunction("delta", "_delta") - copyParserFunction("increase", "_increase") - copyParserFunction("rate", "_rate") - copyFunctionCall("delta", "_delta") - copyFunctionCall("increase", "_increase") - copyFunctionCall("rate", "_rate") -} - -func copyParserFunction(fromName, toName string) { - result := *parser.Functions[fromName] - result.Name = toName - parser.Functions[toName] = &result -} - -func copyFunctionCall(fromName, toName string) { - FunctionCalls[toName] = FunctionCalls[fromName] +// replaceStandardRateFuncs preserves upstream delta/increase/rate as +// _delta/_increase/_rate and repoints the standard names at the x* or y* family +// (per replacementPrefix). +func replaceStandardRateFuncs(replacementPrefix string) { + for _, name := range []string{"delta", "increase", "rate"} { + setParserFunctionFrom("_"+name, name) + setFunctionCallFrom("_"+name, name) + replacement := replacementPrefix + name + setParserFunctionFrom(name, replacement) + setFunctionCallFrom(name, replacement) + } } -// repointParserFunctions makes name resolve to newName's implementation while -// keeping name as the displayed/parser function name. A copy is made so the -// newName entry is not mutated. -func repointParserFunctions(name, newName string) { - result := *parser.Functions[newName] - result.Name = name - parser.Functions[name] = &result +// setParserFunctionFrom registers targetName as a copy of sourceName's parser +// metadata, with Name set to targetName. +func setParserFunctionFrom(targetName, sourceName string) { + result := *parser.Functions[sourceName] + result.Name = targetName + parser.Functions[targetName] = &result } -// repointFunction makes the FunctionCalls entry for name dispatch to the -// implementation currently registered under newName. The newName entry -// is left in place. -func repointFunction(name, newName string) { - FunctionCalls[name] = FunctionCalls[newName] +// setFunctionCallFrom makes targetName dispatch to sourceName's implementation. +func setFunctionCallFrom(targetName, sourceName string) { + FunctionCalls[targetName] = FunctionCalls[sourceName] } type vectorByValueHeap Vector