From 1cd8981be4be8c28fbc2bea1a400002a3028637b Mon Sep 17 00:00:00 2001 From: Kyle Brandt Date: Tue, 23 Mar 2021 12:23:54 -0400 Subject: [PATCH] SSE/NG: Add expression metadata for EvalMatch (#32213) - Add the ability to stick metadata attached to a value in expressions. Currently uses Frame.Meta.Custom. - None of this is consumed by anything yet, so an incremental step. --- pkg/expr/classic/classic.go | 33 ++++++++++++++++++++++++++++++++ pkg/expr/classic/classic_test.go | 26 ++++++++++++++++--------- pkg/expr/mathexp/type_series.go | 8 ++++++++ pkg/expr/mathexp/types.go | 18 +++++++++++++++++ 4 files changed, 76 insertions(+), 9 deletions(-) diff --git a/pkg/expr/classic/classic.go b/pkg/expr/classic/classic.go index 13e96da863c..f4e7b503055 100644 --- a/pkg/expr/classic/classic.go +++ b/pkg/expr/classic/classic.go @@ -5,6 +5,7 @@ import ( "encoding/json" "fmt" + "github.com/grafana/grafana-plugin-sdk-go/data" "github.com/grafana/grafana/pkg/expr/mathexp" ) @@ -59,6 +60,13 @@ func (ccc *ConditionsCmd) NeedsVars() []string { return vars } +// EvalMatch represents the series violating the threshold. +type EvalMatch struct { + Value *float64 `json:"value"` + Metric string `json:"metric"` + Labels data.Labels `json:"labels"` +} + // Execute runs the command and returns the results or an error if the command // failed to execute. func (ccc *ConditionsCmd) Execute(ctx context.Context, vars mathexp.Vars) (mathexp.Results, error) { @@ -66,8 +74,11 @@ func (ccc *ConditionsCmd) Execute(ctx context.Context, vars mathexp.Vars) (mathe newRes := mathexp.Results{} noDataFound := true + matches := []EvalMatch{} + for i, c := range ccc.Conditions { querySeriesSet := vars[c.QueryRefID] + nilReducedCount := 0 for _, val := range querySeriesSet.Values { series, ok := val.(mathexp.Series) if !ok { @@ -78,8 +89,23 @@ func (ccc *ConditionsCmd) Execute(ctx context.Context, vars mathexp.Vars) (mathe // TODO handle error / no data signals thisCondNoDataFound := reducedNum.GetFloat64Value() == nil + if thisCondNoDataFound { + nilReducedCount++ + } + evalRes := c.Evaluator.Eval(reducedNum) + if evalRes { + match := EvalMatch{ + Value: reducedNum.GetFloat64Value(), + Metric: series.GetName(), + } + if reducedNum.GetLabels() != nil { + match.Labels = reducedNum.GetLabels().Copy() + } + matches = append(matches, match) + } + if i == 0 { firing = evalRes noDataFound = thisCondNoDataFound @@ -93,10 +119,17 @@ func (ccc *ConditionsCmd) Execute(ctx context.Context, vars mathexp.Vars) (mathe noDataFound = noDataFound && thisCondNoDataFound } } + if len(querySeriesSet.Values) == nilReducedCount { + matches = append(matches, EvalMatch{ + Metric: "NoData", + }) + } } num := mathexp.NewNumber("", nil) + num.SetMeta(matches) + var v float64 switch { case noDataFound: diff --git a/pkg/expr/classic/classic_test.go b/pkg/expr/classic/classic_test.go index e211dc8289d..2828cf6c998 100644 --- a/pkg/expr/classic/classic_test.go +++ b/pkg/expr/classic/classic_test.go @@ -114,15 +114,11 @@ func TestUnmarshalConditionCMD(t *testing.T) { } func TestConditionsCmdExecute(t *testing.T) { - trueNumber := valBasedNumber(ptr.Float64(1)) - falseNumber := valBasedNumber(ptr.Float64(0)) - noDataNumber := valBasedNumber(nil) - tests := []struct { name string vars mathexp.Vars conditionsCmd *ConditionsCmd - resultNumber mathexp.Number + resultNumber func() mathexp.Number }{ { name: "single query and single condition", @@ -142,7 +138,11 @@ func TestConditionsCmdExecute(t *testing.T) { Evaluator: &thresholdEvaluator{Type: "gt", Threshold: 34}, }, }}, - resultNumber: trueNumber, + resultNumber: func() mathexp.Number { + v := valBasedNumber(ptr.Float64(1)) + v.SetMeta([]EvalMatch{{Value: ptr.Float64(35)}}) + return v + }, }, { name: "single query and single ranged condition", @@ -163,7 +163,11 @@ func TestConditionsCmdExecute(t *testing.T) { }, }, }, - resultNumber: falseNumber, + resultNumber: func() mathexp.Number { + v := valBasedNumber(ptr.Float64(0)) + v.SetMeta([]EvalMatch{}) + return v + }, }, { name: "single query with no data", @@ -182,7 +186,11 @@ func TestConditionsCmdExecute(t *testing.T) { }, }, }, - resultNumber: noDataNumber, + resultNumber: func() mathexp.Number { + v := valBasedNumber(nil) + v.SetMeta([]EvalMatch{{Metric: "NoData"}}) + return v + }, }, } @@ -193,7 +201,7 @@ func TestConditionsCmdExecute(t *testing.T) { require.Equal(t, 1, len(res.Values)) - require.Equal(t, tt.resultNumber, res.Values[0]) + require.Equal(t, tt.resultNumber(), res.Values[0]) }) } } diff --git a/pkg/expr/mathexp/type_series.go b/pkg/expr/mathexp/type_series.go index 3ffd39e81f7..a4146d9abf9 100644 --- a/pkg/expr/mathexp/type_series.go +++ b/pkg/expr/mathexp/type_series.go @@ -97,6 +97,14 @@ func (s Series) SetLabels(ls data.Labels) { s.Frame.Fields[s.ValueIdx].Labels = func (s Series) GetName() string { return s.Frame.Name } +func (s Series) GetMeta() interface{} { + return s.Frame.Meta.Custom +} + +func (s Series) SetMeta(v interface{}) { + s.Frame.SetMeta(&data.FrameMeta{Custom: v}) +} + // AsDataFrame returns the underlying *data.Frame. func (s Series) AsDataFrame() *data.Frame { return s.Frame } diff --git a/pkg/expr/mathexp/types.go b/pkg/expr/mathexp/types.go index 867796eaa70..d60e5df06e7 100644 --- a/pkg/expr/mathexp/types.go +++ b/pkg/expr/mathexp/types.go @@ -30,6 +30,8 @@ type Value interface { Value() interface{} GetLabels() data.Labels SetLabels(data.Labels) + GetMeta() interface{} + SetMeta(interface{}) AsDataFrame() *data.Frame } @@ -48,6 +50,14 @@ func (s Scalar) GetLabels() data.Labels { return nil } func (s Scalar) SetLabels(ls data.Labels) {} +func (s Scalar) GetMeta() interface{} { + return s.Frame.Meta.Custom +} + +func (s Scalar) SetMeta(v interface{}) { + s.Frame.SetMeta(&data.FrameMeta{Custom: v}) +} + // AsDataFrame returns the underlying *data.Frame. func (s Scalar) AsDataFrame() *data.Frame { return s.Frame } @@ -105,3 +115,11 @@ func NewNumber(name string, labels data.Labels) Number { ), } } + +func (n Number) GetMeta() interface{} { + return n.Frame.Meta.Custom +} + +func (n Number) SetMeta(v interface{}) { + n.Frame.SetMeta(&data.FrameMeta{Custom: v}) +}