This commit is contained in:
Aliaksandr Valialkin 2024-05-28 14:51:54 +02:00
parent e25b2e7f05
commit e8e49405ef
No known key found for this signature in database
GPG key ID: 52C003EE2BCDB9EB
2 changed files with 70 additions and 34 deletions

View file

@ -7,6 +7,7 @@ import (
"unsafe" "unsafe"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/bytesutil" "github.com/VictoriaMetrics/VictoriaMetrics/lib/bytesutil"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/decimal"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/logger" "github.com/VictoriaMetrics/VictoriaMetrics/lib/logger"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/slicesutil" "github.com/VictoriaMetrics/VictoriaMetrics/lib/slicesutil"
) )
@ -312,7 +313,9 @@ func (shard *pipeMathProcessorShard) executeExpr(me *mathExpr, br *blockResult)
shard.executeExpr(arg, br) shard.executeExpr(arg, br)
} }
me.f(shard.rs[rIdx], shard.rs[rIdx+1:]) result := shard.rs[rIdx]
args := shard.rs[rIdx+1:]
me.f(result, args)
shard.rs = shard.rs[:rIdx+1] shard.rs = shard.rs[:rIdx+1]
shard.rsBuf = shard.rsBuf[:rsBufLen] shard.rsBuf = shard.rsBuf[:rsBufLen]
@ -386,14 +389,14 @@ func parseMathEntry(lex *lexer) (*mathEntry, error) {
return nil, err return nil, err
} }
if !lex.isKeyword("as") { // skip optional 'as'
return nil, fmt.Errorf("missing 'as' after [%s]", me) if lex.isKeyword("as") {
}
lex.nextToken() lex.nextToken()
}
resultField, err := parseFieldName(lex) resultField, err := parseFieldName(lex)
if err != nil { if err != nil {
return nil, fmt.Errorf("cannot parse result name: %w", err) return nil, fmt.Errorf("cannot parse result name for [%s]: %w", me, err)
} }
e := &mathEntry{ e := &mathEntry{
@ -410,12 +413,12 @@ func parseMathExpr(lex *lexer) (*mathExpr, error) {
return nil, err return nil, err
} }
if lex.isKeyword("as", "|", ")", ",", "") { for {
if !isMathBinaryOp(lex.token) {
// There is no right operand // There is no right operand
return left, nil return left, nil
} }
for {
// parse operator // parse operator
op := lex.token op := lex.token
lex.nextToken() lex.nextToken()
@ -444,11 +447,7 @@ func parseMathExpr(lex *lexer) (*mathExpr, error) {
me = left me = left
} }
if !lex.isKeyword("as", "|", ")", ",", "") {
left = me left = me
continue
}
return me, nil
} }
} }
@ -479,10 +478,12 @@ func parseMathExprOperand(lex *lexer) (*mathExpr, error) {
switch { switch {
case lex.isKeyword("abs"): case lex.isKeyword("abs"):
return parseMathExprAbs(lex) return parseMathExprAbs(lex)
case lex.isKeyword("min"):
return parseMathExprMin(lex)
case lex.isKeyword("max"): case lex.isKeyword("max"):
return parseMathExprMax(lex) return parseMathExprMax(lex)
case lex.isKeyword("min"):
return parseMathExprMin(lex)
case lex.isKeyword("round"):
return parseMathExprRound(lex)
case lex.isKeyword("-"): case lex.isKeyword("-"):
return parseMathExprUnaryMinus(lex) return parseMathExprUnaryMinus(lex)
case lex.isKeyword("+"): case lex.isKeyword("+"):
@ -507,6 +508,17 @@ func parseMathExprAbs(lex *lexer) (*mathExpr, error) {
return me, nil return me, nil
} }
func parseMathExprMax(lex *lexer) (*mathExpr, error) {
me, err := parseMathExprGenericFunc(lex, "max", mathFuncMax)
if err != nil {
return nil, err
}
if len(me.args) < 2 {
return nil, fmt.Errorf("'max' function needs at least 2 args; got %d args: [%s]", len(me.args), me)
}
return me, nil
}
func parseMathExprMin(lex *lexer) (*mathExpr, error) { func parseMathExprMin(lex *lexer) (*mathExpr, error) {
me, err := parseMathExprGenericFunc(lex, "min", mathFuncMin) me, err := parseMathExprGenericFunc(lex, "min", mathFuncMin)
if err != nil { if err != nil {
@ -518,13 +530,13 @@ func parseMathExprMin(lex *lexer) (*mathExpr, error) {
return me, nil return me, nil
} }
func parseMathExprMax(lex *lexer) (*mathExpr, error) { func parseMathExprRound(lex *lexer) (*mathExpr, error) {
me, err := parseMathExprGenericFunc(lex, "max", mathFuncMax) me, err := parseMathExprGenericFunc(lex, "round", mathFuncRound)
if err != nil { if err != nil {
return nil, err return nil, err
} }
if len(me.args) < 2 { if len(me.args) != 2 {
return nil, fmt.Errorf("'max' function needs at least 2 args; got %d args: [%s]", len(me.args), me) return nil, fmt.Errorf("'round' function needs 2 args; got %d args: [%s]", len(me.args), me)
} }
return me, nil return me, nil
} }
@ -618,7 +630,7 @@ func parseMathExprConstNumber(lex *lexer) (*mathExpr, error) {
} }
func parseMathExprFieldName(lex *lexer) (*mathExpr, error) { func parseMathExprFieldName(lex *lexer) (*mathExpr, error) {
fieldName, err := parseFieldName(lex) fieldName, err := getCompoundMathToken(lex)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -711,6 +723,18 @@ func mathFuncUnaryMinus(result []float64, args [][]float64) {
} }
} }
func mathFuncMax(result []float64, args [][]float64) {
for i := range result {
f := nan
for _, arg := range args {
if math.IsNaN(f) || arg[i] > f {
f = arg[i]
}
}
result[i] = f
}
}
func mathFuncMin(result []float64, args [][]float64) { func mathFuncMin(result []float64, args [][]float64) {
for i := range result { for i := range result {
f := nan f := nan
@ -723,14 +747,23 @@ func mathFuncMin(result []float64, args [][]float64) {
} }
} }
func mathFuncMax(result []float64, args [][]float64) { func mathFuncRound(result []float64, args [][]float64) {
arg := args[0]
nearest := args[1]
var f float64
for i := range result { for i := range result {
f := nan if i == 0 || arg[i-1] != arg[i] || nearest[i-1] != nearest[i] {
for _, arg := range args { f = round(arg[i], nearest[i])
if math.IsNaN(f) || arg[i] > f {
f = arg[i]
}
} }
result[i] = f result[i] = f
} }
} }
func round(f, nearest float64) float64 {
_, e := decimal.FromFloat(nearest)
p10 := math.Pow10(int(-e))
f += 0.5 * math.Copysign(nearest, f)
f -= math.Mod(f, nearest)
f, _ = math.Modf(f * p10)
return f / p10
}

View file

@ -14,12 +14,13 @@ func TestParsePipeMathSuccess(t *testing.T) {
f(`math -123 as a`) f(`math -123 as a`)
f(`math 12.345KB as a`) f(`math 12.345KB as a`)
f(`math (-2 + 2) as a`) f(`math (-2 + 2) as a`)
f(`math min(3, foo, (1 + bar) / baz) as a, max(a, b) as b, (abs(c) + 5) as d`)
f(`math x as a, z as y`) f(`math x as a, z as y`)
f(`math (foo / bar + baz * abc % -45ms) as a`) f(`math (foo / bar + baz * abc % -45ms) as a`)
f(`math (foo / (bar + baz) * abc ^ 2) as a`) f(`math (foo / (bar + baz) * abc ^ 2) as a`)
f(`math (foo / ((bar + baz) * abc) ^ -2) as a`) f(`math (foo / ((bar + baz) * abc) ^ -2) as a`)
f(`math (foo + bar / baz - abc) as a`) f(`math (foo + bar / baz - abc) as a`)
f(`math min(3, foo, (1 + bar) / baz) as a, max(a, b) as b, (abs(c) + 5) as d`)
f(`math round(foo, 0.1) as y`)
} }
func TestParsePipeMathFailure(t *testing.T) { func TestParsePipeMathFailure(t *testing.T) {
@ -30,7 +31,6 @@ func TestParsePipeMathFailure(t *testing.T) {
f(`math`) f(`math`)
f(`math x`) f(`math x`)
f(`math x y`)
f(`math x as`) f(`math x as`)
f(`math abs() as x`) f(`math abs() as x`)
f(`math abs(a, b) as x`) f(`math abs(a, b) as x`)
@ -38,6 +38,9 @@ func TestParsePipeMathFailure(t *testing.T) {
f(`math min(a) as x`) f(`math min(a) as x`)
f(`math max() as x`) f(`math max() as x`)
f(`math max(a) as x`) f(`math max(a) as x`)
f(`math round() as x`)
f(`math round(a) as x`)
f(`math round(a, b, c) as x`)
} }
func TestPipeMath(t *testing.T) { func TestPipeMath(t *testing.T) {
@ -60,7 +63,7 @@ func TestPipeMath(t *testing.T) {
}, },
}) })
f("math 10 * 5 - 3 as a", [][]Field{ f("math 10 * 5 - 3 a", [][]Field{
{ {
{"a", "v1"}, {"a", "v1"},
{"b", "2"}, {"b", "2"},
@ -131,7 +134,7 @@ func TestPipeMath(t *testing.T) {
}, },
}) })
f("math (2*c + (b%c))/(c-b)^(b-1) as a", [][]Field{ f("math round((2*c + (b%c))/(c-b)^(b-1), 0.001) as a", [][]Field{
{ {
{"a", "v"}, {"a", "v"},
{"b", "2"}, {"b", "2"},
@ -153,12 +156,12 @@ func TestPipeMath(t *testing.T) {
{"c", "3"}, {"c", "3"},
}, },
{ {
{"a", "42.25"}, {"a", "3.25"},
{"b", "3"}, {"b", "3"},
{"c", "5"}, {"c", "5"},
}, },
{ {
{"a", "25"}, {"a", "1.667"},
{"b", "3"}, {"b", "3"},
{"c", "6"}, {"c", "6"},
}, },