diff --git a/pkg/services/ngalert/eval/eval.go b/pkg/services/ngalert/eval/eval.go index b29ba2f9290..bb1d6dfd2ba 100644 --- a/pkg/services/ngalert/eval/eval.go +++ b/pkg/services/ngalert/eval/eval.go @@ -76,12 +76,16 @@ func (e *invalidEvalResultFormatError) Unwrap() error { // ExecutionResults contains the unevaluated results from executing // a condition. type ExecutionResults struct { - Error error + // Condition contains the results of the condition + Condition data.Frames + + // Results contains the results of all queries, reduce and math expressions + Results map[string]data.Frames // NoData contains the DatasourceUID for RefIDs that returned no data. NoData map[string]string - Results data.Frames + Error error } // Results is a slice of evaluated alert instances states. @@ -101,20 +105,24 @@ func (evalResults Results) HasErrors() bool { type Result struct { Instance data.Labels State State // Enum - // Error message for Error state. should be nil if State != Error. - Error error - EvaluatedAt time.Time - EvaluationDuration time.Duration - // EvaluationString is a string representation of evaluation data such - // as EvalMatches (from "classic condition"), and in the future from operations - // like SSE "math". - EvaluationString string + // Error message for Error state. should be nil if State != Error. + Error error + + // Results contains the results of all queries, reduce and math expressions + Results map[string]data.Frames // Values contains the RefID and value of reduce and math expressions. // It does not contain values for classic conditions as the values // in classic conditions do not have a RefID. Values map[string]NumberValueCapture + + EvaluatedAt time.Time + EvaluationDuration time.Duration + // EvaluationString is a string representation of evaluation data such + // as EvalMatches (from "classic condition"), and in the future from operations + // like SSE "math". + EvaluationString string } // State is an enum of the evaluation State for an alert instance. @@ -243,7 +251,7 @@ func queryDataResponseToExecutionResults(c models.Condition, execResp *backend.Q // datasourceExprUID is a special DatasourceUID for expressions datasourceExprUID := strconv.FormatInt(expr.DatasourceID, 10) - var result ExecutionResults + result := ExecutionResults{Results: make(map[string]data.Frames)} for refID, res := range execResp.Responses { if len(res.Frames) == 0 { // to ensure that NoData is consistent with Results we do not initialize NoData @@ -270,12 +278,13 @@ func queryDataResponseToExecutionResults(c models.Condition, execResp *backend.Q } if refID == c.Condition { - result.Results = res.Frames + result.Condition = res.Frames } + result.Results[refID] = res.Frames } // add capture values as data frame metadata to each result (frame) that has matching labels. - for _, frame := range result.Results { + for _, frame := range result.Condition { // classic conditions already have metadata set and only have one value, there's no need to add anything in this case. if frame.Meta != nil && frame.Meta.Custom != nil { if _, ok := frame.Meta.Custom.([]classic.EvalMatch); ok { @@ -422,12 +431,12 @@ func evaluateExecutionResult(execResults ExecutionResults, ts time.Time) Results return evalResults } - if len(execResults.Results) == 0 { + if len(execResults.Condition) == 0 { appendNoData(nil) return evalResults } - for _, f := range execResults.Results { + for _, f := range execResults.Condition { rowLen, err := f.RowLen() if err != nil { appendErrRes(&invalidEvalResultFormatError{refID: f.RefID, reason: "unable to get frame row length", err: err}) diff --git a/pkg/services/ngalert/eval/eval_test.go b/pkg/services/ngalert/eval/eval_test.go index 7a1ff6215cc..5b717d2fcc3 100644 --- a/pkg/services/ngalert/eval/eval_test.go +++ b/pkg/services/ngalert/eval/eval_test.go @@ -30,7 +30,7 @@ func TestEvaluateExecutionResult(t *testing.T) { { desc: "zero valued single instance is single Normal state result", execResults: ExecutionResults{ - Results: []*data.Frame{ + Condition: []*data.Frame{ data.NewFrame("", data.NewField("", nil, []*float64{ptr.Float64(0)})), }, }, @@ -44,7 +44,7 @@ func TestEvaluateExecutionResult(t *testing.T) { { desc: "non-zero valued single instance is single Alerting state result", execResults: ExecutionResults{ - Results: []*data.Frame{ + Condition: []*data.Frame{ data.NewFrame("", data.NewField("", nil, []*float64{ptr.Float64(1)})), }, }, @@ -58,7 +58,7 @@ func TestEvaluateExecutionResult(t *testing.T) { { desc: "nil value single instance is single a NoData state result", execResults: ExecutionResults{ - Results: []*data.Frame{ + Condition: []*data.Frame{ data.NewFrame("", data.NewField("", nil, []*float64{nil})), }, }, @@ -95,7 +95,7 @@ func TestEvaluateExecutionResult(t *testing.T) { { desc: "frame with no fields produces a NoData state result", execResults: ExecutionResults{ - Results: []*data.Frame{ + Condition: []*data.Frame{ data.NewFrame(""), }, }, @@ -109,7 +109,7 @@ func TestEvaluateExecutionResult(t *testing.T) { { desc: "empty field produces a NoData state result", execResults: ExecutionResults{ - Results: []*data.Frame{ + Condition: []*data.Frame{ data.NewFrame("", data.NewField("", nil, []*float64{})), }, }, @@ -123,7 +123,7 @@ func TestEvaluateExecutionResult(t *testing.T) { { desc: "empty field with labels produces a NoData state result with labels", execResults: ExecutionResults{ - Results: []*data.Frame{ + Condition: []*data.Frame{ data.NewFrame("", data.NewField("", data.Labels{"a": "b"}, []*float64{})), }, }, @@ -138,7 +138,7 @@ func TestEvaluateExecutionResult(t *testing.T) { { desc: "malformed frame (unequal lengths) produces Error state result", execResults: ExecutionResults{ - Results: []*data.Frame{ + Condition: []*data.Frame{ data.NewFrame("", data.NewField("", nil, []*float64{ptr.Float64(23)}), data.NewField("", nil, []*float64{}), @@ -156,7 +156,7 @@ func TestEvaluateExecutionResult(t *testing.T) { { desc: "too many fields produces Error state result", execResults: ExecutionResults{ - Results: []*data.Frame{ + Condition: []*data.Frame{ data.NewFrame("", data.NewField("", nil, []*float64{}), data.NewField("", nil, []*float64{}), @@ -174,7 +174,7 @@ func TestEvaluateExecutionResult(t *testing.T) { { desc: "more than one row produces Error state result", execResults: ExecutionResults{ - Results: []*data.Frame{ + Condition: []*data.Frame{ data.NewFrame("", data.NewField("", nil, []*float64{ptr.Float64(2), ptr.Float64(3)}), ), @@ -191,7 +191,7 @@ func TestEvaluateExecutionResult(t *testing.T) { { desc: "time fields (looks like time series) returns error", execResults: ExecutionResults{ - Results: []*data.Frame{ + Condition: []*data.Frame{ data.NewFrame("", data.NewField("", nil, []time.Time{}), ), @@ -208,7 +208,7 @@ func TestEvaluateExecutionResult(t *testing.T) { { desc: "non []*float64 field will produce Error state result", execResults: ExecutionResults{ - Results: []*data.Frame{ + Condition: []*data.Frame{ data.NewFrame("", data.NewField("", nil, []float64{2}), ), @@ -225,7 +225,7 @@ func TestEvaluateExecutionResult(t *testing.T) { { desc: "duplicate labels produce a single Error state result", execResults: ExecutionResults{ - Results: []*data.Frame{ + Condition: []*data.Frame{ data.NewFrame("", data.NewField("", nil, []*float64{ptr.Float64(1)}), ), @@ -245,7 +245,7 @@ func TestEvaluateExecutionResult(t *testing.T) { { desc: "error that produce duplicate empty labels produce a single Error state result", execResults: ExecutionResults{ - Results: []*data.Frame{ + Condition: []*data.Frame{ data.NewFrame("", data.NewField("", data.Labels{"a": "b"}, []float64{2}), ), @@ -265,7 +265,7 @@ func TestEvaluateExecutionResult(t *testing.T) { { desc: "certain errors will produce multiple mixed Error and other state results", execResults: ExecutionResults{ - Results: []*data.Frame{ + Condition: []*data.Frame{ data.NewFrame("", data.NewField("", nil, []float64{3}), ),