update rule expression

This commit is contained in:
liugq 2022-04-27 15:31:20 +08:00
parent 6ac22f53a3
commit 2461773428
7 changed files with 83 additions and 38 deletions

View File

@ -42,4 +42,13 @@ const (
AlertStateAcknowledge = "acknowledge" AlertStateAcknowledge = "acknowledge"
AlertStateNormal = "normal" AlertStateNormal = "normal"
AlertStateError = "error" AlertStateError = "error"
) )
/*
{
RuleID
ResourceID
ResourceName
}
*/

View File

@ -4,6 +4,8 @@
package alerting package alerting
import "fmt"
type Condition struct { type Condition struct {
Operator string `json:"operator"` Operator string `json:"operator"`
Items []ConditionItem `json:"items"` Items []ConditionItem `json:"items"`
@ -17,6 +19,32 @@ type ConditionItem struct {
Severity string `json:"severity"` Severity string `json:"severity"`
Message string `json:"message"` 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 { type ConditionResult struct {
ResultItems []ConditionResultItem `json:"result_items"` ResultItems []ConditionResultItem `json:"result_items"`

View File

@ -16,13 +16,12 @@ type Metric struct {
Formula string `json:"formula,omitempty"` Formula string `json:"formula,omitempty"`
Expression string `json:"expression" elastic_mapping:"expression:{type:keyword,copy_to:search_text}"` //告警表达式,自动生成 eg: avg(cpu) > 80 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 { if len(m.Items) == 1 {
m.Expression = fmt.Sprintf("%s(%s)", m.Items[0].Statistic, m.Items[0].Field) return fmt.Sprintf("%s(%s)", m.Items[0].Statistic, m.Items[0].Field), nil
return nil
} }
if m.Formula == "" { 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 ( var (
expressionBytes = []byte(m.Formula) expressionBytes = []byte(m.Formula)
@ -32,13 +31,12 @@ func (m *Metric) RefreshExpression() error{
metricExpression = fmt.Sprintf("%s(%s)", item.Statistic, item.Field) metricExpression = fmt.Sprintf("%s(%s)", item.Statistic, item.Field)
reg, err := regexp.Compile(item.Name+`([^\w]|$)`) reg, err := regexp.Compile(item.Name+`([^\w]|$)`)
if err != nil { if err != nil {
return err return "", err
} }
expressionBytes = reg.ReplaceAll(expressionBytes, []byte(metricExpression+"$1")) expressionBytes = reg.ReplaceAll(expressionBytes, []byte(metricExpression+"$1"))
} }
m.Expression = string(expressionBytes) return string(expressionBytes), nil
return nil
} }
type MetricItem struct { type MetricItem struct {

View File

@ -5,6 +5,7 @@
package alerting package alerting
import ( import (
"strings"
"time" "time"
) )
@ -23,6 +24,31 @@ type Rule struct {
LastTermStartTime time.Time `json:"-"` //标识最近一轮告警的开始时间 LastTermStartTime time.Time `json:"-"` //标识最近一轮告警的开始时间
LastEscalationTime 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 }"` 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 { type RuleChannel struct {

View File

@ -25,7 +25,7 @@ func TestCreateRule( t *testing.T) {
Type: "elasticsearch", Type: "elasticsearch",
Objects: []string{".infini_metrics*"}, Objects: []string{".infini_metrics*"},
TimeField: "timestamp", TimeField: "timestamp",
Filter: Filter{ Filter: FilterQuery{
And: []FilterQuery{ And: []FilterQuery{
//{Field: "timestamp", Operator: "gte", Values: []string{"now-15m"}}, //{Field: "timestamp", Operator: "gte", Values: []string{"now-15m"}},
//{Field: "payload.elasticsearch.cluster_health.status", Operator: "equals", Values: []string{"red"}}, //{Field: "payload.elasticsearch.cluster_health.status", Operator: "equals", Values: []string{"red"}},
@ -106,13 +106,17 @@ func TestCreateRule( t *testing.T) {
EscalationThrottlePeriod: "30m", EscalationThrottlePeriod: "30m",
}, },
} }
//err := rule.Metrics.RefreshExpression() //err := rule.Metrics.GenerateExpression()
//if err != nil { //if err != nil {
// t.Fatal(err) // t.Fatal(err)
//} //}
exp, err := rule.GetOrInitExpression()
if err != nil {
t.Fatal(err)
}
fmt.Println(util.MustToJSON(rule)) //fmt.Println(util.MustToJSON(rule))
//fmt.Println(rule.Metrics.Expression) fmt.Println(exp)
} }

View File

@ -37,7 +37,7 @@ func (alertAPI *AlertAPI) createRule(w http.ResponseWriter, req *http.Request, p
}, http.StatusInternalServerError) }, http.StatusInternalServerError)
return return
} }
err = rule.Metrics.RefreshExpression() rule.Metrics.Expression, err = rule.Metrics.GenerateExpression()
if err != nil { if err != nil {
alertAPI.WriteJSON(w, util.MapStr{ alertAPI.WriteJSON(w, util.MapStr{
"error": err.Error(), "error": err.Error(),
@ -135,7 +135,7 @@ func (alertAPI *AlertAPI) updateRule(w http.ResponseWriter, req *http.Request, p
obj.ID = id obj.ID = id
obj.Created = create obj.Created = create
obj.Updated = time.Now() obj.Updated = time.Now()
err = obj.Metrics.RefreshExpression() obj.Metrics.Expression, err = obj.Metrics.GenerateExpression()
if err != nil { if err != nil {
alertAPI.WriteError(w, err.Error(), http.StatusInternalServerError) alertAPI.WriteError(w, err.Error(), http.StatusInternalServerError)
log.Error(err) log.Error(err)

View File

@ -424,29 +424,9 @@ func (engine *Engine) CheckCondition(rule *alerting.Rule)(*alerting.ConditionRes
}) })
LoopCondition: LoopCondition:
for _, cond := range rule.Conditions.Items { for _, cond := range rule.Conditions.Items {
conditionExpression := "" conditionExpression, err := cond.GenerateConditionExpression()
valueLength := len(cond.Values) if err != nil {
if valueLength == 0 { return conditionResult, err
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)
} }
expression, err := govaluate.NewEvaluableExpression(conditionExpression) expression, err := govaluate.NewEvaluableExpression(conditionExpression)
if err != nil { 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 time.ParseInLocation(time.RFC3339, string(timeBytes), time.UTC)
} }
return zeroTime, nil return zeroTime, nil
} }