Merge pull request #450 from influxdata/kapacitor-group-by

Update tickscript generation to use group by time as period.
pull/10616/head
Chris Goller 2016-11-10 12:26:24 -06:00 committed by GitHub
commit e410fdc427
13 changed files with 223 additions and 196 deletions

1
Godeps
View File

@ -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

View File

@ -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

View File

@ -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
}

13
kapacitor/http_out.go Normal file
View File

@ -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
}

View File

@ -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"},

32
kapacitor/operators.go Normal file
View File

@ -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)
}
}

View File

@ -1 +0,0 @@
package kapacitor

View File

@ -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

View File

@ -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))
}
}
}

View File

@ -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
}

View File

@ -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)

View File

@ -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 {

View File

@ -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 {