Rename lib/promql to lib/metricsql and apply small fixes

This commit is contained in:
Aliaksandr Valialkin 2019-12-25 21:35:47 +02:00
parent bec62e4e43
commit 1925ee038d
35 changed files with 2343 additions and 2440 deletions

View file

@ -15,6 +15,7 @@ import (
"github.com/VictoriaMetrics/VictoriaMetrics/app/vmselect/netstorage"
"github.com/VictoriaMetrics/VictoriaMetrics/app/vmselect/promql"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/logger"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/metricsql"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/storage"
"github.com/VictoriaMetrics/metrics"
"github.com/valyala/quicktemplate"
@ -634,14 +635,14 @@ func parseDuration(s string, step int64) (int64, error) {
if len(s) == 0 {
return 0, nil
}
return promql.DurationValue(s, step)
return metricsql.DurationValue(s, step)
}
func parsePositiveDuration(s string, step int64) (int64, error) {
if len(s) == 0 {
return 0, nil
}
return promql.PositiveDurationValue(s, step)
return metricsql.PositiveDurationValue(s, step)
}
// QueryRangeHandler processes /api/v1/query_range request.

View file

@ -8,7 +8,7 @@ import (
"strings"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/logger"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/promql"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/metricsql"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/storage"
"github.com/VictoriaMetrics/metrics"
"github.com/valyala/histogram"
@ -49,7 +49,7 @@ type aggrFunc func(afa *aggrFuncArg) ([]*timeseries, error)
type aggrFuncArg struct {
args [][]*timeseries
ae *promql.AggrFuncExpr
ae *metricsql.AggrFuncExpr
ec *EvalConfig
}
@ -68,7 +68,7 @@ func newAggrFunc(afe func(tss []*timeseries) []*timeseries) aggrFunc {
}
}
func removeGroupTags(metricName *storage.MetricName, modifier *promql.ModifierExpr) {
func removeGroupTags(metricName *storage.MetricName, modifier *metricsql.ModifierExpr) {
groupOp := strings.ToLower(modifier.Op)
switch groupOp {
case "", "by":
@ -80,7 +80,7 @@ func removeGroupTags(metricName *storage.MetricName, modifier *promql.ModifierEx
}
}
func aggrFuncExt(afe func(tss []*timeseries) []*timeseries, argOrig []*timeseries, modifier *promql.ModifierExpr, keepOriginal bool) ([]*timeseries, error) {
func aggrFuncExt(afe func(tss []*timeseries) []*timeseries, argOrig []*timeseries, modifier *metricsql.ModifierExpr, keepOriginal bool) ([]*timeseries, error) {
arg := copyTimeseriesMetricNames(argOrig)
// Perform grouping.

View file

@ -5,11 +5,11 @@ import (
"strings"
"sync"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/promql"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/metricsql"
)
// callbacks for optimized incremental calculations for aggregate functions
// over rollups over metricExpr.
// over rollups over metricsql.MetricExpr.
//
// These calculations save RAM for aggregates over big number of time series.
var incrementalAggrFuncCallbacksMap = map[string]*incrementalAggrFuncCallbacks{
@ -51,7 +51,7 @@ var incrementalAggrFuncCallbacksMap = map[string]*incrementalAggrFuncCallbacks{
}
type incrementalAggrFuncContext struct {
ae *promql.AggrFuncExpr
ae *metricsql.AggrFuncExpr
mLock sync.Mutex
m map[uint]map[string]*incrementalAggrContext
@ -59,7 +59,7 @@ type incrementalAggrFuncContext struct {
callbacks *incrementalAggrFuncCallbacks
}
func newIncrementalAggrFuncContext(ae *promql.AggrFuncExpr, callbacks *incrementalAggrFuncCallbacks) *incrementalAggrFuncContext {
func newIncrementalAggrFuncContext(ae *metricsql.AggrFuncExpr, callbacks *incrementalAggrFuncCallbacks) *incrementalAggrFuncContext {
return &incrementalAggrFuncContext{
ae: ae,
m: make(map[uint]map[string]*incrementalAggrContext),

View file

@ -8,7 +8,7 @@ import (
"sync"
"testing"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/promql"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/metricsql"
)
func TestIncrementalAggr(t *testing.T) {
@ -44,7 +44,7 @@ func TestIncrementalAggr(t *testing.T) {
f := func(name string, valuesExpected []float64) {
t.Helper()
callbacks := getIncrementalAggrFuncCallbacks(name)
ae := &promql.AggrFuncExpr{
ae := &metricsql.AggrFuncExpr{
Name: name,
}
tssExpected := []*timeseries{{

View file

@ -1,9 +0,0 @@
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

View file

@ -6,25 +6,26 @@ import (
"strings"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/logger"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/promql"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/metricsql"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/metricsql/binaryop"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/storage"
)
var binaryOpFuncs = map[string]binaryOpFunc{
"+": newBinaryOpArithFunc(binaryOpPlus),
"-": newBinaryOpArithFunc(binaryOpMinus),
"*": newBinaryOpArithFunc(binaryOpMul),
"/": newBinaryOpArithFunc(binaryOpDiv),
"%": newBinaryOpArithFunc(binaryOpMod),
"^": newBinaryOpArithFunc(binaryOpPow),
"+": newBinaryOpArithFunc(binaryop.Plus),
"-": newBinaryOpArithFunc(binaryop.Minus),
"*": newBinaryOpArithFunc(binaryop.Mul),
"/": newBinaryOpArithFunc(binaryop.Div),
"%": newBinaryOpArithFunc(binaryop.Mod),
"^": newBinaryOpArithFunc(binaryop.Pow),
// cmp ops
"==": newBinaryOpCmpFunc(binaryOpEq),
"!=": newBinaryOpCmpFunc(binaryOpNeq),
">": newBinaryOpCmpFunc(binaryOpGt),
"<": newBinaryOpCmpFunc(binaryOpLt),
">=": newBinaryOpCmpFunc(binaryOpGte),
"<=": newBinaryOpCmpFunc(binaryOpLte),
"==": newBinaryOpCmpFunc(binaryop.Eq),
"!=": newBinaryOpCmpFunc(binaryop.Neq),
">": newBinaryOpCmpFunc(binaryop.Gt),
"<": newBinaryOpCmpFunc(binaryop.Lt),
">=": newBinaryOpCmpFunc(binaryop.Gte),
"<=": newBinaryOpCmpFunc(binaryop.Lte),
// logical set ops
"and": binaryOpAnd,
@ -32,9 +33,9 @@ var binaryOpFuncs = map[string]binaryOpFunc{
"unless": binaryOpUnless,
// New op
"if": newBinaryOpArithFunc(binaryOpIf),
"ifnot": newBinaryOpArithFunc(binaryOpIfnot),
"default": newBinaryOpArithFunc(binaryOpDefault),
"if": newBinaryOpArithFunc(binaryop.If),
"ifnot": newBinaryOpArithFunc(binaryop.Ifnot),
"default": newBinaryOpArithFunc(binaryop.Default),
}
func getBinaryOpFunc(op string) binaryOpFunc {
@ -42,80 +43,8 @@ func getBinaryOpFunc(op string) binaryOpFunc {
return binaryOpFuncs[op]
}
func isBinaryOpCmp(op string) bool {
switch op {
case "==", "!=", ">", "<", ">=", "<=":
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 {
if isBool {
if cf(left, right) {
return 1
}
return 0
}
if cf(left, right) {
return left
}
return nan
}
switch op {
case "==":
left = evalCmp(binaryOpEq)
case "!=":
left = evalCmp(binaryOpNeq)
case ">":
left = evalCmp(binaryOpGt)
case "<":
left = evalCmp(binaryOpLt)
case ">=":
left = evalCmp(binaryOpGte)
case "<=":
left = evalCmp(binaryOpLte)
default:
logger.Panicf("BUG: unexpected comparison binaryOp: %q", op)
}
} else {
switch op {
case "+":
left = binaryOpPlus(left, right)
case "-":
left = binaryOpMinus(left, right)
case "*":
left = binaryOpMul(left, right)
case "/":
left = binaryOpDiv(left, right)
case "%":
left = binaryOpMod(left, right)
case "^":
left = binaryOpPow(left, right)
case "and":
// Nothing to do
case "or":
// Nothing to do
case "unless":
left = nan
case "default":
left = binaryOpDefault(left, right)
case "if":
left = binaryOpIf(left, right)
case "ifnot":
left = binaryOpIfnot(left, right)
default:
logger.Panicf("BUG: unexpected non-comparison binaryOp: %q", op)
}
}
return left
}
type binaryOpFuncArg struct {
be *promql.BinaryOpExpr
be *metricsql.BinaryOpExpr
left []*timeseries
right []*timeseries
}
@ -175,7 +104,7 @@ func newBinaryOpFunc(bf func(left, right float64, isBool bool) float64) binaryOp
}
}
func adjustBinaryOpTags(be *promql.BinaryOpExpr, left, right []*timeseries) ([]*timeseries, []*timeseries, []*timeseries, error) {
func adjustBinaryOpTags(be *metricsql.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`
@ -256,7 +185,7 @@ func adjustBinaryOpTags(be *promql.BinaryOpExpr, left, right []*timeseries) ([]*
return rvsLeft, rvsRight, dst, nil
}
func ensureSingleTimeseries(side string, be *promql.BinaryOpExpr, tss []*timeseries) error {
func ensureSingleTimeseries(side string, be *metricsql.BinaryOpExpr, tss []*timeseries) error {
if len(tss) == 0 {
logger.Panicf("BUG: tss must contain at least one value")
}
@ -270,7 +199,7 @@ func ensureSingleTimeseries(side string, be *promql.BinaryOpExpr, tss []*timeser
return nil
}
func groupJoin(singleTimeseriesSide string, be *promql.BinaryOpExpr, rvsLeft, rvsRight, tssLeft, tssRight []*timeseries) ([]*timeseries, []*timeseries, error) {
func groupJoin(singleTimeseriesSide string, be *metricsql.BinaryOpExpr, rvsLeft, rvsRight, tssLeft, tssRight []*timeseries) ([]*timeseries, []*timeseries, error) {
joinTags := be.JoinModifier.Args
var m map[string]*timeseries
for _, tsLeft := range tssLeft {
@ -340,8 +269,8 @@ func mergeNonOverlappingTimeseries(dst, src *timeseries) bool {
return true
}
func resetMetricGroupIfRequired(be *promql.BinaryOpExpr, ts *timeseries) {
if isBinaryOpCmp(be.Op) && !be.Bool {
func resetMetricGroupIfRequired(be *metricsql.BinaryOpExpr, ts *timeseries) {
if metricsql.IsBinaryOpCmp(be.Op) && !be.Bool {
// Do not reset MetricGroup for non-boolean `compare` binary ops like Prometheus does.
return
}
@ -353,90 +282,6 @@ func resetMetricGroupIfRequired(be *promql.BinaryOpExpr, ts *timeseries) {
ts.MetricName.ResetMetricGroup()
}
func binaryOpPlus(left, right float64) float64 {
return left + right
}
func binaryOpMinus(left, right float64) float64 {
return left - right
}
func binaryOpMul(left, right float64) float64 {
return left * right
}
func binaryOpDiv(left, right float64) float64 {
return left / right
}
func binaryOpMod(left, right float64) float64 {
return math.Mod(left, right)
}
func binaryOpPow(left, right float64) float64 {
return math.Pow(left, right)
}
func binaryOpDefault(left, right float64) float64 {
if math.IsNaN(left) {
return right
}
return left
}
func binaryOpIf(left, right float64) float64 {
if math.IsNaN(right) {
return nan
}
return left
}
func binaryOpIfnot(left, right float64) float64 {
if math.IsNaN(right) {
return left
}
return nan
}
func binaryOpEq(left, right float64) bool {
// Special handling for nan == nan.
// See https://github.com/VictoriaMetrics/VictoriaMetrics/issues/150 .
if math.IsNaN(left) {
return math.IsNaN(right)
}
return left == right
}
func binaryOpNeq(left, right float64) bool {
// Special handling for comparison with nan.
// See https://github.com/VictoriaMetrics/VictoriaMetrics/issues/150 .
if math.IsNaN(left) {
return !math.IsNaN(right)
}
if math.IsNaN(right) {
return true
}
return left != right
}
func binaryOpGt(left, right float64) bool {
return left > right
}
func binaryOpLt(left, right float64) bool {
return left < right
}
func binaryOpGte(left, right float64) bool {
return left >= right
}
func binaryOpLte(left, right float64) bool {
return left <= right
}
func binaryOpAnd(bfa *binaryOpFuncArg) ([]*timeseries, error) {
mLeft, mRight := createTimeseriesMapByTagSet(bfa.be, bfa.left, bfa.right)
var rvs []*timeseries
@ -473,7 +318,7 @@ func binaryOpUnless(bfa *binaryOpFuncArg) ([]*timeseries, error) {
return rvs, nil
}
func createTimeseriesMapByTagSet(be *promql.BinaryOpExpr, left, right []*timeseries) (map[string][]*timeseries, map[string][]*timeseries) {
func createTimeseriesMapByTagSet(be *metricsql.BinaryOpExpr, left, right []*timeseries) (map[string][]*timeseries, map[string][]*timeseries) {
groupTags := be.GroupModifier.Args
groupOp := strings.ToLower(be.GroupModifier.Op)
if len(groupOp) == 0 {

View file

@ -6,13 +6,12 @@ import (
"math"
"runtime"
"sync"
"unsafe"
"github.com/VictoriaMetrics/VictoriaMetrics/app/vmselect/netstorage"
"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/metricsql"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/storage"
"github.com/VictoriaMetrics/metrics"
)
@ -154,9 +153,9 @@ func getTimestamps(start, end, step int64) []int64 {
return timestamps
}
func evalExpr(ec *EvalConfig, e promql.Expr) ([]*timeseries, error) {
if me, ok := e.(*promql.MetricExpr); ok {
re := &promql.RollupExpr{
func evalExpr(ec *EvalConfig, e metricsql.Expr) ([]*timeseries, error) {
if me, ok := e.(*metricsql.MetricExpr); ok {
re := &metricsql.RollupExpr{
Expr: me,
}
rv, err := evalRollupFunc(ec, "default_rollup", rollupDefault, re, nil)
@ -165,14 +164,14 @@ func evalExpr(ec *EvalConfig, e promql.Expr) ([]*timeseries, error) {
}
return rv, nil
}
if re, ok := e.(*promql.RollupExpr); ok {
if re, ok := e.(*metricsql.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.(*promql.FuncExpr); ok {
if fe, ok := e.(*metricsql.FuncExpr); ok {
nrf := getRollupFunc(fe.Name)
if nrf == nil {
args, err := evalExprs(ec, fe.Args)
@ -208,11 +207,11 @@ func evalExpr(ec *EvalConfig, e promql.Expr) ([]*timeseries, error) {
}
return rv, nil
}
if ae, ok := e.(*promql.AggrFuncExpr); ok {
if ae, ok := e.(*metricsql.AggrFuncExpr); ok {
if callbacks := getIncrementalAggrFuncCallbacks(ae.Name); callbacks != nil {
fe, nrf := tryGetArgRollupFuncWithMetricExpr(ae)
if fe != nil {
// There is an optimized path for calculating aggrFuncExpr over rollupFunc over metricExpr.
// There is an optimized path for calculating metricsql.AggrFuncExpr over rollupFunc over metricsql.MetricExpr.
// The optimized path saves RAM for aggregates over big number of time series.
args, re, err := evalRollupFuncArgs(ec, fe)
if err != nil {
@ -245,7 +244,7 @@ func evalExpr(ec *EvalConfig, e promql.Expr) ([]*timeseries, error) {
}
return rv, nil
}
if be, ok := e.(*promql.BinaryOpExpr); ok {
if be, ok := e.(*metricsql.BinaryOpExpr); ok {
left, err := evalExpr(ec, be.Left)
if err != nil {
return nil, err
@ -269,18 +268,18 @@ func evalExpr(ec *EvalConfig, e promql.Expr) ([]*timeseries, error) {
}
return rv, nil
}
if ne, ok := e.(*promql.NumberExpr); ok {
if ne, ok := e.(*metricsql.NumberExpr); ok {
rv := evalNumber(ec, ne.N)
return rv, nil
}
if se, ok := e.(*promql.StringExpr); ok {
if se, ok := e.(*metricsql.StringExpr); ok {
rv := evalString(ec, se.S)
return rv, nil
}
return nil, fmt.Errorf("unexpected expression %q", e.AppendString(nil))
}
func tryGetArgRollupFuncWithMetricExpr(ae *promql.AggrFuncExpr) (*promql.FuncExpr, newRollupFunc) {
func tryGetArgRollupFuncWithMetricExpr(ae *metricsql.AggrFuncExpr) (*metricsql.FuncExpr, newRollupFunc) {
if len(ae.Args) != 1 {
return nil, nil
}
@ -291,31 +290,31 @@ func tryGetArgRollupFuncWithMetricExpr(ae *promql.AggrFuncExpr) (*promql.FuncExp
// - rollupFunc(metricExpr)
// - rollupFunc(metricExpr[d])
if me, ok := e.(*promql.MetricExpr); ok {
if me, ok := e.(*metricsql.MetricExpr); ok {
// e = metricExpr
if me.IsEmpty() {
return nil, nil
}
fe := &promql.FuncExpr{
fe := &metricsql.FuncExpr{
Name: "default_rollup",
Args: []promql.Expr{me},
Args: []metricsql.Expr{me},
}
nrf := getRollupFunc(fe.Name)
return fe, nrf
}
if re, ok := e.(*promql.RollupExpr); ok {
if me, ok := re.Expr.(*promql.MetricExpr); !ok || me.IsEmpty() || re.ForSubquery() {
if re, ok := e.(*metricsql.RollupExpr); ok {
if me, ok := re.Expr.(*metricsql.MetricExpr); !ok || me.IsEmpty() || re.ForSubquery() {
return nil, nil
}
// e = metricExpr[d]
fe := &promql.FuncExpr{
fe := &metricsql.FuncExpr{
Name: "default_rollup",
Args: []promql.Expr{re},
Args: []metricsql.Expr{re},
}
nrf := getRollupFunc(fe.Name)
return fe, nrf
}
fe, ok := e.(*promql.FuncExpr)
fe, ok := e.(*metricsql.FuncExpr)
if !ok {
return nil, nil
}
@ -325,18 +324,18 @@ func tryGetArgRollupFuncWithMetricExpr(ae *promql.AggrFuncExpr) (*promql.FuncExp
}
rollupArgIdx := getRollupArgIdx(fe.Name)
arg := fe.Args[rollupArgIdx]
if me, ok := arg.(*promql.MetricExpr); ok {
if me, ok := arg.(*metricsql.MetricExpr); ok {
if me.IsEmpty() {
return nil, nil
}
// e = rollupFunc(metricExpr)
return &promql.FuncExpr{
return &metricsql.FuncExpr{
Name: fe.Name,
Args: []promql.Expr{me},
Args: []metricsql.Expr{me},
}, nrf
}
if re, ok := arg.(*promql.RollupExpr); ok {
if me, ok := re.Expr.(*promql.MetricExpr); !ok || me.IsEmpty() || re.ForSubquery() {
if re, ok := arg.(*metricsql.RollupExpr); ok {
if me, ok := re.Expr.(*metricsql.MetricExpr); !ok || me.IsEmpty() || re.ForSubquery() {
return nil, nil
}
// e = rollupFunc(metricExpr[d])
@ -345,7 +344,7 @@ func tryGetArgRollupFuncWithMetricExpr(ae *promql.AggrFuncExpr) (*promql.FuncExp
return nil, nil
}
func evalExprs(ec *EvalConfig, es []promql.Expr) ([][]*timeseries, error) {
func evalExprs(ec *EvalConfig, es []metricsql.Expr) ([][]*timeseries, error) {
var rvs [][]*timeseries
for _, e := range es {
rv, err := evalExpr(ec, e)
@ -357,8 +356,8 @@ func evalExprs(ec *EvalConfig, es []promql.Expr) ([][]*timeseries, error) {
return rvs, nil
}
func evalRollupFuncArgs(ec *EvalConfig, fe *promql.FuncExpr) ([]interface{}, *promql.RollupExpr, error) {
var re *promql.RollupExpr
func evalRollupFuncArgs(ec *EvalConfig, fe *metricsql.FuncExpr) ([]interface{}, *metricsql.RollupExpr, error) {
var re *metricsql.RollupExpr
rollupArgIdx := getRollupArgIdx(fe.Name)
args := make([]interface{}, len(fe.Args))
for i, arg := range fe.Args {
@ -376,11 +375,11 @@ func evalRollupFuncArgs(ec *EvalConfig, fe *promql.FuncExpr) ([]interface{}, *pr
return args, re, nil
}
func getRollupExprArg(arg promql.Expr) *promql.RollupExpr {
re, ok := arg.(*promql.RollupExpr)
func getRollupExprArg(arg metricsql.Expr) *metricsql.RollupExpr {
re, ok := arg.(*metricsql.RollupExpr)
if !ok {
// Wrap non-rollup arg into rollupExpr.
return &promql.RollupExpr{
// Wrap non-rollup arg into metricsql.RollupExpr.
return &metricsql.RollupExpr{
Expr: arg,
}
}
@ -388,28 +387,28 @@ func getRollupExprArg(arg promql.Expr) *promql.RollupExpr {
// Return standard rollup if it doesn't contain subquery.
return re
}
me, ok := re.Expr.(*promql.MetricExpr)
me, ok := re.Expr.(*metricsql.MetricExpr)
if !ok {
// arg contains subquery.
return re
}
// Convert me[w:step] -> default_rollup(me)[w:step]
reNew := *re
reNew.Expr = &promql.FuncExpr{
reNew.Expr = &metricsql.FuncExpr{
Name: "default_rollup",
Args: []promql.Expr{
&promql.RollupExpr{Expr: me},
Args: []metricsql.Expr{
&metricsql.RollupExpr{Expr: me},
},
}
return &reNew
}
func evalRollupFunc(ec *EvalConfig, name string, rf rollupFunc, re *promql.RollupExpr, iafc *incrementalAggrFuncContext) ([]*timeseries, error) {
func evalRollupFunc(ec *EvalConfig, name string, rf rollupFunc, re *metricsql.RollupExpr, iafc *incrementalAggrFuncContext) ([]*timeseries, error) {
ecNew := ec
var offset int64
if len(re.Offset) > 0 {
var err error
offset, err = promql.DurationValue(re.Offset, ec.Step)
offset, err = metricsql.DurationValue(re.Offset, ec.Step)
if err != nil {
return nil, err
}
@ -425,7 +424,7 @@ func evalRollupFunc(ec *EvalConfig, name string, rf rollupFunc, re *promql.Rollu
}
var rvs []*timeseries
var err error
if me, ok := re.Expr.(*promql.MetricExpr); ok {
if me, ok := re.Expr.(*metricsql.MetricExpr); ok {
rvs, err = evalRollupFuncWithMetricExpr(ecNew, name, rf, me, iafc, re.Window)
} else {
if iafc != nil {
@ -450,12 +449,12 @@ func evalRollupFunc(ec *EvalConfig, name string, rf rollupFunc, re *promql.Rollu
return rvs, nil
}
func evalRollupFuncWithSubquery(ec *EvalConfig, name string, rf rollupFunc, re *promql.RollupExpr) ([]*timeseries, error) {
// Do not use rollupResultCacheV here, since it works only with metricExpr.
func evalRollupFuncWithSubquery(ec *EvalConfig, name string, rf rollupFunc, re *metricsql.RollupExpr) ([]*timeseries, error) {
// Do not use rollupResultCacheV here, since it works only with metricsql.MetricExpr.
var step int64
if len(re.Step) > 0 {
var err error
step, err = promql.DurationValue(re.Step, ec.Step)
step, err = metricsql.PositiveDurationValue(re.Step, ec.Step)
if err != nil {
return nil, err
}
@ -465,7 +464,7 @@ func evalRollupFuncWithSubquery(ec *EvalConfig, name string, rf rollupFunc, re *
var window int64
if len(re.Window) > 0 {
var err error
window, err = promql.DurationValue(re.Window, ec.Step)
window, err = metricsql.PositiveDurationValue(re.Window, ec.Step)
if err != nil {
return nil, err
}
@ -559,14 +558,14 @@ var (
rollupResultCacheMiss = metrics.NewCounter(`vm_rollup_result_cache_miss_total`)
)
func evalRollupFuncWithMetricExpr(ec *EvalConfig, name string, rf rollupFunc, me *promql.MetricExpr, iafc *incrementalAggrFuncContext, windowStr string) ([]*timeseries, error) {
func evalRollupFuncWithMetricExpr(ec *EvalConfig, name string, rf rollupFunc, me *metricsql.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 = promql.DurationValue(windowStr, ec.Step)
window, err = metricsql.PositiveDurationValue(windowStr, ec.Step)
if err != nil {
return nil, err
}
@ -586,12 +585,11 @@ func evalRollupFuncWithMetricExpr(ec *EvalConfig, name string, rf rollupFunc, me
}
// Fetch the remaining part of the result.
tfs := toTagFilters(me.LabelFilters)
sq := &storage.SearchQuery{
MinTimestamp: start - window - maxSilenceInterval,
MaxTimestamp: ec.End + ec.Step,
TagFilterss: [][]storage.TagFilter{
*(*[]storage.TagFilter)(unsafe.Pointer(&me.TagFilters)),
},
TagFilterss: [][]storage.TagFilter{tfs},
}
rss, err := netstorage.ProcessSearchQuery(sq, true, ec.Deadline)
if err != nil {
@ -815,3 +813,23 @@ func mulNoOverflow(a, b int64) int64 {
}
return a * b
}
func toTagFilters(lfs []metricsql.LabelFilter) []storage.TagFilter {
tfs := make([]storage.TagFilter, len(lfs))
for i := range lfs {
toTagFilter(&tfs[i], &lfs[i])
}
return tfs
}
func toTagFilter(dst *storage.TagFilter, src *metricsql.LabelFilter) {
if src.Label != "__name__" {
dst.Key = []byte(src.Label)
} else {
// This is required for storage.Search.
dst.Key = nil
}
dst.Value = []byte(src.Value)
dst.IsRegexp = src.IsRegexp
dst.IsNegative = src.IsNegative
}

View file

@ -11,7 +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/VictoriaMetrics/lib/metricsql"
"github.com/VictoriaMetrics/metrics"
)
@ -86,12 +86,12 @@ func Exec(ec *EvalConfig, q string, isFirstPointOnly bool) ([]netstorage.Result,
return result, err
}
func maySortResults(e promql.Expr, tss []*timeseries) bool {
func maySortResults(e metricsql.Expr, tss []*timeseries) bool {
if len(tss) > 100 {
// There is no sense in sorting a lot of results
return false
}
fe, ok := e.(*promql.FuncExpr)
fe, ok := e.(*metricsql.FuncExpr)
if !ok {
return true
}
@ -155,19 +155,10 @@ func removeNaNs(tss []*timeseries) []*timeseries {
return rvs
}
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) {
func parsePromQLWithCache(q string) (metricsql.Expr, error) {
pcv := parseCacheV.Get(q)
if pcv == nil {
e, err := parsePromQL(q)
e, err := metricsql.Parse(q)
pcv = &parseCacheValue{
e: e,
err: err,
@ -180,8 +171,6 @@ func parsePromQLWithCache(q string) (promql.Expr, error) {
return pcv.e, nil
}
var parser = promql.NewParser(compileRegexpAnchored)
var parseCacheV = func() *parseCache {
pc := &parseCache{
m: make(map[string]*parseCacheValue),
@ -201,7 +190,7 @@ var parseCacheV = func() *parseCache {
const parseCacheMaxLen = 10e3
type parseCacheValue struct {
e promql.Expr
e metricsql.Expr
err error
}
@ -261,7 +250,3 @@ func (pc *parseCache) Put(q string, pcv *parseCacheValue) {
pc.m[q] = pcv
pc.mu.Unlock()
}
func init() {
promql.Panicf = logger.Panicf
}

View file

@ -2,12 +2,27 @@ package promql
import (
"fmt"
"unsafe"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/promql"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/metricsql"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/storage"
)
// IsRollup verifies whether s is a rollup with non-empty window.
//
// It returns the wrapped query with the corresponding window, step and offset.
func IsRollup(s string) (childQuery string, window, step, offset string) {
expr, err := parsePromQLWithCache(s)
if err != nil {
return
}
re, ok := expr.(*metricsql.RollupExpr)
if !ok || len(re.Window) == 0 {
return
}
wrappedQuery := re.Expr.AppendString(nil)
return string(wrappedQuery), re.Window, re.Step, re.Offset
}
// IsMetricSelectorWithRollup verifies whether s contains PromQL metric selector
// wrapped into rollup.
//
@ -17,12 +32,12 @@ func IsMetricSelectorWithRollup(s string) (childQuery string, window, offset str
if err != nil {
return
}
re, ok := expr.(*promql.RollupExpr)
re, ok := expr.(*metricsql.RollupExpr)
if !ok || len(re.Window) == 0 || len(re.Step) > 0 {
return
}
me, ok := re.Expr.(*promql.MetricExpr)
if !ok || len(me.TagFilters) == 0 {
me, ok := re.Expr.(*metricsql.MetricExpr)
if !ok || len(me.LabelFilters) == 0 {
return
}
wrappedQuery := me.AppendString(nil)
@ -30,18 +45,19 @@ func IsMetricSelectorWithRollup(s string) (childQuery string, window, offset str
}
// ParseMetricSelector parses s containing PromQL metric selector
// and returns the corresponding TagFilters.
// and returns the corresponding LabelFilters.
func ParseMetricSelector(s string) ([]storage.TagFilter, error) {
expr, err := parsePromQLWithCache(s)
if err != nil {
return nil, err
}
me, ok := expr.(*promql.MetricExpr)
me, ok := expr.(*metricsql.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")
if len(me.LabelFilters) == 0 {
return nil, fmt.Errorf("labelFilters cannot be empty")
}
return *(*[]storage.TagFilter)(unsafe.Pointer(&me.TagFilters)), nil
tfs := toTagFilters(me.LabelFilters)
return tfs, nil
}

View file

@ -12,8 +12,7 @@ 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/metricsql"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/workingsetcache"
"github.com/VictoriaMetrics/fastcache"
"github.com/VictoriaMetrics/metrics"
@ -130,7 +129,7 @@ func ResetRollupResultCache() {
rollupResultCacheV.c.Reset()
}
func (rrc *rollupResultCache) Get(funcName string, ec *EvalConfig, me *promql.MetricExpr, iafc *incrementalAggrFuncContext, window int64) (tss []*timeseries, newStart int64) {
func (rrc *rollupResultCache) Get(funcName string, ec *EvalConfig, me *metricsql.MetricExpr, iafc *incrementalAggrFuncContext, window int64) (tss []*timeseries, newStart int64) {
if *disableCache || !ec.mayCache() {
return nil, ec.Start
}
@ -211,7 +210,7 @@ func (rrc *rollupResultCache) Get(funcName string, ec *EvalConfig, me *promql.Me
var resultBufPool bytesutil.ByteBufferPool
func (rrc *rollupResultCache) Put(funcName string, ec *EvalConfig, me *promql.MetricExpr, iafc *incrementalAggrFuncContext, window int64, tss []*timeseries) {
func (rrc *rollupResultCache) Put(funcName string, ec *EvalConfig, me *metricsql.MetricExpr, iafc *incrementalAggrFuncContext, window int64, tss []*timeseries) {
if *disableCache || len(tss) == 0 || !ec.mayCache() {
return
}
@ -292,7 +291,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, me *promql.MetricExpr, iafc *incrementalAggrFuncContext, window, step int64) []byte {
func marshalRollupResultCacheKey(dst []byte, funcName string, me *metricsql.MetricExpr, iafc *incrementalAggrFuncContext, window, step int64) []byte {
dst = append(dst, rollupResultCacheVersion)
if iafc == nil {
dst = append(dst, 0)
@ -304,9 +303,9 @@ func marshalRollupResultCacheKey(dst []byte, funcName string, me *promql.MetricE
dst = append(dst, funcName...)
dst = encoding.MarshalInt64(dst, window)
dst = encoding.MarshalInt64(dst, step)
for i := range me.TagFilters {
stf := storage.TagFilter(me.TagFilters[i])
dst = stf.Marshal(dst)
tfs := toTagFilters(me.LabelFilters)
for i := range tfs {
dst = tfs[i].Marshal(dst)
}
return dst
}

View file

@ -3,7 +3,7 @@ package promql
import (
"testing"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/promql"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/metricsql"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/storage"
)
@ -18,14 +18,14 @@ func TestRollupResultCache(t *testing.T) {
MayCache: true,
}
me := &promql.MetricExpr{
TagFilters: []promql.TagFilter{{
Key: []byte("aaa"),
Value: []byte("xxx"),
me := &metricsql.MetricExpr{
LabelFilters: []metricsql.LabelFilter{{
Label: "aaa",
Value: "xxx",
}},
}
iafc := &incrementalAggrFuncContext{
ae: &promql.AggrFuncExpr{
ae: &metricsql.AggrFuncExpr{
Name: "foobar",
},
}

View file

@ -1,9 +1,10 @@
package promql
import (
"github.com/VictoriaMetrics/VictoriaMetrics/lib/promql"
"math"
"testing"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/metricsql"
)
var (
@ -158,7 +159,7 @@ func TestDerivValues(t *testing.T) {
testRowsEqual(t, values, timestamps, valuesExpected, timestamps)
}
func testRollupFunc(t *testing.T, funcName string, args []interface{}, meExpected *promql.MetricExpr, vExpected float64) {
func testRollupFunc(t *testing.T, funcName string, args []interface{}, meExpected *metricsql.MetricExpr, vExpected float64) {
t.Helper()
nrf := getRollupFunc(funcName)
if nrf == nil {
@ -198,8 +199,8 @@ func TestRollupQuantileOverTime(t *testing.T) {
Values: []float64{phi},
Timestamps: []int64{123},
}}
var me promql.MetricExpr
args := []interface{}{phis, &promql.RollupExpr{Expr: &me}}
var me metricsql.MetricExpr
args := []interface{}{phis, &metricsql.RollupExpr{Expr: &me}}
testRollupFunc(t, "quantile_over_time", args, &me, vExpected)
}
@ -220,8 +221,8 @@ func TestRollupPredictLinear(t *testing.T) {
Values: []float64{sec},
Timestamps: []int64{123},
}}
var me promql.MetricExpr
args := []interface{}{&promql.RollupExpr{Expr: &me}, secs}
var me metricsql.MetricExpr
args := []interface{}{&metricsql.RollupExpr{Expr: &me}, secs}
testRollupFunc(t, "predict_linear", args, &me, vExpected)
}
@ -242,8 +243,8 @@ func TestRollupHoltWinters(t *testing.T) {
Values: []float64{tf},
Timestamps: []int64{123},
}}
var me promql.MetricExpr
args := []interface{}{&promql.RollupExpr{Expr: &me}, sfs, tfs}
var me metricsql.MetricExpr
args := []interface{}{&metricsql.RollupExpr{Expr: &me}, sfs, tfs}
testRollupFunc(t, "holt_winters", args, &me, vExpected)
}
@ -266,8 +267,8 @@ func TestRollupHoltWinters(t *testing.T) {
func TestRollupNewRollupFuncSuccess(t *testing.T) {
f := func(funcName string, vExpected float64) {
t.Helper()
var me promql.MetricExpr
args := []interface{}{&promql.RollupExpr{Expr: &me}}
var me metricsql.MetricExpr
args := []interface{}{&metricsql.RollupExpr{Expr: &me}}
testRollupFunc(t, funcName, args, &me, vExpected)
}
@ -328,7 +329,7 @@ func TestRollupNewRollupFuncError(t *testing.T) {
Values: []float64{321},
Timestamps: []int64{123},
}}
me := &promql.MetricExpr{}
me := &metricsql.MetricExpr{}
f("holt_winters", []interface{}{123, 123, 321})
f("holt_winters", []interface{}{me, 123, 321})
f("holt_winters", []interface{}{me, scalarTs, 321})

View file

@ -1,54 +0,0 @@
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)
}
}

View file

@ -1,89 +0,0 @@
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)`)
}

View file

@ -12,7 +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/metricsql"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/storage"
"github.com/valyala/histogram"
)
@ -102,7 +102,7 @@ func getTransformFunc(s string) transformFunc {
type transformFuncArg struct {
ec *EvalConfig
fe *promql.FuncExpr
fe *metricsql.FuncExpr
args [][]*timeseries
}
@ -123,7 +123,7 @@ func newTransformFuncOneArg(tf func(v float64) float64) transformFunc {
}
}
func doTransformValues(arg []*timeseries, tf func(values []float64), fe *promql.FuncExpr) ([]*timeseries, error) {
func doTransformValues(arg []*timeseries, tf func(values []float64), fe *metricsql.FuncExpr) ([]*timeseries, error) {
name := strings.ToLower(fe.Name)
keepMetricGroup := transformFuncsKeepMetricGroup[name]
for _, ts := range arg {
@ -151,12 +151,13 @@ func transformAbsent(tfa *transformFuncArg) ([]*timeseries, error) {
// Copy tags from arg
rvs := evalNumber(tfa.ec, 1)
rv := rvs[0]
me, ok := tfa.fe.Args[0].(*promql.MetricExpr)
me, ok := tfa.fe.Args[0].(*metricsql.MetricExpr)
if !ok {
return rvs, nil
}
for i := range me.TagFilters {
tf := &me.TagFilters[i]
tfs := toTagFilters(me.LabelFilters)
for i := range tfs {
tf := &tfs[i]
if len(tf.Key) == 0 {
continue
}
@ -1020,7 +1021,7 @@ func transformLabelTransform(tfa *transformFuncArg) ([]*timeseries, error) {
return nil, err
}
r, err := compileRegexp(regex)
r, err := metricsql.CompileRegexp(regex)
if err != nil {
return nil, fmt.Errorf(`cannot compile regex %q: %s`, regex, err)
}
@ -1049,7 +1050,7 @@ func transformLabelReplace(tfa *transformFuncArg) ([]*timeseries, error) {
return nil, err
}
r, err := compileRegexpAnchored(regex)
r, err := metricsql.CompileRegexpAnchored(regex)
if err != nil {
return nil, fmt.Errorf(`cannot compile regex %q: %s`, regex, err)
}
@ -1160,7 +1161,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].(*promql.StringExpr); ok {
if se, ok := tfa.fe.Args[0].(*metricsql.StringExpr); ok {
n, err := strconv.ParseFloat(se.S, 64)
if err != nil {
n = nan

View file

@ -1,4 +1,4 @@
package promql
package metricsql
import (
"strings"
@ -18,7 +18,7 @@ var aggrFuncs = map[string]bool{
"topk": true,
"quantile": true,
// Extended PromQL funcs
// MetricsQL extension funcs
"median": true,
"limitk": true,
"distinct": true,

View file

@ -1,4 +1,4 @@
package promql
package metricsql
import (
"testing"

205
lib/metricsql/binary_op.go Normal file
View file

@ -0,0 +1,205 @@
package metricsql
import (
"fmt"
"math"
"strings"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/metricsql/binaryop"
)
var binaryOps = map[string]bool{
"+": true,
"-": true,
"*": true,
"/": true,
"%": true,
"^": true,
// cmp ops
"==": true,
"!=": true,
">": true,
"<": true,
">=": true,
"<=": true,
// logical set ops
"and": true,
"or": true,
"unless": true,
// New ops for MetricsQL
"if": true,
"ifnot": true,
"default": true,
}
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)
return binaryOps[op]
}
func binaryOpPriority(op string) int {
op = strings.ToLower(op)
return binaryOpPriorities[op]
}
func scanBinaryOpPrefix(s string) int {
n := 0
for op := range binaryOps {
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"
}
// IsBinaryOpCmp returns true if op is comparison operator such as '==', '!=', etc.
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
}
}
func binaryOpEval(op string, left, right float64, isBool bool) float64 {
if IsBinaryOpCmp(op) {
evalCmp := func(cf func(left, right float64) bool) float64 {
if isBool {
if cf(left, right) {
return 1
}
return 0
}
if cf(left, right) {
return left
}
return nan
}
switch op {
case "==":
left = evalCmp(binaryop.Eq)
case "!=":
left = evalCmp(binaryop.Neq)
case ">":
left = evalCmp(binaryop.Gt)
case "<":
left = evalCmp(binaryop.Lt)
case ">=":
left = evalCmp(binaryop.Gte)
case "<=":
left = evalCmp(binaryop.Lte)
default:
panic(fmt.Errorf("BUG: unexpected comparison binaryOp: %q", op))
}
} else {
switch op {
case "+":
left = binaryop.Plus(left, right)
case "-":
left = binaryop.Minus(left, right)
case "*":
left = binaryop.Mul(left, right)
case "/":
left = binaryop.Div(left, right)
case "%":
left = binaryop.Mod(left, right)
case "^":
left = binaryop.Pow(left, right)
case "and":
// Nothing to do
case "or":
// Nothing to do
case "unless":
left = nan
case "default":
left = binaryop.Default(left, right)
case "if":
left = binaryop.If(left, right)
case "ifnot":
left = binaryop.Ifnot(left, right)
default:
panic(fmt.Errorf("BUG: unexpected non-comparison binaryOp: %q", op))
}
}
return left
}
var nan = math.NaN()

View file

@ -1,4 +1,4 @@
package promql
package metricsql
import (
"testing"

View file

@ -0,0 +1,104 @@
package binaryop
import (
"math"
)
var nan = math.NaN()
// Eq returns true of left == right.
func Eq(left, right float64) bool {
// Special handling for nan == nan.
// See https://github.com/VictoriaMetrics/VictoriaMetrics/issues/150 .
if math.IsNaN(left) {
return math.IsNaN(right)
}
return left == right
}
// Neq returns true of left != right.
func Neq(left, right float64) bool {
// Special handling for comparison with nan.
// See https://github.com/VictoriaMetrics/VictoriaMetrics/issues/150 .
if math.IsNaN(left) {
return !math.IsNaN(right)
}
if math.IsNaN(right) {
return true
}
return left != right
}
// Gt returns true of left > right
func Gt(left, right float64) bool {
return left > right
}
// Lt returns true if left < right
func Lt(left, right float64) bool {
return left < right
}
// Gte returns true if left >= right
func Gte(left, right float64) bool {
return left >= right
}
// Lte returns true if left <= right
func Lte(left, right float64) bool {
return left <= right
}
// Plus returns left + right
func Plus(left, right float64) float64 {
return left + right
}
// Minus returns left - right
func Minus(left, right float64) float64 {
return left - right
}
// Mul returns left * right
func Mul(left, right float64) float64 {
return left * right
}
// Div returns left / right
func Div(left, right float64) float64 {
return left / right
}
// Mod returns mod(left, right)
func Mod(left, right float64) float64 {
return math.Mod(left, right)
}
// Pow returns pow(left, right)
func Pow(left, right float64) float64 {
return math.Pow(left, right)
}
// Default returns left or right if left is NaN.
func Default(left, right float64) float64 {
if math.IsNaN(left) {
return right
}
return left
}
// If returns left if right is not NaN. Otherwise NaN is returned.
func If(left, right float64) float64 {
if math.IsNaN(right) {
return nan
}
return left
}
// Ifnot returns left if right is NaN. Otherwise NaN is returned.
func Ifnot(left, right float64) float64 {
if math.IsNaN(right) {
return left
}
return nan
}

15
lib/metricsql/doc.go Normal file
View file

@ -0,0 +1,15 @@
// Package metricsql implements MetricsQL parser.
//
// This parser can parse PromQL. Additionally it can parse MetricsQL extensions.
// See https://github.com/VictoriaMetrics/VictoriaMetrics/wiki/ExtendedPromQL for details about MetricsQL extensions.
//
// Usage:
//
// expr, err := metricsql.Parse(`sum(rate(foo{bar="baz"}[5m])) by (job)`)
// if err != nil {
// // parse error
// }
// // Now expr contains parsed MetricsQL as `*Expr` structs.
// // See metricsql.Parse examples for more details.
//
package metricsql

View file

@ -1,4 +1,4 @@
package promql
package metricsql
import (
"fmt"
@ -220,7 +220,7 @@ func scanIdent(s string) string {
}
}
if i == 0 {
Panicf("BUG: scanIdent couldn't find a single ident char; make sure isIdentPrefix called before scanIdent")
panic("BUG: scanIdent couldn't find a single ident char; make sure isIdentPrefix called before scanIdent")
}
return s[:i]
}
@ -279,7 +279,7 @@ func toHex(n byte) byte {
return 'a' + (n - 10)
}
func appendEscapedIdent(dst, s []byte) []byte {
func appendEscapedIdent(dst []byte, s string) []byte {
for i := 0; i < len(s); i++ {
ch := s[i]
if isIdentChar(ch) {

View file

@ -1,4 +1,4 @@
package promql
package metricsql
import (
"reflect"
@ -28,7 +28,7 @@ func TestUnescapeIdent(t *testing.T) {
func TestAppendEscapedIdent(t *testing.T) {
f := func(s, resultExpected string) {
t.Helper()
result := appendEscapedIdent(nil, []byte(s))
result := appendEscapedIdent(nil, s)
if string(result) != resultExpected {
t.Fatalf("unexpected result for appendEscapedIdent(%q); got %q; want %q", s, result, resultExpected)
}

1720
lib/metricsql/parser.go Normal file

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,35 @@
package metricsql_test
import (
"fmt"
"log"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/metricsql"
)
func ExampleParse() {
expr, err := metricsql.Parse(`sum(rate(foo{bar="baz"}[5m])) by (x,y)`)
if err != nil {
log.Fatalf("parse error: %s", err)
}
fmt.Printf("parsed expr: %s\n", expr.AppendString(nil))
ae := expr.(*metricsql.AggrFuncExpr)
fmt.Printf("aggr func: name=%s, arg=%s, modifier=%s\n", ae.Name, ae.Args[0].AppendString(nil), ae.Modifier.AppendString(nil))
fe := ae.Args[0].(*metricsql.FuncExpr)
fmt.Printf("func: name=%s, arg=%s\n", fe.Name, fe.Args[0].AppendString(nil))
re := fe.Args[0].(*metricsql.RollupExpr)
fmt.Printf("rollup: expr=%s, window=%s\n", re.Expr.AppendString(nil), re.Window)
me := re.Expr.(*metricsql.MetricExpr)
fmt.Printf("metric: labelFilter1=%s, labelFilter2=%s", me.LabelFilters[0].AppendString(nil), me.LabelFilters[1].AppendString(nil))
// Output:
// parsed expr: sum(rate(foo{bar="baz"}[5m])) by (x, y)
// aggr func: name=sum, arg=rate(foo{bar="baz"}[5m]), modifier=by (x, y)
// func: name=rate, arg=foo{bar="baz"}[5m]
// rollup: expr=foo{bar="baz"}, window=5m
// metric: labelFilter1=__name__="foo", labelFilter2=bar="baz"
}

View file

@ -1,24 +1,14 @@
package promql
package metricsql
import (
"regexp"
"testing"
)
var testParser = &Parser{
compileRegexpAnchored: compileRegexpAnchored,
}
func compileRegexpAnchored(re string) (*regexp.Regexp, error) {
reAnchored := "^(?:" + re + ")$"
return regexp.Compile(reAnchored)
}
func TestParsePromQLSuccess(t *testing.T) {
func TestParseSuccess(t *testing.T) {
another := func(s string, sExpected string) {
t.Helper()
e, err := testParser.ParsePromQL(s)
e, err := Parse(s)
if err != nil {
t.Fatalf("unexpected error when parsing %q: %s", s, err)
}
@ -150,24 +140,72 @@ 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`)
@ -176,6 +214,7 @@ 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
@ -192,7 +231,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)`)
@ -215,6 +254,8 @@ 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)`)
@ -237,7 +278,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"}) 2`, `2`)
another(`with (foo = bar{x="x"}) 1+1`, `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"}`)
@ -270,6 +311,7 @@ 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"})`)
@ -328,6 +370,18 @@ 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)
@ -341,15 +395,16 @@ 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) {
func TestParseError(t *testing.T) {
f := func(s string) {
t.Helper()
e, err := testParser.ParsePromQL(s)
e, err := Parse(s)
if err == nil {
t.Fatalf("expecting non-nil error when parsing %q (expr=%v)", s, e)
t.Fatalf("expecting non-nil error when parsing %q", s)
}
if e != nil {
t.Fatalf("expecting nil expr when parsing %q", s)

View file

@ -1,4 +1,4 @@
package promql
package metricsql
import (
"regexp"
@ -8,12 +8,14 @@ import (
"github.com/VictoriaMetrics/metrics"
)
func compileRegexpAnchored(re string) (*regexp.Regexp, error) {
// CompileRegexpAnchored returns compiled regexp `^re$`.
func CompileRegexpAnchored(re string) (*regexp.Regexp, error) {
reAnchored := "^(?:" + re + ")$"
return compileRegexp(reAnchored)
return CompileRegexp(reAnchored)
}
func compileRegexp(re string) (*regexp.Regexp, error) {
// CompileRegexp returns compile regexp re.
func CompileRegexp(re string) (*regexp.Regexp, error) {
rcv := regexpCacheV.Get(re)
if rcv != nil {
return rcv.r, rcv.err

View file

@ -1,12 +1,10 @@
package promql
package metricsql
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,
@ -30,6 +28,7 @@ var rollupFuncs = map[string]bool{
"stdvar_over_time": true,
// Additional rollup funcs.
"default_rollup": true,
"sum2_over_time": true,
"geomean_over_time": true,
"first_over_time": true,

View file

@ -1,4 +1,4 @@
package promql
package metricsql
import (
"strings"
@ -36,7 +36,7 @@ var transformFuncs = map[string]bool{
"vector": true,
"year": true,
// New funcs
// New funcs from MetricsQL
"label_set": true,
"label_del": true,
"label_keep": true,
@ -45,7 +45,7 @@ var transformFuncs = map[string]bool{
"label_transform": true,
"label_value": true,
"union": true,
"": true,
"": true, // empty func is a synonim to union
"keep_last_value": true,
"start": true,
"end": true,

View file

@ -1,109 +0,0 @@
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
}
}

View file

@ -1,423 +0,0 @@
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)
}

File diff suppressed because it is too large Load diff

View file

@ -1,47 +0,0 @@
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)
}
}

View file

@ -1,69 +0,0 @@
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
}