diff --git a/model/alerting/alert.go b/model/alerting/alert.go index 2358d1ab..30ee0e26 100644 --- a/model/alerting/alert.go +++ b/model/alerting/alert.go @@ -42,4 +42,13 @@ const ( AlertStateAcknowledge = "acknowledge" AlertStateNormal = "normal" AlertStateError = "error" -) \ No newline at end of file +) + + +/* +{ + RuleID + ResourceID + ResourceName +} +*/ \ No newline at end of file diff --git a/model/alerting/condition.go b/model/alerting/condition.go index 8614f037..657d2649 100644 --- a/model/alerting/condition.go +++ b/model/alerting/condition.go @@ -4,6 +4,8 @@ package alerting +import "fmt" + type Condition struct { Operator string `json:"operator"` Items []ConditionItem `json:"items"` @@ -17,6 +19,32 @@ type ConditionItem struct { Severity string `json:"severity"` Message string `json:"message"` } +func (cond *ConditionItem) GenerateConditionExpression()(conditionExpression string, err error){ + valueLength := len(cond.Values) + if valueLength == 0 { + return conditionExpression, fmt.Errorf("condition values: %v should not be empty", cond.Values) + } + switch cond.Operator { + case "equals": + conditionExpression = fmt.Sprintf("result == %v", cond.Values[0]) + case "gte": + conditionExpression = fmt.Sprintf("result >= %v", cond.Values[0]) + case "lte": + conditionExpression = fmt.Sprintf("result <= %v", cond.Values[0]) + case "gt": + conditionExpression = fmt.Sprintf("result > %v", cond.Values[0]) + case "lt": + conditionExpression = fmt.Sprintf("result < %v", cond.Values[0]) + case "range": + if len(cond.Values) != 2 { + return conditionExpression, fmt.Errorf("length of %s condition values should be 2", cond.Operator) + } + conditionExpression = fmt.Sprintf("result >= %v && result <= %v", cond.Values[0], cond.Values[1]) + default: + return conditionExpression, fmt.Errorf("unsupport condition operator: %s", cond.Operator) + } + return +} type ConditionResult struct { ResultItems []ConditionResultItem `json:"result_items"` diff --git a/model/alerting/metric.go b/model/alerting/metric.go index 411d43c3..c698e000 100644 --- a/model/alerting/metric.go +++ b/model/alerting/metric.go @@ -16,13 +16,12 @@ type Metric struct { Formula string `json:"formula,omitempty"` Expression string `json:"expression" elastic_mapping:"expression:{type:keyword,copy_to:search_text}"` //告警表达式,自动生成 eg: avg(cpu) > 80 } -func (m *Metric) RefreshExpression() error{ +func (m *Metric) GenerateExpression() (string, error){ if len(m.Items) == 1 { - m.Expression = fmt.Sprintf("%s(%s)", m.Items[0].Statistic, m.Items[0].Field) - return nil + return fmt.Sprintf("%s(%s)", m.Items[0].Statistic, m.Items[0].Field), nil } if m.Formula == "" { - return fmt.Errorf("formula should not be empty since there are %d metrics", len(m.Items)) + return "", fmt.Errorf("formula should not be empty since there are %d metrics", len(m.Items)) } var ( expressionBytes = []byte(m.Formula) @@ -32,13 +31,12 @@ func (m *Metric) RefreshExpression() error{ metricExpression = fmt.Sprintf("%s(%s)", item.Statistic, item.Field) reg, err := regexp.Compile(item.Name+`([^\w]|$)`) if err != nil { - return err + return "", err } expressionBytes = reg.ReplaceAll(expressionBytes, []byte(metricExpression+"$1")) } - m.Expression = string(expressionBytes) - return nil + return string(expressionBytes), nil } type MetricItem struct { diff --git a/model/alerting/rule.go b/model/alerting/rule.go index 508f8869..293f0ec2 100644 --- a/model/alerting/rule.go +++ b/model/alerting/rule.go @@ -5,6 +5,7 @@ package alerting import ( + "strings" "time" ) @@ -23,6 +24,31 @@ type Rule struct { LastTermStartTime time.Time `json:"-"` //标识最近一轮告警的开始时间 LastEscalationTime time.Time `json:"-"` //标识最近一次告警升级发送通知的时间 SearchText string `json:"-" elastic_mapping:"search_text:{type:text,index_prefixes:{},index_phrases:true, analyzer:suggest_text_search }"` + Expression string `json:"-"` +} + +func (rule *Rule) GetOrInitExpression() (string, error){ + if rule.Expression != ""{ + return rule.Expression, nil + } + sb := strings.Builder{} + for i, cond := range rule.Conditions.Items { + condExp, err := cond.GenerateConditionExpression() + if err != nil { + return "", err + } + sb.WriteString(condExp) + + if i < len(rule.Conditions.Items)-1 { + sb.WriteString(" or ") + } + } + metricExp, err := rule.Metrics.GenerateExpression() + if err != nil { + return "", err + } + rule.Expression = strings.ReplaceAll(sb.String(), "result", metricExp) + return rule.Expression, nil } type RuleChannel struct { diff --git a/model/alerting/rule_test.go b/model/alerting/rule_test.go index e6192b68..21583231 100644 --- a/model/alerting/rule_test.go +++ b/model/alerting/rule_test.go @@ -25,7 +25,7 @@ func TestCreateRule( t *testing.T) { Type: "elasticsearch", Objects: []string{".infini_metrics*"}, TimeField: "timestamp", - Filter: Filter{ + Filter: FilterQuery{ And: []FilterQuery{ //{Field: "timestamp", Operator: "gte", Values: []string{"now-15m"}}, //{Field: "payload.elasticsearch.cluster_health.status", Operator: "equals", Values: []string{"red"}}, @@ -106,13 +106,17 @@ func TestCreateRule( t *testing.T) { EscalationThrottlePeriod: "30m", }, } - //err := rule.Metrics.RefreshExpression() + //err := rule.Metrics.GenerateExpression() //if err != nil { // t.Fatal(err) //} + exp, err := rule.GetOrInitExpression() + if err != nil { + t.Fatal(err) + } - fmt.Println(util.MustToJSON(rule)) - //fmt.Println(rule.Metrics.Expression) + //fmt.Println(util.MustToJSON(rule)) + fmt.Println(exp) } diff --git a/plugin/api/alerting/rule.go b/plugin/api/alerting/rule.go index f9157aff..bd556ab5 100644 --- a/plugin/api/alerting/rule.go +++ b/plugin/api/alerting/rule.go @@ -37,7 +37,7 @@ func (alertAPI *AlertAPI) createRule(w http.ResponseWriter, req *http.Request, p }, http.StatusInternalServerError) return } - err = rule.Metrics.RefreshExpression() + rule.Metrics.Expression, err = rule.Metrics.GenerateExpression() if err != nil { alertAPI.WriteJSON(w, util.MapStr{ "error": err.Error(), @@ -135,7 +135,7 @@ func (alertAPI *AlertAPI) updateRule(w http.ResponseWriter, req *http.Request, p obj.ID = id obj.Created = create obj.Updated = time.Now() - err = obj.Metrics.RefreshExpression() + obj.Metrics.Expression, err = obj.Metrics.GenerateExpression() if err != nil { alertAPI.WriteError(w, err.Error(), http.StatusInternalServerError) log.Error(err) diff --git a/service/alerting/elasticsearch/engine.go b/service/alerting/elasticsearch/engine.go index e38028dd..331ff065 100644 --- a/service/alerting/elasticsearch/engine.go +++ b/service/alerting/elasticsearch/engine.go @@ -424,29 +424,9 @@ func (engine *Engine) CheckCondition(rule *alerting.Rule)(*alerting.ConditionRes }) LoopCondition: for _, cond := range rule.Conditions.Items { - conditionExpression := "" - valueLength := len(cond.Values) - if valueLength == 0 { - return conditionResult, fmt.Errorf("condition values: %v should not be empty", cond.Values) - } - switch cond.Operator { - case "equals": - conditionExpression = fmt.Sprintf("result == %v", cond.Values[0]) - case "gte": - conditionExpression = fmt.Sprintf("result >= %v", cond.Values[0]) - case "lte": - conditionExpression = fmt.Sprintf("result <= %v", cond.Values[0]) - case "gt": - conditionExpression = fmt.Sprintf("result > %v", cond.Values[0]) - case "lt": - conditionExpression = fmt.Sprintf("result < %v", cond.Values[0]) - case "range": - if valueLength != 2 { - return conditionResult, fmt.Errorf("length of %s condition values should be 2", cond.Operator) - } - conditionExpression = fmt.Sprintf("result >= %v && result <= %v", cond.Values[0], cond.Values[1]) - default: - return conditionResult, fmt.Errorf("unsupport condition operator: %s", cond.Operator) + conditionExpression, err := cond.GenerateConditionExpression() + if err != nil { + return conditionResult, err } expression, err := govaluate.NewEvaluableExpression(conditionExpression) if err != nil { @@ -882,4 +862,4 @@ func readTimeFromKV(bucketKey string, key []byte)(time.Time, error){ return time.ParseInLocation(time.RFC3339, string(timeBytes), time.UTC) } return zeroTime, nil -} \ No newline at end of file +}