2021-03-02 12:51:33 -06:00
|
|
|
package classic
|
|
|
|
|
|
|
|
import (
|
|
|
|
"context"
|
|
|
|
"encoding/json"
|
|
|
|
"fmt"
|
2021-07-21 09:04:40 -05:00
|
|
|
"strconv"
|
2021-03-02 12:51:33 -06:00
|
|
|
|
2021-03-23 11:23:54 -05:00
|
|
|
"github.com/grafana/grafana-plugin-sdk-go/data"
|
2022-05-23 09:08:14 -05:00
|
|
|
|
2021-03-02 12:51:33 -06:00
|
|
|
"github.com/grafana/grafana/pkg/expr/mathexp"
|
|
|
|
)
|
|
|
|
|
|
|
|
// ConditionsCmd is command for the classic conditions
|
|
|
|
// expression operation.
|
|
|
|
type ConditionsCmd struct {
|
|
|
|
Conditions []condition
|
|
|
|
refID string
|
|
|
|
}
|
|
|
|
|
2021-03-23 11:11:15 -05:00
|
|
|
// ClassicConditionJSON is the JSON model for a single condition.
|
2021-03-02 12:51:33 -06:00
|
|
|
// It is based on services/alerting/conditions/query.go's newQueryCondition().
|
2021-03-23 11:11:15 -05:00
|
|
|
type ClassicConditionJSON struct {
|
|
|
|
Evaluator ConditionEvalJSON `json:"evaluator"`
|
2021-03-02 12:51:33 -06:00
|
|
|
|
|
|
|
Operator struct {
|
|
|
|
Type string `json:"type"`
|
|
|
|
} `json:"operator"`
|
|
|
|
|
|
|
|
Query struct {
|
2021-04-07 09:12:22 -05:00
|
|
|
Params []string `json:"params"`
|
2021-03-02 12:51:33 -06:00
|
|
|
} `json:"query"`
|
|
|
|
|
|
|
|
Reducer struct {
|
|
|
|
// Params []interface{} `json:"params"` (Unused)
|
|
|
|
Type string `json:"type"`
|
|
|
|
} `json:"reducer"`
|
|
|
|
}
|
|
|
|
|
2021-03-23 11:11:15 -05:00
|
|
|
type ConditionEvalJSON struct {
|
2021-03-02 12:51:33 -06:00
|
|
|
Params []float64 `json:"params"`
|
|
|
|
Type string `json:"type"` // e.g. "gt"
|
|
|
|
}
|
|
|
|
|
|
|
|
// condition is a single condition within the ConditionsCmd.
|
|
|
|
type condition struct {
|
|
|
|
QueryRefID string
|
|
|
|
Reducer classicReducer
|
|
|
|
Evaluator evaluator
|
|
|
|
Operator string
|
|
|
|
}
|
|
|
|
|
|
|
|
type classicReducer string
|
|
|
|
|
|
|
|
// NeedsVars returns the variable names (refIds) that are dependencies
|
|
|
|
// to execute the command and allows the command to fulfill the Command interface.
|
|
|
|
func (ccc *ConditionsCmd) NeedsVars() []string {
|
|
|
|
vars := []string{}
|
|
|
|
for _, c := range ccc.Conditions {
|
|
|
|
vars = append(vars, c.QueryRefID)
|
|
|
|
}
|
|
|
|
return vars
|
|
|
|
}
|
|
|
|
|
2021-03-23 11:23:54 -05:00
|
|
|
// EvalMatch represents the series violating the threshold.
|
2021-07-21 09:04:40 -05:00
|
|
|
// It goes into the metadata of data frames so it can be extracted.
|
2021-03-23 11:23:54 -05:00
|
|
|
type EvalMatch struct {
|
|
|
|
Value *float64 `json:"value"`
|
|
|
|
Metric string `json:"metric"`
|
|
|
|
Labels data.Labels `json:"labels"`
|
|
|
|
}
|
|
|
|
|
2021-07-21 09:04:40 -05:00
|
|
|
func (em EvalMatch) MarshalJSON() ([]byte, error) {
|
|
|
|
fs := ""
|
|
|
|
if em.Value != nil {
|
|
|
|
fs = strconv.FormatFloat(*em.Value, 'f', -1, 64)
|
|
|
|
}
|
|
|
|
return json.Marshal(struct {
|
|
|
|
Value string `json:"value"`
|
|
|
|
Metric string `json:"metric"`
|
|
|
|
Labels data.Labels `json:"labels"`
|
|
|
|
}{
|
|
|
|
fs,
|
|
|
|
em.Metric,
|
|
|
|
em.Labels,
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2021-03-02 12:51:33 -06:00
|
|
|
// 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) {
|
|
|
|
firing := true
|
|
|
|
newRes := mathexp.Results{}
|
|
|
|
noDataFound := true
|
|
|
|
|
2021-03-23 11:23:54 -05:00
|
|
|
matches := []EvalMatch{}
|
|
|
|
|
2021-03-02 12:51:33 -06:00
|
|
|
for i, c := range ccc.Conditions {
|
|
|
|
querySeriesSet := vars[c.QueryRefID]
|
2021-03-23 11:23:54 -05:00
|
|
|
nilReducedCount := 0
|
2021-04-02 07:46:32 -05:00
|
|
|
firingCount := 0
|
2021-03-02 12:51:33 -06:00
|
|
|
for _, val := range querySeriesSet.Values {
|
2022-05-23 09:08:14 -05:00
|
|
|
var reducedNum mathexp.Number
|
|
|
|
var name string
|
|
|
|
switch v := val.(type) {
|
|
|
|
case mathexp.Series:
|
|
|
|
reducedNum = c.Reducer.Reduce(v)
|
|
|
|
name = v.GetName()
|
|
|
|
case mathexp.Number:
|
|
|
|
reducedNum = v
|
|
|
|
if len(v.Frame.Fields) > 0 {
|
|
|
|
name = v.Frame.Fields[0].Name
|
|
|
|
}
|
|
|
|
default:
|
2021-03-02 12:51:33 -06:00
|
|
|
return newRes, fmt.Errorf("can only reduce type series, got type %v", val.Type())
|
|
|
|
}
|
|
|
|
|
|
|
|
// TODO handle error / no data signals
|
|
|
|
thisCondNoDataFound := reducedNum.GetFloat64Value() == nil
|
|
|
|
|
2021-03-23 11:23:54 -05:00
|
|
|
if thisCondNoDataFound {
|
|
|
|
nilReducedCount++
|
|
|
|
}
|
|
|
|
|
2021-03-02 12:51:33 -06:00
|
|
|
evalRes := c.Evaluator.Eval(reducedNum)
|
|
|
|
|
2021-03-23 11:23:54 -05:00
|
|
|
if evalRes {
|
|
|
|
match := EvalMatch{
|
|
|
|
Value: reducedNum.GetFloat64Value(),
|
2022-05-23 09:08:14 -05:00
|
|
|
Metric: name,
|
2021-03-23 11:23:54 -05:00
|
|
|
}
|
|
|
|
if reducedNum.GetLabels() != nil {
|
|
|
|
match.Labels = reducedNum.GetLabels().Copy()
|
|
|
|
}
|
|
|
|
matches = append(matches, match)
|
2021-04-02 07:46:32 -05:00
|
|
|
firingCount++
|
2021-03-23 11:23:54 -05:00
|
|
|
}
|
2021-04-02 07:46:32 -05:00
|
|
|
}
|
2021-03-23 11:23:54 -05:00
|
|
|
|
2021-04-02 07:46:32 -05:00
|
|
|
thisCondFiring := firingCount > 0
|
2022-02-24 18:01:04 -06:00
|
|
|
thisCondNoData := len(querySeriesSet.Values) == nilReducedCount
|
2021-03-02 12:51:33 -06:00
|
|
|
|
2021-04-02 07:46:32 -05:00
|
|
|
if i == 0 {
|
|
|
|
firing = thisCondFiring
|
|
|
|
noDataFound = thisCondNoData
|
2021-03-02 12:51:33 -06:00
|
|
|
}
|
2021-04-02 07:46:32 -05:00
|
|
|
|
|
|
|
if c.Operator == "or" {
|
|
|
|
firing = firing || thisCondFiring
|
|
|
|
noDataFound = noDataFound || thisCondNoData
|
|
|
|
} else {
|
|
|
|
firing = firing && thisCondFiring
|
|
|
|
noDataFound = noDataFound && thisCondNoData
|
|
|
|
}
|
|
|
|
|
2022-02-24 18:01:04 -06:00
|
|
|
if thisCondNoData {
|
2021-03-23 11:23:54 -05:00
|
|
|
matches = append(matches, EvalMatch{
|
|
|
|
Metric: "NoData",
|
|
|
|
})
|
2021-04-02 07:46:32 -05:00
|
|
|
noDataFound = true
|
2021-03-23 11:23:54 -05:00
|
|
|
}
|
2021-04-02 07:46:32 -05:00
|
|
|
|
|
|
|
firingCount = 0
|
|
|
|
nilReducedCount = 0
|
2021-03-02 12:51:33 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
num := mathexp.NewNumber("", nil)
|
|
|
|
|
2021-03-23 11:23:54 -05:00
|
|
|
num.SetMeta(matches)
|
|
|
|
|
2021-03-02 12:51:33 -06:00
|
|
|
var v float64
|
|
|
|
switch {
|
|
|
|
case noDataFound:
|
|
|
|
num.SetValue(nil)
|
|
|
|
case firing:
|
|
|
|
v = 1
|
|
|
|
num.SetValue(&v)
|
|
|
|
case !firing:
|
|
|
|
num.SetValue(&v)
|
|
|
|
}
|
|
|
|
|
|
|
|
newRes.Values = append(newRes.Values, num)
|
|
|
|
|
|
|
|
return newRes, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// UnmarshalConditionsCmd creates a new ConditionsCmd.
|
|
|
|
func UnmarshalConditionsCmd(rawQuery map[string]interface{}, refID string) (*ConditionsCmd, error) {
|
|
|
|
jsonFromM, err := json.Marshal(rawQuery["conditions"])
|
|
|
|
if err != nil {
|
|
|
|
return nil, fmt.Errorf("failed to remarshal classic condition body: %w", err)
|
|
|
|
}
|
2021-03-23 11:11:15 -05:00
|
|
|
var ccj []ClassicConditionJSON
|
2021-03-02 12:51:33 -06:00
|
|
|
if err = json.Unmarshal(jsonFromM, &ccj); err != nil {
|
|
|
|
return nil, fmt.Errorf("failed to unmarshal remarshaled classic condition body: %w", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
c := &ConditionsCmd{
|
|
|
|
refID: refID,
|
|
|
|
}
|
|
|
|
|
|
|
|
for i, cj := range ccj {
|
|
|
|
cond := condition{}
|
|
|
|
|
2021-03-23 11:11:15 -05:00
|
|
|
if i > 0 && cj.Operator.Type != "and" && cj.Operator.Type != "or" {
|
2022-09-21 14:14:11 -05:00
|
|
|
return nil, fmt.Errorf("condition %v operator must be `and` or `or`", i+1)
|
2021-03-02 12:51:33 -06:00
|
|
|
}
|
|
|
|
cond.Operator = cj.Operator.Type
|
|
|
|
|
|
|
|
if len(cj.Query.Params) == 0 || cj.Query.Params[0] == "" {
|
2022-09-21 14:14:11 -05:00
|
|
|
return nil, fmt.Errorf("condition %v is missing the query refID argument", i+1)
|
2021-03-02 12:51:33 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
cond.QueryRefID = cj.Query.Params[0]
|
|
|
|
|
|
|
|
cond.Reducer = classicReducer(cj.Reducer.Type)
|
|
|
|
if !cond.Reducer.ValidReduceFunc() {
|
2022-09-21 14:14:11 -05:00
|
|
|
return nil, fmt.Errorf("invalid reducer '%v' in condition %v", cond.Reducer, i+1)
|
2021-03-02 12:51:33 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
cond.Evaluator, err = newAlertEvaluator(cj.Evaluator)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
c.Conditions = append(c.Conditions, cond)
|
|
|
|
}
|
|
|
|
|
|
|
|
return c, nil
|
|
|
|
}
|