mirror of
https://github.com/prometheus/prometheus.git
synced 2026-05-28 04:02:21 -04:00
Merge pull request #18624 from roidelapluie/roidelapluie/dur-expr-in-parse-ast
web/api: emit duration expression trees
This commit is contained in:
commit
42003b2e13
2 changed files with 240 additions and 0 deletions
|
|
@ -84,7 +84,9 @@ func translateAST(node parser.Expr) any {
|
|||
"type": "matrixSelector",
|
||||
"name": vs.Name,
|
||||
"range": n.Range.Milliseconds(),
|
||||
"rangeExpr": translateDurationExpr(n.RangeExpr),
|
||||
"offset": vs.OriginalOffset.Milliseconds(),
|
||||
"offsetExpr": translateDurationExpr(vs.OriginalOffsetExpr),
|
||||
"matchers": translateMatchers(vs.LabelMatchers),
|
||||
"timestamp": vs.Timestamp,
|
||||
"startOrEnd": getStartOrEnd(vs.StartOrEnd),
|
||||
|
|
@ -96,11 +98,16 @@ func translateAST(node parser.Expr) any {
|
|||
"type": "subquery",
|
||||
"expr": translateAST(n.Expr),
|
||||
"range": n.Range.Milliseconds(),
|
||||
"rangeExpr": translateDurationExpr(n.RangeExpr),
|
||||
"offset": n.OriginalOffset.Milliseconds(),
|
||||
"offsetExpr": translateDurationExpr(n.OriginalOffsetExpr),
|
||||
"step": n.Step.Milliseconds(),
|
||||
"stepExpr": translateDurationExpr(n.StepExpr),
|
||||
"timestamp": n.Timestamp,
|
||||
"startOrEnd": getStartOrEnd(n.StartOrEnd),
|
||||
}
|
||||
case *parser.DurationExpr:
|
||||
return translateDurationExpr(n)
|
||||
case *parser.NumberLiteral:
|
||||
return map[string]string{
|
||||
"type": "numberLiteral",
|
||||
|
|
@ -127,6 +134,7 @@ func translateAST(node parser.Expr) any {
|
|||
"type": "vectorSelector",
|
||||
"name": n.Name,
|
||||
"offset": n.OriginalOffset.Milliseconds(),
|
||||
"offsetExpr": translateDurationExpr(n.OriginalOffsetExpr),
|
||||
"matchers": translateMatchers(n.LabelMatchers),
|
||||
"timestamp": n.Timestamp,
|
||||
"startOrEnd": getStartOrEnd(n.StartOrEnd),
|
||||
|
|
@ -137,6 +145,39 @@ func translateAST(node parser.Expr) any {
|
|||
panic("unsupported node type")
|
||||
}
|
||||
|
||||
func translateDurationExpr(node parser.Expr) any {
|
||||
if node == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
switch n := node.(type) {
|
||||
case *parser.DurationExpr:
|
||||
if n == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
return map[string]any{
|
||||
"type": "durationExpr",
|
||||
"op": n.Op.String(),
|
||||
"lhs": translateDurationExpr(n.LHS),
|
||||
"rhs": translateDurationExpr(n.RHS),
|
||||
"wrapped": n.Wrapped,
|
||||
}
|
||||
case *parser.NumberLiteral:
|
||||
if n == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
return map[string]any{
|
||||
"type": "numberLiteral",
|
||||
"val": strconv.FormatFloat(n.Val, 'f', -1, 64),
|
||||
"duration": n.Duration,
|
||||
}
|
||||
default:
|
||||
return translateAST(n)
|
||||
}
|
||||
}
|
||||
|
||||
func sanitizeList(l []string) []string {
|
||||
if l == nil {
|
||||
return []string{}
|
||||
|
|
|
|||
199
web/api/v1/translate_ast_test.go
Normal file
199
web/api/v1/translate_ast_test.go
Normal file
|
|
@ -0,0 +1,199 @@
|
|||
// Copyright 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 v1
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/prometheus/prometheus/promql/parser"
|
||||
)
|
||||
|
||||
func TestTranslateASTDurationExpressions(t *testing.T) {
|
||||
p := parser.NewParser(parser.Options{})
|
||||
|
||||
type tc struct {
|
||||
name string
|
||||
query string
|
||||
wantType string
|
||||
wantFields map[string]any
|
||||
}
|
||||
|
||||
testcases := []tc{
|
||||
{
|
||||
name: "regular matrix selector",
|
||||
query: `foo[5m]`,
|
||||
wantType: "matrixSelector",
|
||||
wantFields: map[string]any{
|
||||
"range": int64(5 * 60 * 1000),
|
||||
"rangeExpr": nil,
|
||||
"offset": int64(0),
|
||||
"offsetExpr": nil,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "regular matrix selector with offset",
|
||||
query: `foo[5m] offset 1m`,
|
||||
wantType: "matrixSelector",
|
||||
wantFields: map[string]any{
|
||||
"range": int64(5 * 60 * 1000),
|
||||
"rangeExpr": nil,
|
||||
"offset": int64(60 * 1000),
|
||||
"offsetExpr": nil,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "matrix selector range expression",
|
||||
query: `foo[5m+1m]`,
|
||||
wantType: "matrixSelector",
|
||||
wantFields: map[string]any{
|
||||
"range": int64(0),
|
||||
"rangeExpr": durationExpr("+", durationNumber("300", true), durationNumber("60", true), false),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "matrix selector range expression with offset expression",
|
||||
query: `foo[5m+1m] offset (10m/2)`,
|
||||
wantType: "matrixSelector",
|
||||
wantFields: map[string]any{
|
||||
"rangeExpr": durationExpr("+", durationNumber("300", true), durationNumber("60", true), false),
|
||||
"offset": int64(0),
|
||||
"offsetExpr": durationExpr("/", durationNumber("600", true), durationNumber("2", false), true),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "complex matrix selector range expression",
|
||||
query: `foo[max(step(),5m+3m) ]`,
|
||||
wantType: "matrixSelector",
|
||||
wantFields: map[string]any{
|
||||
"range": int64(0),
|
||||
"rangeExpr": durationExpr("max",
|
||||
durationExpr("step", nil, nil, false),
|
||||
durationExpr("+", durationNumber("300", true), durationNumber("180", true), false),
|
||||
false,
|
||||
),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "nested min and max matrix selector range expression",
|
||||
query: `foo[min(max(step(),5m+3m),10m-2m)]`,
|
||||
wantType: "matrixSelector",
|
||||
wantFields: map[string]any{
|
||||
"range": int64(0),
|
||||
"rangeExpr": durationExpr("min",
|
||||
durationExpr("max",
|
||||
durationExpr("step", nil, nil, false),
|
||||
durationExpr("+", durationNumber("300", true), durationNumber("180", true), false),
|
||||
false,
|
||||
),
|
||||
durationExpr("-", durationNumber("600", true), durationNumber("120", true), false),
|
||||
false,
|
||||
),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "range preprocessor expression",
|
||||
query: `foo[range()]`,
|
||||
wantType: "matrixSelector",
|
||||
wantFields: map[string]any{
|
||||
"range": int64(0),
|
||||
"rangeExpr": durationExpr("range", nil, nil, false),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "regular subquery selector",
|
||||
query: `foo[5m:1m]`,
|
||||
wantType: "subquery",
|
||||
wantFields: map[string]any{
|
||||
"range": int64(5 * 60 * 1000),
|
||||
"rangeExpr": nil,
|
||||
"step": int64(60 * 1000),
|
||||
"stepExpr": nil,
|
||||
"offset": int64(0),
|
||||
"offsetExpr": nil,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "subquery selector duration expressions",
|
||||
query: `foo[4s+4s:1s*2] offset (5s-8)`,
|
||||
wantType: "subquery",
|
||||
wantFields: map[string]any{
|
||||
"range": int64(0),
|
||||
"rangeExpr": durationExpr("+", durationNumber("4", true), durationNumber("4", true), false),
|
||||
"step": int64(0),
|
||||
"stepExpr": durationExpr("*", durationNumber("1", true), durationNumber("2", false), false),
|
||||
"offset": int64(0),
|
||||
"offsetExpr": durationExpr("-", durationNumber("5", true), durationNumber("8", false), true),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "regular vector selector offset",
|
||||
query: `foo offset 5m`,
|
||||
wantType: "vectorSelector",
|
||||
wantFields: map[string]any{
|
||||
"offset": int64(5 * 60 * 1000),
|
||||
"offsetExpr": nil,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "vector selector offset expression",
|
||||
query: `foo offset -min(5s,step()+8s)`,
|
||||
wantType: "vectorSelector",
|
||||
wantFields: map[string]any{
|
||||
"offset": int64(0),
|
||||
"offsetExpr": durationExpr("-", nil,
|
||||
durationExpr("min",
|
||||
durationNumber("5", true),
|
||||
durationExpr("+", durationExpr("step", nil, nil, false), durationNumber("8", true), false),
|
||||
false,
|
||||
),
|
||||
false,
|
||||
),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tcase := range testcases {
|
||||
t.Run(tcase.name, func(t *testing.T) {
|
||||
expr, err := p.ParseExpr(tcase.query)
|
||||
require.NoError(t, err)
|
||||
|
||||
got := translateAST(expr).(map[string]any)
|
||||
require.Equal(t, tcase.wantType, got["type"])
|
||||
for field, want := range tcase.wantFields {
|
||||
require.Contains(t, got, field)
|
||||
require.Equal(t, want, got[field], field)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func durationExpr(op string, lhs, rhs any, wrapped bool) map[string]any {
|
||||
return map[string]any{
|
||||
"type": "durationExpr",
|
||||
"op": op,
|
||||
"lhs": lhs,
|
||||
"rhs": rhs,
|
||||
"wrapped": wrapped,
|
||||
}
|
||||
}
|
||||
|
||||
func durationNumber(val string, duration bool) map[string]any {
|
||||
return map[string]any{
|
||||
"type": "numberLiteral",
|
||||
"val": val,
|
||||
"duration": duration,
|
||||
}
|
||||
}
|
||||
Loading…
Reference in a new issue