Merge pull request #16896 from bboreham/wrap-less

[PERF] PromQL: Move more work to preprocessing step
This commit is contained in:
Bryan Boreham 2025-08-13 14:17:30 +01:00 committed by GitHub
commit 8fff489c53
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 97 additions and 103 deletions

View File

@ -1670,12 +1670,8 @@ func (ev *evaluator) eval(ctx context.Context, expr parser.Expr) (parser.Value,
sortedGrouping := e.Grouping sortedGrouping := e.Grouping
slices.Sort(sortedGrouping) slices.Sort(sortedGrouping)
unwrapParenExpr(&e.Param)
param := unwrapStepInvariantExpr(e.Param)
unwrapParenExpr(&param)
if e.Op == parser.COUNT_VALUES { if e.Op == parser.COUNT_VALUES {
valueLabel := param.(*parser.StringLiteral) valueLabel := e.Param.(*parser.StringLiteral)
if !model.LabelName(valueLabel.Val).IsValid() { if !model.LabelName(valueLabel.Val).IsValid() {
ev.errorf("invalid label name %s", valueLabel) ev.errorf("invalid label name %s", valueLabel)
} }
@ -1690,8 +1686,8 @@ func (ev *evaluator) eval(ctx context.Context, expr parser.Expr) (parser.Value,
var warnings annotations.Annotations var warnings annotations.Annotations
originalNumSamples := ev.currentSamples originalNumSamples := ev.currentSamples
// param is the number k for topk/bottomk, or q for quantile. // e.Param is the number k for topk/bottomk, or q for quantile.
fp, ws := newFParams(ctx, ev, param) fp, ws := newFParams(ctx, ev, e.Param)
warnings.Merge(ws) warnings.Merge(ws)
// Now fetch the data to be aggregated. // Now fetch the data to be aggregated.
val, ws := ev.eval(ctx, e.Expr) val, ws := ev.eval(ctx, e.Expr)
@ -1711,9 +1707,7 @@ func (ev *evaluator) eval(ctx context.Context, expr parser.Expr) (parser.Value,
// Matrix evaluation always returns the evaluation time, // Matrix evaluation always returns the evaluation time,
// so this function needs special handling when given // so this function needs special handling when given
// a vector selector. // a vector selector.
unwrapParenExpr(&e.Args[0])
arg := unwrapStepInvariantExpr(e.Args[0]) arg := unwrapStepInvariantExpr(e.Args[0])
unwrapParenExpr(&arg)
vs, ok := arg.(*parser.VectorSelector) vs, ok := arg.(*parser.VectorSelector)
if ok { if ok {
return ev.rangeEvalTimestampFunctionOverVectorSelector(ctx, vs, call, e) return ev.rangeEvalTimestampFunctionOverVectorSelector(ctx, vs, call, e)
@ -1727,9 +1721,7 @@ func (ev *evaluator) eval(ctx context.Context, expr parser.Expr) (parser.Value,
warnings annotations.Annotations warnings annotations.Annotations
) )
for i := range e.Args { for i := range e.Args {
unwrapParenExpr(&e.Args[i]) a := e.Args[i]
a := unwrapStepInvariantExpr(e.Args[i])
unwrapParenExpr(&a)
if _, ok := a.(*parser.MatrixSelector); ok { if _, ok := a.(*parser.MatrixSelector); ok {
matrixArgIndex = i matrixArgIndex = i
matrixArg = true matrixArg = true
@ -1780,9 +1772,7 @@ func (ev *evaluator) eval(ctx context.Context, expr parser.Expr) (parser.Value,
} }
} }
unwrapParenExpr(&e.Args[matrixArgIndex]) arg := e.Args[matrixArgIndex]
arg := unwrapStepInvariantExpr(e.Args[matrixArgIndex])
unwrapParenExpr(&arg)
sel := arg.(*parser.MatrixSelector) sel := arg.(*parser.MatrixSelector)
selVS := sel.VectorSelector.(*parser.VectorSelector) selVS := sel.VectorSelector.(*parser.VectorSelector)
@ -2108,11 +2098,6 @@ func (ev *evaluator) eval(ctx context.Context, expr parser.Expr) (parser.Value,
ev.samplesStats.IncrementSamplesAtTimestamp(ev.endTimestamp, newEv.samplesStats.TotalSamples) ev.samplesStats.IncrementSamplesAtTimestamp(ev.endTimestamp, newEv.samplesStats.TotalSamples)
return res, ws return res, ws
case *parser.StepInvariantExpr: case *parser.StepInvariantExpr:
switch ce := e.Expr.(type) {
case *parser.StringLiteral, *parser.NumberLiteral:
return ev.eval(ctx, ce)
}
newEv := &evaluator{ newEv := &evaluator{
startTimestamp: ev.startTimestamp, startTimestamp: ev.startTimestamp,
endTimestamp: ev.startTimestamp, // Always a single evaluation. endTimestamp: ev.startTimestamp, // Always a single evaluation.
@ -3725,8 +3710,8 @@ func unwrapStepInvariantExpr(e parser.Expr) parser.Expr {
} }
// PreprocessExpr wraps all possible step invariant parts of the given expression with // PreprocessExpr wraps all possible step invariant parts of the given expression with
// StepInvariantExpr. It also resolves the preprocessors and evaluates duration expressions // StepInvariantExpr. It also resolves the preprocessors, evaluates duration expressions
// into their numeric values. // into their numeric values and removes superfluous parenthesis on parameters to functions and aggregations.
func PreprocessExpr(expr parser.Expr, start, end time.Time, step time.Duration) (parser.Expr, error) { func PreprocessExpr(expr parser.Expr, start, end time.Time, step time.Duration) (parser.Expr, error) {
detectHistogramStatsDecoding(expr) detectHistogramStatsDecoding(expr)
@ -3734,18 +3719,20 @@ func PreprocessExpr(expr parser.Expr, start, end time.Time, step time.Duration)
return nil, err return nil, err
} }
isStepInvariant := preprocessExprHelper(expr, start, end) _, shouldWrap := preprocessExprHelper(expr, start, end)
if isStepInvariant { if shouldWrap {
return newStepInvariantExpr(expr), nil return newStepInvariantExpr(expr), nil
} }
return expr, nil return expr, nil
} }
// preprocessExprHelper wraps the child nodes of the expression // preprocessExprHelper wraps child nodes of expr with a StepInvariantExpr,
// with a StepInvariantExpr wherever it's step invariant. The returned boolean is true if the // at the highest level within the tree that is step-invariant.
// passed expression qualifies to be wrapped by StepInvariantExpr. // Also resolves start() and end() on selector and subquery nodes.
// It also resolves the preprocessors. // Also remove superfluous parenthesis on parameters to functions and aggregations.
func preprocessExprHelper(expr parser.Expr, start, end time.Time) bool { // Return isStepInvariant is true when the whole subexpression is step invariant.
// Return shoudlWrap is false for cases like MatrixSelector and StringLiteral that never need to be wrapped.
func preprocessExprHelper(expr parser.Expr, start, end time.Time) (isStepInvariant, shouldWrap bool) {
switch n := expr.(type) { switch n := expr.(type) {
case *parser.VectorSelector: case *parser.VectorSelector:
switch n.StartOrEnd { switch n.StartOrEnd {
@ -3754,49 +3741,56 @@ func preprocessExprHelper(expr parser.Expr, start, end time.Time) bool {
case parser.END: case parser.END:
n.Timestamp = makeInt64Pointer(timestamp.FromTime(end)) n.Timestamp = makeInt64Pointer(timestamp.FromTime(end))
} }
return n.Timestamp != nil return n.Timestamp != nil, n.Timestamp != nil
case *parser.AggregateExpr: case *parser.AggregateExpr:
unwrapParenExpr(&n.Expr)
unwrapParenExpr(&n.Param)
return preprocessExprHelper(n.Expr, start, end) return preprocessExprHelper(n.Expr, start, end)
case *parser.BinaryExpr: case *parser.BinaryExpr:
isInvariant1, isInvariant2 := preprocessExprHelper(n.LHS, start, end), preprocessExprHelper(n.RHS, start, end) isInvariantLHS, shouldWrapLHS := preprocessExprHelper(n.LHS, start, end)
if isInvariant1 && isInvariant2 { isInvariantRHS, shouldWrapRHS := preprocessExprHelper(n.RHS, start, end)
return true if isInvariantLHS && isInvariantRHS {
return true, true
} }
if isInvariant1 { if shouldWrapLHS {
n.LHS = newStepInvariantExpr(n.LHS) n.LHS = newStepInvariantExpr(n.LHS)
} }
if isInvariant2 { if shouldWrapRHS {
n.RHS = newStepInvariantExpr(n.RHS) n.RHS = newStepInvariantExpr(n.RHS)
} }
return false return false, false
case *parser.Call: case *parser.Call:
_, ok := AtModifierUnsafeFunctions[n.Func.Name] _, ok := AtModifierUnsafeFunctions[n.Func.Name]
isStepInvariant := !ok isStepInvariant := !ok
isStepInvariantSlice := make([]bool, len(n.Args)) shouldWrap := make([]bool, len(n.Args))
for i := range n.Args { for i := range n.Args {
isStepInvariantSlice[i] = preprocessExprHelper(n.Args[i], start, end) unwrapParenExpr(&n.Args[i])
isStepInvariant = isStepInvariant && isStepInvariantSlice[i] var argIsStepInvariant bool
argIsStepInvariant, shouldWrap[i] = preprocessExprHelper(n.Args[i], start, end)
isStepInvariant = isStepInvariant && argIsStepInvariant
} }
if isStepInvariant { if isStepInvariant {
// The function and all arguments are step invariant. // The function and all arguments are step invariant.
return true return true, true
} }
for i, isi := range isStepInvariantSlice { for i, isi := range shouldWrap {
if isi { if isi {
n.Args[i] = newStepInvariantExpr(n.Args[i]) n.Args[i] = newStepInvariantExpr(n.Args[i])
} }
} }
return false return false, false
case *parser.MatrixSelector: case *parser.MatrixSelector:
return preprocessExprHelper(n.VectorSelector, start, end) // We don't need to wrap a MatrixSelector because functions over range vectors evaluate those directly.
isStepInvariant, _ := preprocessExprHelper(n.VectorSelector, start, end)
return isStepInvariant, false
case *parser.SubqueryExpr: case *parser.SubqueryExpr:
// Since we adjust offset for the @ modifier evaluation, // Since we adjust offset for the @ modifier evaluation,
@ -3804,7 +3798,7 @@ func preprocessExprHelper(expr parser.Expr, start, end time.Time) bool {
// Hence we wrap the inside of subquery irrespective of // Hence we wrap the inside of subquery irrespective of
// @ on subquery (given it is also step invariant) so that // @ on subquery (given it is also step invariant) so that
// it is evaluated only once w.r.t. the start time of subquery. // it is evaluated only once w.r.t. the start time of subquery.
isInvariant := preprocessExprHelper(n.Expr, start, end) isInvariant, _ := preprocessExprHelper(n.Expr, start, end)
if isInvariant { if isInvariant {
n.Expr = newStepInvariantExpr(n.Expr) n.Expr = newStepInvariantExpr(n.Expr)
} }
@ -3814,7 +3808,7 @@ func preprocessExprHelper(expr parser.Expr, start, end time.Time) bool {
case parser.END: case parser.END:
n.Timestamp = makeInt64Pointer(timestamp.FromTime(end)) n.Timestamp = makeInt64Pointer(timestamp.FromTime(end))
} }
return n.Timestamp != nil return n.Timestamp != nil, n.Timestamp != nil
case *parser.ParenExpr: case *parser.ParenExpr:
return preprocessExprHelper(n.Expr, start, end) return preprocessExprHelper(n.Expr, start, end)
@ -3823,7 +3817,7 @@ func preprocessExprHelper(expr parser.Expr, start, end time.Time) bool {
return preprocessExprHelper(n.Expr, start, end) return preprocessExprHelper(n.Expr, start, end)
case *parser.StringLiteral, *parser.NumberLiteral: case *parser.StringLiteral, *parser.NumberLiteral:
return true return true, false
} }
panic(fmt.Sprintf("found unexpected node %#v", expr)) panic(fmt.Sprintf("found unexpected node %#v", expr))

View File

@ -2304,22 +2304,18 @@ func TestPreprocessAndWrapWithStepInvariantExpr(t *testing.T) {
}{ }{
{ {
input: "123.4567", input: "123.4567",
expected: &parser.StepInvariantExpr{ expected: &parser.NumberLiteral{
Expr: &parser.NumberLiteral{
Val: 123.4567, Val: 123.4567,
PosRange: posrange.PositionRange{Start: 0, End: 8}, PosRange: posrange.PositionRange{Start: 0, End: 8},
}, },
}, },
},
{ {
input: `"foo"`, input: `"foo"`,
expected: &parser.StepInvariantExpr{ expected: &parser.StringLiteral{
Expr: &parser.StringLiteral{
Val: "foo", Val: "foo",
PosRange: posrange.PositionRange{Start: 0, End: 5}, PosRange: posrange.PositionRange{Start: 0, End: 5},
}, },
}, },
},
{ {
input: "foo * bar", input: "foo * bar",
expected: &parser.BinaryExpr{ expected: &parser.BinaryExpr{
@ -2427,8 +2423,7 @@ func TestPreprocessAndWrapWithStepInvariantExpr(t *testing.T) {
}, },
{ {
input: `test{a="b"}[5y] @ 1603774699`, input: `test{a="b"}[5y] @ 1603774699`,
expected: &parser.StepInvariantExpr{ expected: &parser.MatrixSelector{
Expr: &parser.MatrixSelector{
VectorSelector: &parser.VectorSelector{ VectorSelector: &parser.VectorSelector{
Name: "test", Name: "test",
Timestamp: makeInt64Pointer(1603774699000), Timestamp: makeInt64Pointer(1603774699000),
@ -2445,7 +2440,6 @@ func TestPreprocessAndWrapWithStepInvariantExpr(t *testing.T) {
EndPos: 28, EndPos: 28,
}, },
}, },
},
{ {
input: "sum by (foo)(some_metric)", input: "sum by (foo)(some_metric)",
expected: &parser.AggregateExpr{ expected: &parser.AggregateExpr{
@ -2942,9 +2936,16 @@ func TestPreprocessAndWrapWithStepInvariantExpr(t *testing.T) {
}, },
}, },
{ {
input: `test[5y] @ start()`, input: `sum_over_time(test[5y] @ start())`,
expected: &parser.StepInvariantExpr{ expected: &parser.StepInvariantExpr{
Expr: &parser.MatrixSelector{ Expr: &parser.Call{
Func: &parser.Function{
Name: "sum_over_time",
ArgTypes: []parser.ValueType{parser.ValueTypeMatrix},
ReturnType: parser.ValueTypeVector,
},
Args: parser.Expressions{
&parser.MatrixSelector{
VectorSelector: &parser.VectorSelector{ VectorSelector: &parser.VectorSelector{
Name: "test", Name: "test",
Timestamp: makeInt64Pointer(timestamp.FromTime(startTime)), Timestamp: makeInt64Pointer(timestamp.FromTime(startTime)),
@ -2953,19 +2954,21 @@ func TestPreprocessAndWrapWithStepInvariantExpr(t *testing.T) {
parser.MustLabelMatcher(labels.MatchEqual, "__name__", "test"), parser.MustLabelMatcher(labels.MatchEqual, "__name__", "test"),
}, },
PosRange: posrange.PositionRange{ PosRange: posrange.PositionRange{
Start: 0, Start: 14,
End: 4, End: 18,
}, },
}, },
Range: 5 * 365 * 24 * time.Hour, Range: 5 * 365 * 24 * time.Hour,
EndPos: 18, EndPos: 32,
},
},
PosRange: posrange.PositionRange{Start: 0, End: 33},
}, },
}, },
}, },
{ {
input: `test[5y] @ end()`, input: `test[5y] @ end()`,
expected: &parser.StepInvariantExpr{ expected: &parser.MatrixSelector{
Expr: &parser.MatrixSelector{
VectorSelector: &parser.VectorSelector{ VectorSelector: &parser.VectorSelector{
Name: "test", Name: "test",
Timestamp: makeInt64Pointer(timestamp.FromTime(endTime)), Timestamp: makeInt64Pointer(timestamp.FromTime(endTime)),
@ -2982,7 +2985,6 @@ func TestPreprocessAndWrapWithStepInvariantExpr(t *testing.T) {
EndPos: 16, EndPos: 16,
}, },
}, },
},
{ {
input: `some_metric[10m:5s] @ start()`, input: `some_metric[10m:5s] @ start()`,
expected: &parser.StepInvariantExpr{ expected: &parser.StepInvariantExpr{

View File

@ -1946,9 +1946,7 @@ func createLabelsForAbsentFunction(expr parser.Expr) labels.Labels {
} }
func stringFromArg(e parser.Expr) string { func stringFromArg(e parser.Expr) string {
tmp := unwrapStepInvariantExpr(e) // Unwrap StepInvariant return e.(*parser.StringLiteral).Val
unwrapParenExpr(&tmp) // Optionally unwrap ParenExpr
return tmp.(*parser.StringLiteral).Val
} }
func stringSliceFromArgs(args parser.Expressions) []string { func stringSliceFromArgs(args parser.Expressions) []string {