Merge pull request #450 from influxdata/kapacitor-group-by
Update tickscript generation to use group by time as period.pull/10616/head
commit
e410fdc427
1
Godeps
1
Godeps
|
@ -10,6 +10,7 @@ github.com/influxdata/kapacitor 0eb8c348b210dd3d32cb240a417f9e6ded1b691d
|
|||
github.com/influxdata/usage-client 6d3895376368aa52a3a81d2a16e90f0f52371967
|
||||
github.com/jessevdk/go-flags 4cc2832a6e6d1d3b815e2b9d544b2a4dfb3ce8fa
|
||||
github.com/satori/go.uuid b061729afc07e77a8aa4fad0a2fd840958f1942a
|
||||
github.com/sergi/go-diff 1d28411638c1e67fe1930830df207bef72496ae9
|
||||
github.com/tylerb/graceful 50a48b6e73fcc75b45e22c05b79629a67c79e938
|
||||
golang.org/x/net 749a502dd1eaf3e5bfd4f8956748c502357c0bbe
|
||||
golang.org/x/oauth2 1e695b1c8febf17aad3bfa7bf0a819ef94b98ad5
|
||||
|
|
|
@ -128,32 +128,36 @@ 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 is the window to search for alerting criteria
|
||||
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
|
||||
Percentile string `json:"percentile,omitempty"` // Percentile is defined only when Relation is not "Once"
|
||||
Relation string `json:"relation,omitempty"` // Relation defines the logic about how often the threshold is met to be an alert.
|
||||
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
|
||||
}
|
||||
|
||||
// Field represent influxql fields and functions from the UI
|
||||
type Field struct {
|
||||
Field string `json:"field"`
|
||||
Funcs []string `json:"funcs"`
|
||||
}
|
||||
|
||||
// GroupBy represents influxql group by tags from the UI
|
||||
type GroupBy struct {
|
||||
Time string `json:"time"`
|
||||
Tags []string `json:"tags"`
|
||||
}
|
||||
|
||||
// QueryConfig represents UI query from the data explorer
|
||||
type QueryConfig struct {
|
||||
ID string `json:"id,omitempty"`
|
||||
Database string `json:"database"`
|
||||
Measurement string `json:"measurement"`
|
||||
RetentionPolicy string `json:"retentionPolicy"`
|
||||
Fields []struct {
|
||||
Field string `json:"field"`
|
||||
Funcs []string `json:"funcs"`
|
||||
} `json:"fields"`
|
||||
Tags map[string][]string `json:"tags"`
|
||||
GroupBy struct {
|
||||
Time string `json:"time"`
|
||||
Tags []string `json:"tags"`
|
||||
} `json:"groupBy"`
|
||||
AreTagsAccepted bool `json:"areTagsAccepted"`
|
||||
RawText string `json:"rawText,omitempty"`
|
||||
ID string `json:"id,omitempty"`
|
||||
Database string `json:"database"`
|
||||
Measurement string `json:"measurement"`
|
||||
RetentionPolicy string `json:"retentionPolicy"`
|
||||
Fields []Field `json:"fields"`
|
||||
Tags map[string][]string `json:"tags"`
|
||||
GroupBy GroupBy `json:"groupBy"`
|
||||
AreTagsAccepted bool `json:"areTagsAccepted"`
|
||||
RawText string `json:"rawText,omitempty"`
|
||||
}
|
||||
|
||||
// Server represents a proxy connection to an HTTP server
|
||||
|
|
|
@ -3,6 +3,7 @@ package kapacitor
|
|||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"path"
|
||||
|
||||
"github.com/influxdata/chronograf"
|
||||
client "github.com/influxdata/kapacitor/client/v1"
|
||||
|
@ -26,6 +27,7 @@ const (
|
|||
type Task struct {
|
||||
ID string // Kapacitor ID
|
||||
Href string // Kapacitor relative URI
|
||||
HrefOutput string // Kapacitor relative URI to HTTPOutNode
|
||||
TICKScript chronograf.TICKScript // TICKScript is the running script
|
||||
}
|
||||
|
||||
|
@ -66,6 +68,7 @@ func (c *Client) Create(ctx context.Context, rule chronograf.AlertRule) (*Task,
|
|||
return &Task{
|
||||
ID: kapaID,
|
||||
Href: task.Link.Href,
|
||||
HrefOutput: path.Join(task.Link.Href, HTTPEndpoint),
|
||||
TICKScript: script,
|
||||
}, nil
|
||||
}
|
||||
|
|
|
@ -0,0 +1,13 @@
|
|||
package kapacitor
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/influxdata/chronograf"
|
||||
)
|
||||
|
||||
const HTTPEndpoint = "output"
|
||||
|
||||
func HTTPOut(rule chronograf.AlertRule) (string, error) {
|
||||
return fmt.Sprintf(`trigger|httpOut('%s')`, HTTPEndpoint), nil
|
||||
}
|
|
@ -29,10 +29,7 @@ func TestInfluxOut(t *testing.T) {
|
|||
Name: "name",
|
||||
Trigger: "deadman",
|
||||
Query: chronograf.QueryConfig{
|
||||
Fields: []struct {
|
||||
Field string `json:"field"`
|
||||
Funcs []string `json:"funcs"`
|
||||
}{
|
||||
Fields: []chronograf.Field{
|
||||
{
|
||||
Field: "usage_user",
|
||||
Funcs: []string{"mean"},
|
||||
|
|
|
@ -0,0 +1,32 @@
|
|||
package kapacitor
|
||||
|
||||
import "fmt"
|
||||
|
||||
const (
|
||||
GreaterThan = "greater than"
|
||||
LessThan = "less than"
|
||||
LessThanEqual = "equal to or less than"
|
||||
GreaterThanEqual = "equal to or greater than"
|
||||
Equal = "equal"
|
||||
NotEqual = "not equal"
|
||||
)
|
||||
|
||||
// kapaOperator converts UI strings to kapacitor operators
|
||||
func kapaOperator(operator string) (string, error) {
|
||||
switch operator {
|
||||
case GreaterThan:
|
||||
return ">", nil
|
||||
case LessThan:
|
||||
return "<", nil
|
||||
case LessThanEqual:
|
||||
return "<=", nil
|
||||
case GreaterThanEqual:
|
||||
return ">=", nil
|
||||
case Equal:
|
||||
return "==", nil
|
||||
case NotEqual:
|
||||
return "!=", nil
|
||||
default:
|
||||
return "", fmt.Errorf("invalid operator: %s is unknown", operator)
|
||||
}
|
||||
}
|
|
@ -1 +0,0 @@
|
|||
package kapacitor
|
|
@ -33,7 +33,12 @@ func (a *Alert) Generate(rule chronograf.AlertRule) (chronograf.TICKScript, erro
|
|||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
raw := fmt.Sprintf("%s\n%s\n%s%s\n%s", vars, data, trigger, services, output)
|
||||
http, err := HTTPOut(rule)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
raw := fmt.Sprintf("%s\n%s\n%s%s\n%s\n%s", vars, data, trigger, services, output, http)
|
||||
tick, err := formatTick(raw)
|
||||
if err != nil {
|
||||
return "", err
|
||||
|
|
|
@ -5,6 +5,7 @@ import (
|
|||
"testing"
|
||||
|
||||
"github.com/influxdata/chronograf"
|
||||
"github.com/sergi/go-diff/diffmatchpatch"
|
||||
)
|
||||
|
||||
func TestGenerate(t *testing.T) {
|
||||
|
@ -14,7 +15,6 @@ func TestGenerate(t *testing.T) {
|
|||
Alerts: []string{"slack", "victorops", "email"},
|
||||
TriggerValues: chronograf.TriggerValues{
|
||||
Change: "change",
|
||||
Period: "10m",
|
||||
Shift: "1m",
|
||||
Operator: "greater than",
|
||||
Value: "90",
|
||||
|
@ -24,10 +24,7 @@ func TestGenerate(t *testing.T) {
|
|||
Database: "telegraf",
|
||||
Measurement: "cpu",
|
||||
RetentionPolicy: "autogen",
|
||||
Fields: []struct {
|
||||
Field string `json:"field"`
|
||||
Funcs []string `json:"funcs"`
|
||||
}{
|
||||
Fields: []chronograf.Field{
|
||||
{
|
||||
Field: "usage_user",
|
||||
Funcs: []string{"mean"},
|
||||
|
@ -42,11 +39,8 @@ func TestGenerate(t *testing.T) {
|
|||
"cpu_total",
|
||||
},
|
||||
},
|
||||
GroupBy: struct {
|
||||
Time string `json:"time"`
|
||||
Tags []string `json:"tags"`
|
||||
}{
|
||||
Time: "",
|
||||
GroupBy: chronograf.GroupBy{
|
||||
Time: "10m",
|
||||
Tags: []string{"host", "cluster_id"},
|
||||
},
|
||||
AreTagsAccepted: true,
|
||||
|
@ -57,7 +51,7 @@ func TestGenerate(t *testing.T) {
|
|||
tick, err := gen.Generate(alert)
|
||||
if err != nil {
|
||||
fmt.Printf("%s", tick)
|
||||
t.Errorf("Error generating alert: %v", err)
|
||||
t.Errorf("Error generating alert: %v %s", err, tick)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -67,11 +61,8 @@ func TestThreshold(t *testing.T) {
|
|||
Trigger: "threshold",
|
||||
Alerts: []string{"slack", "victorops", "email"},
|
||||
TriggerValues: chronograf.TriggerValues{
|
||||
Relation: "once",
|
||||
Period: "10m",
|
||||
Percentile: "", // TODO: if relation is not once then this will have a number
|
||||
Operator: "greater than",
|
||||
Value: "90",
|
||||
Operator: "greater than",
|
||||
Value: "90",
|
||||
},
|
||||
Every: "30s",
|
||||
Message: "message",
|
||||
|
@ -79,10 +70,7 @@ func TestThreshold(t *testing.T) {
|
|||
Database: "telegraf",
|
||||
Measurement: "cpu",
|
||||
RetentionPolicy: "autogen",
|
||||
Fields: []struct {
|
||||
Field string `json:"field"`
|
||||
Funcs []string `json:"funcs"`
|
||||
}{
|
||||
Fields: []chronograf.Field{
|
||||
{
|
||||
Field: "usage_user",
|
||||
Funcs: []string{"mean"},
|
||||
|
@ -97,11 +85,8 @@ func TestThreshold(t *testing.T) {
|
|||
"cpu_total",
|
||||
},
|
||||
},
|
||||
GroupBy: struct {
|
||||
Time string `json:"time"`
|
||||
Tags []string `json:"tags"`
|
||||
}{
|
||||
Time: "",
|
||||
GroupBy: chronograf.GroupBy{
|
||||
Time: "10m",
|
||||
Tags: []string{"host", "cluster_id"},
|
||||
},
|
||||
AreTagsAccepted: true,
|
||||
|
@ -130,6 +115,8 @@ var whereFilter = lambda: ("cpu" == 'cpu_total') AND ("host" == 'acc-0eabc309-eu
|
|||
|
||||
var period = 10m
|
||||
|
||||
var every = 30s
|
||||
|
||||
var name = 'name'
|
||||
|
||||
var idVar = name + ':{{.Group}}'
|
||||
|
@ -152,8 +139,6 @@ var outputMeasurement = 'alerts'
|
|||
|
||||
var triggerType = 'threshold'
|
||||
|
||||
var every = 30s
|
||||
|
||||
var crit = 90
|
||||
|
||||
var data = stream
|
||||
|
@ -172,8 +157,8 @@ var data = stream
|
|||
|
||||
var trigger = data
|
||||
|alert()
|
||||
.stateChangesOnly()
|
||||
.crit(lambda: "value" > crit)
|
||||
.stateChangesOnly()
|
||||
.message(message)
|
||||
.id(idVar)
|
||||
.idTag(idTag)
|
||||
|
@ -192,6 +177,9 @@ trigger
|
|||
.measurement(outputMeasurement)
|
||||
.tag('alertName', name)
|
||||
.tag('triggerType', triggerType)
|
||||
|
||||
trigger
|
||||
|httpOut('output')
|
||||
`,
|
||||
wantErr: false,
|
||||
},
|
||||
|
@ -204,8 +192,9 @@ trigger
|
|||
continue
|
||||
}
|
||||
if got != tt.want {
|
||||
fmt.Printf("%s", got)
|
||||
t.Errorf("%q. Threshold() = %v, want %v", tt.name, 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))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -216,11 +205,8 @@ func TestThresholdNoAggregate(t *testing.T) {
|
|||
Trigger: "threshold",
|
||||
Alerts: []string{"slack", "victorops", "email"},
|
||||
TriggerValues: chronograf.TriggerValues{
|
||||
Relation: "once",
|
||||
Period: "10m",
|
||||
Percentile: "", // TODO: if relation is not once then this will have a number
|
||||
Operator: "greater than",
|
||||
Value: "90",
|
||||
Operator: "greater than",
|
||||
Value: "90",
|
||||
},
|
||||
Every: "30s",
|
||||
Message: "message",
|
||||
|
@ -228,10 +214,7 @@ func TestThresholdNoAggregate(t *testing.T) {
|
|||
Database: "telegraf",
|
||||
Measurement: "cpu",
|
||||
RetentionPolicy: "autogen",
|
||||
Fields: []struct {
|
||||
Field string `json:"field"`
|
||||
Funcs []string `json:"funcs"`
|
||||
}{
|
||||
Fields: []chronograf.Field{
|
||||
{
|
||||
Field: "usage_user",
|
||||
Funcs: []string{},
|
||||
|
@ -246,11 +229,8 @@ func TestThresholdNoAggregate(t *testing.T) {
|
|||
"cpu_total",
|
||||
},
|
||||
},
|
||||
GroupBy: struct {
|
||||
Time string `json:"time"`
|
||||
Tags []string `json:"tags"`
|
||||
}{
|
||||
Time: "",
|
||||
GroupBy: chronograf.GroupBy{
|
||||
Time: "10m",
|
||||
Tags: []string{"host", "cluster_id"},
|
||||
},
|
||||
AreTagsAccepted: true,
|
||||
|
@ -277,8 +257,6 @@ 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 name = 'name'
|
||||
|
||||
var idVar = name + ':{{.Group}}'
|
||||
|
@ -301,8 +279,6 @@ var outputMeasurement = 'alerts'
|
|||
|
||||
var triggerType = 'threshold'
|
||||
|
||||
var every = 30s
|
||||
|
||||
var crit = 90
|
||||
|
||||
var data = stream
|
||||
|
@ -317,8 +293,8 @@ var data = stream
|
|||
|
||||
var trigger = data
|
||||
|alert()
|
||||
.stateChangesOnly()
|
||||
.crit(lambda: "value" > crit)
|
||||
.stateChangesOnly()
|
||||
.message(message)
|
||||
.id(idVar)
|
||||
.idTag(idTag)
|
||||
|
@ -337,6 +313,9 @@ trigger
|
|||
.measurement(outputMeasurement)
|
||||
.tag('alertName', name)
|
||||
.tag('triggerType', triggerType)
|
||||
|
||||
trigger
|
||||
|httpOut('output')
|
||||
`,
|
||||
wantErr: false,
|
||||
},
|
||||
|
@ -349,8 +328,9 @@ trigger
|
|||
continue
|
||||
}
|
||||
if got != tt.want {
|
||||
fmt.Printf("%s", got)
|
||||
t.Errorf("%q. Threshold() = %v, want %v", tt.name, 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))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -362,7 +342,6 @@ func TestRelative(t *testing.T) {
|
|||
Alerts: []string{"slack", "victorops", "email"},
|
||||
TriggerValues: chronograf.TriggerValues{
|
||||
Change: "% change",
|
||||
Period: "10m",
|
||||
Shift: "1m",
|
||||
Operator: "greater than",
|
||||
Value: "90",
|
||||
|
@ -373,10 +352,7 @@ func TestRelative(t *testing.T) {
|
|||
Database: "telegraf",
|
||||
Measurement: "cpu",
|
||||
RetentionPolicy: "autogen",
|
||||
Fields: []struct {
|
||||
Field string `json:"field"`
|
||||
Funcs []string `json:"funcs"`
|
||||
}{
|
||||
Fields: []chronograf.Field{
|
||||
{
|
||||
Field: "usage_user",
|
||||
Funcs: []string{"mean"},
|
||||
|
@ -391,11 +367,8 @@ func TestRelative(t *testing.T) {
|
|||
"cpu_total",
|
||||
},
|
||||
},
|
||||
GroupBy: struct {
|
||||
Time string `json:"time"`
|
||||
Tags []string `json:"tags"`
|
||||
}{
|
||||
Time: "",
|
||||
GroupBy: chronograf.GroupBy{
|
||||
Time: "10m",
|
||||
Tags: []string{"host", "cluster_id"},
|
||||
},
|
||||
AreTagsAccepted: true,
|
||||
|
@ -424,6 +397,8 @@ var whereFilter = lambda: ("cpu" == 'cpu_total') AND ("host" == 'acc-0eabc309-eu
|
|||
|
||||
var period = 10m
|
||||
|
||||
var every = 30s
|
||||
|
||||
var name = 'name'
|
||||
|
||||
var idVar = name + ':{{.Group}}'
|
||||
|
@ -446,8 +421,6 @@ var outputMeasurement = 'alerts'
|
|||
|
||||
var triggerType = 'relative'
|
||||
|
||||
var every = 30s
|
||||
|
||||
var shift = -1m
|
||||
|
||||
var crit = 90
|
||||
|
@ -478,8 +451,8 @@ var trigger = past
|
|||
.keep()
|
||||
.as('value')
|
||||
|alert()
|
||||
.stateChangesOnly()
|
||||
.crit(lambda: "value" > crit)
|
||||
.stateChangesOnly()
|
||||
.message(message)
|
||||
.id(idVar)
|
||||
.idTag(idTag)
|
||||
|
@ -498,6 +471,9 @@ trigger
|
|||
.measurement(outputMeasurement)
|
||||
.tag('alertName', name)
|
||||
.tag('triggerType', triggerType)
|
||||
|
||||
trigger
|
||||
|httpOut('output')
|
||||
`,
|
||||
wantErr: false,
|
||||
},
|
||||
|
@ -510,8 +486,9 @@ trigger
|
|||
continue
|
||||
}
|
||||
if got != tt.want {
|
||||
fmt.Printf("%s", got)
|
||||
t.Errorf("%q. Relative() = %v, want %v", tt.name, 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))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -523,7 +500,6 @@ func TestRelativeChange(t *testing.T) {
|
|||
Alerts: []string{"slack", "victorops", "email"},
|
||||
TriggerValues: chronograf.TriggerValues{
|
||||
Change: "change",
|
||||
Period: "10m",
|
||||
Shift: "1m",
|
||||
Operator: "greater than",
|
||||
Value: "90",
|
||||
|
@ -534,10 +510,7 @@ func TestRelativeChange(t *testing.T) {
|
|||
Database: "telegraf",
|
||||
Measurement: "cpu",
|
||||
RetentionPolicy: "autogen",
|
||||
Fields: []struct {
|
||||
Field string `json:"field"`
|
||||
Funcs []string `json:"funcs"`
|
||||
}{
|
||||
Fields: []chronograf.Field{
|
||||
{
|
||||
Field: "usage_user",
|
||||
Funcs: []string{"mean"},
|
||||
|
@ -552,11 +525,8 @@ func TestRelativeChange(t *testing.T) {
|
|||
"cpu_total",
|
||||
},
|
||||
},
|
||||
GroupBy: struct {
|
||||
Time string `json:"time"`
|
||||
Tags []string `json:"tags"`
|
||||
}{
|
||||
Time: "",
|
||||
GroupBy: chronograf.GroupBy{
|
||||
Time: "10m",
|
||||
Tags: []string{"host", "cluster_id"},
|
||||
},
|
||||
AreTagsAccepted: true,
|
||||
|
@ -585,6 +555,8 @@ var whereFilter = lambda: ("cpu" == 'cpu_total') AND ("host" == 'acc-0eabc309-eu
|
|||
|
||||
var period = 10m
|
||||
|
||||
var every = 30s
|
||||
|
||||
var name = 'name'
|
||||
|
||||
var idVar = name + ':{{.Group}}'
|
||||
|
@ -607,8 +579,6 @@ var outputMeasurement = 'alerts'
|
|||
|
||||
var triggerType = 'relative'
|
||||
|
||||
var every = 30s
|
||||
|
||||
var shift = -1m
|
||||
|
||||
var crit = 90
|
||||
|
@ -639,8 +609,8 @@ var trigger = past
|
|||
.keep()
|
||||
.as('value')
|
||||
|alert()
|
||||
.stateChangesOnly()
|
||||
.crit(lambda: "value" > crit)
|
||||
.stateChangesOnly()
|
||||
.message(message)
|
||||
.id(idVar)
|
||||
.idTag(idTag)
|
||||
|
@ -659,6 +629,9 @@ trigger
|
|||
.measurement(outputMeasurement)
|
||||
.tag('alertName', name)
|
||||
.tag('triggerType', triggerType)
|
||||
|
||||
trigger
|
||||
|httpOut('output')
|
||||
`,
|
||||
wantErr: false,
|
||||
},
|
||||
|
@ -671,8 +644,9 @@ trigger
|
|||
continue
|
||||
}
|
||||
if got != tt.want {
|
||||
fmt.Printf("%s", got)
|
||||
t.Errorf("%q. Relative() = %v, want %v", tt.name, 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))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -691,10 +665,7 @@ func TestDeadman(t *testing.T) {
|
|||
Database: "telegraf",
|
||||
Measurement: "cpu",
|
||||
RetentionPolicy: "autogen",
|
||||
Fields: []struct {
|
||||
Field string `json:"field"`
|
||||
Funcs []string `json:"funcs"`
|
||||
}{
|
||||
Fields: []chronograf.Field{
|
||||
{
|
||||
Field: "usage_user",
|
||||
Funcs: []string{"mean"},
|
||||
|
@ -709,10 +680,7 @@ func TestDeadman(t *testing.T) {
|
|||
"cpu_total",
|
||||
},
|
||||
},
|
||||
GroupBy: struct {
|
||||
Time string `json:"time"`
|
||||
Tags []string `json:"tags"`
|
||||
}{
|
||||
GroupBy: chronograf.GroupBy{
|
||||
Time: "",
|
||||
Tags: []string{"host", "cluster_id"},
|
||||
},
|
||||
|
@ -798,6 +766,9 @@ trigger
|
|||
.measurement(outputMeasurement)
|
||||
.tag('alertName', name)
|
||||
.tag('triggerType', triggerType)
|
||||
|
||||
trigger
|
||||
|httpOut('output')
|
||||
`,
|
||||
wantErr: false,
|
||||
},
|
||||
|
@ -810,8 +781,9 @@ trigger
|
|||
continue
|
||||
}
|
||||
if got != tt.want {
|
||||
fmt.Printf("%s", got)
|
||||
t.Errorf("%q. Deadman() = %v, want %v", tt.name, 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))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,12 +3,18 @@ package kapacitor
|
|||
import "github.com/influxdata/chronograf"
|
||||
import "fmt"
|
||||
|
||||
// ThresholdTrigger is the trickscript trigger for alerts that exceed a value
|
||||
var ThresholdTrigger = `
|
||||
var trigger = data
|
||||
|alert()
|
||||
const (
|
||||
// Deadman triggers when data is missing for a period of time
|
||||
Deadman = "deadman"
|
||||
// Relative triggers when the value has changed compared to the past
|
||||
Relative = "relative"
|
||||
// Threshold triggers when value crosses a threshold
|
||||
Threshold = "threshold"
|
||||
)
|
||||
|
||||
// AllAlerts are properties all alert types will have
|
||||
var AllAlerts = `
|
||||
.stateChangesOnly()
|
||||
.crit(lambda: "value" %s crit)
|
||||
.message(message)
|
||||
.id(idVar)
|
||||
.idTag(idTag)
|
||||
|
@ -17,6 +23,13 @@ var ThresholdTrigger = `
|
|||
.durationField(durationField)
|
||||
`
|
||||
|
||||
// ThresholdTrigger is the trickscript trigger for alerts that exceed a value
|
||||
var ThresholdTrigger = `
|
||||
var trigger = data
|
||||
|alert()
|
||||
.crit(lambda: "value" %s crit)
|
||||
`
|
||||
|
||||
// RelativeAbsoluteTrigger compares one window of data versus another (current - past)
|
||||
var RelativeAbsoluteTrigger = `
|
||||
var past = data
|
||||
|
@ -31,14 +44,7 @@ var trigger = past
|
|||
.keep()
|
||||
.as('value')
|
||||
|alert()
|
||||
.stateChangesOnly()
|
||||
.crit(lambda: "value" %s crit)
|
||||
.message(message)
|
||||
.id(idVar)
|
||||
.idTag(idTag)
|
||||
.levelTag(levelTag)
|
||||
.messageField(messageField)
|
||||
.durationField(durationField)
|
||||
`
|
||||
|
||||
// RelativePercentTrigger compares one window of data versus another as a percent change.
|
||||
|
@ -55,72 +61,54 @@ var trigger = past
|
|||
.keep()
|
||||
.as('value')
|
||||
|alert()
|
||||
.stateChangesOnly()
|
||||
.crit(lambda: "value" %s crit)
|
||||
.message(message)
|
||||
.id(idVar)
|
||||
.idTag(idTag)
|
||||
.levelTag(levelTag)
|
||||
.messageField(messageField)
|
||||
.durationField(durationField)
|
||||
`
|
||||
|
||||
// DeadmanTrigger checks if any data has been streamed in the last period of time
|
||||
var DeadmanTrigger = `
|
||||
var trigger = data|deadman(threshold, period)
|
||||
.stateChangesOnly()
|
||||
.message(message)
|
||||
.id(idVar)
|
||||
.idTag(idTag)
|
||||
.levelTag(levelTag)
|
||||
.messageField(messageField)
|
||||
.durationField(durationField)
|
||||
`
|
||||
|
||||
// Trigger returns the trigger mechanism for a tickscript
|
||||
func Trigger(rule chronograf.AlertRule) (string, error) {
|
||||
var trigger string
|
||||
var err error
|
||||
switch rule.Trigger {
|
||||
case "deadman":
|
||||
return DeadmanTrigger, nil
|
||||
case "relative":
|
||||
op, err := kapaOperator(rule.TriggerValues.Operator)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if rule.TriggerValues.Change == "% change" {
|
||||
return fmt.Sprintf(RelativePercentTrigger, op), nil
|
||||
} else if rule.TriggerValues.Change == "change" {
|
||||
return fmt.Sprintf(RelativeAbsoluteTrigger, op), nil
|
||||
} else {
|
||||
return "", fmt.Errorf("Unknown change type %s", rule.TriggerValues.Change)
|
||||
}
|
||||
case "threshold":
|
||||
op, err := kapaOperator(rule.TriggerValues.Operator)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return fmt.Sprintf(ThresholdTrigger, op), nil
|
||||
case Deadman:
|
||||
trigger, err = DeadmanTrigger, nil
|
||||
case Relative:
|
||||
trigger, err = relativeTrigger(rule)
|
||||
case Threshold:
|
||||
trigger, err = thresholdTrigger(rule)
|
||||
default:
|
||||
return "", fmt.Errorf("Unknown trigger type: %s", rule.Trigger)
|
||||
trigger, err = "", fmt.Errorf("Unknown trigger type: %s", rule.Trigger)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return trigger + AllAlerts, nil
|
||||
}
|
||||
|
||||
func relativeTrigger(rule chronograf.AlertRule) (string, error) {
|
||||
op, err := kapaOperator(rule.TriggerValues.Operator)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if rule.TriggerValues.Change == "% change" {
|
||||
return fmt.Sprintf(RelativePercentTrigger, op), nil
|
||||
} else if rule.TriggerValues.Change == "change" {
|
||||
return fmt.Sprintf(RelativeAbsoluteTrigger, op), nil
|
||||
} else {
|
||||
return "", fmt.Errorf("Unknown change type %s", rule.TriggerValues.Change)
|
||||
}
|
||||
}
|
||||
|
||||
// kapaOperator converts UI strings to kapacitor operators
|
||||
func kapaOperator(operator string) (string, error) {
|
||||
switch operator {
|
||||
case "greater than":
|
||||
return ">", nil
|
||||
case "less than":
|
||||
return "<", nil
|
||||
case "equal to or less than":
|
||||
return "<=", nil
|
||||
case "equal to or greater than":
|
||||
return ">=", nil
|
||||
case "equal":
|
||||
return "==", nil
|
||||
case "not equal":
|
||||
return "!=", nil
|
||||
default:
|
||||
return "", fmt.Errorf("invalid operator: %s is unknown", operator)
|
||||
func thresholdTrigger(rule chronograf.AlertRule) (string, error) {
|
||||
op, err := kapaOperator(rule.TriggerValues.Operator)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return fmt.Sprintf(ThresholdTrigger, op), nil
|
||||
}
|
||||
|
|
|
@ -51,8 +51,8 @@ var trigger = past
|
|||
.keep()
|
||||
.as('value')
|
||||
|alert()
|
||||
.stateChangesOnly()
|
||||
.crit(lambda: "value" > crit)
|
||||
.stateChangesOnly()
|
||||
.message(message)
|
||||
.id(idVar)
|
||||
.idTag(idTag)
|
||||
|
@ -83,8 +83,8 @@ var trigger = past
|
|||
.keep()
|
||||
.as('value')
|
||||
|alert()
|
||||
.stateChangesOnly()
|
||||
.crit(lambda: "value" > crit)
|
||||
.stateChangesOnly()
|
||||
.message(message)
|
||||
.id(idVar)
|
||||
.idTag(idTag)
|
||||
|
@ -104,8 +104,8 @@ var trigger = past
|
|||
},
|
||||
want: `var trigger = data
|
||||
|alert()
|
||||
.stateChangesOnly()
|
||||
.crit(lambda: "value" > crit)
|
||||
.stateChangesOnly()
|
||||
.message(message)
|
||||
.id(idVar)
|
||||
.idTag(idTag)
|
||||
|
|
|
@ -33,30 +33,26 @@ func Vars(rule chronograf.AlertRule) (string, error) {
|
|||
}
|
||||
|
||||
switch rule.Trigger {
|
||||
case "threshold":
|
||||
case Threshold:
|
||||
vars := `
|
||||
%s
|
||||
var every = %s
|
||||
var crit = %s
|
||||
`
|
||||
return fmt.Sprintf(vars,
|
||||
common,
|
||||
rule.Every,
|
||||
rule.TriggerValues.Value), nil
|
||||
case "relative":
|
||||
case Relative:
|
||||
vars := `
|
||||
%s
|
||||
var every = %s
|
||||
var shift = -%s
|
||||
var crit = %s
|
||||
`
|
||||
return fmt.Sprintf(vars,
|
||||
common,
|
||||
rule.Every,
|
||||
rule.TriggerValues.Shift,
|
||||
rule.TriggerValues.Value,
|
||||
), nil
|
||||
case "deadman":
|
||||
case Deadman:
|
||||
vars := `
|
||||
%s
|
||||
var threshold = %s
|
||||
|
@ -77,7 +73,7 @@ func commonVars(rule chronograf.AlertRule) (string, error) {
|
|||
var measurement = '%s'
|
||||
var groupBy = %s
|
||||
var whereFilter = %s
|
||||
var period = %s
|
||||
%s
|
||||
|
||||
var name = '%s'
|
||||
var idVar = name + ':{{.Group}}'
|
||||
|
@ -98,7 +94,7 @@ func commonVars(rule chronograf.AlertRule) (string, error) {
|
|||
rule.Query.Measurement,
|
||||
groupBy(rule.Query),
|
||||
whereFilter(rule.Query),
|
||||
rule.TriggerValues.Period,
|
||||
window(rule),
|
||||
rule.Name,
|
||||
rule.Message,
|
||||
IDTag,
|
||||
|
@ -112,6 +108,21 @@ func commonVars(rule chronograf.AlertRule) (string, error) {
|
|||
), nil
|
||||
}
|
||||
|
||||
// window is only used if deadman or threshold/relative with aggregate. Will return empty
|
||||
// if no period.
|
||||
func window(rule chronograf.AlertRule) string {
|
||||
if rule.Trigger == Deadman {
|
||||
return fmt.Sprintf("var period = %s", rule.TriggerValues.Period)
|
||||
}
|
||||
// Period only makes sense if the field has a been grouped via a time duration.
|
||||
for _, field := range rule.Query.Fields {
|
||||
if len(field.Funcs) > 0 {
|
||||
return fmt.Sprintf("var period = %s\nvar every = %s", rule.Query.GroupBy.Time, rule.Every)
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func groupBy(q chronograf.QueryConfig) string {
|
||||
groups := []string{}
|
||||
for _, tag := range q.GroupBy.Tags {
|
||||
|
|
|
@ -39,7 +39,7 @@ func (p *postKapacitorRequest) Valid() error {
|
|||
type kapaLinks struct {
|
||||
Proxy string `json:"proxy"` // URL location of proxy endpoint for this source
|
||||
Self string `json:"self"` // Self link mapping to this resource
|
||||
Rules string `json:"rules"` // Riles link for defining roles alerts for kapacitor
|
||||
Rules string `json:"rules"` // Rules link for defining roles alerts for kapacitor
|
||||
}
|
||||
|
||||
type kapacitor struct {
|
||||
|
@ -323,6 +323,7 @@ func (h *Service) KapacitorRulesPost(w http.ResponseWriter, r *http.Request) {
|
|||
Links: alertLinks{
|
||||
Self: fmt.Sprintf("/chronograf/v1/sources/%d/kapacitors/%d/rules/%s", srv.SrcID, srv.ID, req.ID),
|
||||
Kapacitor: fmt.Sprintf("/chronograf/v1/sources/%d/kapacitors/%d/proxy?path=%s", srv.SrcID, srv.ID, url.QueryEscape(task.Href)),
|
||||
Output: fmt.Sprintf("/chronograf/v1/sources/%d/kapacitors/%d/proxy?path=%s", srv.SrcID, srv.ID, url.QueryEscape(task.HrefOutput)),
|
||||
},
|
||||
TICKScript: string(task.TICKScript),
|
||||
}
|
||||
|
@ -334,6 +335,7 @@ func (h *Service) KapacitorRulesPost(w http.ResponseWriter, r *http.Request) {
|
|||
type alertLinks struct {
|
||||
Self string `json:"self"`
|
||||
Kapacitor string `json:"kapacitor"`
|
||||
Output string `json:"output"`
|
||||
}
|
||||
|
||||
type alertResponse struct {
|
||||
|
|
Loading…
Reference in New Issue