mirror of
https://github.com/VictoriaMetrics/VictoriaMetrics.git
synced 2024-12-15 08:23:34 +01:00
Split Extended PromQL parsing to a separate library
This commit is contained in:
parent
ff18101d30
commit
009d1559db
@ -8,6 +8,7 @@ import (
|
||||
"strings"
|
||||
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/logger"
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/promql"
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/storage"
|
||||
"github.com/VictoriaMetrics/metrics"
|
||||
"github.com/valyala/histogram"
|
||||
@ -48,7 +49,7 @@ type aggrFunc func(afa *aggrFuncArg) ([]*timeseries, error)
|
||||
|
||||
type aggrFuncArg struct {
|
||||
args [][]*timeseries
|
||||
ae *aggrFuncExpr
|
||||
ae *promql.AggrFuncExpr
|
||||
ec *EvalConfig
|
||||
}
|
||||
|
||||
@ -57,20 +58,6 @@ func getAggrFunc(s string) aggrFunc {
|
||||
return aggrFuncs[s]
|
||||
}
|
||||
|
||||
func isAggrFunc(s string) bool {
|
||||
return getAggrFunc(s) != nil
|
||||
}
|
||||
|
||||
func isAggrFuncModifier(s string) bool {
|
||||
s = strings.ToLower(s)
|
||||
switch s {
|
||||
case "by", "without":
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
func newAggrFunc(afe func(tss []*timeseries) []*timeseries) aggrFunc {
|
||||
return func(afa *aggrFuncArg) ([]*timeseries, error) {
|
||||
args := afa.args
|
||||
@ -81,7 +68,7 @@ func newAggrFunc(afe func(tss []*timeseries) []*timeseries) aggrFunc {
|
||||
}
|
||||
}
|
||||
|
||||
func removeGroupTags(metricName *storage.MetricName, modifier *modifierExpr) {
|
||||
func removeGroupTags(metricName *storage.MetricName, modifier *promql.ModifierExpr) {
|
||||
groupOp := strings.ToLower(modifier.Op)
|
||||
switch groupOp {
|
||||
case "", "by":
|
||||
@ -93,7 +80,7 @@ func removeGroupTags(metricName *storage.MetricName, modifier *modifierExpr) {
|
||||
}
|
||||
}
|
||||
|
||||
func aggrFuncExt(afe func(tss []*timeseries) []*timeseries, argOrig []*timeseries, modifier *modifierExpr, keepOriginal bool) ([]*timeseries, error) {
|
||||
func aggrFuncExt(afe func(tss []*timeseries) []*timeseries, argOrig []*timeseries, modifier *promql.ModifierExpr, keepOriginal bool) ([]*timeseries, error) {
|
||||
arg := copyTimeseriesMetricNames(argOrig)
|
||||
|
||||
// Perform grouping.
|
||||
|
@ -4,6 +4,8 @@ import (
|
||||
"math"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/promql"
|
||||
)
|
||||
|
||||
// callbacks for optimized incremental calculations for aggregate functions
|
||||
@ -49,7 +51,7 @@ var incrementalAggrFuncCallbacksMap = map[string]*incrementalAggrFuncCallbacks{
|
||||
}
|
||||
|
||||
type incrementalAggrFuncContext struct {
|
||||
ae *aggrFuncExpr
|
||||
ae *promql.AggrFuncExpr
|
||||
|
||||
mLock sync.Mutex
|
||||
m map[uint]map[string]*incrementalAggrContext
|
||||
@ -57,7 +59,7 @@ type incrementalAggrFuncContext struct {
|
||||
callbacks *incrementalAggrFuncCallbacks
|
||||
}
|
||||
|
||||
func newIncrementalAggrFuncContext(ae *aggrFuncExpr, callbacks *incrementalAggrFuncCallbacks) *incrementalAggrFuncContext {
|
||||
func newIncrementalAggrFuncContext(ae *promql.AggrFuncExpr, callbacks *incrementalAggrFuncCallbacks) *incrementalAggrFuncContext {
|
||||
return &incrementalAggrFuncContext{
|
||||
ae: ae,
|
||||
m: make(map[uint]map[string]*incrementalAggrContext),
|
||||
|
@ -7,6 +7,8 @@ import (
|
||||
"runtime"
|
||||
"sync"
|
||||
"testing"
|
||||
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/promql"
|
||||
)
|
||||
|
||||
func TestIncrementalAggr(t *testing.T) {
|
||||
@ -42,7 +44,7 @@ func TestIncrementalAggr(t *testing.T) {
|
||||
f := func(name string, valuesExpected []float64) {
|
||||
t.Helper()
|
||||
callbacks := getIncrementalAggrFuncCallbacks(name)
|
||||
ae := &aggrFuncExpr{
|
||||
ae := &promql.AggrFuncExpr{
|
||||
Name: name,
|
||||
}
|
||||
tssExpected := []*timeseries{{
|
||||
|
9
app/vmselect/promql/alias.go
Normal file
9
app/vmselect/promql/alias.go
Normal file
@ -0,0 +1,9 @@
|
||||
package promql
|
||||
|
||||
import (
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/promql"
|
||||
)
|
||||
|
||||
// DurationValue returns the duration in milliseconds for the given s
|
||||
// and the given step.
|
||||
var DurationValue = promql.DurationValue
|
@ -6,6 +6,7 @@ import (
|
||||
"strings"
|
||||
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/logger"
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/promql"
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/storage"
|
||||
)
|
||||
|
||||
@ -36,94 +37,11 @@ var binaryOpFuncs = map[string]binaryOpFunc{
|
||||
"default": newBinaryOpArithFunc(binaryOpDefault),
|
||||
}
|
||||
|
||||
var binaryOpPriorities = map[string]int{
|
||||
"default": -1,
|
||||
|
||||
"if": 0,
|
||||
"ifnot": 0,
|
||||
|
||||
// See https://prometheus.io/docs/prometheus/latest/querying/operators/#binary-operator-precedence
|
||||
"or": 1,
|
||||
|
||||
"and": 2,
|
||||
"unless": 2,
|
||||
|
||||
"==": 3,
|
||||
"!=": 3,
|
||||
"<": 3,
|
||||
">": 3,
|
||||
"<=": 3,
|
||||
">=": 3,
|
||||
|
||||
"+": 4,
|
||||
"-": 4,
|
||||
|
||||
"*": 5,
|
||||
"/": 5,
|
||||
"%": 5,
|
||||
|
||||
"^": 6,
|
||||
}
|
||||
|
||||
func getBinaryOpFunc(op string) binaryOpFunc {
|
||||
op = strings.ToLower(op)
|
||||
return binaryOpFuncs[op]
|
||||
}
|
||||
|
||||
func isBinaryOp(op string) bool {
|
||||
return getBinaryOpFunc(op) != nil
|
||||
}
|
||||
|
||||
func binaryOpPriority(op string) int {
|
||||
op = strings.ToLower(op)
|
||||
return binaryOpPriorities[op]
|
||||
}
|
||||
|
||||
func scanBinaryOpPrefix(s string) int {
|
||||
n := 0
|
||||
for op := range binaryOpFuncs {
|
||||
if len(s) < len(op) {
|
||||
continue
|
||||
}
|
||||
ss := strings.ToLower(s[:len(op)])
|
||||
if ss == op && len(op) > n {
|
||||
n = len(op)
|
||||
}
|
||||
}
|
||||
return n
|
||||
}
|
||||
|
||||
func isRightAssociativeBinaryOp(op string) bool {
|
||||
// See https://prometheus.io/docs/prometheus/latest/querying/operators/#binary-operator-precedence
|
||||
return op == "^"
|
||||
}
|
||||
|
||||
func isBinaryOpGroupModifier(s string) bool {
|
||||
s = strings.ToLower(s)
|
||||
switch s {
|
||||
// See https://prometheus.io/docs/prometheus/latest/querying/operators/#vector-matching
|
||||
case "on", "ignoring":
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
func isBinaryOpJoinModifier(s string) bool {
|
||||
s = strings.ToLower(s)
|
||||
switch s {
|
||||
case "group_left", "group_right":
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
func isBinaryOpBoolModifier(s string) bool {
|
||||
s = strings.ToLower(s)
|
||||
return s == "bool"
|
||||
}
|
||||
|
||||
func isBinaryOpCmp(op string) bool {
|
||||
switch op {
|
||||
case "==", "!=", ">", "<", ">=", "<=":
|
||||
@ -133,16 +51,6 @@ func isBinaryOpCmp(op string) bool {
|
||||
}
|
||||
}
|
||||
|
||||
func isBinaryOpLogicalSet(op string) bool {
|
||||
op = strings.ToLower(op)
|
||||
switch op {
|
||||
case "and", "or", "unless":
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
func binaryOpConstants(op string, left, right float64, isBool bool) float64 {
|
||||
if isBinaryOpCmp(op) {
|
||||
evalCmp := func(cf func(left, right float64) bool) float64 {
|
||||
@ -207,7 +115,7 @@ func binaryOpConstants(op string, left, right float64, isBool bool) float64 {
|
||||
}
|
||||
|
||||
type binaryOpFuncArg struct {
|
||||
be *binaryOpExpr
|
||||
be *promql.BinaryOpExpr
|
||||
left []*timeseries
|
||||
right []*timeseries
|
||||
}
|
||||
@ -267,7 +175,7 @@ func newBinaryOpFunc(bf func(left, right float64, isBool bool) float64) binaryOp
|
||||
}
|
||||
}
|
||||
|
||||
func adjustBinaryOpTags(be *binaryOpExpr, left, right []*timeseries) ([]*timeseries, []*timeseries, []*timeseries, error) {
|
||||
func adjustBinaryOpTags(be *promql.BinaryOpExpr, left, right []*timeseries) ([]*timeseries, []*timeseries, []*timeseries, error) {
|
||||
if len(be.GroupModifier.Op) == 0 && len(be.JoinModifier.Op) == 0 {
|
||||
if isScalar(left) {
|
||||
// Fast path: `scalar op vector`
|
||||
@ -348,7 +256,7 @@ func adjustBinaryOpTags(be *binaryOpExpr, left, right []*timeseries) ([]*timeser
|
||||
return rvsLeft, rvsRight, dst, nil
|
||||
}
|
||||
|
||||
func ensureSingleTimeseries(side string, be *binaryOpExpr, tss []*timeseries) error {
|
||||
func ensureSingleTimeseries(side string, be *promql.BinaryOpExpr, tss []*timeseries) error {
|
||||
if len(tss) == 0 {
|
||||
logger.Panicf("BUG: tss must contain at least one value")
|
||||
}
|
||||
@ -362,7 +270,7 @@ func ensureSingleTimeseries(side string, be *binaryOpExpr, tss []*timeseries) er
|
||||
return nil
|
||||
}
|
||||
|
||||
func groupJoin(singleTimeseriesSide string, be *binaryOpExpr, rvsLeft, rvsRight, tssLeft, tssRight []*timeseries) ([]*timeseries, []*timeseries, error) {
|
||||
func groupJoin(singleTimeseriesSide string, be *promql.BinaryOpExpr, rvsLeft, rvsRight, tssLeft, tssRight []*timeseries) ([]*timeseries, []*timeseries, error) {
|
||||
joinTags := be.JoinModifier.Args
|
||||
var m map[string]*timeseries
|
||||
for _, tsLeft := range tssLeft {
|
||||
@ -432,7 +340,7 @@ func mergeNonOverlappingTimeseries(dst, src *timeseries) bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func resetMetricGroupIfRequired(be *binaryOpExpr, ts *timeseries) {
|
||||
func resetMetricGroupIfRequired(be *promql.BinaryOpExpr, ts *timeseries) {
|
||||
if isBinaryOpCmp(be.Op) && !be.Bool {
|
||||
// Do not reset MetricGroup for non-boolean `compare` binary ops like Prometheus does.
|
||||
return
|
||||
@ -565,7 +473,7 @@ func binaryOpUnless(bfa *binaryOpFuncArg) ([]*timeseries, error) {
|
||||
return rvs, nil
|
||||
}
|
||||
|
||||
func createTimeseriesMapByTagSet(be *binaryOpExpr, left, right []*timeseries) (map[string][]*timeseries, map[string][]*timeseries) {
|
||||
func createTimeseriesMapByTagSet(be *promql.BinaryOpExpr, left, right []*timeseries) (map[string][]*timeseries, map[string][]*timeseries) {
|
||||
groupTags := be.GroupModifier.Args
|
||||
groupOp := strings.ToLower(be.GroupModifier.Op)
|
||||
if len(groupOp) == 0 {
|
||||
|
@ -6,12 +6,14 @@ import (
|
||||
"math"
|
||||
"runtime"
|
||||
"sync"
|
||||
"unsafe"
|
||||
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/app/vmselect/netstorage"
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/auth"
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/bytesutil"
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/logger"
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/memory"
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/promql"
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/storage"
|
||||
"github.com/VictoriaMetrics/metrics"
|
||||
)
|
||||
@ -158,9 +160,9 @@ func getTimestamps(start, end, step int64) []int64 {
|
||||
return timestamps
|
||||
}
|
||||
|
||||
func evalExpr(ec *EvalConfig, e expr) ([]*timeseries, error) {
|
||||
if me, ok := e.(*metricExpr); ok {
|
||||
re := &rollupExpr{
|
||||
func evalExpr(ec *EvalConfig, e promql.Expr) ([]*timeseries, error) {
|
||||
if me, ok := e.(*promql.MetricExpr); ok {
|
||||
re := &promql.RollupExpr{
|
||||
Expr: me,
|
||||
}
|
||||
rv, err := evalRollupFunc(ec, "default_rollup", rollupDefault, re, nil)
|
||||
@ -169,14 +171,14 @@ func evalExpr(ec *EvalConfig, e expr) ([]*timeseries, error) {
|
||||
}
|
||||
return rv, nil
|
||||
}
|
||||
if re, ok := e.(*rollupExpr); ok {
|
||||
if re, ok := e.(*promql.RollupExpr); ok {
|
||||
rv, err := evalRollupFunc(ec, "default_rollup", rollupDefault, re, nil)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf(`cannot evaluate %q: %s`, re.AppendString(nil), err)
|
||||
}
|
||||
return rv, nil
|
||||
}
|
||||
if fe, ok := e.(*funcExpr); ok {
|
||||
if fe, ok := e.(*promql.FuncExpr); ok {
|
||||
nrf := getRollupFunc(fe.Name)
|
||||
if nrf == nil {
|
||||
args, err := evalExprs(ec, fe.Args)
|
||||
@ -212,7 +214,7 @@ func evalExpr(ec *EvalConfig, e expr) ([]*timeseries, error) {
|
||||
}
|
||||
return rv, nil
|
||||
}
|
||||
if ae, ok := e.(*aggrFuncExpr); ok {
|
||||
if ae, ok := e.(*promql.AggrFuncExpr); ok {
|
||||
if callbacks := getIncrementalAggrFuncCallbacks(ae.Name); callbacks != nil {
|
||||
fe, nrf := tryGetArgRollupFuncWithMetricExpr(ae)
|
||||
if fe != nil {
|
||||
@ -249,7 +251,7 @@ func evalExpr(ec *EvalConfig, e expr) ([]*timeseries, error) {
|
||||
}
|
||||
return rv, nil
|
||||
}
|
||||
if be, ok := e.(*binaryOpExpr); ok {
|
||||
if be, ok := e.(*promql.BinaryOpExpr); ok {
|
||||
left, err := evalExpr(ec, be.Left)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@ -273,18 +275,18 @@ func evalExpr(ec *EvalConfig, e expr) ([]*timeseries, error) {
|
||||
}
|
||||
return rv, nil
|
||||
}
|
||||
if ne, ok := e.(*numberExpr); ok {
|
||||
if ne, ok := e.(*promql.NumberExpr); ok {
|
||||
rv := evalNumber(ec, ne.N)
|
||||
return rv, nil
|
||||
}
|
||||
if se, ok := e.(*stringExpr); ok {
|
||||
if se, ok := e.(*promql.StringExpr); ok {
|
||||
rv := evalString(ec, se.S)
|
||||
return rv, nil
|
||||
}
|
||||
return nil, fmt.Errorf("unexpected expression %q", e.AppendString(nil))
|
||||
}
|
||||
|
||||
func tryGetArgRollupFuncWithMetricExpr(ae *aggrFuncExpr) (*funcExpr, newRollupFunc) {
|
||||
func tryGetArgRollupFuncWithMetricExpr(ae *promql.AggrFuncExpr) (*promql.FuncExpr, newRollupFunc) {
|
||||
if len(ae.Args) != 1 {
|
||||
return nil, nil
|
||||
}
|
||||
@ -295,31 +297,31 @@ func tryGetArgRollupFuncWithMetricExpr(ae *aggrFuncExpr) (*funcExpr, newRollupFu
|
||||
// - rollupFunc(metricExpr)
|
||||
// - rollupFunc(metricExpr[d])
|
||||
|
||||
if me, ok := e.(*metricExpr); ok {
|
||||
if me, ok := e.(*promql.MetricExpr); ok {
|
||||
// e = metricExpr
|
||||
if me.IsEmpty() {
|
||||
return nil, nil
|
||||
}
|
||||
fe := &funcExpr{
|
||||
fe := &promql.FuncExpr{
|
||||
Name: "default_rollup",
|
||||
Args: []expr{me},
|
||||
Args: []promql.Expr{me},
|
||||
}
|
||||
nrf := getRollupFunc(fe.Name)
|
||||
return fe, nrf
|
||||
}
|
||||
if re, ok := e.(*rollupExpr); ok {
|
||||
if me, ok := re.Expr.(*metricExpr); !ok || me.IsEmpty() || re.ForSubquery() {
|
||||
if re, ok := e.(*promql.RollupExpr); ok {
|
||||
if me, ok := re.Expr.(*promql.MetricExpr); !ok || me.IsEmpty() || re.ForSubquery() {
|
||||
return nil, nil
|
||||
}
|
||||
// e = metricExpr[d]
|
||||
fe := &funcExpr{
|
||||
fe := &promql.FuncExpr{
|
||||
Name: "default_rollup",
|
||||
Args: []expr{re},
|
||||
Args: []promql.Expr{re},
|
||||
}
|
||||
nrf := getRollupFunc(fe.Name)
|
||||
return fe, nrf
|
||||
}
|
||||
fe, ok := e.(*funcExpr)
|
||||
fe, ok := e.(*promql.FuncExpr)
|
||||
if !ok {
|
||||
return nil, nil
|
||||
}
|
||||
@ -329,18 +331,18 @@ func tryGetArgRollupFuncWithMetricExpr(ae *aggrFuncExpr) (*funcExpr, newRollupFu
|
||||
}
|
||||
rollupArgIdx := getRollupArgIdx(fe.Name)
|
||||
arg := fe.Args[rollupArgIdx]
|
||||
if me, ok := arg.(*metricExpr); ok {
|
||||
if me, ok := arg.(*promql.MetricExpr); ok {
|
||||
if me.IsEmpty() {
|
||||
return nil, nil
|
||||
}
|
||||
// e = rollupFunc(metricExpr)
|
||||
return &funcExpr{
|
||||
return &promql.FuncExpr{
|
||||
Name: fe.Name,
|
||||
Args: []expr{me},
|
||||
Args: []promql.Expr{me},
|
||||
}, nrf
|
||||
}
|
||||
if re, ok := arg.(*rollupExpr); ok {
|
||||
if me, ok := re.Expr.(*metricExpr); !ok || me.IsEmpty() || re.ForSubquery() {
|
||||
if re, ok := arg.(*promql.RollupExpr); ok {
|
||||
if me, ok := re.Expr.(*promql.MetricExpr); !ok || me.IsEmpty() || re.ForSubquery() {
|
||||
return nil, nil
|
||||
}
|
||||
// e = rollupFunc(metricExpr[d])
|
||||
@ -349,7 +351,7 @@ func tryGetArgRollupFuncWithMetricExpr(ae *aggrFuncExpr) (*funcExpr, newRollupFu
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func evalExprs(ec *EvalConfig, es []expr) ([][]*timeseries, error) {
|
||||
func evalExprs(ec *EvalConfig, es []promql.Expr) ([][]*timeseries, error) {
|
||||
var rvs [][]*timeseries
|
||||
for _, e := range es {
|
||||
rv, err := evalExpr(ec, e)
|
||||
@ -361,8 +363,8 @@ func evalExprs(ec *EvalConfig, es []expr) ([][]*timeseries, error) {
|
||||
return rvs, nil
|
||||
}
|
||||
|
||||
func evalRollupFuncArgs(ec *EvalConfig, fe *funcExpr) ([]interface{}, *rollupExpr, error) {
|
||||
var re *rollupExpr
|
||||
func evalRollupFuncArgs(ec *EvalConfig, fe *promql.FuncExpr) ([]interface{}, *promql.RollupExpr, error) {
|
||||
var re *promql.RollupExpr
|
||||
rollupArgIdx := getRollupArgIdx(fe.Name)
|
||||
args := make([]interface{}, len(fe.Args))
|
||||
for i, arg := range fe.Args {
|
||||
@ -380,11 +382,11 @@ func evalRollupFuncArgs(ec *EvalConfig, fe *funcExpr) ([]interface{}, *rollupExp
|
||||
return args, re, nil
|
||||
}
|
||||
|
||||
func getRollupExprArg(arg expr) *rollupExpr {
|
||||
re, ok := arg.(*rollupExpr)
|
||||
func getRollupExprArg(arg promql.Expr) *promql.RollupExpr {
|
||||
re, ok := arg.(*promql.RollupExpr)
|
||||
if !ok {
|
||||
// Wrap non-rollup arg into rollupExpr.
|
||||
return &rollupExpr{
|
||||
return &promql.RollupExpr{
|
||||
Expr: arg,
|
||||
}
|
||||
}
|
||||
@ -392,28 +394,28 @@ func getRollupExprArg(arg expr) *rollupExpr {
|
||||
// Return standard rollup if it doesn't contain subquery.
|
||||
return re
|
||||
}
|
||||
me, ok := re.Expr.(*metricExpr)
|
||||
me, ok := re.Expr.(*promql.MetricExpr)
|
||||
if !ok {
|
||||
// arg contains subquery.
|
||||
return re
|
||||
}
|
||||
// Convert me[w:step] -> default_rollup(me)[w:step]
|
||||
reNew := *re
|
||||
reNew.Expr = &funcExpr{
|
||||
reNew.Expr = &promql.FuncExpr{
|
||||
Name: "default_rollup",
|
||||
Args: []expr{
|
||||
&rollupExpr{Expr: me},
|
||||
Args: []promql.Expr{
|
||||
&promql.RollupExpr{Expr: me},
|
||||
},
|
||||
}
|
||||
return &reNew
|
||||
}
|
||||
|
||||
func evalRollupFunc(ec *EvalConfig, name string, rf rollupFunc, re *rollupExpr, iafc *incrementalAggrFuncContext) ([]*timeseries, error) {
|
||||
func evalRollupFunc(ec *EvalConfig, name string, rf rollupFunc, re *promql.RollupExpr, iafc *incrementalAggrFuncContext) ([]*timeseries, error) {
|
||||
ecNew := ec
|
||||
var offset int64
|
||||
if len(re.Offset) > 0 {
|
||||
var err error
|
||||
offset, err = DurationValue(re.Offset, ec.Step)
|
||||
offset, err = promql.DurationValue(re.Offset, ec.Step)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -429,7 +431,7 @@ func evalRollupFunc(ec *EvalConfig, name string, rf rollupFunc, re *rollupExpr,
|
||||
}
|
||||
var rvs []*timeseries
|
||||
var err error
|
||||
if me, ok := re.Expr.(*metricExpr); ok {
|
||||
if me, ok := re.Expr.(*promql.MetricExpr); ok {
|
||||
rvs, err = evalRollupFuncWithMetricExpr(ecNew, name, rf, me, iafc, re.Window)
|
||||
} else {
|
||||
if iafc != nil {
|
||||
@ -454,12 +456,12 @@ func evalRollupFunc(ec *EvalConfig, name string, rf rollupFunc, re *rollupExpr,
|
||||
return rvs, nil
|
||||
}
|
||||
|
||||
func evalRollupFuncWithSubquery(ec *EvalConfig, name string, rf rollupFunc, re *rollupExpr) ([]*timeseries, error) {
|
||||
func evalRollupFuncWithSubquery(ec *EvalConfig, name string, rf rollupFunc, re *promql.RollupExpr) ([]*timeseries, error) {
|
||||
// Do not use rollupResultCacheV here, since it works only with metricExpr.
|
||||
var step int64
|
||||
if len(re.Step) > 0 {
|
||||
var err error
|
||||
step, err = PositiveDurationValue(re.Step, ec.Step)
|
||||
step, err = promql.DurationValue(re.Step, ec.Step)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -469,7 +471,7 @@ func evalRollupFuncWithSubquery(ec *EvalConfig, name string, rf rollupFunc, re *
|
||||
var window int64
|
||||
if len(re.Window) > 0 {
|
||||
var err error
|
||||
window, err = PositiveDurationValue(re.Window, ec.Step)
|
||||
window, err = promql.DurationValue(re.Window, ec.Step)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -563,14 +565,14 @@ var (
|
||||
rollupResultCacheMiss = metrics.NewCounter(`vm_rollup_result_cache_miss_total`)
|
||||
)
|
||||
|
||||
func evalRollupFuncWithMetricExpr(ec *EvalConfig, name string, rf rollupFunc, me *metricExpr, iafc *incrementalAggrFuncContext, windowStr string) ([]*timeseries, error) {
|
||||
func evalRollupFuncWithMetricExpr(ec *EvalConfig, name string, rf rollupFunc, me *promql.MetricExpr, iafc *incrementalAggrFuncContext, windowStr string) ([]*timeseries, error) {
|
||||
if me.IsEmpty() {
|
||||
return evalNumber(ec, nan), nil
|
||||
}
|
||||
var window int64
|
||||
if len(windowStr) > 0 {
|
||||
var err error
|
||||
window, err = PositiveDurationValue(windowStr, ec.Step)
|
||||
window, err = promql.DurationValue(windowStr, ec.Step)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -595,7 +597,9 @@ func evalRollupFuncWithMetricExpr(ec *EvalConfig, name string, rf rollupFunc, me
|
||||
ProjectID: ec.AuthToken.ProjectID,
|
||||
MinTimestamp: start - window - maxSilenceInterval,
|
||||
MaxTimestamp: ec.End + ec.Step,
|
||||
TagFilterss: [][]storage.TagFilter{me.TagFilters},
|
||||
TagFilterss: [][]storage.TagFilter{
|
||||
*(*[]storage.TagFilter)(unsafe.Pointer(&me.TagFilters)),
|
||||
},
|
||||
}
|
||||
rss, isPartial, err := netstorage.ProcessSearchQuery(ec.AuthToken, sq, true, ec.Deadline)
|
||||
if err != nil {
|
||||
|
@ -11,6 +11,7 @@ import (
|
||||
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/app/vmselect/netstorage"
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/logger"
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/promql"
|
||||
"github.com/VictoriaMetrics/metrics"
|
||||
)
|
||||
|
||||
@ -85,12 +86,12 @@ func Exec(ec *EvalConfig, q string, isFirstPointOnly bool) ([]netstorage.Result,
|
||||
return result, err
|
||||
}
|
||||
|
||||
func maySortResults(e expr, tss []*timeseries) bool {
|
||||
func maySortResults(e promql.Expr, tss []*timeseries) bool {
|
||||
if len(tss) > 100 {
|
||||
// There is no sense in sorting a lot of results
|
||||
return false
|
||||
}
|
||||
fe, ok := e.(*funcExpr)
|
||||
fe, ok := e.(*promql.FuncExpr)
|
||||
if !ok {
|
||||
return true
|
||||
}
|
||||
@ -154,7 +155,16 @@ func removeNaNs(tss []*timeseries) []*timeseries {
|
||||
return rvs
|
||||
}
|
||||
|
||||
func parsePromQLWithCache(q string) (expr, error) {
|
||||
func parsePromQL(q string) (promql.Expr, error) {
|
||||
e, err := parser.ParsePromQL(q)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return simplifyConstants(e), nil
|
||||
}
|
||||
|
||||
func parsePromQLWithCache(q string) (promql.Expr, error) {
|
||||
pcv := parseCacheV.Get(q)
|
||||
if pcv == nil {
|
||||
e, err := parsePromQL(q)
|
||||
@ -170,6 +180,8 @@ func parsePromQLWithCache(q string) (expr, error) {
|
||||
return pcv.e, nil
|
||||
}
|
||||
|
||||
var parser = promql.NewParser(compileRegexpAnchored)
|
||||
|
||||
var parseCacheV = func() *parseCache {
|
||||
pc := &parseCache{
|
||||
m: make(map[string]*parseCacheValue),
|
||||
@ -189,7 +201,7 @@ var parseCacheV = func() *parseCache {
|
||||
const parseCacheMaxLen = 10e3
|
||||
|
||||
type parseCacheValue struct {
|
||||
e expr
|
||||
e promql.Expr
|
||||
err error
|
||||
}
|
||||
|
||||
@ -249,3 +261,7 @@ func (pc *parseCache) Put(q string, pcv *parseCacheValue) {
|
||||
pc.m[q] = pcv
|
||||
pc.mu.Unlock()
|
||||
}
|
||||
|
||||
func init() {
|
||||
promql.Panicf = logger.Panicf
|
||||
}
|
||||
|
47
app/vmselect/promql/helper.go
Normal file
47
app/vmselect/promql/helper.go
Normal file
@ -0,0 +1,47 @@
|
||||
package promql
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"unsafe"
|
||||
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/promql"
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/storage"
|
||||
)
|
||||
|
||||
// IsMetricSelectorWithRollup verifies whether s contains PromQL metric selector
|
||||
// wrapped into rollup.
|
||||
//
|
||||
// It returns the wrapped query with the corresponding window with offset.
|
||||
func IsMetricSelectorWithRollup(s string) (childQuery string, window, offset string) {
|
||||
expr, err := parsePromQLWithCache(s)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
re, ok := expr.(*promql.RollupExpr)
|
||||
if !ok || len(re.Window) == 0 || len(re.Step) > 0 {
|
||||
return
|
||||
}
|
||||
me, ok := re.Expr.(*promql.MetricExpr)
|
||||
if !ok || len(me.TagFilters) == 0 {
|
||||
return
|
||||
}
|
||||
wrappedQuery := me.AppendString(nil)
|
||||
return string(wrappedQuery), re.Window, re.Offset
|
||||
}
|
||||
|
||||
// ParseMetricSelector parses s containing PromQL metric selector
|
||||
// and returns the corresponding TagFilters.
|
||||
func ParseMetricSelector(s string) ([]storage.TagFilter, error) {
|
||||
expr, err := parsePromQLWithCache(s)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
me, ok := expr.(*promql.MetricExpr)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("expecting metricSelector; got %q", expr.AppendString(nil))
|
||||
}
|
||||
if len(me.TagFilters) == 0 {
|
||||
return nil, fmt.Errorf("tagFilters cannot be empty")
|
||||
}
|
||||
return *(*[]storage.TagFilter)(unsafe.Pointer(&me.TagFilters)), nil
|
||||
}
|
49
app/vmselect/promql/helper_test.go
Normal file
49
app/vmselect/promql/helper_test.go
Normal file
@ -0,0 +1,49 @@
|
||||
package promql
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestParseMetricSelectorSuccess(t *testing.T) {
|
||||
f := func(s string) {
|
||||
t.Helper()
|
||||
tfs, err := ParseMetricSelector(s)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error when parsing %q: %s", s, err)
|
||||
}
|
||||
if tfs == nil {
|
||||
t.Fatalf("expecting non-nil tfs when parsing %q", s)
|
||||
}
|
||||
}
|
||||
f("foo")
|
||||
f(":foo")
|
||||
f(" :fo:bar.baz")
|
||||
f(`a{}`)
|
||||
f(`{foo="bar"}`)
|
||||
f(`{:f:oo=~"bar.+"}`)
|
||||
f(`foo {bar != "baz"}`)
|
||||
f(` foo { bar !~ "^ddd(x+)$", a="ss", __name__="sffd"} `)
|
||||
f(`(foo)`)
|
||||
}
|
||||
|
||||
func TestParseMetricSelectorError(t *testing.T) {
|
||||
f := func(s string) {
|
||||
t.Helper()
|
||||
tfs, err := ParseMetricSelector(s)
|
||||
if err == nil {
|
||||
t.Fatalf("expecting non-nil error when parsing %q", s)
|
||||
}
|
||||
if tfs != nil {
|
||||
t.Fatalf("expecting nil tfs when parsing %q", s)
|
||||
}
|
||||
}
|
||||
f("")
|
||||
f(`{}`)
|
||||
f(`foo bar`)
|
||||
f(`foo+bar`)
|
||||
f(`sum(bar)`)
|
||||
f(`x{y}`)
|
||||
f(`x{y+z}`)
|
||||
f(`foo[5m]`)
|
||||
f(`foo offset 5m`)
|
||||
}
|
File diff suppressed because it is too large
Load Diff
@ -103,10 +103,6 @@ func getRollupFunc(funcName string) newRollupFunc {
|
||||
return rollupFuncs[funcName]
|
||||
}
|
||||
|
||||
func isRollupFunc(funcName string) bool {
|
||||
return getRollupFunc(funcName) != nil
|
||||
}
|
||||
|
||||
type rollupFuncArg struct {
|
||||
prevValue float64
|
||||
prevTimestamp int64
|
||||
|
@ -13,6 +13,8 @@ import (
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/encoding"
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/logger"
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/memory"
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/promql"
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/storage"
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/workingsetcache"
|
||||
"github.com/VictoriaMetrics/fastcache"
|
||||
"github.com/VictoriaMetrics/metrics"
|
||||
@ -133,7 +135,7 @@ func ResetRollupResultCache() {
|
||||
rollupResultCacheV.c.Reset()
|
||||
}
|
||||
|
||||
func (rrc *rollupResultCache) Get(funcName string, ec *EvalConfig, me *metricExpr, iafc *incrementalAggrFuncContext, window int64) (tss []*timeseries, newStart int64) {
|
||||
func (rrc *rollupResultCache) Get(funcName string, ec *EvalConfig, me *promql.MetricExpr, iafc *incrementalAggrFuncContext, window int64) (tss []*timeseries, newStart int64) {
|
||||
if *disableCache || !ec.mayCache() {
|
||||
return nil, ec.Start
|
||||
}
|
||||
@ -214,7 +216,7 @@ func (rrc *rollupResultCache) Get(funcName string, ec *EvalConfig, me *metricExp
|
||||
|
||||
var resultBufPool bytesutil.ByteBufferPool
|
||||
|
||||
func (rrc *rollupResultCache) Put(funcName string, ec *EvalConfig, me *metricExpr, iafc *incrementalAggrFuncContext, window int64, tss []*timeseries) {
|
||||
func (rrc *rollupResultCache) Put(funcName string, ec *EvalConfig, me *promql.MetricExpr, iafc *incrementalAggrFuncContext, window int64, tss []*timeseries) {
|
||||
if *disableCache || len(tss) == 0 || !ec.mayCache() {
|
||||
return
|
||||
}
|
||||
@ -295,7 +297,7 @@ var tooBigRollupResults = metrics.NewCounter("vm_too_big_rollup_results_total")
|
||||
// Increment this value every time the format of the cache changes.
|
||||
const rollupResultCacheVersion = 6
|
||||
|
||||
func marshalRollupResultCacheKey(dst []byte, funcName string, at *auth.Token, me *metricExpr, iafc *incrementalAggrFuncContext, window, step int64) []byte {
|
||||
func marshalRollupResultCacheKey(dst []byte, funcName string, at *auth.Token, me *promql.MetricExpr, iafc *incrementalAggrFuncContext, window, step int64) []byte {
|
||||
dst = append(dst, rollupResultCacheVersion)
|
||||
if iafc == nil {
|
||||
dst = append(dst, 0)
|
||||
@ -310,7 +312,8 @@ func marshalRollupResultCacheKey(dst []byte, funcName string, at *auth.Token, me
|
||||
dst = encoding.MarshalInt64(dst, window)
|
||||
dst = encoding.MarshalInt64(dst, step)
|
||||
for i := range me.TagFilters {
|
||||
dst = me.TagFilters[i].Marshal(dst)
|
||||
stf := storage.TagFilter(me.TagFilters[i])
|
||||
dst = stf.Marshal(dst)
|
||||
}
|
||||
return dst
|
||||
}
|
||||
|
@ -4,6 +4,7 @@ import (
|
||||
"testing"
|
||||
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/auth"
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/promql"
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/storage"
|
||||
)
|
||||
|
||||
@ -23,14 +24,14 @@ func TestRollupResultCache(t *testing.T) {
|
||||
|
||||
MayCache: true,
|
||||
}
|
||||
me := &metricExpr{
|
||||
TagFilters: []storage.TagFilter{{
|
||||
me := &promql.MetricExpr{
|
||||
TagFilters: []promql.TagFilter{{
|
||||
Key: []byte("aaa"),
|
||||
Value: []byte("xxx"),
|
||||
}},
|
||||
}
|
||||
iafc := &incrementalAggrFuncContext{
|
||||
ae: &aggrFuncExpr{
|
||||
ae: &promql.AggrFuncExpr{
|
||||
Name: "foobar",
|
||||
},
|
||||
}
|
||||
|
@ -1,6 +1,7 @@
|
||||
package promql
|
||||
|
||||
import (
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/promql"
|
||||
"math"
|
||||
"testing"
|
||||
)
|
||||
@ -157,7 +158,7 @@ func TestDerivValues(t *testing.T) {
|
||||
testRowsEqual(t, values, timestamps, valuesExpected, timestamps)
|
||||
}
|
||||
|
||||
func testRollupFunc(t *testing.T, funcName string, args []interface{}, meExpected *metricExpr, vExpected float64) {
|
||||
func testRollupFunc(t *testing.T, funcName string, args []interface{}, meExpected *promql.MetricExpr, vExpected float64) {
|
||||
t.Helper()
|
||||
nrf := getRollupFunc(funcName)
|
||||
if nrf == nil {
|
||||
@ -197,8 +198,8 @@ func TestRollupQuantileOverTime(t *testing.T) {
|
||||
Values: []float64{phi},
|
||||
Timestamps: []int64{123},
|
||||
}}
|
||||
var me metricExpr
|
||||
args := []interface{}{phis, &rollupExpr{Expr: &me}}
|
||||
var me promql.MetricExpr
|
||||
args := []interface{}{phis, &promql.RollupExpr{Expr: &me}}
|
||||
testRollupFunc(t, "quantile_over_time", args, &me, vExpected)
|
||||
}
|
||||
|
||||
@ -219,8 +220,8 @@ func TestRollupPredictLinear(t *testing.T) {
|
||||
Values: []float64{sec},
|
||||
Timestamps: []int64{123},
|
||||
}}
|
||||
var me metricExpr
|
||||
args := []interface{}{&rollupExpr{Expr: &me}, secs}
|
||||
var me promql.MetricExpr
|
||||
args := []interface{}{&promql.RollupExpr{Expr: &me}, secs}
|
||||
testRollupFunc(t, "predict_linear", args, &me, vExpected)
|
||||
}
|
||||
|
||||
@ -241,8 +242,8 @@ func TestRollupHoltWinters(t *testing.T) {
|
||||
Values: []float64{tf},
|
||||
Timestamps: []int64{123},
|
||||
}}
|
||||
var me metricExpr
|
||||
args := []interface{}{&rollupExpr{Expr: &me}, sfs, tfs}
|
||||
var me promql.MetricExpr
|
||||
args := []interface{}{&promql.RollupExpr{Expr: &me}, sfs, tfs}
|
||||
testRollupFunc(t, "holt_winters", args, &me, vExpected)
|
||||
}
|
||||
|
||||
@ -265,8 +266,8 @@ func TestRollupHoltWinters(t *testing.T) {
|
||||
func TestRollupNewRollupFuncSuccess(t *testing.T) {
|
||||
f := func(funcName string, vExpected float64) {
|
||||
t.Helper()
|
||||
var me metricExpr
|
||||
args := []interface{}{&rollupExpr{Expr: &me}}
|
||||
var me promql.MetricExpr
|
||||
args := []interface{}{&promql.RollupExpr{Expr: &me}}
|
||||
testRollupFunc(t, funcName, args, &me, vExpected)
|
||||
}
|
||||
|
||||
@ -327,7 +328,7 @@ func TestRollupNewRollupFuncError(t *testing.T) {
|
||||
Values: []float64{321},
|
||||
Timestamps: []int64{123},
|
||||
}}
|
||||
me := &metricExpr{}
|
||||
me := &promql.MetricExpr{}
|
||||
f("holt_winters", []interface{}{123, 123, 321})
|
||||
f("holt_winters", []interface{}{me, 123, 321})
|
||||
f("holt_winters", []interface{}{me, scalarTs, 321})
|
||||
|
54
app/vmselect/promql/simplify.go
Normal file
54
app/vmselect/promql/simplify.go
Normal file
@ -0,0 +1,54 @@
|
||||
package promql
|
||||
|
||||
import (
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/promql"
|
||||
)
|
||||
|
||||
func simplifyConstants(e promql.Expr) promql.Expr {
|
||||
if re, ok := e.(*promql.RollupExpr); ok {
|
||||
re.Expr = simplifyConstants(re.Expr)
|
||||
return re
|
||||
}
|
||||
if ae, ok := e.(*promql.AggrFuncExpr); ok {
|
||||
simplifyConstantsInplace(ae.Args)
|
||||
return ae
|
||||
}
|
||||
if fe, ok := e.(*promql.FuncExpr); ok {
|
||||
simplifyConstantsInplace(fe.Args)
|
||||
return fe
|
||||
}
|
||||
if pe, ok := e.(*promql.ParensExpr); ok {
|
||||
if len(*pe) == 1 {
|
||||
return simplifyConstants((*pe)[0])
|
||||
}
|
||||
simplifyConstantsInplace(*pe)
|
||||
return pe
|
||||
}
|
||||
be, ok := e.(*promql.BinaryOpExpr)
|
||||
if !ok {
|
||||
return e
|
||||
}
|
||||
|
||||
be.Left = simplifyConstants(be.Left)
|
||||
be.Right = simplifyConstants(be.Right)
|
||||
|
||||
lne, ok := be.Left.(*promql.NumberExpr)
|
||||
if !ok {
|
||||
return be
|
||||
}
|
||||
rne, ok := be.Right.(*promql.NumberExpr)
|
||||
if !ok {
|
||||
return be
|
||||
}
|
||||
n := binaryOpConstants(be.Op, lne.N, rne.N, be.Bool)
|
||||
ne := &promql.NumberExpr{
|
||||
N: n,
|
||||
}
|
||||
return ne
|
||||
}
|
||||
|
||||
func simplifyConstantsInplace(args []promql.Expr) {
|
||||
for i, arg := range args {
|
||||
args[i] = simplifyConstants(arg)
|
||||
}
|
||||
}
|
89
app/vmselect/promql/simplify_test.go
Normal file
89
app/vmselect/promql/simplify_test.go
Normal file
@ -0,0 +1,89 @@
|
||||
package promql
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestSimplify(t *testing.T) {
|
||||
another := func(s string, sExpected string) {
|
||||
t.Helper()
|
||||
|
||||
e, err := parsePromQL(s)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error when parsing %q: %s", s, err)
|
||||
}
|
||||
res := e.AppendString(nil)
|
||||
if string(res) != sExpected {
|
||||
t.Fatalf("unexpected string constructed;\ngot\n%q\nwant\n%q", res, sExpected)
|
||||
}
|
||||
}
|
||||
|
||||
// Simplification cases
|
||||
another(`nan == nan`, `NaN`)
|
||||
another(`nan ==bool nan`, `1`)
|
||||
another(`nan !=bool nan`, `0`)
|
||||
another(`nan !=bool 2`, `1`)
|
||||
another(`2 !=bool nan`, `1`)
|
||||
another(`nan >bool nan`, `0`)
|
||||
another(`nan <bool nan`, `0`)
|
||||
another(`1 ==bool nan`, `0`)
|
||||
another(`NaN !=bool 1`, `1`)
|
||||
another(`inf >=bool 2`, `1`)
|
||||
another(`-1 >bool -inf`, `1`)
|
||||
another(`-1 <bool -inf`, `0`)
|
||||
another(`nan + 2 *3 * inf`, `NaN`)
|
||||
another(`INF - Inf`, `NaN`)
|
||||
another(`Inf + inf`, `+Inf`)
|
||||
another(`1/0`, `+Inf`)
|
||||
another(`0/0`, `NaN`)
|
||||
another(`1 or 2`, `1`)
|
||||
another(`1 and 2`, `1`)
|
||||
another(`1 unless 2`, `NaN`)
|
||||
another(`1 default 2`, `1`)
|
||||
another(`1 default NaN`, `1`)
|
||||
another(`NaN default 2`, `2`)
|
||||
another(`1 > 2`, `NaN`)
|
||||
another(`1 > bool 2`, `0`)
|
||||
another(`3 >= 2`, `3`)
|
||||
another(`3 <= bool 2`, `0`)
|
||||
another(`1 + -2 - 3`, `-4`)
|
||||
another(`1 / 0 + 2`, `+Inf`)
|
||||
another(`2 + -1 / 0`, `-Inf`)
|
||||
another(`-1 ^ 0.5`, `NaN`)
|
||||
another(`512.5 - (1 + 3) * (2 ^ 2) ^ 3`, `256.5`)
|
||||
another(`1 == bool 1 != bool 24 < bool 4 > bool -1`, `1`)
|
||||
another(`1 == bOOl 1 != BOOL 24 < Bool 4 > booL -1`, `1`)
|
||||
another(`1+2 if 2>3`, `NaN`)
|
||||
another(`1+4 if 2<3`, `5`)
|
||||
another(`2+6 default 3 if 2>3`, `8`)
|
||||
another(`2+6 if 2>3 default NaN`, `NaN`)
|
||||
another(`42 if 3>2 if 2+2<5`, `42`)
|
||||
another(`42 if 3>2 if 2+2>=5`, `NaN`)
|
||||
another(`1+2 ifnot 2>3`, `3`)
|
||||
another(`1+4 ifnot 2<3`, `NaN`)
|
||||
another(`2+6 default 3 ifnot 2>3`, `8`)
|
||||
another(`2+6 ifnot 2>3 default NaN`, `8`)
|
||||
another(`42 if 3>2 ifnot 2+2<5`, `NaN`)
|
||||
another(`42 if 3>2 ifnot 2+2>=5`, `42`)
|
||||
another(`5 - 1 + 3 * 2 ^ 2 ^ 3 - 2 OR Metric {Bar= "Baz", aaa!="bb",cc=~"dd" ,zz !~"ff" } `,
|
||||
`770 or Metric{Bar="Baz", aaa!="bb", cc=~"dd", zz!~"ff"}`)
|
||||
another(`((foo(bar,baz)), (1+(2)+(3,4)+()))`, `(foo(bar, baz), (3 + (3, 4)) + ())`)
|
||||
another(` FOO (bar) + f ( m ( ),ff(1 + ( 2.5)) ,M[5m ] , "ff" )`, `FOO(bar) + f(m(), ff(3.5), M[5m], "ff")`)
|
||||
another(`sum without (a, b) (xx,2+2)`, `sum(xx, 4) without (a, b)`)
|
||||
another(`Sum WIthout (a, B) (XX,2+2)`, `sum(XX, 4) without (a, B)`)
|
||||
another(`with (foo = bar{x="x"}) 1+1`, `2`)
|
||||
another(`with (x(a, b) = a + b) x(foo, x(1, 2))`, `foo + 3`)
|
||||
another(`WITH (
|
||||
x2(x) = x^2,
|
||||
f(x, y) = x2(x) + x*y + x2(y)
|
||||
)
|
||||
f(a, 3)
|
||||
`, `((a ^ 2) + (a * 3)) + 9`)
|
||||
another(`WITH (
|
||||
x2(x) = x^2,
|
||||
f(x, y) = x2(x) + x*y + x2(y)
|
||||
)
|
||||
f(2, 3)
|
||||
`, `19`)
|
||||
another(`with(y=123,z=5) union(with(y=3,f(x)=x*y) f(2) + f(3), with(x=5,y=2) x*y*z)`, `union(15, 50)`)
|
||||
}
|
@ -12,6 +12,7 @@ import (
|
||||
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/bytesutil"
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/decimal"
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/promql"
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/storage"
|
||||
"github.com/valyala/histogram"
|
||||
)
|
||||
@ -99,13 +100,9 @@ func getTransformFunc(s string) transformFunc {
|
||||
return transformFuncs[s]
|
||||
}
|
||||
|
||||
func isTransformFunc(s string) bool {
|
||||
return getTransformFunc(s) != nil
|
||||
}
|
||||
|
||||
type transformFuncArg struct {
|
||||
ec *EvalConfig
|
||||
fe *funcExpr
|
||||
fe *promql.FuncExpr
|
||||
args [][]*timeseries
|
||||
}
|
||||
|
||||
@ -126,7 +123,7 @@ func newTransformFuncOneArg(tf func(v float64) float64) transformFunc {
|
||||
}
|
||||
}
|
||||
|
||||
func doTransformValues(arg []*timeseries, tf func(values []float64), fe *funcExpr) ([]*timeseries, error) {
|
||||
func doTransformValues(arg []*timeseries, tf func(values []float64), fe *promql.FuncExpr) ([]*timeseries, error) {
|
||||
name := strings.ToLower(fe.Name)
|
||||
keepMetricGroup := transformFuncsKeepMetricGroup[name]
|
||||
for _, ts := range arg {
|
||||
@ -154,7 +151,7 @@ func transformAbsent(tfa *transformFuncArg) ([]*timeseries, error) {
|
||||
// Copy tags from arg
|
||||
rvs := evalNumber(tfa.ec, 1)
|
||||
rv := rvs[0]
|
||||
me, ok := tfa.fe.Args[0].(*metricExpr)
|
||||
me, ok := tfa.fe.Args[0].(*promql.MetricExpr)
|
||||
if !ok {
|
||||
return rvs, nil
|
||||
}
|
||||
@ -1163,7 +1160,7 @@ func transformScalar(tfa *transformFuncArg) ([]*timeseries, error) {
|
||||
|
||||
// Verify whether the arg is a string.
|
||||
// Then try converting the string to number.
|
||||
if se, ok := tfa.fe.Args[0].(*stringExpr); ok {
|
||||
if se, ok := tfa.fe.Args[0].(*promql.StringExpr); ok {
|
||||
n, err := strconv.ParseFloat(se.S, 64)
|
||||
if err != nil {
|
||||
n = nan
|
||||
|
51
lib/promql/aggr.go
Normal file
51
lib/promql/aggr.go
Normal file
@ -0,0 +1,51 @@
|
||||
package promql
|
||||
|
||||
import (
|
||||
"strings"
|
||||
)
|
||||
|
||||
var aggrFuncs = map[string]bool{
|
||||
// See https://prometheus.io/docs/prometheus/latest/querying/operators/#aggregation-operators
|
||||
"sum": true,
|
||||
"min": true,
|
||||
"max": true,
|
||||
"avg": true,
|
||||
"stddev": true,
|
||||
"stdvar": true,
|
||||
"count": true,
|
||||
"count_values": true,
|
||||
"bottomk": true,
|
||||
"topk": true,
|
||||
"quantile": true,
|
||||
|
||||
// Extended PromQL funcs
|
||||
"median": true,
|
||||
"limitk": true,
|
||||
"distinct": true,
|
||||
"sum2": true,
|
||||
"geomean": true,
|
||||
"histogram": true,
|
||||
"topk_min": true,
|
||||
"topk_max": true,
|
||||
"topk_avg": true,
|
||||
"topk_median": true,
|
||||
"bottomk_min": true,
|
||||
"bottomk_max": true,
|
||||
"bottomk_avg": true,
|
||||
"bottomk_median": true,
|
||||
}
|
||||
|
||||
func isAggrFunc(s string) bool {
|
||||
s = strings.ToLower(s)
|
||||
return aggrFuncs[s]
|
||||
}
|
||||
|
||||
func isAggrFuncModifier(s string) bool {
|
||||
s = strings.ToLower(s)
|
||||
switch s {
|
||||
case "by", "without":
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
109
lib/promql/binary_op.go
Normal file
109
lib/promql/binary_op.go
Normal file
@ -0,0 +1,109 @@
|
||||
package promql
|
||||
|
||||
import (
|
||||
"strings"
|
||||
)
|
||||
|
||||
var binaryOpPriorities = map[string]int{
|
||||
"default": -1,
|
||||
|
||||
"if": 0,
|
||||
"ifnot": 0,
|
||||
|
||||
// See https://prometheus.io/docs/prometheus/latest/querying/operators/#binary-operator-precedence
|
||||
"or": 1,
|
||||
|
||||
"and": 2,
|
||||
"unless": 2,
|
||||
|
||||
"==": 3,
|
||||
"!=": 3,
|
||||
"<": 3,
|
||||
">": 3,
|
||||
"<=": 3,
|
||||
">=": 3,
|
||||
|
||||
"+": 4,
|
||||
"-": 4,
|
||||
|
||||
"*": 5,
|
||||
"/": 5,
|
||||
"%": 5,
|
||||
|
||||
"^": 6,
|
||||
}
|
||||
|
||||
func isBinaryOp(op string) bool {
|
||||
op = strings.ToLower(op)
|
||||
_, ok := binaryOpPriorities[op]
|
||||
return ok
|
||||
}
|
||||
|
||||
func binaryOpPriority(op string) int {
|
||||
op = strings.ToLower(op)
|
||||
return binaryOpPriorities[op]
|
||||
}
|
||||
|
||||
func scanBinaryOpPrefix(s string) int {
|
||||
n := 0
|
||||
for op := range binaryOpPriorities {
|
||||
if len(s) < len(op) {
|
||||
continue
|
||||
}
|
||||
ss := strings.ToLower(s[:len(op)])
|
||||
if ss == op && len(op) > n {
|
||||
n = len(op)
|
||||
}
|
||||
}
|
||||
return n
|
||||
}
|
||||
|
||||
func isRightAssociativeBinaryOp(op string) bool {
|
||||
// See https://prometheus.io/docs/prometheus/latest/querying/operators/#binary-operator-precedence
|
||||
return op == "^"
|
||||
}
|
||||
|
||||
func isBinaryOpGroupModifier(s string) bool {
|
||||
s = strings.ToLower(s)
|
||||
switch s {
|
||||
// See https://prometheus.io/docs/prometheus/latest/querying/operators/#vector-matching
|
||||
case "on", "ignoring":
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
func isBinaryOpJoinModifier(s string) bool {
|
||||
s = strings.ToLower(s)
|
||||
switch s {
|
||||
case "group_left", "group_right":
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
func isBinaryOpBoolModifier(s string) bool {
|
||||
s = strings.ToLower(s)
|
||||
return s == "bool"
|
||||
}
|
||||
|
||||
func isBinaryOpCmp(op string) bool {
|
||||
switch op {
|
||||
case "==", "!=", ">", "<", ">=", "<=":
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
func isBinaryOpLogicalSet(op string) bool {
|
||||
op = strings.ToLower(op)
|
||||
switch op {
|
||||
case "and", "or", "unless":
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
423
lib/promql/expr.go
Normal file
423
lib/promql/expr.go
Normal file
@ -0,0 +1,423 @@
|
||||
package promql
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
// An Expr represents a parsed Extended PromQL expression
|
||||
type Expr interface {
|
||||
// AppendString appends string representation of expr to dst.
|
||||
AppendString(dst []byte) []byte
|
||||
}
|
||||
|
||||
// A StringExpr represents a string
|
||||
type StringExpr struct {
|
||||
S string
|
||||
}
|
||||
|
||||
// AppendString appends string representation of expr to dst.
|
||||
func (se *StringExpr) AppendString(dst []byte) []byte {
|
||||
return strconv.AppendQuote(dst, se.S)
|
||||
}
|
||||
|
||||
// A StringTemplateExpr represents a string prior to applying a With clause
|
||||
type StringTemplateExpr struct {
|
||||
// Composite string has non-empty tokens.
|
||||
Tokens []StringToken
|
||||
}
|
||||
|
||||
// AppendString appends string representation of expr to dst.
|
||||
func (ste *StringTemplateExpr) AppendString(dst []byte) []byte {
|
||||
if ste == nil {
|
||||
return dst
|
||||
}
|
||||
for i, tok := range ste.Tokens {
|
||||
if i > 0 {
|
||||
dst = append(dst, " + "...)
|
||||
}
|
||||
dst = tok.AppendString(dst)
|
||||
}
|
||||
return dst
|
||||
}
|
||||
|
||||
// A StringToken represents a portion of a string expression
|
||||
type StringToken struct {
|
||||
Ident bool
|
||||
S string
|
||||
}
|
||||
|
||||
// AppendString appends string representation of st to dst.
|
||||
func (st *StringToken) AppendString(dst []byte) []byte {
|
||||
if st.Ident {
|
||||
return appendEscapedIdent(dst, []byte(st.S))
|
||||
}
|
||||
return strconv.AppendQuote(dst, st.S)
|
||||
}
|
||||
|
||||
// A NumberExpr represents a number
|
||||
type NumberExpr struct {
|
||||
N float64
|
||||
}
|
||||
|
||||
// AppendString appends string representation of expr to dst.
|
||||
func (ne *NumberExpr) AppendString(dst []byte) []byte {
|
||||
return strconv.AppendFloat(dst, ne.N, 'g', -1, 64)
|
||||
}
|
||||
|
||||
// A ParensExpr represents a parens expression
|
||||
type ParensExpr []Expr
|
||||
|
||||
// AppendString appends string representation of expr to dst.
|
||||
func (pe ParensExpr) AppendString(dst []byte) []byte {
|
||||
return appendStringArgListExpr(dst, pe)
|
||||
}
|
||||
|
||||
// A BinaryOpExpr represents a binary operator
|
||||
type BinaryOpExpr struct {
|
||||
Op string
|
||||
|
||||
Bool bool
|
||||
GroupModifier ModifierExpr
|
||||
JoinModifier ModifierExpr
|
||||
|
||||
Left Expr
|
||||
Right Expr
|
||||
}
|
||||
|
||||
// AppendString appends string representation of expr to dst.
|
||||
func (be *BinaryOpExpr) AppendString(dst []byte) []byte {
|
||||
if _, ok := be.Left.(*BinaryOpExpr); ok {
|
||||
dst = append(dst, '(')
|
||||
dst = be.Left.AppendString(dst)
|
||||
dst = append(dst, ')')
|
||||
} else {
|
||||
dst = be.Left.AppendString(dst)
|
||||
}
|
||||
dst = append(dst, ' ')
|
||||
dst = append(dst, be.Op...)
|
||||
if be.Bool {
|
||||
dst = append(dst, " bool"...)
|
||||
}
|
||||
if be.GroupModifier.Op != "" {
|
||||
dst = append(dst, ' ')
|
||||
dst = be.GroupModifier.AppendString(dst)
|
||||
}
|
||||
if be.JoinModifier.Op != "" {
|
||||
dst = append(dst, ' ')
|
||||
dst = be.JoinModifier.AppendString(dst)
|
||||
}
|
||||
dst = append(dst, ' ')
|
||||
if _, ok := be.Right.(*BinaryOpExpr); ok {
|
||||
dst = append(dst, '(')
|
||||
dst = be.Right.AppendString(dst)
|
||||
dst = append(dst, ')')
|
||||
} else {
|
||||
dst = be.Right.AppendString(dst)
|
||||
}
|
||||
return dst
|
||||
}
|
||||
|
||||
// A ModifierExpr represents a modifier attached to a parent expression
|
||||
type ModifierExpr struct {
|
||||
Op string
|
||||
|
||||
Args []string
|
||||
}
|
||||
|
||||
// AppendString appends string representation of expr to dst.
|
||||
func (me *ModifierExpr) AppendString(dst []byte) []byte {
|
||||
dst = append(dst, me.Op...)
|
||||
dst = append(dst, " ("...)
|
||||
for i, arg := range me.Args {
|
||||
dst = append(dst, arg...)
|
||||
if i+1 < len(me.Args) {
|
||||
dst = append(dst, ", "...)
|
||||
}
|
||||
}
|
||||
dst = append(dst, ')')
|
||||
return dst
|
||||
}
|
||||
|
||||
func appendStringArgListExpr(dst []byte, args []Expr) []byte {
|
||||
dst = append(dst, '(')
|
||||
for i, arg := range args {
|
||||
dst = arg.AppendString(dst)
|
||||
if i+1 < len(args) {
|
||||
dst = append(dst, ", "...)
|
||||
}
|
||||
}
|
||||
dst = append(dst, ')')
|
||||
return dst
|
||||
}
|
||||
|
||||
// A FuncExpr represents a function invocation
|
||||
type FuncExpr struct {
|
||||
Name string
|
||||
|
||||
Args []Expr
|
||||
}
|
||||
|
||||
// AppendString appends string representation of expr to dst.
|
||||
func (fe *FuncExpr) AppendString(dst []byte) []byte {
|
||||
dst = append(dst, fe.Name...)
|
||||
dst = appendStringArgListExpr(dst, fe.Args)
|
||||
return dst
|
||||
}
|
||||
|
||||
// An AggrFuncExpr represents the invocation of an aggregate function
|
||||
type AggrFuncExpr struct {
|
||||
Name string
|
||||
|
||||
Args []Expr
|
||||
|
||||
Modifier ModifierExpr
|
||||
}
|
||||
|
||||
// AppendString appends string representation of expr to dst.
|
||||
func (ae *AggrFuncExpr) AppendString(dst []byte) []byte {
|
||||
dst = append(dst, ae.Name...)
|
||||
dst = appendStringArgListExpr(dst, ae.Args)
|
||||
if ae.Modifier.Op != "" {
|
||||
dst = append(dst, ' ')
|
||||
dst = ae.Modifier.AppendString(dst)
|
||||
}
|
||||
return dst
|
||||
}
|
||||
|
||||
// A WithExpr represents a With expression
|
||||
type WithExpr struct {
|
||||
Was []*WithArgExpr
|
||||
Expr Expr
|
||||
}
|
||||
|
||||
// AppendString appends string representation of expr to dst.
|
||||
func (we *WithExpr) AppendString(dst []byte) []byte {
|
||||
dst = append(dst, "WITH ("...)
|
||||
for i, wa := range we.Was {
|
||||
dst = wa.AppendString(dst)
|
||||
if i+1 < len(we.Was) {
|
||||
dst = append(dst, ',')
|
||||
}
|
||||
}
|
||||
dst = append(dst, ") "...)
|
||||
dst = we.Expr.AppendString(dst)
|
||||
return dst
|
||||
}
|
||||
|
||||
// A WithArgExpr represents an arg in a With expression
|
||||
type WithArgExpr struct {
|
||||
Name string
|
||||
Args []string
|
||||
Expr Expr
|
||||
}
|
||||
|
||||
// AppendString appends string representation of expr to dst.
|
||||
func (wa *WithArgExpr) AppendString(dst []byte) []byte {
|
||||
dst = append(dst, wa.Name...)
|
||||
if len(wa.Args) > 0 {
|
||||
dst = append(dst, '(')
|
||||
for i, arg := range wa.Args {
|
||||
dst = append(dst, arg...)
|
||||
if i+1 < len(wa.Args) {
|
||||
dst = append(dst, ',')
|
||||
}
|
||||
}
|
||||
dst = append(dst, ')')
|
||||
}
|
||||
dst = append(dst, " = "...)
|
||||
dst = wa.Expr.AppendString(dst)
|
||||
return dst
|
||||
}
|
||||
|
||||
// A RollupExpr represents a rollup expression
|
||||
type RollupExpr struct {
|
||||
// The expression for the rollup. Usually it is metricExpr, but may be arbitrary expr
|
||||
// if subquery is used. https://prometheus.io/blog/2019/01/28/subquery-support/
|
||||
Expr Expr
|
||||
|
||||
// Window contains optional window value from square brackets
|
||||
//
|
||||
// For example, `http_requests_total[5m]` will have Window value `5m`.
|
||||
Window string
|
||||
|
||||
// Offset contains optional value from `offset` part.
|
||||
//
|
||||
// For example, `foobar{baz="aa"} offset 5m` will have Offset value `5m`.
|
||||
Offset string
|
||||
|
||||
// Step contains optional step value from square brackets.
|
||||
//
|
||||
// For example, `foobar[1h:3m]` will have Step value '3m'.
|
||||
Step string
|
||||
|
||||
// If set to true, then `foo[1h:]` would print the same
|
||||
// instead of `foo[1h]`.
|
||||
InheritStep bool
|
||||
}
|
||||
|
||||
// ForSubquery returns whether is rollup is for a subquery
|
||||
func (re *RollupExpr) ForSubquery() bool {
|
||||
return len(re.Step) > 0 || re.InheritStep
|
||||
}
|
||||
|
||||
// AppendString appends string representation of expr to dst.
|
||||
func (re *RollupExpr) AppendString(dst []byte) []byte {
|
||||
needParens := func() bool {
|
||||
if _, ok := re.Expr.(*RollupExpr); ok {
|
||||
return true
|
||||
}
|
||||
if _, ok := re.Expr.(*BinaryOpExpr); ok {
|
||||
return true
|
||||
}
|
||||
if ae, ok := re.Expr.(*AggrFuncExpr); ok && ae.Modifier.Op != "" {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}()
|
||||
if needParens {
|
||||
dst = append(dst, '(')
|
||||
}
|
||||
dst = re.Expr.AppendString(dst)
|
||||
if needParens {
|
||||
dst = append(dst, ')')
|
||||
}
|
||||
if len(re.Window) > 0 || re.InheritStep || len(re.Step) > 0 {
|
||||
dst = append(dst, '[')
|
||||
if len(re.Window) > 0 {
|
||||
dst = append(dst, re.Window...)
|
||||
}
|
||||
if len(re.Step) > 0 {
|
||||
dst = append(dst, ':')
|
||||
dst = append(dst, re.Step...)
|
||||
} else if re.InheritStep {
|
||||
dst = append(dst, ':')
|
||||
}
|
||||
dst = append(dst, ']')
|
||||
}
|
||||
if len(re.Offset) > 0 {
|
||||
dst = append(dst, " offset "...)
|
||||
dst = append(dst, re.Offset...)
|
||||
}
|
||||
return dst
|
||||
}
|
||||
|
||||
// A MetricExpr represents a metric expression
|
||||
type MetricExpr struct {
|
||||
// TagFilters contains a list of tag filters from curly braces.
|
||||
// The first item may be the metric name.
|
||||
TagFilters []TagFilter
|
||||
}
|
||||
|
||||
// AppendString appends string representation of expr to dst.
|
||||
func (me *MetricExpr) AppendString(dst []byte) []byte {
|
||||
tfs := me.TagFilters
|
||||
if len(tfs) > 0 {
|
||||
tf := &tfs[0]
|
||||
if len(tf.Key) == 0 && !tf.IsNegative && !tf.IsRegexp {
|
||||
dst = appendEscapedIdent(dst, tf.Value)
|
||||
tfs = tfs[1:]
|
||||
}
|
||||
}
|
||||
if len(tfs) > 0 {
|
||||
dst = append(dst, '{')
|
||||
for i := range tfs {
|
||||
tf := &tfs[i]
|
||||
dst = appendStringTagFilter(dst, tf)
|
||||
if i+1 < len(tfs) {
|
||||
dst = append(dst, ", "...)
|
||||
}
|
||||
}
|
||||
dst = append(dst, '}')
|
||||
} else if len(me.TagFilters) == 0 {
|
||||
dst = append(dst, "{}"...)
|
||||
}
|
||||
return dst
|
||||
}
|
||||
|
||||
// IsEmpty returns whether this is an empty metric expression
|
||||
func (me *MetricExpr) IsEmpty() bool {
|
||||
return len(me.TagFilters) == 0
|
||||
}
|
||||
|
||||
// IsOnlyMetricGroup returns whether this is a metric group only
|
||||
func (me *MetricExpr) IsOnlyMetricGroup() bool {
|
||||
if !me.HasNonEmptyMetricGroup() {
|
||||
return false
|
||||
}
|
||||
return len(me.TagFilters) == 1
|
||||
}
|
||||
|
||||
// HasNonEmptyMetricGroup returns whether this has a non-empty metric group
|
||||
func (me *MetricExpr) HasNonEmptyMetricGroup() bool {
|
||||
if len(me.TagFilters) == 0 {
|
||||
return false
|
||||
}
|
||||
tf := &me.TagFilters[0]
|
||||
return len(tf.Key) == 0 && !tf.IsNegative && !tf.IsRegexp
|
||||
}
|
||||
|
||||
// A TagFilter is a single key <op> value filter tag in a metric filter
|
||||
//
|
||||
// Note that this should exactly match the definition in the stroage package
|
||||
type TagFilter struct {
|
||||
Key []byte
|
||||
Value []byte
|
||||
IsNegative bool
|
||||
IsRegexp bool
|
||||
}
|
||||
|
||||
// A MetricTemplateExpr represents a metric expression prior to expansion via
|
||||
// a with clause
|
||||
type MetricTemplateExpr struct {
|
||||
TagFilters []*TagFilterExpr
|
||||
}
|
||||
|
||||
// AppendString appends string representation of expr to dst.
|
||||
func (mte *MetricTemplateExpr) AppendString(dst []byte) []byte {
|
||||
tfs := mte.TagFilters
|
||||
if len(tfs) > 0 {
|
||||
tf := tfs[0]
|
||||
if len(tf.Key) == 0 && !tf.IsNegative && !tf.IsRegexp && len(tf.Value.Tokens) == 1 && !tf.Value.Tokens[0].Ident {
|
||||
dst = appendEscapedIdent(dst, []byte(tf.Value.Tokens[0].S))
|
||||
tfs = tfs[1:]
|
||||
}
|
||||
}
|
||||
if len(tfs) > 0 {
|
||||
dst = append(dst, '{')
|
||||
for i := range tfs {
|
||||
tf := tfs[i]
|
||||
dst = tf.AppendString(dst)
|
||||
if i+1 < len(tfs) {
|
||||
dst = append(dst, ", "...)
|
||||
}
|
||||
}
|
||||
dst = append(dst, '}')
|
||||
} else if len(mte.TagFilters) == 0 {
|
||||
dst = append(dst, "{}"...)
|
||||
}
|
||||
return dst
|
||||
}
|
||||
|
||||
// A TagFilterExpr represents a tag filter
|
||||
type TagFilterExpr struct {
|
||||
Key string
|
||||
Value *StringTemplateExpr
|
||||
IsRegexp bool
|
||||
IsNegative bool
|
||||
}
|
||||
|
||||
func (tfe *TagFilterExpr) String() string {
|
||||
return fmt.Sprintf("[key=%q, value=%+v, isRegexp=%v, isNegative=%v]", tfe.Key, tfe.Value, tfe.IsRegexp, tfe.IsNegative)
|
||||
}
|
||||
|
||||
// AppendString appends string representation of expr to dst.
|
||||
func (tfe *TagFilterExpr) AppendString(dst []byte) []byte {
|
||||
if len(tfe.Key) == 0 {
|
||||
dst = append(dst, "__name__"...)
|
||||
} else {
|
||||
dst = append(dst, tfe.Key...)
|
||||
}
|
||||
dst = appendStringTagFilterOp(dst, tfe.IsRegexp, tfe.IsNegative)
|
||||
return tfe.Value.AppendString(dst)
|
||||
}
|
@ -4,8 +4,6 @@ import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/logger"
|
||||
)
|
||||
|
||||
type lexer struct {
|
||||
@ -222,7 +220,7 @@ func scanIdent(s string) string {
|
||||
}
|
||||
}
|
||||
if i == 0 {
|
||||
logger.Panicf("BUG: scanIdent couldn't find a single ident char; make sure isIdentPrefix called before scanIdent")
|
||||
Panicf("BUG: scanIdent couldn't find a single ident char; make sure isIdentPrefix called before scanIdent")
|
||||
}
|
||||
return s[:i]
|
||||
}
|
1298
lib/promql/parser.go
Normal file
1298
lib/promql/parser.go
Normal file
File diff suppressed because it is too large
Load Diff
@ -1,58 +1,24 @@
|
||||
package promql
|
||||
|
||||
import (
|
||||
"regexp"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestParseMetricSelectorSuccess(t *testing.T) {
|
||||
f := func(s string) {
|
||||
t.Helper()
|
||||
tfs, err := ParseMetricSelector(s)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error when parsing %q: %s", s, err)
|
||||
}
|
||||
if tfs == nil {
|
||||
t.Fatalf("expecting non-nil tfs when parsing %q", s)
|
||||
}
|
||||
}
|
||||
f("foo")
|
||||
f(":foo")
|
||||
f(" :fo:bar.baz")
|
||||
f(`a{}`)
|
||||
f(`{foo="bar"}`)
|
||||
f(`{:f:oo=~"bar.+"}`)
|
||||
f(`foo {bar != "baz"}`)
|
||||
f(` foo { bar !~ "^ddd(x+)$", a="ss", __name__="sffd"} `)
|
||||
f(`(foo)`)
|
||||
var testParser = &Parser{
|
||||
compileRegexpAnchored: compileRegexpAnchored,
|
||||
}
|
||||
|
||||
func TestParseMetricSelectorError(t *testing.T) {
|
||||
f := func(s string) {
|
||||
t.Helper()
|
||||
tfs, err := ParseMetricSelector(s)
|
||||
if err == nil {
|
||||
t.Fatalf("expecting non-nil error when parsing %q", s)
|
||||
}
|
||||
if tfs != nil {
|
||||
t.Fatalf("expecting nil tfs when parsing %q", s)
|
||||
}
|
||||
}
|
||||
f("")
|
||||
f(`{}`)
|
||||
f(`foo bar`)
|
||||
f(`foo+bar`)
|
||||
f(`sum(bar)`)
|
||||
f(`x{y}`)
|
||||
f(`x{y+z}`)
|
||||
f(`foo[5m]`)
|
||||
f(`foo offset 5m`)
|
||||
func compileRegexpAnchored(re string) (*regexp.Regexp, error) {
|
||||
reAnchored := "^(?:" + re + ")$"
|
||||
return regexp.Compile(reAnchored)
|
||||
}
|
||||
|
||||
func TestParsePromQLSuccess(t *testing.T) {
|
||||
another := func(s string, sExpected string) {
|
||||
t.Helper()
|
||||
|
||||
e, err := parsePromQL(s)
|
||||
e, err := testParser.ParsePromQL(s)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error when parsing %q: %s", s, err)
|
||||
}
|
||||
@ -184,72 +150,24 @@ func TestParsePromQLSuccess(t *testing.T) {
|
||||
another(`-inF`, `-Inf`)
|
||||
|
||||
// binaryOpExpr
|
||||
another(`nan == nan`, `NaN`)
|
||||
another(`nan ==bool nan`, `1`)
|
||||
another(`nan !=bool nan`, `0`)
|
||||
another(`nan !=bool 2`, `1`)
|
||||
another(`2 !=bool nan`, `1`)
|
||||
another(`nan >bool nan`, `0`)
|
||||
another(`nan <bool nan`, `0`)
|
||||
another(`1 ==bool nan`, `0`)
|
||||
another(`NaN !=bool 1`, `1`)
|
||||
another(`inf >=bool 2`, `1`)
|
||||
another(`-1 >bool -inf`, `1`)
|
||||
another(`-1 <bool -inf`, `0`)
|
||||
another(`nan + 2 *3 * inf`, `NaN`)
|
||||
another(`INF - Inf`, `NaN`)
|
||||
another(`Inf + inf`, `+Inf`)
|
||||
another(`1/0`, `+Inf`)
|
||||
another(`0/0`, `NaN`)
|
||||
another(`-m`, `0 - m`)
|
||||
same(`m + ignoring () n[5m]`)
|
||||
another(`M + IGNORING () N[5m]`, `M + ignoring () N[5m]`)
|
||||
same(`m + on (foo) n[5m]`)
|
||||
another(`m + ON (Foo) n[5m]`, `m + on (Foo) n[5m]`)
|
||||
same(`m + ignoring (a, b) n[5m]`)
|
||||
another(`1 or 2`, `1`)
|
||||
another(`1 and 2`, `1`)
|
||||
another(`1 unless 2`, `NaN`)
|
||||
another(`1 default 2`, `1`)
|
||||
another(`1 default NaN`, `1`)
|
||||
another(`NaN default 2`, `2`)
|
||||
another(`1 > 2`, `NaN`)
|
||||
another(`1 > bool 2`, `0`)
|
||||
another(`3 >= 2`, `3`)
|
||||
another(`3 <= bool 2`, `0`)
|
||||
another(`1 + -2 - 3`, `-4`)
|
||||
another(`1 / 0 + 2`, `+Inf`)
|
||||
another(`2 + -1 / 0`, `-Inf`)
|
||||
another(`-1 ^ 0.5`, `NaN`)
|
||||
another(`512.5 - (1 + 3) * (2 ^ 2) ^ 3`, `256.5`)
|
||||
another(`1 == bool 1 != bool 24 < bool 4 > bool -1`, `1`)
|
||||
another(`1 == bOOl 1 != BOOL 24 < Bool 4 > booL -1`, `1`)
|
||||
another(`m1+on(foo)group_left m2`, `m1 + on (foo) group_left () m2`)
|
||||
another(`M1+ON(FOO)GROUP_left M2`, `M1 + on (FOO) group_left () M2`)
|
||||
same(`m1 + on (foo) group_right () m2`)
|
||||
same(`m1 + on (foo, bar) group_right (x, y) m2`)
|
||||
another(`m1 + on (foo, bar,) group_right (x, y,) m2`, `m1 + on (foo, bar) group_right (x, y) m2`)
|
||||
same(`m1 == bool on (foo, bar) group_right (x, y) m2`)
|
||||
another(`5 - 1 + 3 * 2 ^ 2 ^ 3 - 2 OR Metric {Bar= "Baz", aaa!="bb",cc=~"dd" ,zz !~"ff" } `,
|
||||
`770 or Metric{Bar="Baz", aaa!="bb", cc=~"dd", zz!~"ff"}`)
|
||||
same(`"foo" + bar()`)
|
||||
same(`"foo" + bar{x="y"}`)
|
||||
same(`("foo"[3s] + bar{x="y"})[5m:3s] offset 10s`)
|
||||
same(`("foo"[3s] + bar{x="y"})[5i:3i] offset 10i`)
|
||||
same(`bar + "foo" offset 3s`)
|
||||
same(`bar + "foo" offset 3i`)
|
||||
another(`1+2 if 2>3`, `NaN`)
|
||||
another(`1+4 if 2<3`, `5`)
|
||||
another(`2+6 default 3 if 2>3`, `8`)
|
||||
another(`2+6 if 2>3 default NaN`, `NaN`)
|
||||
another(`42 if 3>2 if 2+2<5`, `42`)
|
||||
another(`42 if 3>2 if 2+2>=5`, `NaN`)
|
||||
another(`1+2 ifnot 2>3`, `3`)
|
||||
another(`1+4 ifnot 2<3`, `NaN`)
|
||||
another(`2+6 default 3 ifnot 2>3`, `8`)
|
||||
another(`2+6 ifnot 2>3 default NaN`, `8`)
|
||||
another(`42 if 3>2 ifnot 2+2<5`, `NaN`)
|
||||
another(`42 if 3>2 ifnot 2+2>=5`, `42`)
|
||||
|
||||
// parensExpr
|
||||
another(`(-foo + ((bar) / (baz))) + ((23))`, `((0 - foo) + (bar / baz)) + 23`)
|
||||
@ -258,7 +176,6 @@ func TestParsePromQLSuccess(t *testing.T) {
|
||||
another(`((foo, bar),(baz))`, `((foo, bar), baz)`)
|
||||
same(`(foo, (bar, baz), ((x, y), (z, y), xx))`)
|
||||
another(`1+(foo, bar,)`, `1 + (foo, bar)`)
|
||||
another(`((foo(bar,baz)), (1+(2)+(3,4)+()))`, `(foo(bar, baz), (3 + (3, 4)) + ())`)
|
||||
same(`()`)
|
||||
|
||||
// funcExpr
|
||||
@ -275,7 +192,7 @@ func TestParsePromQLSuccess(t *testing.T) {
|
||||
same(`F(HttpServerRequest)`)
|
||||
same(`f(job, foo)`)
|
||||
same(`F(Job, Foo)`)
|
||||
another(` FOO (bar) + f ( m ( ),ff(1 + ( 2.5)) ,M[5m ] , "ff" )`, `FOO(bar) + f(m(), ff(3.5), M[5m], "ff")`)
|
||||
|
||||
// funcName matching keywords
|
||||
same(`by(2)`)
|
||||
same(`BY(2)`)
|
||||
@ -298,8 +215,6 @@ func TestParsePromQLSuccess(t *testing.T) {
|
||||
another(`sum by () (xx)`, `sum(xx) by ()`)
|
||||
another(`sum by (s) (xx)[5s]`, `(sum(xx) by (s))[5s]`)
|
||||
another(`SUM BY (ZZ, aa) (XX)`, `sum(XX) by (ZZ, aa)`)
|
||||
another(`sum without (a, b) (xx,2+2)`, `sum(xx, 4) without (a, b)`)
|
||||
another(`Sum WIthout (a, B) (XX,2+2)`, `sum(XX, 4) without (a, B)`)
|
||||
same(`sum(a) or sum(b)`)
|
||||
same(`sum(a) by () or sum(b) without (x, y)`)
|
||||
same(`sum(a) + sum(b)`)
|
||||
@ -322,7 +237,7 @@ func TestParsePromQLSuccess(t *testing.T) {
|
||||
another(`with (foo = bar{x="x"}) "x"`, `"x"`)
|
||||
another(`with (f="x") f`, `"x"`)
|
||||
another(`with (foo = bar{x="x"}) x{x="y"}`, `x{x="y"}`)
|
||||
another(`with (foo = bar{x="x"}) 1+1`, `2`)
|
||||
another(`with (foo = bar{x="x"}) 2`, `2`)
|
||||
another(`with (foo = bar{x="x"}) f()`, `f()`)
|
||||
another(`with (foo = bar{x="x"}) sum(x)`, `sum(x)`)
|
||||
another(`with (foo = bar{x="x"}) baz{foo="bar"}`, `baz{foo="bar"}`)
|
||||
@ -355,7 +270,6 @@ func TestParsePromQLSuccess(t *testing.T) {
|
||||
another(`with (x() = y+1) x`, `y + 1`)
|
||||
another(`with (x(foo) = foo+1) x(a)`, `a + 1`)
|
||||
another(`with (x(a, b) = a + b) x(foo, bar)`, `foo + bar`)
|
||||
another(`with (x(a, b) = a + b) x(foo, x(1, 2))`, `foo + 3`)
|
||||
another(`with (x(a) = sum(a) by (b)) x(xx) / x(y)`, `sum(xx) by (b) / sum(y) by (b)`)
|
||||
another(`with (f(a,f,x)=ff(x,f,a)) f(f(x,y,z),1,2)`, `ff(2, 1, ff(z, y, x))`)
|
||||
another(`with (f(x)=1+f(x)) f(foo{bar="baz"})`, `1 + f(foo{bar="baz"})`)
|
||||
@ -414,18 +328,6 @@ func TestParsePromQLSuccess(t *testing.T) {
|
||||
)
|
||||
hitRatio < treshold`,
|
||||
`(sum(rate(cache{type="hit", job="cacher", instance=~"1.2.3.4"}[5m])) by (instance) / sum(rate(cache{type="hit", job="cacher", instance=~"1.2.3.4"}[5m]) + rate(cache{type="miss", job="cacher", instance=~"1.2.3.4"}[5m])) by (instance)) < 0.9`)
|
||||
another(`WITH (
|
||||
x2(x) = x^2,
|
||||
f(x, y) = x2(x) + x*y + x2(y)
|
||||
)
|
||||
f(a, 3)
|
||||
`, `((a ^ 2) + (a * 3)) + 9`)
|
||||
another(`WITH (
|
||||
x2(x) = x^2,
|
||||
f(x, y) = x2(x) + x*y + x2(y)
|
||||
)
|
||||
f(2, 3)
|
||||
`, `19`)
|
||||
another(`WITH (
|
||||
commonFilters = {instance="foo"},
|
||||
timeToFuckup(currv, maxv) = (maxv - currv) / rate(currv)
|
||||
@ -439,16 +341,15 @@ func TestParsePromQLSuccess(t *testing.T) {
|
||||
)
|
||||
hitRate(cacheHits, cacheMisses)`,
|
||||
`sum(rate(cacheHits{job="foo", instance="bar"})) by (job, instance) / (sum(rate(cacheHits{job="foo", instance="bar"})) by (job, instance) + sum(rate(cacheMisses{job="foo", instance="bar"})) by (job, instance))`)
|
||||
another(`with(y=123,z=5) union(with(y=3,f(x)=x*y) f(2) + f(3), with(x=5,y=2) x*y*z)`, `union(15, 50)`)
|
||||
}
|
||||
|
||||
func TestParsePromQLError(t *testing.T) {
|
||||
f := func(s string) {
|
||||
t.Helper()
|
||||
|
||||
e, err := parsePromQL(s)
|
||||
e, err := testParser.ParsePromQL(s)
|
||||
if err == nil {
|
||||
t.Fatalf("expecting non-nil error when parsing %q", s)
|
||||
t.Fatalf("expecting non-nil error when parsing %q (expr=%v)", s, e)
|
||||
}
|
||||
if e != nil {
|
||||
t.Fatalf("expecting nil expr when parsing %q", s)
|
56
lib/promql/rollup.go
Normal file
56
lib/promql/rollup.go
Normal file
@ -0,0 +1,56 @@
|
||||
package promql
|
||||
|
||||
import (
|
||||
"strings"
|
||||
)
|
||||
|
||||
var rollupFuncs = map[string]bool{
|
||||
"default_rollup": true, // default rollup func
|
||||
|
||||
// Standard rollup funcs from PromQL.
|
||||
// See funcs accepting range-vector on https://prometheus.io/docs/prometheus/latest/querying/functions/ .
|
||||
"changes": true,
|
||||
"delta": true,
|
||||
"deriv": true,
|
||||
"deriv_fast": true,
|
||||
"holt_winters": true,
|
||||
"idelta": true,
|
||||
"increase": true,
|
||||
"irate": true,
|
||||
"predict_linear": true,
|
||||
"rate": true,
|
||||
"resets": true,
|
||||
"avg_over_time": true,
|
||||
"min_over_time": true,
|
||||
"max_over_time": true,
|
||||
"sum_over_time": true,
|
||||
"count_over_time": true,
|
||||
"quantile_over_time": true,
|
||||
"stddev_over_time": true,
|
||||
"stdvar_over_time": true,
|
||||
|
||||
// Additional rollup funcs.
|
||||
"sum2_over_time": true,
|
||||
"geomean_over_time": true,
|
||||
"first_over_time": true,
|
||||
"last_over_time": true,
|
||||
"distinct_over_time": true,
|
||||
"increases_over_time": true,
|
||||
"decreases_over_time": true,
|
||||
"integrate": true,
|
||||
"ideriv": true,
|
||||
"lifetime": true,
|
||||
"lag": true,
|
||||
"scrape_interval": true,
|
||||
"rollup": true,
|
||||
"rollup_rate": true,
|
||||
"rollup_deriv": true,
|
||||
"rollup_delta": true,
|
||||
"rollup_increase": true,
|
||||
"rollup_candlestick": true,
|
||||
}
|
||||
|
||||
func isRollupFunc(funcName string) bool {
|
||||
funcName = strings.ToLower(funcName)
|
||||
return rollupFuncs[funcName]
|
||||
}
|
80
lib/promql/transform.go
Normal file
80
lib/promql/transform.go
Normal file
@ -0,0 +1,80 @@
|
||||
package promql
|
||||
|
||||
import (
|
||||
"strings"
|
||||
)
|
||||
|
||||
var transformFuncs = map[string]bool{
|
||||
// Standard promql funcs
|
||||
// See funcs accepting instant-vector on https://prometheus.io/docs/prometheus/latest/querying/functions/ .
|
||||
"abs": true,
|
||||
"absent": true,
|
||||
"ceil": true,
|
||||
"clamp_max": true,
|
||||
"clamp_min": true,
|
||||
"day_of_month": true,
|
||||
"day_of_week": true,
|
||||
"days_in_month": true,
|
||||
"exp": true,
|
||||
"floor": true,
|
||||
"histogram_quantile": true,
|
||||
"hour": true,
|
||||
"label_join": true,
|
||||
"label_replace": true,
|
||||
"ln": true,
|
||||
"log2": true,
|
||||
"log10": true,
|
||||
"minute": true,
|
||||
"month": true,
|
||||
"round": true,
|
||||
"scalar": true,
|
||||
"sort": true,
|
||||
"sort_desc": true,
|
||||
"sqrt": true,
|
||||
"time": true,
|
||||
"timestamp": true,
|
||||
"vector": true,
|
||||
"year": true,
|
||||
|
||||
// New funcs
|
||||
"label_set": true,
|
||||
"label_del": true,
|
||||
"label_keep": true,
|
||||
"label_copy": true,
|
||||
"label_move": true,
|
||||
"label_transform": true,
|
||||
"label_value": true,
|
||||
"union": true,
|
||||
"": true,
|
||||
"keep_last_value": true,
|
||||
"start": true,
|
||||
"end": true,
|
||||
"step": true,
|
||||
"running_sum": true,
|
||||
"running_max": true,
|
||||
"running_min": true,
|
||||
"running_avg": true,
|
||||
"range_sum": true,
|
||||
"range_max": true,
|
||||
"range_min": true,
|
||||
"range_avg": true,
|
||||
"range_first": true,
|
||||
"range_last": true,
|
||||
"range_quantile": true,
|
||||
"smooth_exponential": true,
|
||||
"remove_resets": true,
|
||||
"rand": true,
|
||||
"rand_normal": true,
|
||||
"rand_exponential": true,
|
||||
"pi": true,
|
||||
"sin": true,
|
||||
"cos": true,
|
||||
"asin": true,
|
||||
"acos": true,
|
||||
"prometheus_buckets": true,
|
||||
}
|
||||
|
||||
func isTransformFunc(s string) bool {
|
||||
s = strings.ToLower(s)
|
||||
return transformFuncs[s]
|
||||
}
|
47
lib/promql/visitor.go
Normal file
47
lib/promql/visitor.go
Normal file
@ -0,0 +1,47 @@
|
||||
package promql
|
||||
|
||||
// A Visitor is used to walk a parsed query
|
||||
type Visitor interface {
|
||||
Visit(expr Expr) Visitor
|
||||
}
|
||||
|
||||
// Walk invokes Visit on v for each node in the parsed query tree
|
||||
func Walk(expr Expr, v Visitor) {
|
||||
nv := v.Visit(expr)
|
||||
if nv == nil {
|
||||
return
|
||||
}
|
||||
switch t := expr.(type) {
|
||||
case *ParensExpr:
|
||||
for _, e := range *t {
|
||||
Walk(e, nv)
|
||||
}
|
||||
case *BinaryOpExpr:
|
||||
Walk(t.Left, nv)
|
||||
Walk(t.Right, nv)
|
||||
case *FuncExpr:
|
||||
for _, ae := range t.Args {
|
||||
Walk(ae, nv)
|
||||
}
|
||||
case *AggrFuncExpr:
|
||||
for _, ae := range t.Args {
|
||||
Walk(ae, nv)
|
||||
}
|
||||
Walk(&t.Modifier, nv)
|
||||
case *WithExpr:
|
||||
for _, wa := range t.Was {
|
||||
Walk(wa, nv)
|
||||
}
|
||||
Walk(t.Expr, nv)
|
||||
case *WithArgExpr:
|
||||
Walk(t.Expr, nv)
|
||||
case *RollupExpr:
|
||||
Walk(t.Expr, nv)
|
||||
case *MetricTemplateExpr:
|
||||
for _, tfe := range t.TagFilters {
|
||||
Walk(tfe, nv)
|
||||
}
|
||||
case *TagFilterExpr:
|
||||
Walk(t.Value, nv)
|
||||
}
|
||||
}
|
69
lib/promql/visitor_test.go
Normal file
69
lib/promql/visitor_test.go
Normal file
@ -0,0 +1,69 @@
|
||||
package promql
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestWalk(t *testing.T) {
|
||||
e, err := testParser.ParseRawPromQL(`
|
||||
WITH (
|
||||
rf(a, b) = a + b
|
||||
)
|
||||
rf(metric1{foo="bar"}, metric2) or (sum(abs(changes(metric3))))
|
||||
`)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error when parsing: %s", err)
|
||||
}
|
||||
var cv collectVisitor
|
||||
Walk(e, &cv)
|
||||
expected := []string{
|
||||
`*promql.WithExpr WITH (rf(a,b) = a + b) rf(metric1{foo="bar"}, metric2) or (sum(abs(changes(metric3))))`,
|
||||
`*promql.WithArgExpr rf(a,b) = a + b`,
|
||||
`*promql.BinaryOpExpr a + b`,
|
||||
`*promql.MetricTemplateExpr a`,
|
||||
`*promql.TagFilterExpr __name__="a"`,
|
||||
`*promql.StringTemplateExpr "a"`,
|
||||
`*promql.MetricTemplateExpr b`,
|
||||
`*promql.TagFilterExpr __name__="b"`,
|
||||
`*promql.StringTemplateExpr "b"`,
|
||||
`*promql.BinaryOpExpr rf(metric1{foo="bar"}, metric2) or (sum(abs(changes(metric3))))`,
|
||||
`*promql.FuncExpr rf(metric1{foo="bar"}, metric2)`,
|
||||
`*promql.MetricTemplateExpr metric1{foo="bar"}`,
|
||||
`*promql.TagFilterExpr __name__="metric1"`,
|
||||
`*promql.StringTemplateExpr "metric1"`,
|
||||
`*promql.TagFilterExpr foo="bar"`,
|
||||
`*promql.StringTemplateExpr "bar"`,
|
||||
`*promql.MetricTemplateExpr metric2`,
|
||||
`*promql.TagFilterExpr __name__="metric2"`,
|
||||
`*promql.StringTemplateExpr "metric2"`,
|
||||
`*promql.ParensExpr (sum(abs(changes(metric3))))`,
|
||||
`*promql.AggrFuncExpr sum(abs(changes(metric3)))`,
|
||||
`*promql.FuncExpr abs(changes(metric3))`,
|
||||
`*promql.FuncExpr changes(metric3)`,
|
||||
`*promql.MetricTemplateExpr metric3`,
|
||||
`*promql.TagFilterExpr __name__="metric3"`,
|
||||
`*promql.StringTemplateExpr "metric3"`,
|
||||
`*promql.ModifierExpr ()`,
|
||||
}
|
||||
if len(cv.visited) != len(expected) {
|
||||
t.Fatal("Expected", len(expected), "elements visited, got", len(cv.visited))
|
||||
}
|
||||
for i, v := range cv.visited {
|
||||
if strings.TrimSpace(v) != expected[i] {
|
||||
t.Fatalf("Expected %s, got %s at position %v", expected[i], v, i)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type collectVisitor struct {
|
||||
visited []string
|
||||
}
|
||||
|
||||
func (cv *collectVisitor) Visit(e Expr) Visitor {
|
||||
sb := e.AppendString(nil)
|
||||
s := fmt.Sprintf("%T %v\n", e, string(sb))
|
||||
cv.visited = append(cv.visited, s)
|
||||
return cv
|
||||
}
|
Loading…
Reference in New Issue
Block a user