From 4009de27973a99751f0652071b8f80a6539f9021 Mon Sep 17 00:00:00 2001 From: Aliaksandr Valialkin Date: Tue, 4 Jun 2024 18:14:34 +0200 Subject: [PATCH] wip --- docs/VictoriaLogs/CHANGELOG.md | 1 + docs/VictoriaLogs/LogsQL.md | 4 +++- lib/logstorage/pipe_math.go | 40 ++++++++++++++++++++++++++++++++ lib/logstorage/pipe_math_test.go | 12 +++++++++- 4 files changed, 55 insertions(+), 2 deletions(-) diff --git a/docs/VictoriaLogs/CHANGELOG.md b/docs/VictoriaLogs/CHANGELOG.md index 8a99318ae..b7e14bfda 100644 --- a/docs/VictoriaLogs/CHANGELOG.md +++ b/docs/VictoriaLogs/CHANGELOG.md @@ -19,6 +19,7 @@ according to [these docs](https://docs.victoriametrics.com/victorialogs/quicksta ## tip +* FEATURE: add `ceil()` and `floor()` functions to [`math` pipe](https://docs.victoriametrics.com/victorialogs/logsql/#math-pipe). * FEATURE: add support for bitwise `and`, `or` and `xor` operations at [`math` pipe](https://docs.victoriametrics.com/victorialogs/logsql/#math-pipe). * FEATURE: add support for automatic conversion of [RFC3339 time](https://www.rfc-editor.org/rfc/rfc3339) and IPv4 addresses into numeric representation at [`math` pipe](https://docs.victoriametrics.com/victorialogs/logsql/#math-pipe). * FEATURE: add ability to format numeric fields into string representation of time, duration and IPv4 with [`format` pipe](https://docs.victoriametrics.com/victorialogs/logsql/#format-pipe). diff --git a/docs/VictoriaLogs/LogsQL.md b/docs/VictoriaLogs/LogsQL.md index fb3371fb6..132e0ccb0 100644 --- a/docs/VictoriaLogs/LogsQL.md +++ b/docs/VictoriaLogs/LogsQL.md @@ -1662,7 +1662,9 @@ The following mathematical operations are supported by `math` pipe: - `arg1 xor arg2` - returns bitwise `xor` for `arg1` and `arg2`. It is expected that `arg1` and `arg2` are in the range `[0 .. 2^53-1]` - `arg1 default arg2` - returns `arg2` if `arg1` is non-[numeric](#numeric-values) or 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`. +- `ceil(arg)` - returns the least integer value greater than or equal to `arg` +- `exp(arg)` - powers [`e`](https://en.wikipedia.org/wiki/E_(mathematical_constant)) by `arg` +- `floor(arg)` - returns the greatest integer values less than or equal to `arg` - `ln(arg)` - returns [natural logarithm](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` diff --git a/lib/logstorage/pipe_math.go b/lib/logstorage/pipe_math.go index 5bfbc56a4..1138a38ea 100644 --- a/lib/logstorage/pipe_math.go +++ b/lib/logstorage/pipe_math.go @@ -497,6 +497,10 @@ func parseMathExprOperand(lex *lexer) (*mathExpr, error) { return parseMathExprMin(lex) case lex.isKeyword("round"): return parseMathExprRound(lex) + case lex.isKeyword("ceil"): + return parseMathExprCeil(lex) + case lex.isKeyword("floor"): + return parseMathExprFloor(lex) case lex.isKeyword("-"): return parseMathExprUnaryMinus(lex) case lex.isKeyword("+"): @@ -576,6 +580,28 @@ func parseMathExprRound(lex *lexer) (*mathExpr, error) { return me, nil } +func parseMathExprCeil(lex *lexer) (*mathExpr, error) { + me, err := parseMathExprGenericFunc(lex, "ceil", mathFuncCeil) + if err != nil { + return nil, err + } + if len(me.args) != 1 { + return nil, fmt.Errorf("'ceil' function needs one arg; got %d args: [%s]", len(me.args), me) + } + return me, nil +} + +func parseMathExprFloor(lex *lexer) (*mathExpr, error) { + me, err := parseMathExprGenericFunc(lex, "floor", mathFuncFloor) + if err != nil { + return nil, err + } + if len(me.args) != 1 { + return nil, fmt.Errorf("'floor' function needs one arg; got %d args: [%s]", len(me.args), me) + } + return me, nil +} + func parseMathExprGenericFunc(lex *lexer, funcName string, f mathFunc) (*mathExpr, error) { if !lex.isKeyword(funcName) { return nil, fmt.Errorf("missing %q keyword", funcName) @@ -844,6 +870,20 @@ func mathFuncMin(result []float64, args [][]float64) { } } +func mathFuncCeil(result []float64, args [][]float64) { + arg := args[0] + for i := range result { + result[i] = math.Ceil(arg[i]) + } +} + +func mathFuncFloor(result []float64, args [][]float64) { + arg := args[0] + for i := range result { + result[i] = math.Floor(arg[i]) + } +} + func mathFuncRound(result []float64, args [][]float64) { arg := args[0] if len(args) == 1 { diff --git a/lib/logstorage/pipe_math_test.go b/lib/logstorage/pipe_math_test.go index c6d73488b..67d446cc0 100644 --- a/lib/logstorage/pipe_math_test.go +++ b/lib/logstorage/pipe_math_test.go @@ -135,7 +135,7 @@ func TestPipeMath(t *testing.T) { }, }) - f("math round(exp(a), 0.01), round(ln(a), 0.01)", [][]Field{ + f("math round(exp(a), 0.01), round(ln(a), 0.01), ceil(exp(a)), floor(exp(a))", [][]Field{ { {"a", "v1"}, }, @@ -156,26 +156,36 @@ func TestPipeMath(t *testing.T) { {"a", "v1"}, {"round(exp(a), 0.01)", "NaN"}, {"round(ln(a), 0.01)", "NaN"}, + {"ceil(exp(a))", "NaN"}, + {"floor(exp(a))", "NaN"}, }, { {"a", "0"}, {"round(exp(a), 0.01)", "1"}, {"round(ln(a), 0.01)", "NaN"}, + {"ceil(exp(a))", "1"}, + {"floor(exp(a))", "1"}, }, { {"a", "1"}, {"round(exp(a), 0.01)", "2.72"}, {"round(ln(a), 0.01)", "0"}, + {"ceil(exp(a))", "3"}, + {"floor(exp(a))", "2"}, }, { {"a", "2"}, {"round(exp(a), 0.01)", "7.39"}, {"round(ln(a), 0.01)", "0.69"}, + {"ceil(exp(a))", "8"}, + {"floor(exp(a))", "7"}, }, { {"a", "3"}, {"round(exp(a), 0.01)", "20.09"}, {"round(ln(a), 0.01)", "1.1"}, + {"ceil(exp(a))", "21"}, + {"floor(exp(a))", "20"}, }, })