From 9bf4cc993ed091c79452b65a999d134de0c84a07 Mon Sep 17 00:00:00 2001 From: Jeanette Tan Date: Fri, 1 Dec 2023 01:22:58 +0800 Subject: [PATCH 1/2] Add mad_over_time function Signed-off-by: Jeanette Tan --- docs/querying/functions.md | 1 + promql/functions.go | 20 ++++++++ promql/functions_test.go | 50 +++++++++++++++++++ promql/parser/functions.go | 5 ++ .../src/complete/promql.terms.ts | 6 +++ .../src/parser/parser.test.ts | 5 ++ .../codemirror-promql/src/types/function.ts | 7 +++ web/ui/module/lezer-promql/src/promql.grammar | 2 + 8 files changed, 96 insertions(+) diff --git a/docs/querying/functions.md b/docs/querying/functions.md index 8a4b2e80f..dda88fccd 100644 --- a/docs/querying/functions.md +++ b/docs/querying/functions.md @@ -640,6 +640,7 @@ over time and return an instant vector with per-series aggregation results: * `quantile_over_time(scalar, range-vector)`: the φ-quantile (0 ≤ φ ≤ 1) of the values in the specified interval. * `stddev_over_time(range-vector)`: the population standard deviation of the values in the specified interval. * `stdvar_over_time(range-vector)`: the population standard variance of the values in the specified interval. +* `mad_over_time(range-vector)`: the median absolute deviation of all points in the specified interval. * `last_over_time(range-vector)`: the most recent point value in the specified interval. * `present_over_time(range-vector)`: the value 1 for any series in the specified interval. diff --git a/promql/functions.go b/promql/functions.go index 06f6f8c71..407a11b50 100644 --- a/promql/functions.go +++ b/promql/functions.go @@ -609,6 +609,25 @@ func funcLastOverTime(vals []parser.Value, args parser.Expressions, enh *EvalNod }), nil } +// === mad_over_time(Matrix parser.ValueTypeMatrix) (Vector, Annotations) === +func funcMadOverTime(vals []parser.Value, args parser.Expressions, enh *EvalNodeHelper) (Vector, annotations.Annotations) { + if len(vals[0].(Matrix)[0].Floats) == 0 { + return enh.Out, nil + } + return aggrOverTime(vals, enh, func(s Series) float64 { + values := make(vectorByValueHeap, 0, len(s.Floats)) + for _, f := range s.Floats { + values = append(values, Sample{F: f.F}) + } + median := quantile(0.5, values) + values = make(vectorByValueHeap, 0, len(s.Floats)) + for _, f := range s.Floats { + values = append(values, Sample{F: math.Abs(f.F - median)}) + } + return quantile(0.5, values) + }), nil +} + // === max_over_time(Matrix parser.ValueTypeMatrix) (Vector, Annotations) === func funcMaxOverTime(vals []parser.Value, args parser.Expressions, enh *EvalNodeHelper) (Vector, annotations.Annotations) { if len(vals[0].(Matrix)[0].Floats) == 0 { @@ -1538,6 +1557,7 @@ var FunctionCalls = map[string]FunctionCall{ "log10": funcLog10, "log2": funcLog2, "last_over_time": funcLastOverTime, + "mad_over_time": funcMadOverTime, "max_over_time": funcMaxOverTime, "min_over_time": funcMinOverTime, "minute": funcMinute, diff --git a/promql/functions_test.go b/promql/functions_test.go index faf6859e7..abc8f9dc6 100644 --- a/promql/functions_test.go +++ b/promql/functions_test.go @@ -15,6 +15,7 @@ package promql import ( "context" + "fmt" "math" "testing" "time" @@ -86,3 +87,52 @@ func TestKahanSum(t *testing.T) { expected := 2.0 require.Equal(t, expected, kahanSum(vals)) } + +func TestMadOverTime(t *testing.T) { + cases := []struct { + series []int + expectedRes float64 + }{ + { + series: []int{4, 6, 2, 1, 999, 1, 2}, + expectedRes: 1, + }, + } + + for i, c := range cases { + t.Run(fmt.Sprintf("case %d", i), func(t *testing.T) { + engine := newTestEngine() + storage := teststorage.New(t) + t.Cleanup(func() { storage.Close() }) + + seriesName := "float_series" + + ts := int64(0) + app := storage.Appender(context.Background()) + lbls := labels.FromStrings("__name__", seriesName) + var err error + for _, num := range c.series { + _, err = app.Append(0, lbls, ts, float64(num)) + require.NoError(t, err) + ts += int64(1 * time.Minute / time.Millisecond) + } + require.NoError(t, app.Commit()) + + queryAndCheck := func(queryString string, exp Vector) { + qry, err := engine.NewInstantQuery(context.Background(), storage, nil, queryString, timestamp.Time(ts)) + require.NoError(t, err) + + res := qry.Exec(context.Background()) + require.NoError(t, res.Err) + + vector, err := res.Vector() + require.NoError(t, err) + + require.Equal(t, exp, vector) + } + + queryString := fmt.Sprintf(`mad_over_time(%s[%dm])`, seriesName, len(c.series)) + queryAndCheck(queryString, []Sample{{T: ts, F: c.expectedRes, Metric: labels.EmptyLabels()}}) + }) + } +} diff --git a/promql/parser/functions.go b/promql/parser/functions.go index ee2e90c55..aafb375da 100644 --- a/promql/parser/functions.go +++ b/promql/parser/functions.go @@ -254,6 +254,11 @@ var Functions = map[string]*Function{ ArgTypes: []ValueType{ValueTypeVector}, ReturnType: ValueTypeVector, }, + "mad_over_time": { + Name: "mad_over_time", + ArgTypes: []ValueType{ValueTypeMatrix}, + ReturnType: ValueTypeVector, + }, "max_over_time": { Name: "max_over_time", ArgTypes: []ValueType{ValueTypeMatrix}, diff --git a/web/ui/module/codemirror-promql/src/complete/promql.terms.ts b/web/ui/module/codemirror-promql/src/complete/promql.terms.ts index 77a87c8cc..963fc95f2 100644 --- a/web/ui/module/codemirror-promql/src/complete/promql.terms.ts +++ b/web/ui/module/codemirror-promql/src/complete/promql.terms.ts @@ -317,6 +317,12 @@ export const functionIdentifierTerms = [ info: 'Calculate base-2 logarithm of input series', type: 'function', }, + { + label: 'mad_over_time', + detail: 'function', + info: 'Return the median absolute deviation over time for input series', + type: 'function', + }, { label: 'max_over_time', detail: 'function', diff --git a/web/ui/module/codemirror-promql/src/parser/parser.test.ts b/web/ui/module/codemirror-promql/src/parser/parser.test.ts index 5ef9c1f90..78195a5c6 100644 --- a/web/ui/module/codemirror-promql/src/parser/parser.test.ts +++ b/web/ui/module/codemirror-promql/src/parser/parser.test.ts @@ -95,6 +95,11 @@ describe('promql operations', () => { expectedValueType: ValueType.vector, expectedDiag: [] as Diagnostic[], }, + { + expr: 'mad_over_time(rate(metric_name[5m])[1h:] offset 1m)', + expectedValueType: ValueType.vector, + expectedDiag: [] as Diagnostic[], + }, { expr: 'max_over_time(rate(metric_name[5m])[1h:] offset 1m)', expectedValueType: ValueType.vector, diff --git a/web/ui/module/codemirror-promql/src/types/function.ts b/web/ui/module/codemirror-promql/src/types/function.ts index cceeef90b..369478158 100644 --- a/web/ui/module/codemirror-promql/src/types/function.ts +++ b/web/ui/module/codemirror-promql/src/types/function.ts @@ -56,6 +56,7 @@ import { Ln, Log10, Log2, + MadOverTime, MaxOverTime, MinOverTime, Minute, @@ -370,6 +371,12 @@ const promqlFunctions: { [key: number]: PromQLFunction } = { variadic: 0, returnType: ValueType.vector, }, + [MadOverTime]: { + name: 'mad_over_time', + argTypes: [ValueType.matrix], + variadic: 0, + returnType: ValueType.vector, + }, [MaxOverTime]: { name: 'max_over_time', argTypes: [ValueType.matrix], diff --git a/web/ui/module/lezer-promql/src/promql.grammar b/web/ui/module/lezer-promql/src/promql.grammar index 5280ea800..ab627c829 100644 --- a/web/ui/module/lezer-promql/src/promql.grammar +++ b/web/ui/module/lezer-promql/src/promql.grammar @@ -149,6 +149,7 @@ FunctionIdentifier { Ln | Log10 | Log2 | + MadOverTime | MaxOverTime | MinOverTime | Minute | @@ -380,6 +381,7 @@ NumberLiteral { Ln { condFn<"ln"> } Log10 { condFn<"log10"> } Log2 { condFn<"log2"> } + MadOverTime { condFn<"mad_over_time"> } MaxOverTime { condFn<"max_over_time"> } MinOverTime { condFn<"min_over_time"> } Minute { condFn<"minute"> } From 2910b48180e4988adf90839c7629de11e2fb6129 Mon Sep 17 00:00:00 2001 From: Jeanette Tan Date: Fri, 1 Dec 2023 01:55:01 +0800 Subject: [PATCH 2/2] Make mad_over_time experimental and move tests Signed-off-by: Jeanette Tan --- promql/functions_test.go | 50 ---------------------------------- promql/parser/functions.go | 7 +++-- promql/testdata/functions.test | 8 ++++++ 3 files changed, 12 insertions(+), 53 deletions(-) diff --git a/promql/functions_test.go b/promql/functions_test.go index abc8f9dc6..faf6859e7 100644 --- a/promql/functions_test.go +++ b/promql/functions_test.go @@ -15,7 +15,6 @@ package promql import ( "context" - "fmt" "math" "testing" "time" @@ -87,52 +86,3 @@ func TestKahanSum(t *testing.T) { expected := 2.0 require.Equal(t, expected, kahanSum(vals)) } - -func TestMadOverTime(t *testing.T) { - cases := []struct { - series []int - expectedRes float64 - }{ - { - series: []int{4, 6, 2, 1, 999, 1, 2}, - expectedRes: 1, - }, - } - - for i, c := range cases { - t.Run(fmt.Sprintf("case %d", i), func(t *testing.T) { - engine := newTestEngine() - storage := teststorage.New(t) - t.Cleanup(func() { storage.Close() }) - - seriesName := "float_series" - - ts := int64(0) - app := storage.Appender(context.Background()) - lbls := labels.FromStrings("__name__", seriesName) - var err error - for _, num := range c.series { - _, err = app.Append(0, lbls, ts, float64(num)) - require.NoError(t, err) - ts += int64(1 * time.Minute / time.Millisecond) - } - require.NoError(t, app.Commit()) - - queryAndCheck := func(queryString string, exp Vector) { - qry, err := engine.NewInstantQuery(context.Background(), storage, nil, queryString, timestamp.Time(ts)) - require.NoError(t, err) - - res := qry.Exec(context.Background()) - require.NoError(t, res.Err) - - vector, err := res.Vector() - require.NoError(t, err) - - require.Equal(t, exp, vector) - } - - queryString := fmt.Sprintf(`mad_over_time(%s[%dm])`, seriesName, len(c.series)) - queryAndCheck(queryString, []Sample{{T: ts, F: c.expectedRes, Metric: labels.EmptyLabels()}}) - }) - } -} diff --git a/promql/parser/functions.go b/promql/parser/functions.go index aafb375da..46d50d547 100644 --- a/promql/parser/functions.go +++ b/promql/parser/functions.go @@ -255,9 +255,10 @@ var Functions = map[string]*Function{ ReturnType: ValueTypeVector, }, "mad_over_time": { - Name: "mad_over_time", - ArgTypes: []ValueType{ValueTypeMatrix}, - ReturnType: ValueTypeVector, + Name: "mad_over_time", + ArgTypes: []ValueType{ValueTypeMatrix}, + ReturnType: ValueTypeVector, + Experimental: true, }, "max_over_time": { Name: "max_over_time", diff --git a/promql/testdata/functions.test b/promql/testdata/functions.test index b5263a96f..b4547886a 100644 --- a/promql/testdata/functions.test +++ b/promql/testdata/functions.test @@ -739,6 +739,14 @@ eval instant at 1m stdvar_over_time(metric[1m]) eval instant at 1m stddev_over_time(metric[1m]) {} 0 +# Tests for mad_over_time. +clear +load 10s + metric 4 6 2 1 999 1 2 + +eval instant at 70s mad_over_time(metric[70s]) + {} 1 + # Tests for quantile_over_time clear