From c75c8d7953bb7df0403a71022c6990cc2f115992 Mon Sep 17 00:00:00 2001 From: Aliaksandr Valialkin Date: Thu, 30 May 2024 15:09:39 +0200 Subject: [PATCH] wip --- docs/VictoriaLogs/LogsQL.md | 3 +++ lib/logstorage/pipe_math.go | 40 ++++++++++++++++++++++++++++ lib/logstorage/pipe_math_test.go | 45 ++++++++++++++++++++++++++++++++ 3 files changed, 88 insertions(+) diff --git a/docs/VictoriaLogs/LogsQL.md b/docs/VictoriaLogs/LogsQL.md index d1ae2dae4..6b785e977 100644 --- a/docs/VictoriaLogs/LogsQL.md +++ b/docs/VictoriaLogs/LogsQL.md @@ -1623,6 +1623,8 @@ The following mathematical operations are supported by `math` pipe: - `arg1 ^ arg2` - returns the power of `arg1` by `arg2` - `arg1 default arg2` - returns `arg2` if `arg1` equals to `NaN`. - `abs(arg)` - returns an absolute value for the given `arg` +- `exp(arg)` - powers [`e`](https://en.wikipedia.org/wiki/E_(mathematical_constant)) by `arg`. +- `ln(arg)` - returns [natural logarightm](https://en.wikipedia.org/wiki/Natural_logarithm) for the given `arg`. - `max(arg1, ..., argN)` - returns the maximum value among the given `arg1`, ..., `argN` - `min(arg1, ..., argN)` - returns the minimum value among the given `arg1`, ..., `argN` - `round(arg)` - returns rounded to integer value for the given `arg`. The `round()` accepts optional `nearest` arg, which allows rounding the number to the given `nearest` multiple. @@ -1631,6 +1633,7 @@ The following mathematical operations are supported by `math` pipe: Every `argX` argument in every mathematical operation can contain one of the following values: - The name of [log field](https://docs.victoriametrics.com/victorialogs/keyconcepts/#data-model). For example, `errors_total / requests_total`. + If the log field contains value, which cannot be parsed into [supported numeric value](#numeric-values), then it is replaced with `NaN`. - Any [supported numeric value](#numeric-values). For example, `response_size_bytes / 1MiB`. - Another mathematical expression. Optionally, it may be put inside `(...)`. For example, `(a + b) * c`. diff --git a/lib/logstorage/pipe_math.go b/lib/logstorage/pipe_math.go index 50702b372..11bd5d5cd 100644 --- a/lib/logstorage/pipe_math.go +++ b/lib/logstorage/pipe_math.go @@ -479,6 +479,10 @@ func parseMathExprOperand(lex *lexer) (*mathExpr, error) { switch { case lex.isKeyword("abs"): return parseMathExprAbs(lex) + case lex.isKeyword("exp"): + return parseMathExprExp(lex) + case lex.isKeyword("ln"): + return parseMathExprLn(lex) case lex.isKeyword("max"): return parseMathExprMax(lex) case lex.isKeyword("min"): @@ -509,6 +513,28 @@ func parseMathExprAbs(lex *lexer) (*mathExpr, error) { return me, nil } +func parseMathExprExp(lex *lexer) (*mathExpr, error) { + me, err := parseMathExprGenericFunc(lex, "exp", mathFuncExp) + if err != nil { + return nil, err + } + if len(me.args) != 1 { + return nil, fmt.Errorf("'exp' function accepts only one arg; got %d args: [%s]", len(me.args), me) + } + return me, nil +} + +func parseMathExprLn(lex *lexer) (*mathExpr, error) { + me, err := parseMathExprGenericFunc(lex, "ln", mathFuncLn) + if err != nil { + return nil, err + } + if len(me.args) != 1 { + return nil, fmt.Errorf("'ln' function accepts only one arg; got %d args: [%s]", len(me.args), me) + } + return me, nil +} + func parseMathExprMax(lex *lexer) (*mathExpr, error) { me, err := parseMathExprGenericFunc(lex, "max", mathFuncMax) if err != nil { @@ -729,6 +755,20 @@ func mathFuncAbs(result []float64, args [][]float64) { } } +func mathFuncExp(result []float64, args [][]float64) { + arg := args[0] + for i := range result { + result[i] = math.Exp(arg[i]) + } +} + +func mathFuncLn(result []float64, args [][]float64) { + arg := args[0] + for i := range result { + result[i] = math.Log(arg[i]) + } +} + func mathFuncUnaryMinus(result []float64, args [][]float64) { arg := args[0] for i := range result { diff --git a/lib/logstorage/pipe_math_test.go b/lib/logstorage/pipe_math_test.go index 4b1536268..7795263ac 100644 --- a/lib/logstorage/pipe_math_test.go +++ b/lib/logstorage/pipe_math_test.go @@ -23,6 +23,7 @@ func TestParsePipeMathSuccess(t *testing.T) { f(`math round(foo) as x`) f(`math round(foo, 0.1) as y`) f(`math (a / b default 10) as z`) + f(`math (ln(a) + exp(b)) as x`) } func TestParsePipeMathFailure(t *testing.T) { @@ -107,6 +108,50 @@ func TestPipeMath(t *testing.T) { }, }) + f("math round(exp(a), 0.01), round(ln(a), 0.01)", [][]Field{ + { + {"a", "v1"}, + }, + { + {"a", "0"}, + }, + { + {"a", "1"}, + }, + { + {"a", "2"}, + }, + { + {"a", "3"}, + }, + }, [][]Field{ + { + {"a", "v1"}, + {"round(exp(a), 0.01)", "NaN"}, + {"round(ln(a), 0.01)", "NaN"}, + }, + { + {"a", "0"}, + {"round(exp(a), 0.01)", "1"}, + {"round(ln(a), 0.01)", "NaN"}, + }, + { + {"a", "1"}, + {"round(exp(a), 0.01)", "2.72"}, + {"round(ln(a), 0.01)", "0"}, + }, + { + {"a", "2"}, + {"round(exp(a), 0.01)", "7.39"}, + {"round(ln(a), 0.01)", "0.69"}, + }, + { + {"a", "3"}, + {"round(exp(a), 0.01)", "20.09"}, + {"round(ln(a), 0.01)", "1.1"}, + }, + }) + f("math 1 as a", [][]Field{ { {"a", "v1"},