merge with master
This commit is contained in:
@@ -5,6 +5,7 @@ import (
|
||||
|
||||
"github.com/grafana/grafana/pkg/components/simplejson"
|
||||
"github.com/grafana/grafana/pkg/services/alerting"
|
||||
"gopkg.in/guregu/null.v3"
|
||||
)
|
||||
|
||||
var (
|
||||
@@ -13,13 +14,13 @@ var (
|
||||
)
|
||||
|
||||
type AlertEvaluator interface {
|
||||
Eval(reducedValue *float64) bool
|
||||
Eval(reducedValue null.Float) bool
|
||||
}
|
||||
|
||||
type NoDataEvaluator struct{}
|
||||
|
||||
func (e *NoDataEvaluator) Eval(reducedValue *float64) bool {
|
||||
return reducedValue == nil
|
||||
func (e *NoDataEvaluator) Eval(reducedValue null.Float) bool {
|
||||
return reducedValue.Valid == false
|
||||
}
|
||||
|
||||
type ThresholdEvaluator struct {
|
||||
@@ -43,16 +44,16 @@ func newThresholdEvaludator(typ string, model *simplejson.Json) (*ThresholdEvalu
|
||||
return defaultEval, nil
|
||||
}
|
||||
|
||||
func (e *ThresholdEvaluator) Eval(reducedValue *float64) bool {
|
||||
if reducedValue == nil {
|
||||
func (e *ThresholdEvaluator) Eval(reducedValue null.Float) bool {
|
||||
if reducedValue.Valid == false {
|
||||
return false
|
||||
}
|
||||
|
||||
switch e.Type {
|
||||
case "gt":
|
||||
return *reducedValue > e.Threshold
|
||||
return reducedValue.Float64 > e.Threshold
|
||||
case "lt":
|
||||
return *reducedValue < e.Threshold
|
||||
return reducedValue.Float64 < e.Threshold
|
||||
}
|
||||
|
||||
return false
|
||||
@@ -86,16 +87,18 @@ func newRangedEvaluator(typ string, model *simplejson.Json) (*RangedEvaluator, e
|
||||
return rangedEval, nil
|
||||
}
|
||||
|
||||
func (e *RangedEvaluator) Eval(reducedValue *float64) bool {
|
||||
if reducedValue == nil {
|
||||
func (e *RangedEvaluator) Eval(reducedValue null.Float) bool {
|
||||
if reducedValue.Valid == false {
|
||||
return false
|
||||
}
|
||||
|
||||
floatValue := reducedValue.Float64
|
||||
|
||||
switch e.Type {
|
||||
case "within_range":
|
||||
return (e.Lower < *reducedValue && e.Upper > *reducedValue) || (e.Upper < *reducedValue && e.Lower > *reducedValue)
|
||||
return (e.Lower < floatValue && e.Upper > floatValue) || (e.Upper < floatValue && e.Lower > floatValue)
|
||||
case "outside_range":
|
||||
return (e.Upper < *reducedValue && e.Lower < *reducedValue) || (e.Upper > *reducedValue && e.Lower > *reducedValue)
|
||||
return (e.Upper < floatValue && e.Lower < floatValue) || (e.Upper > floatValue && e.Lower > floatValue)
|
||||
}
|
||||
|
||||
return false
|
||||
|
||||
@@ -3,6 +3,8 @@ package conditions
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"gopkg.in/guregu/null.v3"
|
||||
|
||||
"github.com/grafana/grafana/pkg/components/simplejson"
|
||||
. "github.com/smartystreets/goconvey/convey"
|
||||
)
|
||||
@@ -14,7 +16,7 @@ func evalutorScenario(json string, reducedValue float64, datapoints ...float64)
|
||||
evaluator, err := NewAlertEvaluator(jsonModel)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
return evaluator.Eval(&reducedValue)
|
||||
return evaluator.Eval(null.FloatFrom(reducedValue))
|
||||
}
|
||||
|
||||
func TestEvalutors(t *testing.T) {
|
||||
@@ -51,6 +53,6 @@ func TestEvalutors(t *testing.T) {
|
||||
evaluator, err := NewAlertEvaluator(jsonModel)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
So(evaluator.Eval(nil), ShouldBeTrue)
|
||||
So(evaluator.Eval(null.FloatFromPtr(nil)), ShouldBeTrue)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -2,6 +2,8 @@ package conditions
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/grafana/grafana/pkg/bus"
|
||||
"github.com/grafana/grafana/pkg/components/simplejson"
|
||||
@@ -32,7 +34,8 @@ type AlertQuery struct {
|
||||
}
|
||||
|
||||
func (c *QueryCondition) Eval(context *alerting.EvalContext) {
|
||||
seriesList, err := c.executeQuery(context)
|
||||
timeRange := tsdb.NewTimeRange(c.Query.From, c.Query.To)
|
||||
seriesList, err := c.executeQuery(context, timeRange)
|
||||
if err != nil {
|
||||
context.Error = err
|
||||
return
|
||||
@@ -43,21 +46,21 @@ func (c *QueryCondition) Eval(context *alerting.EvalContext) {
|
||||
reducedValue := c.Reducer.Reduce(series)
|
||||
evalMatch := c.Evaluator.Eval(reducedValue)
|
||||
|
||||
if reducedValue == nil {
|
||||
if reducedValue.Valid == false {
|
||||
emptySerieCount++
|
||||
continue
|
||||
}
|
||||
|
||||
if context.IsTestRun {
|
||||
context.Logs = append(context.Logs, &alerting.ResultLogEntry{
|
||||
Message: fmt.Sprintf("Condition[%d]: Eval: %v, Metric: %s, Value: %1.3f", c.Index, evalMatch, series.Name, *reducedValue),
|
||||
Message: fmt.Sprintf("Condition[%d]: Eval: %v, Metric: %s, Value: %1.3f", c.Index, evalMatch, series.Name, reducedValue.Float64),
|
||||
})
|
||||
}
|
||||
|
||||
if evalMatch {
|
||||
context.EvalMatches = append(context.EvalMatches, &alerting.EvalMatch{
|
||||
Metric: series.Name,
|
||||
Value: *reducedValue,
|
||||
Value: reducedValue.Float64,
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -66,7 +69,7 @@ func (c *QueryCondition) Eval(context *alerting.EvalContext) {
|
||||
context.Firing = len(context.EvalMatches) > 0
|
||||
}
|
||||
|
||||
func (c *QueryCondition) executeQuery(context *alerting.EvalContext) (tsdb.TimeSeriesSlice, error) {
|
||||
func (c *QueryCondition) executeQuery(context *alerting.EvalContext, timeRange *tsdb.TimeRange) (tsdb.TimeSeriesSlice, error) {
|
||||
getDsInfo := &m.GetDataSourceByIdQuery{
|
||||
Id: c.Query.DatasourceId,
|
||||
OrgId: context.Rule.OrgId,
|
||||
@@ -76,7 +79,7 @@ func (c *QueryCondition) executeQuery(context *alerting.EvalContext) (tsdb.TimeS
|
||||
return nil, fmt.Errorf("Could not find datasource")
|
||||
}
|
||||
|
||||
req := c.getRequestForAlertRule(getDsInfo.Result)
|
||||
req := c.getRequestForAlertRule(getDsInfo.Result, timeRange)
|
||||
result := make(tsdb.TimeSeriesSlice, 0)
|
||||
|
||||
resp, err := c.HandleRequest(req)
|
||||
@@ -102,16 +105,13 @@ func (c *QueryCondition) executeQuery(context *alerting.EvalContext) (tsdb.TimeS
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func (c *QueryCondition) getRequestForAlertRule(datasource *m.DataSource) *tsdb.Request {
|
||||
func (c *QueryCondition) getRequestForAlertRule(datasource *m.DataSource, timeRange *tsdb.TimeRange) *tsdb.Request {
|
||||
req := &tsdb.Request{
|
||||
TimeRange: tsdb.TimeRange{
|
||||
From: c.Query.From,
|
||||
To: c.Query.To,
|
||||
},
|
||||
TimeRange: timeRange,
|
||||
Queries: []*tsdb.Query{
|
||||
{
|
||||
RefId: "A",
|
||||
Query: c.Query.Model.Get("target").MustString(),
|
||||
Model: c.Query.Model,
|
||||
DataSource: &tsdb.DataSourceInfo{
|
||||
Id: datasource.Id,
|
||||
Name: datasource.Name,
|
||||
@@ -141,6 +141,15 @@ func NewQueryCondition(model *simplejson.Json, index int) (*QueryCondition, erro
|
||||
condition.Query.Model = queryJson.Get("model")
|
||||
condition.Query.From = queryJson.Get("params").MustArray()[1].(string)
|
||||
condition.Query.To = queryJson.Get("params").MustArray()[2].(string)
|
||||
|
||||
if err := validateFromValue(condition.Query.From); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := validateToValue(condition.Query.To); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
condition.Query.DatasourceId = queryJson.Get("datasourceId").MustInt64()
|
||||
|
||||
reducerJson := model.Get("reducer")
|
||||
@@ -155,3 +164,26 @@ func NewQueryCondition(model *simplejson.Json, index int) (*QueryCondition, erro
|
||||
condition.Evaluator = evaluator
|
||||
return &condition, nil
|
||||
}
|
||||
|
||||
func validateFromValue(from string) error {
|
||||
fromRaw := strings.Replace(from, "now-", "", 1)
|
||||
|
||||
_, err := time.ParseDuration("-" + fromRaw)
|
||||
return err
|
||||
}
|
||||
|
||||
func validateToValue(to string) error {
|
||||
if to == "now" {
|
||||
return nil
|
||||
} else if strings.HasPrefix(to, "now-") {
|
||||
withoutNow := strings.Replace(to, "now-", "", 1)
|
||||
|
||||
_, err := time.ParseDuration("-" + withoutNow)
|
||||
if err == nil {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
_, err := time.ParseDuration(to)
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -3,6 +3,8 @@ package conditions
|
||||
import (
|
||||
"testing"
|
||||
|
||||
null "gopkg.in/guregu/null.v3"
|
||||
|
||||
"github.com/grafana/grafana/pkg/bus"
|
||||
"github.com/grafana/grafana/pkg/components/simplejson"
|
||||
m "github.com/grafana/grafana/pkg/models"
|
||||
@@ -41,9 +43,8 @@ func TestQueryCondition(t *testing.T) {
|
||||
})
|
||||
|
||||
Convey("should fire when avg is above 100", func() {
|
||||
one := float64(120)
|
||||
two := float64(0)
|
||||
ctx.series = tsdb.TimeSeriesSlice{tsdb.NewTimeSeries("test1", [][2]*float64{{&one, &two}})}
|
||||
points := tsdb.NewTimeSeriesPointsFromArgs(120, 0)
|
||||
ctx.series = tsdb.TimeSeriesSlice{tsdb.NewTimeSeries("test1", points)}
|
||||
ctx.exec()
|
||||
|
||||
So(ctx.result.Error, ShouldBeNil)
|
||||
@@ -51,9 +52,8 @@ func TestQueryCondition(t *testing.T) {
|
||||
})
|
||||
|
||||
Convey("Should not fire when avg is below 100", func() {
|
||||
one := float64(90)
|
||||
two := float64(0)
|
||||
ctx.series = tsdb.TimeSeriesSlice{tsdb.NewTimeSeries("test1", [][2]*float64{{&one, &two}})}
|
||||
points := tsdb.NewTimeSeriesPointsFromArgs(90, 0)
|
||||
ctx.series = tsdb.TimeSeriesSlice{tsdb.NewTimeSeries("test1", points)}
|
||||
ctx.exec()
|
||||
|
||||
So(ctx.result.Error, ShouldBeNil)
|
||||
@@ -61,11 +61,9 @@ func TestQueryCondition(t *testing.T) {
|
||||
})
|
||||
|
||||
Convey("Should fire if only first serie matches", func() {
|
||||
one := float64(120)
|
||||
two := float64(0)
|
||||
ctx.series = tsdb.TimeSeriesSlice{
|
||||
tsdb.NewTimeSeries("test1", [][2]*float64{{&one, &two}}),
|
||||
tsdb.NewTimeSeries("test2", [][2]*float64{{&two, &two}}),
|
||||
tsdb.NewTimeSeries("test1", tsdb.NewTimeSeriesPointsFromArgs(120, 0)),
|
||||
tsdb.NewTimeSeries("test2", tsdb.NewTimeSeriesPointsFromArgs(0, 0)),
|
||||
}
|
||||
ctx.exec()
|
||||
|
||||
@@ -76,8 +74,8 @@ func TestQueryCondition(t *testing.T) {
|
||||
Convey("Empty series", func() {
|
||||
Convey("Should set NoDataFound both series are empty", func() {
|
||||
ctx.series = tsdb.TimeSeriesSlice{
|
||||
tsdb.NewTimeSeries("test1", [][2]*float64{}),
|
||||
tsdb.NewTimeSeries("test2", [][2]*float64{}),
|
||||
tsdb.NewTimeSeries("test1", tsdb.NewTimeSeriesPointsFromArgs()),
|
||||
tsdb.NewTimeSeries("test2", tsdb.NewTimeSeriesPointsFromArgs()),
|
||||
}
|
||||
ctx.exec()
|
||||
|
||||
@@ -86,10 +84,9 @@ func TestQueryCondition(t *testing.T) {
|
||||
})
|
||||
|
||||
Convey("Should set NoDataFound both series contains null", func() {
|
||||
one := float64(120)
|
||||
ctx.series = tsdb.TimeSeriesSlice{
|
||||
tsdb.NewTimeSeries("test1", [][2]*float64{{nil, &one}}),
|
||||
tsdb.NewTimeSeries("test2", [][2]*float64{{nil, &one}}),
|
||||
tsdb.NewTimeSeries("test1", tsdb.TimeSeriesPoints{tsdb.TimePoint{null.FloatFromPtr(nil), null.FloatFrom(0)}}),
|
||||
tsdb.NewTimeSeries("test2", tsdb.TimeSeriesPoints{tsdb.TimePoint{null.FloatFromPtr(nil), null.FloatFrom(0)}}),
|
||||
}
|
||||
ctx.exec()
|
||||
|
||||
@@ -98,11 +95,9 @@ func TestQueryCondition(t *testing.T) {
|
||||
})
|
||||
|
||||
Convey("Should not set NoDataFound if one serie is empty", func() {
|
||||
one := float64(120)
|
||||
two := float64(0)
|
||||
ctx.series = tsdb.TimeSeriesSlice{
|
||||
tsdb.NewTimeSeries("test1", [][2]*float64{}),
|
||||
tsdb.NewTimeSeries("test2", [][2]*float64{{&one, &two}}),
|
||||
tsdb.NewTimeSeries("test1", tsdb.NewTimeSeriesPointsFromArgs()),
|
||||
tsdb.NewTimeSeries("test2", tsdb.NewTimeSeriesPointsFromArgs(120, 0)),
|
||||
}
|
||||
ctx.exec()
|
||||
|
||||
|
||||
@@ -4,19 +4,20 @@ import (
|
||||
"math"
|
||||
|
||||
"github.com/grafana/grafana/pkg/tsdb"
|
||||
"gopkg.in/guregu/null.v3"
|
||||
)
|
||||
|
||||
type QueryReducer interface {
|
||||
Reduce(timeSeries *tsdb.TimeSeries) *float64
|
||||
Reduce(timeSeries *tsdb.TimeSeries) null.Float
|
||||
}
|
||||
|
||||
type SimpleReducer struct {
|
||||
Type string
|
||||
}
|
||||
|
||||
func (s *SimpleReducer) Reduce(series *tsdb.TimeSeries) *float64 {
|
||||
func (s *SimpleReducer) Reduce(series *tsdb.TimeSeries) null.Float {
|
||||
if len(series.Points) == 0 {
|
||||
return nil
|
||||
return null.FloatFromPtr(nil)
|
||||
}
|
||||
|
||||
value := float64(0)
|
||||
@@ -25,36 +26,36 @@ func (s *SimpleReducer) Reduce(series *tsdb.TimeSeries) *float64 {
|
||||
switch s.Type {
|
||||
case "avg":
|
||||
for _, point := range series.Points {
|
||||
if point[0] != nil {
|
||||
value += *point[0]
|
||||
if point[0].Valid {
|
||||
value += point[0].Float64
|
||||
allNull = false
|
||||
}
|
||||
}
|
||||
value = value / float64(len(series.Points))
|
||||
case "sum":
|
||||
for _, point := range series.Points {
|
||||
if point[0] != nil {
|
||||
value += *point[0]
|
||||
if point[0].Valid {
|
||||
value += point[0].Float64
|
||||
allNull = false
|
||||
}
|
||||
}
|
||||
case "min":
|
||||
value = math.MaxFloat64
|
||||
for _, point := range series.Points {
|
||||
if point[0] != nil {
|
||||
if point[0].Valid {
|
||||
allNull = false
|
||||
if value > *point[0] {
|
||||
value = *point[0]
|
||||
if value > point[0].Float64 {
|
||||
value = point[0].Float64
|
||||
}
|
||||
}
|
||||
}
|
||||
case "max":
|
||||
value = -math.MaxFloat64
|
||||
for _, point := range series.Points {
|
||||
if point[0] != nil {
|
||||
if point[0].Valid {
|
||||
allNull = false
|
||||
if value < *point[0] {
|
||||
value = *point[0]
|
||||
if value < point[0].Float64 {
|
||||
value = point[0].Float64
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -64,10 +65,10 @@ func (s *SimpleReducer) Reduce(series *tsdb.TimeSeries) *float64 {
|
||||
}
|
||||
|
||||
if allNull {
|
||||
return nil
|
||||
return null.FloatFromPtr(nil)
|
||||
}
|
||||
|
||||
return &value
|
||||
return null.FloatFrom(value)
|
||||
}
|
||||
|
||||
func NewSimpleReducer(typ string) *SimpleReducer {
|
||||
|
||||
@@ -10,44 +10,41 @@ import (
|
||||
func TestSimpleReducer(t *testing.T) {
|
||||
Convey("Test simple reducer by calculating", t, func() {
|
||||
Convey("avg", func() {
|
||||
result := *testReducer("avg", 1, 2, 3)
|
||||
result := testReducer("avg", 1, 2, 3)
|
||||
So(result, ShouldEqual, float64(2))
|
||||
})
|
||||
|
||||
Convey("sum", func() {
|
||||
result := *testReducer("sum", 1, 2, 3)
|
||||
result := testReducer("sum", 1, 2, 3)
|
||||
So(result, ShouldEqual, float64(6))
|
||||
})
|
||||
|
||||
Convey("min", func() {
|
||||
result := *testReducer("min", 3, 2, 1)
|
||||
result := testReducer("min", 3, 2, 1)
|
||||
So(result, ShouldEqual, float64(1))
|
||||
})
|
||||
|
||||
Convey("max", func() {
|
||||
result := *testReducer("max", 1, 2, 3)
|
||||
result := testReducer("max", 1, 2, 3)
|
||||
So(result, ShouldEqual, float64(3))
|
||||
})
|
||||
|
||||
Convey("count", func() {
|
||||
result := *testReducer("count", 1, 2, 3000)
|
||||
result := testReducer("count", 1, 2, 3000)
|
||||
So(result, ShouldEqual, float64(3))
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
func testReducer(typ string, datapoints ...float64) *float64 {
|
||||
func testReducer(typ string, datapoints ...float64) float64 {
|
||||
reducer := NewSimpleReducer(typ)
|
||||
var timeserie [][2]*float64
|
||||
dummieTimestamp := float64(521452145)
|
||||
series := &tsdb.TimeSeries{
|
||||
Name: "test time serie",
|
||||
}
|
||||
|
||||
for idx := range datapoints {
|
||||
timeserie = append(timeserie, [2]*float64{&datapoints[idx], &dummieTimestamp})
|
||||
series.Points = append(series.Points, tsdb.NewTimePoint(datapoints[idx], 1234134))
|
||||
}
|
||||
|
||||
tsdb := &tsdb.TimeSeries{
|
||||
Name: "test time serie",
|
||||
Points: timeserie,
|
||||
}
|
||||
return reducer.Reduce(tsdb)
|
||||
return reducer.Reduce(series).Float64
|
||||
}
|
||||
|
||||
@@ -93,14 +93,18 @@ func (e *Engine) executeJob(job *Job) {
|
||||
}
|
||||
|
||||
func (e *Engine) resultDispatcher() {
|
||||
for result := range e.resultQueue {
|
||||
go e.handleResponse(result)
|
||||
}
|
||||
}
|
||||
|
||||
func (e *Engine) handleResponse(result *EvalContext) {
|
||||
defer func() {
|
||||
if err := recover(); err != nil {
|
||||
e.log.Error("Panic in resultDispatcher", "error", err, "stack", log.Stack(1))
|
||||
}
|
||||
}()
|
||||
|
||||
for result := range e.resultQueue {
|
||||
e.log.Debug("Alert Rule Result", "ruleId", result.Rule.Id, "firing", result.Firing)
|
||||
e.resultHandler.Handle(result)
|
||||
}
|
||||
e.log.Debug("Alert Rule Result", "ruleId", result.Rule.Id, "firing", result.Firing)
|
||||
e.resultHandler.Handle(result)
|
||||
}
|
||||
|
||||
@@ -71,7 +71,7 @@ func (c *EvalContext) GetNotificationTitle() string {
|
||||
return "[" + c.GetStateModel().Text + "] " + c.Rule.Name
|
||||
}
|
||||
|
||||
func (c *EvalContext) getDashboardSlug() (string, error) {
|
||||
func (c *EvalContext) GetDashboardSlug() (string, error) {
|
||||
if c.dashboardSlug != "" {
|
||||
return c.dashboardSlug, nil
|
||||
}
|
||||
@@ -86,7 +86,7 @@ func (c *EvalContext) getDashboardSlug() (string, error) {
|
||||
}
|
||||
|
||||
func (c *EvalContext) GetRuleUrl() (string, error) {
|
||||
if slug, err := c.getDashboardSlug(); err != nil {
|
||||
if slug, err := c.GetDashboardSlug(); err != nil {
|
||||
return "", err
|
||||
} else {
|
||||
ruleUrl := fmt.Sprintf("%sdashboard/db/%s?fullscreen&edit&tab=alert&panelId=%d", setting.AppUrl, slug, c.Rule.PanelId)
|
||||
@@ -94,15 +94,6 @@ func (c *EvalContext) GetRuleUrl() (string, error) {
|
||||
}
|
||||
}
|
||||
|
||||
func (c *EvalContext) GetImageUrl() (string, error) {
|
||||
if slug, err := c.getDashboardSlug(); err != nil {
|
||||
return "", err
|
||||
} else {
|
||||
ruleUrl := fmt.Sprintf("%sdashboard-solo/db/%s?&panelId=%d", setting.AppUrl, slug, c.Rule.PanelId)
|
||||
return ruleUrl, nil
|
||||
}
|
||||
}
|
||||
|
||||
func NewEvalContext(rule *Rule) *EvalContext {
|
||||
return &EvalContext{
|
||||
StartTime: time.Now(),
|
||||
|
||||
@@ -20,7 +20,7 @@ type DefaultEvalHandler struct {
|
||||
func NewEvalHandler() *DefaultEvalHandler {
|
||||
return &DefaultEvalHandler{
|
||||
log: log.New("alerting.evalHandler"),
|
||||
alertJobTimeout: time.Second * 10,
|
||||
alertJobTimeout: time.Second * 15,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -6,6 +6,8 @@ import (
|
||||
_ "github.com/grafana/grafana/pkg/services/alerting/notifiers"
|
||||
"github.com/grafana/grafana/pkg/setting"
|
||||
_ "github.com/grafana/grafana/pkg/tsdb/graphite"
|
||||
_ "github.com/grafana/grafana/pkg/tsdb/prometheus"
|
||||
_ "github.com/grafana/grafana/pkg/tsdb/testdata"
|
||||
)
|
||||
|
||||
var engine *alerting.Engine
|
||||
|
||||
@@ -2,6 +2,7 @@ package alerting
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"github.com/grafana/grafana/pkg/bus"
|
||||
"github.com/grafana/grafana/pkg/components/imguploader"
|
||||
@@ -60,20 +61,20 @@ func (n *RootNotifier) sendNotifications(notifiers []Notifier, context *EvalCont
|
||||
}
|
||||
}
|
||||
|
||||
func (n *RootNotifier) uploadImage(context *EvalContext) error {
|
||||
func (n *RootNotifier) uploadImage(context *EvalContext) (err error) {
|
||||
uploader, _ := imguploader.NewImageUploader()
|
||||
|
||||
imageUrl, err := context.GetImageUrl()
|
||||
if err != nil {
|
||||
return err
|
||||
renderOpts := &renderer.RenderOpts{
|
||||
Width: "800",
|
||||
Height: "400",
|
||||
Timeout: "30",
|
||||
OrgId: context.Rule.OrgId,
|
||||
}
|
||||
|
||||
renderOpts := &renderer.RenderOpts{
|
||||
Url: imageUrl,
|
||||
Width: "800",
|
||||
Height: "400",
|
||||
SessionId: "123",
|
||||
Timeout: "10",
|
||||
if slug, err := context.GetDashboardSlug(); err != nil {
|
||||
return err
|
||||
} else {
|
||||
renderOpts.Path = fmt.Sprintf("dashboard-solo/db/%s?&panelId=%d", slug, context.Rule.PanelId)
|
||||
}
|
||||
|
||||
if imagePath, err := renderer.RenderToPng(renderOpts); err != nil {
|
||||
|
||||
@@ -52,9 +52,8 @@ func (this *WebhookNotifier) Notify(context *alerting.EvalContext) {
|
||||
bodyJSON.Set("rule_url", ruleUrl)
|
||||
}
|
||||
|
||||
imageUrl, err := context.GetImageUrl()
|
||||
if err == nil {
|
||||
bodyJSON.Set("image_url", imageUrl)
|
||||
if context.ImagePublicUrl != "" {
|
||||
bodyJSON.Set("image_url", context.ImagePublicUrl)
|
||||
}
|
||||
|
||||
body, _ := bodyJSON.MarshalJSON()
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
//"I want to be a cleaner, just like you," said Mathilda
|
||||
//"Okay," replied Leon
|
||||
|
||||
package backgroundtasks
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/grafana/grafana/pkg/bus"
|
||||
"github.com/grafana/grafana/pkg/log"
|
||||
"github.com/grafana/grafana/pkg/models"
|
||||
)
|
||||
|
||||
var (
|
||||
tlog log.Logger = log.New("ticker")
|
||||
)
|
||||
|
||||
func Init() {
|
||||
go start()
|
||||
}
|
||||
|
||||
func start() {
|
||||
go cleanup(time.Now())
|
||||
|
||||
ticker := time.NewTicker(time.Hour * 1)
|
||||
for {
|
||||
select {
|
||||
case tick := <-ticker.C:
|
||||
go cleanup(tick)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func cleanup(now time.Time) {
|
||||
err := bus.Publish(&models.HourCommand{Time: now})
|
||||
if err != nil {
|
||||
tlog.Error("Cleanup job failed", "error", err)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
package backgroundtasks
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path"
|
||||
|
||||
"github.com/grafana/grafana/pkg/bus"
|
||||
"github.com/grafana/grafana/pkg/models"
|
||||
"github.com/grafana/grafana/pkg/setting"
|
||||
)
|
||||
|
||||
func init() {
|
||||
bus.AddEventListener(CleanTmpFiles)
|
||||
}
|
||||
|
||||
func CleanTmpFiles(cmd *models.HourCommand) error {
|
||||
files, err := ioutil.ReadDir(setting.ImagesDir)
|
||||
|
||||
var toDelete []os.FileInfo
|
||||
for _, file := range files {
|
||||
if file.ModTime().AddDate(0, 0, setting.RenderedImageTTLDays).Before(cmd.Time) {
|
||||
toDelete = append(toDelete, file)
|
||||
}
|
||||
}
|
||||
|
||||
for _, file := range toDelete {
|
||||
fullPath := path.Join(setting.ImagesDir, file.Name())
|
||||
err := os.Remove(fullPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
tlog.Debug("Found old rendered image to delete", "deleted", len(toDelete), "keept", len(files))
|
||||
|
||||
return err
|
||||
}
|
||||
@@ -12,6 +12,7 @@ import (
|
||||
"net/smtp"
|
||||
"os"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/grafana/grafana/pkg/log"
|
||||
"github.com/grafana/grafana/pkg/setting"
|
||||
@@ -66,7 +67,7 @@ func sendToSmtpServer(recipients []string, msgContent []byte) error {
|
||||
tlsconfig.Certificates = []tls.Certificate{cert}
|
||||
}
|
||||
|
||||
conn, err := net.Dial("tcp", net.JoinHostPort(host, port))
|
||||
conn, err := net.DialTimeout("tcp", net.JoinHostPort(host, port), time.Second*10)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -44,7 +44,7 @@ func sendWebRequest(webhook *Webhook) error {
|
||||
webhookLog.Debug("Sending webhook", "url", webhook.Url)
|
||||
|
||||
client := http.Client{
|
||||
Timeout: time.Duration(3 * time.Second),
|
||||
Timeout: time.Duration(10 * time.Second),
|
||||
}
|
||||
|
||||
request, err := http.NewRequest("POST", webhook.Url, bytes.NewReader([]byte(webhook.Body)))
|
||||
|
||||
@@ -92,7 +92,7 @@ func HandleAlertsQuery(query *m.GetAlertsQuery) error {
|
||||
params = append(params, query.Limit)
|
||||
}
|
||||
|
||||
sql.WriteString("ORDER BY name ASC")
|
||||
sql.WriteString(" ORDER BY name ASC")
|
||||
|
||||
alerts := make([]*m.Alert, 0)
|
||||
if err := x.Sql(sql.String(), params...).Find(&alerts); err != nil {
|
||||
|
||||
@@ -66,7 +66,8 @@ func GetAlertNotificationsToSend(query *m.GetAlertNotificationsToSendQuery) erro
|
||||
sql.WriteString(` WHERE alert_notification.org_id = ?`)
|
||||
params = append(params, query.OrgId)
|
||||
|
||||
sql.WriteString(` AND ((alert_notification.is_default = 1)`)
|
||||
sql.WriteString(` AND ((alert_notification.is_default = ?)`)
|
||||
params = append(params, dialect.BooleanStr(true))
|
||||
if len(query.Ids) > 0 {
|
||||
sql.WriteString(` OR alert_notification.id IN (?` + strings.Repeat(",?", len(query.Ids)-1) + ")")
|
||||
for _, v := range query.Ids {
|
||||
|
||||
@@ -75,7 +75,7 @@ func (r *SqlAnnotationRepo) Find(query *annotations.ItemQuery) ([]*annotations.I
|
||||
query.Limit = 10
|
||||
}
|
||||
|
||||
sql.WriteString(fmt.Sprintf("ORDER BY epoch DESC LIMIT %v", query.Limit))
|
||||
sql.WriteString(fmt.Sprintf(" ORDER BY epoch DESC LIMIT %v", query.Limit))
|
||||
|
||||
items := make([]*annotations.Item, 0)
|
||||
if err := x.Sql(sql.String(), params...).Find(&items); err != nil {
|
||||
|
||||
@@ -5,7 +5,9 @@ import (
|
||||
|
||||
"github.com/go-xorm/xorm"
|
||||
"github.com/grafana/grafana/pkg/bus"
|
||||
"github.com/grafana/grafana/pkg/log"
|
||||
m "github.com/grafana/grafana/pkg/models"
|
||||
"github.com/grafana/grafana/pkg/setting"
|
||||
)
|
||||
|
||||
func init() {
|
||||
@@ -13,6 +15,31 @@ func init() {
|
||||
bus.AddHandler("sql", GetDashboardSnapshot)
|
||||
bus.AddHandler("sql", DeleteDashboardSnapshot)
|
||||
bus.AddHandler("sql", SearchDashboardSnapshots)
|
||||
bus.AddEventListener(DeleteExpiredSnapshots)
|
||||
}
|
||||
|
||||
func DeleteExpiredSnapshots(cmd *m.HourCommand) error {
|
||||
return inTransaction(func(sess *xorm.Session) error {
|
||||
var expiredCount int64 = 0
|
||||
var oldCount int64 = 0
|
||||
|
||||
if setting.SnapShotRemoveExpired {
|
||||
deleteExpiredSql := "DELETE FROM dashboard_snapshot WHERE expires < ?"
|
||||
expiredResponse, err := x.Exec(deleteExpiredSql, cmd.Time)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
expiredCount, _ = expiredResponse.RowsAffected()
|
||||
}
|
||||
|
||||
oldSnapshotsSql := "DELETE FROM dashboard_snapshot WHERE created < ?"
|
||||
oldResponse, err := x.Exec(oldSnapshotsSql, cmd.Time.AddDate(0, 0, setting.SnapShotTTLDays*-1))
|
||||
oldCount, _ = oldResponse.RowsAffected()
|
||||
|
||||
log.Debug2("Deleted old/expired snaphots", "to old", oldCount, "expired", expiredCount)
|
||||
|
||||
return err
|
||||
})
|
||||
}
|
||||
|
||||
func CreateDashboardSnapshot(cmd *m.CreateDashboardSnapshotCommand) error {
|
||||
|
||||
@@ -120,4 +120,9 @@ func addDashboardMigration(mg *Migrator) {
|
||||
mg.AddMigration("Add index for plugin_id in dashboard", NewAddIndexMigration(dashboardV2, &Index{
|
||||
Cols: []string{"org_id", "plugin_id"}, Type: IndexType,
|
||||
}))
|
||||
|
||||
// dashboard_id index for dashboard_tag table
|
||||
mg.AddMigration("Add index for dashboard_id in dashboard_tag", NewAddIndexMigration(dashboardTagV1, &Index{
|
||||
Cols: []string{"dashboard_id"}, Type: IndexType,
|
||||
}))
|
||||
}
|
||||
|
||||
@@ -18,6 +18,7 @@ type Dialect interface {
|
||||
SupportEngine() bool
|
||||
LikeStr() string
|
||||
Default(col *Column) string
|
||||
BooleanStr(bool) string
|
||||
|
||||
CreateIndexSql(tableName string, index *Index) string
|
||||
CreateTableSql(table *Table) string
|
||||
|
||||
@@ -29,6 +29,10 @@ func (db *Mysql) AutoIncrStr() string {
|
||||
return "AUTO_INCREMENT"
|
||||
}
|
||||
|
||||
func (db *Mysql) BooleanStr(value bool) string {
|
||||
return strconv.FormatBool(value)
|
||||
}
|
||||
|
||||
func (db *Mysql) SqlType(c *Column) string {
|
||||
var res string
|
||||
switch c.Type {
|
||||
|
||||
@@ -36,6 +36,10 @@ func (db *Postgres) AutoIncrStr() string {
|
||||
return ""
|
||||
}
|
||||
|
||||
func (db *Postgres) BooleanStr(value bool) string {
|
||||
return strconv.FormatBool(value)
|
||||
}
|
||||
|
||||
func (b *Postgres) Default(col *Column) string {
|
||||
if col.Type == DB_Bool {
|
||||
if col.Default == "0" {
|
||||
|
||||
@@ -29,6 +29,13 @@ func (db *Sqlite3) AutoIncrStr() string {
|
||||
return "AUTOINCREMENT"
|
||||
}
|
||||
|
||||
func (db *Sqlite3) BooleanStr(value bool) string {
|
||||
if value {
|
||||
return "1"
|
||||
}
|
||||
return "0"
|
||||
}
|
||||
|
||||
func (db *Sqlite3) SqlType(c *Column) string {
|
||||
switch c.Type {
|
||||
case DB_Date, DB_DateTime, DB_TimeStamp, DB_Time:
|
||||
|
||||
Reference in New Issue
Block a user