mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
feat(alerting): working on alerting conditions model
This commit is contained in:
parent
f38c954639
commit
20fcffb71e
@ -4,7 +4,6 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"regexp"
|
"regexp"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/grafana/grafana/pkg/components/simplejson"
|
"github.com/grafana/grafana/pkg/components/simplejson"
|
||||||
"github.com/grafana/grafana/pkg/services/alerting/transformers"
|
"github.com/grafana/grafana/pkg/services/alerting/transformers"
|
||||||
@ -31,6 +30,19 @@ type AlertRule struct {
|
|||||||
NotificationGroups []int64
|
NotificationGroups []int64
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type AlertRule2 struct {
|
||||||
|
Id int64
|
||||||
|
OrgId int64
|
||||||
|
DashboardId int64
|
||||||
|
PanelId int64
|
||||||
|
Frequency int64
|
||||||
|
Name string
|
||||||
|
Description string
|
||||||
|
State string
|
||||||
|
Conditions []AlertCondition
|
||||||
|
Notifications []int64
|
||||||
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
ValueFormatRegex = regexp.MustCompile("^\\d+")
|
ValueFormatRegex = regexp.MustCompile("^\\d+")
|
||||||
UnitFormatRegex = regexp.MustCompile("\\w{1}$")
|
UnitFormatRegex = regexp.MustCompile("\\w{1}$")
|
||||||
@ -56,7 +68,11 @@ func getTimeDurationStringToSeconds(str string) int64 {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func NewAlertRuleFromDBModel(ruleDef *m.Alert) (*AlertRule, error) {
|
func NewAlertRuleFromDBModel(ruleDef *m.Alert) (*AlertRule, error) {
|
||||||
model := &AlertRule{}
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewAlertRuleFromDBModel2(ruleDef *m.Alert) (*AlertRule2, error) {
|
||||||
|
model := &AlertRule2{}
|
||||||
model.Id = ruleDef.Id
|
model.Id = ruleDef.Id
|
||||||
model.OrgId = ruleDef.OrgId
|
model.OrgId = ruleDef.OrgId
|
||||||
model.Name = ruleDef.Name
|
model.Name = ruleDef.Name
|
||||||
@ -64,55 +80,26 @@ func NewAlertRuleFromDBModel(ruleDef *m.Alert) (*AlertRule, error) {
|
|||||||
model.State = ruleDef.State
|
model.State = ruleDef.State
|
||||||
model.Frequency = ruleDef.Frequency
|
model.Frequency = ruleDef.Frequency
|
||||||
|
|
||||||
ngs := ruleDef.Settings.Get("notificationGroups").MustString()
|
for _, v := range ruleDef.Settings.Get("notifications").MustArray() {
|
||||||
var ids []int64
|
if id, ok := v.(int64); ok {
|
||||||
for _, v := range strings.Split(ngs, ",") {
|
model.Notifications = append(model.Notifications, int64(id))
|
||||||
id, err := strconv.Atoi(v)
|
|
||||||
if err == nil {
|
|
||||||
ids = append(ids, int64(id))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
model.NotificationGroups = ids
|
for _, condition := range ruleDef.Settings.Get("conditions").MustArray() {
|
||||||
|
conditionModel := simplejson.NewFromAny(condition)
|
||||||
critical := ruleDef.Settings.Get("crit")
|
switch conditionModel.Get("type").MustString() {
|
||||||
model.Critical = Level{
|
case "query":
|
||||||
Operator: critical.Get("op").MustString(),
|
queryCondition, err := NewQueryCondition(conditionModel)
|
||||||
Value: critical.Get("value").MustFloat64(),
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
model.Conditions = append(model.Conditions, queryCondition)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
warning := ruleDef.Settings.Get("warn")
|
if len(model.Conditions) == 0 {
|
||||||
model.Warning = Level{
|
return nil, fmt.Errorf("Alert is missing conditions")
|
||||||
Operator: warning.Get("op").MustString(),
|
|
||||||
Value: warning.Get("value").MustFloat64(),
|
|
||||||
}
|
|
||||||
|
|
||||||
model.Transform = ruleDef.Settings.Get("transform").Get("type").MustString()
|
|
||||||
if model.Transform == "" {
|
|
||||||
return nil, fmt.Errorf("missing transform")
|
|
||||||
}
|
|
||||||
|
|
||||||
model.TransformParams = *ruleDef.Settings.Get("transform")
|
|
||||||
|
|
||||||
if model.Transform == "aggregation" {
|
|
||||||
method := ruleDef.Settings.Get("transform").Get("method").MustString()
|
|
||||||
model.Transformer = transformers.NewAggregationTransformer(method)
|
|
||||||
}
|
|
||||||
|
|
||||||
query := ruleDef.Settings.Get("query")
|
|
||||||
model.Query = AlertQuery{
|
|
||||||
Query: query.Get("query").MustString(),
|
|
||||||
DatasourceId: query.Get("datasourceId").MustInt64(),
|
|
||||||
From: query.Get("from").MustString(),
|
|
||||||
To: query.Get("to").MustString(),
|
|
||||||
}
|
|
||||||
|
|
||||||
if model.Query.Query == "" {
|
|
||||||
return nil, fmt.Errorf("missing query.query")
|
|
||||||
}
|
|
||||||
|
|
||||||
if model.Query.DatasourceId == 0 {
|
|
||||||
return nil, fmt.Errorf("missing query.datasourceId")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return model, nil
|
return model, nil
|
||||||
|
@ -38,26 +38,19 @@ func TestAlertRuleModel(t *testing.T) {
|
|||||||
"description": "desc2",
|
"description": "desc2",
|
||||||
"handler": 0,
|
"handler": 0,
|
||||||
"enabled": true,
|
"enabled": true,
|
||||||
"crit": {
|
|
||||||
"value": 20,
|
|
||||||
"op": ">"
|
|
||||||
},
|
|
||||||
"warn": {
|
|
||||||
"value": 10,
|
|
||||||
"op": ">"
|
|
||||||
},
|
|
||||||
"frequency": "60s",
|
"frequency": "60s",
|
||||||
"query": {
|
"conditions": [
|
||||||
"from": "5m",
|
{
|
||||||
"refId": "A",
|
"type": "query",
|
||||||
"to": "now",
|
"query": {
|
||||||
"query": "aliasByNode(statsd.fakesite.counters.session_start.mobile.count, 4)",
|
"params": ["A", "5m", "now"],
|
||||||
"datasourceId": 1
|
"datasourceId": 1,
|
||||||
},
|
"query": "aliasByNode(statsd.fakesite.counters.session_start.mobile.count, 4)"
|
||||||
"transform": {
|
},
|
||||||
"type": "avg",
|
"reducer": {"type": "avg", "params": []},
|
||||||
"name": "aggregation"
|
"evaluator": {"type": ">", "params": [100]}
|
||||||
}
|
}
|
||||||
|
]
|
||||||
}
|
}
|
||||||
`
|
`
|
||||||
|
|
||||||
@ -72,15 +65,11 @@ func TestAlertRuleModel(t *testing.T) {
|
|||||||
|
|
||||||
Settings: alertJSON,
|
Settings: alertJSON,
|
||||||
}
|
}
|
||||||
alertRule, err := NewAlertRuleFromDBModel(alert)
|
|
||||||
|
|
||||||
|
alertRule, err := NewAlertRuleFromDBModel2(alert)
|
||||||
So(err, ShouldBeNil)
|
So(err, ShouldBeNil)
|
||||||
|
|
||||||
So(alertRule.Warning.Operator, ShouldEqual, ">")
|
So(alertRule.Conditions, ShouldHaveLength, 1)
|
||||||
So(alertRule.Warning.Value, ShouldEqual, 10)
|
|
||||||
|
|
||||||
So(alertRule.Critical.Operator, ShouldEqual, ">")
|
|
||||||
So(alertRule.Critical.Value, ShouldEqual, 20)
|
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
32
pkg/services/alerting/conditions.go
Normal file
32
pkg/services/alerting/conditions.go
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
package alerting
|
||||||
|
|
||||||
|
import "github.com/grafana/grafana/pkg/components/simplejson"
|
||||||
|
|
||||||
|
type AlertCondition interface {
|
||||||
|
Eval()
|
||||||
|
}
|
||||||
|
|
||||||
|
type QueryCondition struct {
|
||||||
|
Query AlertQuery
|
||||||
|
Reducer AlertReducerModel
|
||||||
|
Evaluator AlertEvaluatorModel
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *QueryCondition) Eval() {
|
||||||
|
}
|
||||||
|
|
||||||
|
type AlertReducerModel struct {
|
||||||
|
Type string
|
||||||
|
Params []interface{}
|
||||||
|
}
|
||||||
|
|
||||||
|
type AlertEvaluatorModel struct {
|
||||||
|
Type string
|
||||||
|
Params []interface{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewQueryCondition(model *simplejson.Json) (*QueryCondition, error) {
|
||||||
|
condition := QueryCondition{}
|
||||||
|
|
||||||
|
return &condition, nil
|
||||||
|
}
|
@ -38,7 +38,7 @@ func TestAlertResultHandler(t *testing.T) {
|
|||||||
|
|
||||||
Convey("alert state have changed", func() {
|
Convey("alert state have changed", func() {
|
||||||
mockAlertState = &m.AlertState{
|
mockAlertState = &m.AlertState{
|
||||||
NewState: alertstates.Critical,
|
State: alertstates.Critical,
|
||||||
}
|
}
|
||||||
mockResult.State = alertstates.Ok
|
mockResult.State = alertstates.Ok
|
||||||
So(resultHandler.shouldUpdateState(mockResult), ShouldBeTrue)
|
So(resultHandler.shouldUpdateState(mockResult), ShouldBeTrue)
|
||||||
@ -47,11 +47,11 @@ func TestAlertResultHandler(t *testing.T) {
|
|||||||
Convey("last alert state was 15min ago", func() {
|
Convey("last alert state was 15min ago", func() {
|
||||||
now := time.Now()
|
now := time.Now()
|
||||||
mockAlertState = &m.AlertState{
|
mockAlertState = &m.AlertState{
|
||||||
NewState: alertstates.Critical,
|
State: alertstates.Critical,
|
||||||
Created: now.Add(time.Minute * -30),
|
Created: now.Add(time.Minute * -30),
|
||||||
}
|
}
|
||||||
mockResult.State = alertstates.Critical
|
mockResult.State = alertstates.Critical
|
||||||
mockResult.ExeuctionTime = time.Now()
|
mockResult.StartTime = time.Now()
|
||||||
So(resultHandler.shouldUpdateState(mockResult), ShouldBeTrue)
|
So(resultHandler.shouldUpdateState(mockResult), ShouldBeTrue)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
@ -38,11 +38,15 @@ export class AlertTabCtrl {
|
|||||||
];
|
];
|
||||||
alert: any;
|
alert: any;
|
||||||
conditionModels: any;
|
conditionModels: any;
|
||||||
levelOpList = [
|
evalFunctions = [
|
||||||
{text: '>', value: '>'},
|
{text: '>', value: '>'},
|
||||||
{text: '<', value: '<'},
|
{text: '<', value: '<'},
|
||||||
{text: '=', value: '='},
|
|
||||||
];
|
];
|
||||||
|
severityLevels = [
|
||||||
|
{text: 'Critical', value: 'critical'},
|
||||||
|
{text: 'Warning', value: 'warning'},
|
||||||
|
];
|
||||||
|
|
||||||
|
|
||||||
/** @ngInject */
|
/** @ngInject */
|
||||||
constructor($scope, private $timeout) {
|
constructor($scope, private $timeout) {
|
||||||
@ -60,21 +64,15 @@ export class AlertTabCtrl {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
getThresholdWithDefaults(threshold) {
|
|
||||||
threshold = threshold || {};
|
|
||||||
threshold.op = threshold.op || '>';
|
|
||||||
threshold.value = threshold.value || undefined;
|
|
||||||
return threshold;
|
|
||||||
}
|
|
||||||
|
|
||||||
initModel() {
|
initModel() {
|
||||||
var alert = this.alert = this.panel.alert = this.panel.alert || {};
|
var alert = this.alert = this.panel.alert = this.panel.alert || {};
|
||||||
|
|
||||||
alert.conditions = alert.conditions || [];
|
alert.conditions = [];
|
||||||
if (alert.conditions.length === 0) {
|
if (alert.conditions.length === 0) {
|
||||||
alert.conditions.push(this.buildDefaultCondition());
|
alert.conditions.push(this.buildDefaultCondition());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
alert.severity = alert.severity || 'critical';
|
||||||
alert.frequency = alert.frequency || '60s';
|
alert.frequency = alert.frequency || '60s';
|
||||||
alert.handler = alert.handler || 1;
|
alert.handler = alert.handler || 1;
|
||||||
alert.notifications = alert.notifications || [];
|
alert.notifications = alert.notifications || [];
|
||||||
@ -95,32 +93,23 @@ export class AlertTabCtrl {
|
|||||||
buildDefaultCondition() {
|
buildDefaultCondition() {
|
||||||
return {
|
return {
|
||||||
type: 'query',
|
type: 'query',
|
||||||
refId: 'A',
|
query: {params: ['A', '5m', 'now']},
|
||||||
from: '5m',
|
reducer: {type: 'avg', params: []},
|
||||||
to: 'now',
|
evaluator: {type: '>', params: [null]},
|
||||||
reducer: 'avg',
|
|
||||||
reducerParams: [],
|
|
||||||
warn: this.getThresholdWithDefaults({}),
|
|
||||||
crit: this.getThresholdWithDefaults({}),
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
buildConditionModel(source) {
|
buildConditionModel(source) {
|
||||||
var cm: any = {source: source, type: source.type};
|
var cm: any = {source: source, type: source.type};
|
||||||
|
|
||||||
var queryPartModel = {
|
cm.queryPart = new QueryPart(source.query, alertQueryDef);
|
||||||
params: [source.refId, source.from, source.to]
|
|
||||||
};
|
|
||||||
|
|
||||||
cm.queryPart = new QueryPart(queryPartModel, alertQueryDef);
|
|
||||||
cm.reducerPart = new QueryPart({params: []}, reducerAvgDef);
|
cm.reducerPart = new QueryPart({params: []}, reducerAvgDef);
|
||||||
|
cm.evaluator = source.evaluator;
|
||||||
|
|
||||||
return cm;
|
return cm;
|
||||||
}
|
}
|
||||||
|
|
||||||
queryPartUpdated(conditionModel) {
|
queryPartUpdated(conditionModel) {
|
||||||
conditionModel.source.refId = conditionModel.queryPart.params[0];
|
|
||||||
conditionModel.source.from = conditionModel.queryPart.params[1];
|
|
||||||
conditionModel.source.to = conditionModel.queryPart.params[2];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
addCondition(type) {
|
addCondition(type) {
|
||||||
@ -138,10 +127,6 @@ export class AlertTabCtrl {
|
|||||||
|
|
||||||
delete() {
|
delete() {
|
||||||
this.alert.enabled = false;
|
this.alert.enabled = false;
|
||||||
this.alert.warn.value = undefined;
|
|
||||||
this.alert.crit.value = undefined;
|
|
||||||
|
|
||||||
// reset model but keep thresholds instance
|
|
||||||
this.initModel();
|
this.initModel();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -27,31 +27,42 @@
|
|||||||
<div class="gf-form-group">
|
<div class="gf-form-group">
|
||||||
<h5 class="section-heading">Alert Rule</h5>
|
<h5 class="section-heading">Alert Rule</h5>
|
||||||
<div class="gf-form-inline">
|
<div class="gf-form-inline">
|
||||||
<div class="gf-form">
|
<div class="gf-form max-width-30">
|
||||||
<span class="gf-form-label width-8">Name</span>
|
<span class="gf-form-label width-8">Name</span>
|
||||||
<input type="text" class="gf-form-input width-22" ng-model="ctrl.alert.name">
|
<input type="text" class="gf-form-input width-22" ng-model="ctrl.alert.name">
|
||||||
</div>
|
</div>
|
||||||
<div class="gf-form">
|
<!-- <div class="gf-form"> -->
|
||||||
<span class="gf-form-label">Handler</span>
|
<!-- <span class="gf-form-label width-6">Handler</span> -->
|
||||||
<div class="gf-form-select-wrapper">
|
<!-- <div class="gf-form-select-wrapper"> -->
|
||||||
<select class="gf-form-input"
|
<!-- <select class="gf-form-input" -->
|
||||||
ng-model="ctrl.alert.handler"
|
<!-- ng-model="ctrl.alert.handler" -->
|
||||||
ng-options="f.value as f.text for f in ctrl.handlers">
|
<!-- ng-options="f.value as f.text for f in ctrl.handlers"> -->
|
||||||
</select>
|
<!-- </select> -->
|
||||||
</div>
|
<!-- </div> -->
|
||||||
</div>
|
<!-- </div> -->
|
||||||
<div class="gf-form">
|
<div class="gf-form">
|
||||||
<span class="gf-form-label width-8">Evaluate every</span>
|
<span class="gf-form-label width-8">Evaluate every</span>
|
||||||
<input class="gf-form-input max-width-7" type="text" ng-model="ctrl.alert.frequency"></input>
|
<input class="gf-form-input max-width-7" type="text" ng-model="ctrl.alert.frequency"></input>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="gf-form">
|
<div class="gf-form-inline">
|
||||||
<span class="gf-form-label width-8">Notifications</span>
|
<div class="gf-form max-width-30">
|
||||||
<input class="gf-form-input max-width-22" type="text" ng-model="ctrl.alert.notify"></input>
|
<span class="gf-form-label width-8">Notifications</span>
|
||||||
|
<input class="gf-form-input max-width-22" type="text" ng-model="ctrl.alert.notifications"></input>
|
||||||
|
</div>
|
||||||
<!--
|
<!--
|
||||||
<bootstrap-tagsinput ng-model="ctrl.alert.notify" tagclass="label label-tag" placeholder="add tags">
|
<bootstrap-tagsinput ng-model="ctrl.alert.notify" tagclass="label label-tag" placeholder="add tags">
|
||||||
</bootstrap-tagsinput>
|
</bootstrap-tagsinput>
|
||||||
-->
|
-->
|
||||||
|
<div class="gf-form">
|
||||||
|
<span class="gf-form-label width-8">Severity</span>
|
||||||
|
<div class="gf-form-select-wrapper">
|
||||||
|
<select class="gf-form-input"
|
||||||
|
ng-model="ctrl.alert.severity"
|
||||||
|
ng-options="f.value as f.text for f in ctrl.severityLevels">
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -59,7 +70,7 @@
|
|||||||
<h5 class="section-heading">Conditions</h5>
|
<h5 class="section-heading">Conditions</h5>
|
||||||
<div class="gf-form-inline" ng-repeat="conditionModel in ctrl.conditionModels">
|
<div class="gf-form-inline" ng-repeat="conditionModel in ctrl.conditionModels">
|
||||||
<div class="gf-form">
|
<div class="gf-form">
|
||||||
<span class="gf-form-label">{{$index+1}}</span>
|
<span class="gf-form-label query-keyword">AND</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="gf-form">
|
<div class="gf-form">
|
||||||
<query-part-editor
|
<query-part-editor
|
||||||
@ -77,21 +88,11 @@
|
|||||||
</query-part-editor>
|
</query-part-editor>
|
||||||
</div>
|
</div>
|
||||||
<div class="gf-form">
|
<div class="gf-form">
|
||||||
<span class="gf-form-label">
|
<span class="gf-form-label">When Value</span>
|
||||||
<i class="icon-gf icon-gf-warn alert-icon-critical"></i>
|
<metric-segment-model property="conditionModel.evaluator.type" options="ctrl.evalFunctions" custom="false" css-class="query-segment-operator" on-change="ctrl.thresholdUpdated()"></metric-segment-model>
|
||||||
Critcal if
|
<input class="gf-form-input max-width-7" type="number" ng-model="conditionModel.evaluator.params[0]" ng-change="ctrl.thresholdsUpdated()"></input>
|
||||||
</span>
|
|
||||||
<metric-segment-model property="ctrl.alert.crit.op" options="ctrl.operatorList" custom="false" css-class="query-segment-operator" on-change="ctrl.thresholdsUpdated()"></metric-segment-model>
|
|
||||||
<input class="gf-form-input max-width-7" type="number" ng-model="ctrl.alert.crit.value" ng-change="ctrl.thresholdsUpdated()"></input>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="gf-form">
|
<div class="gf-form">
|
||||||
<span class="gf-form-label">
|
|
||||||
<i class="icon-gf icon-gf-warn alert-icon-warn"></i>
|
|
||||||
Warn if
|
|
||||||
</span>
|
|
||||||
<metric-segment-model property="ctrl.alert.warn.op" options="ctrl.operatorList" custom="false" css-class="query-segment-operator" on-change="ctrl.thresholdsUpdated()"></metric-segment-model>
|
|
||||||
<input class="gf-form-input max-width-7" type="number" ng-model="ctrl.alert.warn.value" ng-change="ctrl.thresholdsUpdated()"></input>
|
|
||||||
|
|
||||||
<label class="gf-form-label">
|
<label class="gf-form-label">
|
||||||
<a class="pointer" tabindex="1" ng-click="ctrl.removeCondition($index)">
|
<a class="pointer" tabindex="1" ng-click="ctrl.removeCondition($index)">
|
||||||
<i class="fa fa-trash"></i>
|
<i class="fa fa-trash"></i>
|
||||||
|
Loading…
Reference in New Issue
Block a user