Merge pull request #729 from influxdata/feature/go-inside-outside-range
Update kapacitor alert rules to accept inside and outside rangepull/10616/head
commit
8a80e66856
|
@ -139,13 +139,12 @@ type Ticker interface {
|
|||
|
||||
// TriggerValues specifies the alerting logic for a specific trigger type
|
||||
type TriggerValues struct {
|
||||
Change string `json:"change,omitempty"` // Change specifies if the change is a percent or absolute
|
||||
Period string `json:"period,omitempty"` // Period length of time before deadman is alerted
|
||||
Shift string `json:"shift,omitempty"` // Shift is the amount of time to look into the past for the alert to compare to the present
|
||||
Operator string `json:"operator,omitempty"` // Operator for alert comparison
|
||||
RangeOperator string `json:"range_operator,omitempty"` // RangeOperator is an optional operator for range comparisons
|
||||
Value string `json:"value,omitempty"` // Value is the boundary value when alert goes critical
|
||||
RangeValue string `json:"range_value,omitempty"` // RangeValue is an optional value for range comparisons
|
||||
Change string `json:"change,omitempty"` // Change specifies if the change is a percent or absolute
|
||||
Period string `json:"period,omitempty"` // Period length of time before deadman is alerted
|
||||
Shift string `json:"shift,omitempty"` // Shift is the amount of time to look into the past for the alert to compare to the present
|
||||
Operator string `json:"operator,omitempty"` // Operator for alert comparison
|
||||
Value string `json:"value,omitempty"` // Value is the boundary value when alert goes critical
|
||||
RangeValue string `json:"rangeValue,omitempty"` // RangeValue is an optional value for range comparisons
|
||||
}
|
||||
|
||||
// Field represent influxql fields and functions from the UI
|
||||
|
|
|
@ -9,6 +9,8 @@ const (
|
|||
GreaterThanEqual = "equal to or greater"
|
||||
Equal = "equal to"
|
||||
NotEqual = "not equal to"
|
||||
InsideRange = "is inside range"
|
||||
OutsideRange = "is outside range"
|
||||
)
|
||||
|
||||
// kapaOperator converts UI strings to kapacitor operators
|
||||
|
@ -30,3 +32,14 @@ func kapaOperator(operator string) (string, error) {
|
|||
return "", fmt.Errorf("invalid operator: %s is unknown", operator)
|
||||
}
|
||||
}
|
||||
|
||||
func rangeOperators(operator string) ([]string, error) {
|
||||
switch operator {
|
||||
case InsideRange:
|
||||
return []string{">=", "AND", "<="}, nil
|
||||
case OutsideRange:
|
||||
return []string{"<", "OR", ">"}, nil
|
||||
default:
|
||||
return nil, fmt.Errorf("invalid operator: %s is unknown", operator)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -199,16 +199,15 @@ trigger
|
|||
}
|
||||
}
|
||||
|
||||
func TestThresholdRange(t *testing.T) {
|
||||
func TestThresholdInsideRange(t *testing.T) {
|
||||
alert := chronograf.AlertRule{
|
||||
Name: "name",
|
||||
Trigger: "threshold",
|
||||
Alerts: []string{"slack", "victorops", "email"},
|
||||
TriggerValues: chronograf.TriggerValues{
|
||||
Operator: "greater than",
|
||||
Value: "90",
|
||||
RangeOperator: "less than",
|
||||
RangeValue: "100",
|
||||
Operator: "is inside range",
|
||||
Value: "90",
|
||||
RangeValue: "100",
|
||||
},
|
||||
Every: "30s",
|
||||
Message: "message",
|
||||
|
@ -305,7 +304,154 @@ var data = stream
|
|||
|
||||
var trigger = data
|
||||
|alert()
|
||||
.crit(lambda: "value" > lower AND "value" < upper)
|
||||
.crit(lambda: "value" >= lower AND "value" <= upper)
|
||||
.stateChangesOnly()
|
||||
.message(message)
|
||||
.id(idVar)
|
||||
.idTag(idTag)
|
||||
.levelTag(levelTag)
|
||||
.messageField(messageField)
|
||||
.durationField(durationField)
|
||||
.slack()
|
||||
.victorOps()
|
||||
.email()
|
||||
|
||||
trigger
|
||||
|influxDBOut()
|
||||
.create()
|
||||
.database(outputDB)
|
||||
.retentionPolicy(outputRP)
|
||||
.measurement(outputMeasurement)
|
||||
.tag('alertName', name)
|
||||
.tag('triggerType', triggerType)
|
||||
|
||||
trigger
|
||||
|httpOut('output')
|
||||
`,
|
||||
wantErr: false,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
gen := Alert{}
|
||||
got, err := gen.Generate(tt.alert)
|
||||
if (err != nil) != tt.wantErr {
|
||||
t.Errorf("%q. Threshold() error = %v, wantErr %v", tt.name, err, tt.wantErr)
|
||||
continue
|
||||
}
|
||||
if got != tt.want {
|
||||
diff := diffmatchpatch.New()
|
||||
delta := diff.DiffMain(string(tt.want), string(got), true)
|
||||
t.Errorf("%q\n%s", tt.name, diff.DiffPrettyText(delta))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestThresholdOutsideRange(t *testing.T) {
|
||||
alert := chronograf.AlertRule{
|
||||
Name: "name",
|
||||
Trigger: "threshold",
|
||||
Alerts: []string{"slack", "victorops", "email"},
|
||||
TriggerValues: chronograf.TriggerValues{
|
||||
Operator: "is outside range",
|
||||
Value: "90",
|
||||
RangeValue: "100",
|
||||
},
|
||||
Every: "30s",
|
||||
Message: "message",
|
||||
Query: chronograf.QueryConfig{
|
||||
Database: "telegraf",
|
||||
Measurement: "cpu",
|
||||
RetentionPolicy: "autogen",
|
||||
Fields: []chronograf.Field{
|
||||
{
|
||||
Field: "usage_user",
|
||||
Funcs: []string{"mean"},
|
||||
},
|
||||
},
|
||||
Tags: map[string][]string{
|
||||
"host": []string{
|
||||
"acc-0eabc309-eu-west-1-data-3",
|
||||
"prod",
|
||||
},
|
||||
"cpu": []string{
|
||||
"cpu_total",
|
||||
},
|
||||
},
|
||||
GroupBy: chronograf.GroupBy{
|
||||
Time: "10m",
|
||||
Tags: []string{"host", "cluster_id"},
|
||||
},
|
||||
AreTagsAccepted: true,
|
||||
RawText: "",
|
||||
},
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
alert chronograf.AlertRule
|
||||
want chronograf.TICKScript
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "Test valid template alert",
|
||||
alert: alert,
|
||||
want: `var db = 'telegraf'
|
||||
|
||||
var rp = 'autogen'
|
||||
|
||||
var measurement = 'cpu'
|
||||
|
||||
var groupBy = ['host', 'cluster_id']
|
||||
|
||||
var whereFilter = lambda: ("cpu" == 'cpu_total') AND ("host" == 'acc-0eabc309-eu-west-1-data-3' OR "host" == 'prod')
|
||||
|
||||
var period = 10m
|
||||
|
||||
var every = 30s
|
||||
|
||||
var name = 'name'
|
||||
|
||||
var idVar = name + ':{{.Group}}'
|
||||
|
||||
var message = 'message'
|
||||
|
||||
var idTag = 'alertID'
|
||||
|
||||
var levelTag = 'level'
|
||||
|
||||
var messageField = 'message'
|
||||
|
||||
var durationField = 'duration'
|
||||
|
||||
var outputDB = 'chronograf'
|
||||
|
||||
var outputRP = 'autogen'
|
||||
|
||||
var outputMeasurement = 'alerts'
|
||||
|
||||
var triggerType = 'threshold'
|
||||
|
||||
var lower = 90
|
||||
|
||||
var upper = 100
|
||||
|
||||
var data = stream
|
||||
|from()
|
||||
.database(db)
|
||||
.retentionPolicy(rp)
|
||||
.measurement(measurement)
|
||||
.groupBy(groupBy)
|
||||
.where(whereFilter)
|
||||
|window()
|
||||
.period(period)
|
||||
.every(every)
|
||||
.align()
|
||||
|mean('usage_user')
|
||||
.as('value')
|
||||
|
||||
var trigger = data
|
||||
|alert()
|
||||
.crit(lambda: "value" < lower OR "value" > upper)
|
||||
.stateChangesOnly()
|
||||
.message(message)
|
||||
.id(idVar)
|
||||
|
|
|
@ -37,7 +37,7 @@ var ThresholdTrigger = `
|
|||
var ThresholdRangeTrigger = `
|
||||
var trigger = data
|
||||
|alert()
|
||||
.crit(lambda: "value" %s lower AND "value" %s upper)
|
||||
.crit(lambda: "value" %s lower %s "value" %s upper)
|
||||
`
|
||||
|
||||
// RelativeAbsoluteTrigger compares one window of data versus another (current - past)
|
||||
|
@ -89,7 +89,7 @@ func Trigger(rule chronograf.AlertRule) (string, error) {
|
|||
case Relative:
|
||||
trigger, err = relativeTrigger(rule)
|
||||
case Threshold:
|
||||
if rule.TriggerValues.RangeOperator == "" || rule.TriggerValues.RangeValue == "" {
|
||||
if rule.TriggerValues.RangeValue == "" {
|
||||
trigger, err = thresholdTrigger(rule)
|
||||
} else {
|
||||
trigger, err = thresholdRangeTrigger(rule)
|
||||
|
@ -128,13 +128,13 @@ func thresholdTrigger(rule chronograf.AlertRule) (string, error) {
|
|||
}
|
||||
|
||||
func thresholdRangeTrigger(rule chronograf.AlertRule) (string, error) {
|
||||
op, err := kapaOperator(rule.TriggerValues.Operator)
|
||||
ops, err := rangeOperators(rule.TriggerValues.Operator)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
rangeOp, err := kapaOperator(rule.TriggerValues.RangeOperator)
|
||||
if err != nil {
|
||||
return "", err
|
||||
var iops []interface{} = make([]interface{}, len(ops))
|
||||
for i, o := range ops {
|
||||
iops[i] = o
|
||||
}
|
||||
return fmt.Sprintf(ThresholdRangeTrigger, op, rangeOp), nil
|
||||
return fmt.Sprintf(ThresholdRangeTrigger, iops...), nil
|
||||
}
|
||||
|
|
|
@ -34,7 +34,7 @@ func Vars(rule chronograf.AlertRule) (string, error) {
|
|||
|
||||
switch rule.Trigger {
|
||||
case Threshold:
|
||||
if rule.TriggerValues.RangeOperator == "" || rule.TriggerValues.RangeValue == "" {
|
||||
if rule.TriggerValues.RangeValue == "" {
|
||||
vars := `
|
||||
%s
|
||||
var crit = %s
|
||||
|
|
|
@ -1747,7 +1747,7 @@
|
|||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
},
|
||||
"definitions": {
|
||||
"Kapacitors": {
|
||||
|
@ -1837,9 +1837,84 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"QueryConfig": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "string"
|
||||
},
|
||||
"database": {
|
||||
"type": "string"
|
||||
},
|
||||
"measurement": {
|
||||
"type": "string"
|
||||
},
|
||||
"retentionPolicy": {
|
||||
"type": "string"
|
||||
},
|
||||
"areTagsAccepted": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"rawText": {
|
||||
"type": "string"
|
||||
},
|
||||
"tags": {
|
||||
"type": "object"
|
||||
},
|
||||
"groupBy": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"time": {
|
||||
"type": "string"
|
||||
},
|
||||
"tags": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"time",
|
||||
"tags"
|
||||
]
|
||||
},
|
||||
"fields": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"field": {
|
||||
"type": "string"
|
||||
},
|
||||
"funcs": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"field",
|
||||
"funcs"
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"database",
|
||||
"measurement",
|
||||
"retentionPolicy",
|
||||
"areTagsAccepted",
|
||||
"tags",
|
||||
"groupBy",
|
||||
"fields"
|
||||
]
|
||||
},
|
||||
"Rule": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"query",
|
||||
"every",
|
||||
"trigger"
|
||||
],
|
||||
|
@ -1848,6 +1923,10 @@
|
|||
"type": "string",
|
||||
"description": "ID for this rule; the ID is shared with kapacitor"
|
||||
},
|
||||
"query": {
|
||||
"description": "Query config structure is historical from chronograf 1.0",
|
||||
"$ref": "#/definitions/QueryConfig"
|
||||
},
|
||||
"name": {
|
||||
"type": "string",
|
||||
"description": "User facing name of the alerting rule"
|
||||
|
@ -1888,6 +1967,50 @@
|
|||
"threshold"
|
||||
]
|
||||
},
|
||||
"values": {
|
||||
"type": "object",
|
||||
"description": "Alerting logic for trigger type",
|
||||
"properties": {
|
||||
"change": {
|
||||
"description": "Specifies if the change is percent or absolute",
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"% change",
|
||||
"change"
|
||||
]
|
||||
},
|
||||
"period": {
|
||||
"description": "Length of time before deadman is alerted (golang duration)",
|
||||
"type": "string"
|
||||
},
|
||||
"shift": {
|
||||
"description": "Amount of time to look into the past to compare to the present (golang duration)",
|
||||
"type": "string"
|
||||
},
|
||||
"operator": {
|
||||
"description": "Operator for alert comparison",
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"greater than",
|
||||
"less than",
|
||||
"equal to or less than",
|
||||
"equal to or greater",
|
||||
"equal to",
|
||||
"not equal to",
|
||||
"is inside range",
|
||||
"is outside range"
|
||||
]
|
||||
},
|
||||
"value": {
|
||||
"description": "Value is the boundary value when alert goes critical",
|
||||
"type": "string"
|
||||
},
|
||||
"rangeValue": {
|
||||
"description": "Optional value for range comparisions",
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"tickscript": {
|
||||
"type": "string",
|
||||
"description": "TICKscript representing this rule"
|
||||
|
@ -2438,4 +2561,4 @@
|
|||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue