Introduce TSDB service (#31520)

* Introduce TSDB service

Signed-off-by: Arve Knudsen <arve.knudsen@gmail.com>

Co-authored-by: Erik Sundell <erik.sundell87@gmail.com>
Co-authored-by: Will Browne <will.browne@grafana.com>
Co-authored-by: Torkel Ödegaard <torkel@grafana.org>
Co-authored-by: Will Browne <wbrowne@users.noreply.github.com>
Co-authored-by: Zoltán Bedi <zoltan.bedi@gmail.com>
This commit is contained in:
Arve Knudsen
2021-03-08 07:02:49 +01:00
committed by GitHub
co-authored by Erik Sundell Will Browne Torkel Ödegaard Will Browne Zoltán Bedi
parent c899bf3592
commit b79e61656a
203 changed files with 5269 additions and 4776 deletions
+40 -37
View File
@@ -6,7 +6,9 @@ import (
"strings"
"time"
"github.com/grafana/grafana/pkg/plugins"
"github.com/grafana/grafana/pkg/tsdb/prometheus"
"github.com/grafana/grafana/pkg/tsdb/tsdbifaces"
gocontext "context"
@@ -16,7 +18,6 @@ import (
"github.com/grafana/grafana/pkg/components/simplejson"
"github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/services/alerting"
"github.com/grafana/grafana/pkg/tsdb"
"github.com/grafana/grafana/pkg/util/errutil"
)
@@ -29,12 +30,11 @@ func init() {
// QueryCondition is responsible for issue and query, reduce the
// timeseries into single values and evaluate if they are firing or not.
type QueryCondition struct {
Index int
Query AlertQuery
Reducer *queryReducer
Evaluator AlertEvaluator
Operator string
HandleRequest tsdb.HandleRequestFunc
Index int
Query AlertQuery
Reducer *queryReducer
Evaluator AlertEvaluator
Operator string
}
// AlertQuery contains information about what datasource a query
@@ -47,10 +47,10 @@ type AlertQuery struct {
}
// Eval evaluates the `QueryCondition`.
func (c *QueryCondition) Eval(context *alerting.EvalContext) (*alerting.ConditionResult, error) {
timeRange := tsdb.NewTimeRange(c.Query.From, c.Query.To)
func (c *QueryCondition) Eval(context *alerting.EvalContext, requestHandler tsdbifaces.RequestHandler) (*alerting.ConditionResult, error) {
timeRange := plugins.NewDataTimeRange(c.Query.From, c.Query.To)
seriesList, err := c.executeQuery(context, timeRange)
seriesList, err := c.executeQuery(context, timeRange, requestHandler)
if err != nil {
return nil, err
}
@@ -109,7 +109,8 @@ func (c *QueryCondition) Eval(context *alerting.EvalContext) (*alerting.Conditio
}, nil
}
func (c *QueryCondition) executeQuery(context *alerting.EvalContext, timeRange *tsdb.TimeRange) (tsdb.TimeSeriesSlice, error) {
func (c *QueryCondition) executeQuery(context *alerting.EvalContext, timeRange plugins.DataTimeRange,
requestHandler tsdbifaces.RequestHandler) (plugins.DataTimeSeriesSlice, error) {
getDsInfo := &models.GetDataSourceQuery{
Id: c.Query.DatasourceID,
OrgId: context.Rule.OrgID,
@@ -125,7 +126,7 @@ func (c *QueryCondition) executeQuery(context *alerting.EvalContext, timeRange *
}
req := c.getRequestForAlertRule(getDsInfo.Result, timeRange, context.IsDebug)
result := make(tsdb.TimeSeriesSlice, 0)
result := make(plugins.DataTimeSeriesSlice, 0)
if context.IsDebug {
data := simplejson.New()
@@ -139,20 +140,20 @@ func (c *QueryCondition) executeQuery(context *alerting.EvalContext, timeRange *
Model *simplejson.Json `json:"model"`
Datasource *simplejson.Json `json:"datasource"`
MaxDataPoints int64 `json:"maxDataPoints"`
IntervalMs int64 `json:"intervalMs"`
IntervalMS int64 `json:"intervalMs"`
}
queries := []*queryDto{}
for _, q := range req.Queries {
queries = append(queries, &queryDto{
RefID: q.RefId,
RefID: q.RefID,
Model: q.Model,
Datasource: simplejson.NewFromAny(map[string]interface{}{
"id": q.DataSource.Id,
"name": q.DataSource.Name,
}),
MaxDataPoints: q.MaxDataPoints,
IntervalMs: q.IntervalMs,
IntervalMS: q.IntervalMS,
})
}
@@ -164,29 +165,30 @@ func (c *QueryCondition) executeQuery(context *alerting.EvalContext, timeRange *
})
}
resp, err := c.HandleRequest(context.Ctx, getDsInfo.Result, req)
resp, err := requestHandler.HandleRequest(context.Ctx, getDsInfo.Result, req)
if err != nil {
return nil, toCustomError(err)
}
for _, v := range resp.Results {
if v.Error != nil {
return nil, fmt.Errorf("tsdb.HandleRequest() response error %v", v)
return nil, fmt.Errorf("request handler response error %v", v)
}
// If there are dataframes but no series on the result
useDataframes := v.Dataframes != nil && (v.Series == nil || len(v.Series) == 0)
if useDataframes { // convert the dataframes to tsdb.TimeSeries
if useDataframes { // convert the dataframes to plugins.DataTimeSeries
frames, err := v.Dataframes.Decoded()
if err != nil {
return nil, errutil.Wrap("tsdb.HandleRequest() failed to unmarshal arrow dataframes from bytes", err)
return nil, errutil.Wrap("request handler failed to unmarshal arrow dataframes from bytes", err)
}
for _, frame := range frames {
ss, err := FrameToSeriesSlice(frame)
if err != nil {
return nil, errutil.Wrapf(err, `tsdb.HandleRequest() failed to convert dataframe "%v" to tsdb.TimeSeriesSlice`, frame.Name)
return nil, errutil.Wrapf(err,
`request handler failed to convert dataframe "%v" to plugins.DataTimeSeriesSlice`, frame.Name)
}
result = append(result, ss...)
}
@@ -218,13 +220,14 @@ func (c *QueryCondition) executeQuery(context *alerting.EvalContext, timeRange *
return result, nil
}
func (c *QueryCondition) getRequestForAlertRule(datasource *models.DataSource, timeRange *tsdb.TimeRange, debug bool) *tsdb.TsdbQuery {
func (c *QueryCondition) getRequestForAlertRule(datasource *models.DataSource, timeRange plugins.DataTimeRange,
debug bool) plugins.DataQuery {
queryModel := c.Query.Model
req := &tsdb.TsdbQuery{
TimeRange: timeRange,
Queries: []*tsdb.Query{
req := plugins.DataQuery{
TimeRange: &timeRange,
Queries: []plugins.DataSubQuery{
{
RefId: "A",
RefID: "A",
Model: queryModel,
DataSource: datasource,
QueryType: queryModel.Get("queryType").MustString(""),
@@ -242,7 +245,6 @@ func (c *QueryCondition) getRequestForAlertRule(datasource *models.DataSource, t
func newQueryCondition(model *simplejson.Json, index int) (*QueryCondition, error) {
condition := QueryCondition{}
condition.Index = index
condition.HandleRequest = tsdb.HandleRequest
queryJSON := model.Get("query")
@@ -301,23 +303,23 @@ func validateToValue(to string) error {
}
// FrameToSeriesSlice converts a frame that is a valid time series as per data.TimeSeriesSchema()
// to a TimeSeriesSlice.
func FrameToSeriesSlice(frame *data.Frame) (tsdb.TimeSeriesSlice, error) {
// to a DataTimeSeriesSlice.
func FrameToSeriesSlice(frame *data.Frame) (plugins.DataTimeSeriesSlice, error) {
tsSchema := frame.TimeSeriesSchema()
if tsSchema.Type == data.TimeSeriesTypeNot {
// If no fields, or only a time field, create an empty tsdb.TimeSeriesSlice with a single
// If no fields, or only a time field, create an empty plugins.DataTimeSeriesSlice with a single
// time series in order to trigger "no data" in alerting.
if len(frame.Fields) == 0 || (len(frame.Fields) == 1 && frame.Fields[0].Type().Time()) {
return tsdb.TimeSeriesSlice{{
return plugins.DataTimeSeriesSlice{{
Name: frame.Name,
Points: make(tsdb.TimeSeriesPoints, 0),
Points: make(plugins.DataTimeSeriesPoints, 0),
}}, nil
}
return nil, fmt.Errorf("input frame is not recognized as a time series")
}
seriesCount := len(tsSchema.ValueIndices)
seriesSlice := make(tsdb.TimeSeriesSlice, 0, seriesCount)
seriesSlice := make(plugins.DataTimeSeriesSlice, 0, seriesCount)
timeField := frame.Fields[tsSchema.TimeIndex]
timeNullFloatSlice := make([]null.Float, timeField.Len())
@@ -331,8 +333,8 @@ func FrameToSeriesSlice(frame *data.Frame) (tsdb.TimeSeriesSlice, error) {
for _, fieldIdx := range tsSchema.ValueIndices { // create a TimeSeries for each value Field
field := frame.Fields[fieldIdx]
ts := &tsdb.TimeSeries{
Points: make(tsdb.TimeSeriesPoints, field.Len()),
ts := plugins.DataTimeSeries{
Points: make(plugins.DataTimeSeriesPoints, field.Len()),
}
if len(field.Labels) > 0 {
@@ -355,9 +357,10 @@ func FrameToSeriesSlice(frame *data.Frame) (tsdb.TimeSeriesSlice, error) {
for rowIdx := 0; rowIdx < field.Len(); rowIdx++ { // for each value in the field, make a TimePoint
val, err := field.FloatAt(rowIdx)
if err != nil {
return nil, errutil.Wrapf(err, "failed to convert frame to tsdb.series, can not convert value %v to float", field.At(rowIdx))
return nil, errutil.Wrapf(err,
"failed to convert frame to DataTimeSeriesSlice, can not convert value %v to float", field.At(rowIdx))
}
ts.Points[rowIdx] = tsdb.TimePoint{
ts.Points[rowIdx] = plugins.DataTimePoint{
null.FloatFrom(val),
timeNullFloatSlice[rowIdx],
}
@@ -381,5 +384,5 @@ func toCustomError(err error) error {
}
// generic fallback
return fmt.Errorf("tsdb.HandleRequest() error %v", err)
return fmt.Errorf("request handler error: %w", err)
}
+64 -56
View File
@@ -15,18 +15,18 @@ import (
"github.com/grafana/grafana/pkg/components/null"
"github.com/grafana/grafana/pkg/components/simplejson"
"github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/plugins"
"github.com/grafana/grafana/pkg/services/alerting"
"github.com/grafana/grafana/pkg/tsdb"
. "github.com/smartystreets/goconvey/convey"
"github.com/stretchr/testify/require"
"github.com/xorcare/pointer"
)
func newTimeSeriesPointsFromArgs(values ...float64) tsdb.TimeSeriesPoints {
points := make(tsdb.TimeSeriesPoints, 0)
func newTimeSeriesPointsFromArgs(values ...float64) plugins.DataTimeSeriesPoints {
points := make(plugins.DataTimeSeriesPoints, 0)
for i := 0; i < len(values); i += 2 {
points = append(points, tsdb.NewTimePoint(null.FloatFrom(values[i]), values[i+1]))
points = append(points, plugins.DataTimePoint{null.FloatFrom(values[i]), null.FloatFrom(values[i+1])})
}
return points
@@ -60,7 +60,7 @@ func TestQueryCondition(t *testing.T) {
Convey("should fire when avg is above 100", func() {
points := newTimeSeriesPointsFromArgs(120, 0)
ctx.series = tsdb.TimeSeriesSlice{&tsdb.TimeSeries{Name: "test1", Points: points}}
ctx.series = plugins.DataTimeSeriesSlice{plugins.DataTimeSeries{Name: "test1", Points: points}}
cr, err := ctx.exec()
So(err, ShouldBeNil)
@@ -80,7 +80,7 @@ func TestQueryCondition(t *testing.T) {
Convey("Should not fire when avg is below 100", func() {
points := newTimeSeriesPointsFromArgs(90, 0)
ctx.series = tsdb.TimeSeriesSlice{&tsdb.TimeSeries{Name: "test1", Points: points}}
ctx.series = plugins.DataTimeSeriesSlice{plugins.DataTimeSeries{Name: "test1", Points: points}}
cr, err := ctx.exec()
So(err, ShouldBeNil)
@@ -99,9 +99,9 @@ func TestQueryCondition(t *testing.T) {
})
Convey("Should fire if only first series matches", func() {
ctx.series = tsdb.TimeSeriesSlice{
&tsdb.TimeSeries{Name: "test1", Points: newTimeSeriesPointsFromArgs(120, 0)},
&tsdb.TimeSeries{Name: "test2", Points: newTimeSeriesPointsFromArgs(0, 0)},
ctx.series = plugins.DataTimeSeriesSlice{
plugins.DataTimeSeries{Name: "test1", Points: newTimeSeriesPointsFromArgs(120, 0)},
plugins.DataTimeSeries{Name: "test2", Points: newTimeSeriesPointsFromArgs(0, 0)},
}
cr, err := ctx.exec()
@@ -111,7 +111,7 @@ func TestQueryCondition(t *testing.T) {
Convey("No series", func() {
Convey("Should set NoDataFound when condition is gt", func() {
ctx.series = tsdb.TimeSeriesSlice{}
ctx.series = plugins.DataTimeSeriesSlice{}
cr, err := ctx.exec()
So(err, ShouldBeNil)
@@ -121,7 +121,7 @@ func TestQueryCondition(t *testing.T) {
Convey("Should be firing when condition is no_value", func() {
ctx.evaluator = `{"type": "no_value", "params": []}`
ctx.series = tsdb.TimeSeriesSlice{}
ctx.series = plugins.DataTimeSeriesSlice{}
cr, err := ctx.exec()
So(err, ShouldBeNil)
@@ -132,8 +132,8 @@ func TestQueryCondition(t *testing.T) {
Convey("Empty series", func() {
Convey("Should set Firing if eval match", func() {
ctx.evaluator = `{"type": "no_value", "params": []}`
ctx.series = tsdb.TimeSeriesSlice{
&tsdb.TimeSeries{Name: "test1", Points: newTimeSeriesPointsFromArgs()},
ctx.series = plugins.DataTimeSeriesSlice{
plugins.DataTimeSeries{Name: "test1", Points: newTimeSeriesPointsFromArgs()},
}
cr, err := ctx.exec()
@@ -142,9 +142,9 @@ func TestQueryCondition(t *testing.T) {
})
Convey("Should set NoDataFound both series are empty", func() {
ctx.series = tsdb.TimeSeriesSlice{
&tsdb.TimeSeries{Name: "test1", Points: newTimeSeriesPointsFromArgs()},
&tsdb.TimeSeries{Name: "test2", Points: newTimeSeriesPointsFromArgs()},
ctx.series = plugins.DataTimeSeriesSlice{
plugins.DataTimeSeries{Name: "test1", Points: newTimeSeriesPointsFromArgs()},
plugins.DataTimeSeries{Name: "test2", Points: newTimeSeriesPointsFromArgs()},
}
cr, err := ctx.exec()
@@ -153,9 +153,9 @@ func TestQueryCondition(t *testing.T) {
})
Convey("Should set NoDataFound both series contains null", func() {
ctx.series = tsdb.TimeSeriesSlice{
&tsdb.TimeSeries{Name: "test1", Points: tsdb.TimeSeriesPoints{tsdb.TimePoint{null.FloatFromPtr(nil), null.FloatFrom(0)}}},
&tsdb.TimeSeries{Name: "test2", Points: tsdb.TimeSeriesPoints{tsdb.TimePoint{null.FloatFromPtr(nil), null.FloatFrom(0)}}},
ctx.series = plugins.DataTimeSeriesSlice{
plugins.DataTimeSeries{Name: "test1", Points: plugins.DataTimeSeriesPoints{plugins.DataTimePoint{null.FloatFromPtr(nil), null.FloatFrom(0)}}},
plugins.DataTimeSeries{Name: "test2", Points: plugins.DataTimeSeriesPoints{plugins.DataTimePoint{null.FloatFromPtr(nil), null.FloatFrom(0)}}},
}
cr, err := ctx.exec()
@@ -164,9 +164,9 @@ func TestQueryCondition(t *testing.T) {
})
Convey("Should not set NoDataFound if one series is empty", func() {
ctx.series = tsdb.TimeSeriesSlice{
&tsdb.TimeSeries{Name: "test1", Points: newTimeSeriesPointsFromArgs()},
&tsdb.TimeSeries{Name: "test2", Points: newTimeSeriesPointsFromArgs(120, 0)},
ctx.series = plugins.DataTimeSeriesSlice{
plugins.DataTimeSeries{Name: "test1", Points: newTimeSeriesPointsFromArgs()},
plugins.DataTimeSeries{Name: "test2", Points: newTimeSeriesPointsFromArgs(120, 0)},
}
cr, err := ctx.exec()
@@ -181,7 +181,7 @@ func TestQueryCondition(t *testing.T) {
type queryConditionTestContext struct {
reducer string
evaluator string
series tsdb.TimeSeriesSlice
series plugins.DataTimeSeriesSlice
frame *data.Frame
result *alerting.EvalContext
condition *QueryCondition
@@ -207,25 +207,33 @@ func (ctx *queryConditionTestContext) exec() (*alerting.ConditionResult, error)
ctx.condition = condition
qr := &tsdb.QueryResult{
qr := plugins.DataQueryResult{
Series: ctx.series,
}
if ctx.frame != nil {
qr = &tsdb.QueryResult{
Dataframes: tsdb.NewDecodedDataFrames(data.Frames{ctx.frame}),
qr = plugins.DataQueryResult{
Dataframes: plugins.NewDecodedDataFrames(data.Frames{ctx.frame}),
}
}
condition.HandleRequest = func(context context.Context, dsInfo *models.DataSource, req *tsdb.TsdbQuery) (*tsdb.Response, error) {
return &tsdb.Response{
Results: map[string]*tsdb.QueryResult{
reqHandler := fakeReqHandler{
response: plugins.DataResponse{
Results: map[string]plugins.DataQueryResult{
"A": qr,
},
}, nil
},
}
return condition.Eval(ctx.result)
return condition.Eval(ctx.result, reqHandler)
}
type fakeReqHandler struct {
response plugins.DataResponse
}
func (rh fakeReqHandler) HandleRequest(context.Context, *models.DataSource, plugins.DataQuery) (
plugins.DataResponse, error) {
return rh.response, nil
}
func queryConditionScenario(desc string, fn queryConditionScenarioFunc) {
@@ -249,7 +257,7 @@ func TestFrameToSeriesSlice(t *testing.T) {
tests := []struct {
name string
frame *data.Frame
seriesSlice tsdb.TimeSeriesSlice
seriesSlice plugins.DataTimeSeriesSlice
Err require.ErrorAssertionFunc
}{
{
@@ -268,21 +276,21 @@ func TestFrameToSeriesSlice(t *testing.T) {
4.0,
})),
seriesSlice: tsdb.TimeSeriesSlice{
&tsdb.TimeSeries{
seriesSlice: plugins.DataTimeSeriesSlice{
plugins.DataTimeSeries{
Name: "Values Int64s {Animal Factor=cat}",
Tags: map[string]string{"Animal Factor": "cat"},
Points: tsdb.TimeSeriesPoints{
tsdb.TimePoint{null.FloatFrom(math.NaN()), null.FloatFrom(1577934240000)},
tsdb.TimePoint{null.FloatFrom(3), null.FloatFrom(1577934270000)},
Points: plugins.DataTimeSeriesPoints{
plugins.DataTimePoint{null.FloatFrom(math.NaN()), null.FloatFrom(1577934240000)},
plugins.DataTimePoint{null.FloatFrom(3), null.FloatFrom(1577934270000)},
},
},
&tsdb.TimeSeries{
plugins.DataTimeSeries{
Name: "Values Floats {Animal Factor=sloth}",
Tags: map[string]string{"Animal Factor": "sloth"},
Points: tsdb.TimeSeriesPoints{
tsdb.TimePoint{null.FloatFrom(2), null.FloatFrom(1577934240000)},
tsdb.TimePoint{null.FloatFrom(4), null.FloatFrom(1577934270000)},
Points: plugins.DataTimeSeriesPoints{
plugins.DataTimePoint{null.FloatFrom(2), null.FloatFrom(1577934240000)},
plugins.DataTimePoint{null.FloatFrom(4), null.FloatFrom(1577934270000)},
},
},
},
@@ -295,16 +303,16 @@ func TestFrameToSeriesSlice(t *testing.T) {
data.NewField(`Values Int64s`, data.Labels{"Animal Factor": "cat"}, []*int64{}),
data.NewField(`Values Floats`, data.Labels{"Animal Factor": "sloth"}, []float64{})),
seriesSlice: tsdb.TimeSeriesSlice{
&tsdb.TimeSeries{
seriesSlice: plugins.DataTimeSeriesSlice{
plugins.DataTimeSeries{
Name: "Values Int64s {Animal Factor=cat}",
Tags: map[string]string{"Animal Factor": "cat"},
Points: tsdb.TimeSeriesPoints{},
Points: plugins.DataTimeSeriesPoints{},
},
&tsdb.TimeSeries{
plugins.DataTimeSeries{
Name: "Values Floats {Animal Factor=sloth}",
Tags: map[string]string{"Animal Factor": "sloth"},
Points: tsdb.TimeSeriesPoints{},
Points: plugins.DataTimeSeriesPoints{},
},
},
Err: require.NoError,
@@ -315,10 +323,10 @@ func TestFrameToSeriesSlice(t *testing.T) {
data.NewField("Time", data.Labels{}, []time.Time{}),
data.NewField(`Values`, data.Labels{}, []float64{})),
seriesSlice: tsdb.TimeSeriesSlice{
&tsdb.TimeSeries{
seriesSlice: plugins.DataTimeSeriesSlice{
plugins.DataTimeSeries{
Name: "Values",
Points: tsdb.TimeSeriesPoints{},
Points: plugins.DataTimeSeriesPoints{},
},
},
Err: require.NoError,
@@ -331,10 +339,10 @@ func TestFrameToSeriesSlice(t *testing.T) {
DisplayNameFromDS: "sloth",
})),
seriesSlice: tsdb.TimeSeriesSlice{
&tsdb.TimeSeries{
seriesSlice: plugins.DataTimeSeriesSlice{
plugins.DataTimeSeries{
Name: "sloth",
Points: tsdb.TimeSeriesPoints{},
Points: plugins.DataTimeSeriesPoints{},
Tags: map[string]string{"Rating": "10"},
},
},
@@ -349,10 +357,10 @@ func TestFrameToSeriesSlice(t *testing.T) {
DisplayNameFromDS: "sloth #2",
})),
seriesSlice: tsdb.TimeSeriesSlice{
&tsdb.TimeSeries{
seriesSlice: plugins.DataTimeSeriesSlice{
plugins.DataTimeSeries{
Name: "sloth #1",
Points: tsdb.TimeSeriesPoints{},
Points: plugins.DataTimeSeriesPoints{},
},
},
Err: require.NoError,
+3 -3
View File
@@ -6,7 +6,7 @@ import (
"sort"
"github.com/grafana/grafana/pkg/components/null"
"github.com/grafana/grafana/pkg/tsdb"
"github.com/grafana/grafana/pkg/plugins"
)
// queryReducer reduces a timeseries to a nullable float
@@ -18,7 +18,7 @@ type queryReducer struct {
}
//nolint: gocyclo
func (s *queryReducer) Reduce(series *tsdb.TimeSeries) null.Float {
func (s *queryReducer) Reduce(series plugins.DataTimeSeries) null.Float {
if len(series.Points) == 0 {
return null.FloatFromPtr(nil)
}
@@ -126,7 +126,7 @@ func newSimpleReducer(t string) *queryReducer {
return &queryReducer{Type: t}
}
func calculateDiff(series *tsdb.TimeSeries, allNull bool, value float64, fn func(float64, float64) float64) (bool, float64) {
func calculateDiff(series plugins.DataTimeSeries, allNull bool, value float64, fn func(float64, float64) float64) (bool, float64) {
var (
points = series.Points
first float64
@@ -7,7 +7,7 @@ import (
. "github.com/smartystreets/goconvey/convey"
"github.com/grafana/grafana/pkg/components/null"
"github.com/grafana/grafana/pkg/tsdb"
"github.com/grafana/grafana/pkg/plugins"
)
func TestSimpleReducer(t *testing.T) {
@@ -54,16 +54,16 @@ func TestSimpleReducer(t *testing.T) {
Convey("median should ignore null values", func() {
reducer := newSimpleReducer("median")
series := &tsdb.TimeSeries{
series := plugins.DataTimeSeries{
Name: "test time series",
}
series.Points = append(series.Points, tsdb.NewTimePoint(null.FloatFromPtr(nil), 1))
series.Points = append(series.Points, tsdb.NewTimePoint(null.FloatFromPtr(nil), 2))
series.Points = append(series.Points, tsdb.NewTimePoint(null.FloatFromPtr(nil), 3))
series.Points = append(series.Points, tsdb.NewTimePoint(null.FloatFrom(float64(1)), 4))
series.Points = append(series.Points, tsdb.NewTimePoint(null.FloatFrom(float64(2)), 5))
series.Points = append(series.Points, tsdb.NewTimePoint(null.FloatFrom(float64(3)), 6))
series.Points = append(series.Points, plugins.DataTimePoint{null.FloatFromPtr(nil), null.FloatFrom(1)})
series.Points = append(series.Points, plugins.DataTimePoint{null.FloatFromPtr(nil), null.FloatFrom(2)})
series.Points = append(series.Points, plugins.DataTimePoint{null.FloatFromPtr(nil), null.FloatFrom(3)})
series.Points = append(series.Points, plugins.DataTimePoint{null.FloatFrom(float64(1)), null.FloatFrom(4)})
series.Points = append(series.Points, plugins.DataTimePoint{null.FloatFrom(float64(2)), null.FloatFrom(5)})
series.Points = append(series.Points, plugins.DataTimePoint{null.FloatFrom(float64(3)), null.FloatFrom(6)})
result := reducer.Reduce(series)
So(result.Valid, ShouldEqual, true)
@@ -77,25 +77,25 @@ func TestSimpleReducer(t *testing.T) {
Convey("avg with only nulls", func() {
reducer := newSimpleReducer("avg")
series := &tsdb.TimeSeries{
series := plugins.DataTimeSeries{
Name: "test time series",
}
series.Points = append(series.Points, tsdb.NewTimePoint(null.FloatFromPtr(nil), 1))
series.Points = append(series.Points, plugins.DataTimePoint{null.FloatFromPtr(nil), null.FloatFrom(1)})
So(reducer.Reduce(series).Valid, ShouldEqual, false)
})
Convey("count_non_null", func() {
Convey("with null values and real values", func() {
reducer := newSimpleReducer("count_non_null")
series := &tsdb.TimeSeries{
series := plugins.DataTimeSeries{
Name: "test time series",
}
series.Points = append(series.Points, tsdb.NewTimePoint(null.FloatFromPtr(nil), 1))
series.Points = append(series.Points, tsdb.NewTimePoint(null.FloatFromPtr(nil), 2))
series.Points = append(series.Points, tsdb.NewTimePoint(null.FloatFrom(3), 3))
series.Points = append(series.Points, tsdb.NewTimePoint(null.FloatFrom(3), 4))
series.Points = append(series.Points, plugins.DataTimePoint{null.FloatFromPtr(nil), null.FloatFrom(1)})
series.Points = append(series.Points, plugins.DataTimePoint{null.FloatFromPtr(nil), null.FloatFrom(2)})
series.Points = append(series.Points, plugins.DataTimePoint{null.FloatFrom(3), null.FloatFrom(3)})
series.Points = append(series.Points, plugins.DataTimePoint{null.FloatFrom(3), null.FloatFrom(4)})
So(reducer.Reduce(series).Valid, ShouldEqual, true)
So(reducer.Reduce(series).Float64, ShouldEqual, 2)
@@ -103,12 +103,12 @@ func TestSimpleReducer(t *testing.T) {
Convey("with null values", func() {
reducer := newSimpleReducer("count_non_null")
series := &tsdb.TimeSeries{
series := plugins.DataTimeSeries{
Name: "test time series",
}
series.Points = append(series.Points, tsdb.NewTimePoint(null.FloatFromPtr(nil), 1))
series.Points = append(series.Points, tsdb.NewTimePoint(null.FloatFromPtr(nil), 2))
series.Points = append(series.Points, plugins.DataTimePoint{null.FloatFromPtr(nil), null.FloatFrom(1)})
series.Points = append(series.Points, plugins.DataTimePoint{null.FloatFromPtr(nil), null.FloatFrom(2)})
So(reducer.Reduce(series).Valid, ShouldEqual, false)
})
@@ -116,14 +116,14 @@ func TestSimpleReducer(t *testing.T) {
Convey("avg of number values and null values should ignore nulls", func() {
reducer := newSimpleReducer("avg")
series := &tsdb.TimeSeries{
series := plugins.DataTimeSeries{
Name: "test time series",
}
series.Points = append(series.Points, tsdb.NewTimePoint(null.FloatFrom(3), 1))
series.Points = append(series.Points, tsdb.NewTimePoint(null.FloatFromPtr(nil), 2))
series.Points = append(series.Points, tsdb.NewTimePoint(null.FloatFromPtr(nil), 3))
series.Points = append(series.Points, tsdb.NewTimePoint(null.FloatFrom(3), 4))
series.Points = append(series.Points, plugins.DataTimePoint{null.FloatFrom(3), null.FloatFrom(1)})
series.Points = append(series.Points, plugins.DataTimePoint{null.FloatFromPtr(nil), null.FloatFrom(2)})
series.Points = append(series.Points, plugins.DataTimePoint{null.FloatFromPtr(nil), null.FloatFrom(3)})
series.Points = append(series.Points, plugins.DataTimePoint{null.FloatFrom(3), null.FloatFrom(4)})
So(reducer.Reduce(series).Float64, ShouldEqual, float64(3))
})
@@ -181,12 +181,12 @@ func TestSimpleReducer(t *testing.T) {
Convey("diff with only nulls", func() {
reducer := newSimpleReducer("diff")
series := &tsdb.TimeSeries{
series := plugins.DataTimeSeries{
Name: "test time series",
}
series.Points = append(series.Points, tsdb.NewTimePoint(null.FloatFromPtr(nil), 1))
series.Points = append(series.Points, tsdb.NewTimePoint(null.FloatFromPtr(nil), 2))
series.Points = append(series.Points, plugins.DataTimePoint{null.FloatFromPtr(nil), null.FloatFrom(1)})
series.Points = append(series.Points, plugins.DataTimePoint{null.FloatFromPtr(nil), null.FloatFrom(2)})
So(reducer.Reduce(series).Valid, ShouldEqual, false)
})
@@ -244,12 +244,12 @@ func TestSimpleReducer(t *testing.T) {
Convey("diff_abs with only nulls", func() {
reducer := newSimpleReducer("diff_abs")
series := &tsdb.TimeSeries{
series := plugins.DataTimeSeries{
Name: "test time series",
}
series.Points = append(series.Points, tsdb.NewTimePoint(null.FloatFromPtr(nil), 1))
series.Points = append(series.Points, tsdb.NewTimePoint(null.FloatFromPtr(nil), 2))
series.Points = append(series.Points, plugins.DataTimePoint{null.FloatFromPtr(nil), null.FloatFrom(1)})
series.Points = append(series.Points, plugins.DataTimePoint{null.FloatFromPtr(nil), null.FloatFrom(2)})
So(reducer.Reduce(series).Valid, ShouldEqual, false)
})
@@ -307,12 +307,12 @@ func TestSimpleReducer(t *testing.T) {
Convey("percent_diff with only nulls", func() {
reducer := newSimpleReducer("percent_diff")
series := &tsdb.TimeSeries{
series := plugins.DataTimeSeries{
Name: "test time series",
}
series.Points = append(series.Points, tsdb.NewTimePoint(null.FloatFromPtr(nil), 1))
series.Points = append(series.Points, tsdb.NewTimePoint(null.FloatFromPtr(nil), 2))
series.Points = append(series.Points, plugins.DataTimePoint{null.FloatFromPtr(nil), null.FloatFrom(1)})
series.Points = append(series.Points, plugins.DataTimePoint{null.FloatFromPtr(nil), null.FloatFrom(2)})
So(reducer.Reduce(series).Valid, ShouldEqual, false)
})
@@ -370,12 +370,12 @@ func TestSimpleReducer(t *testing.T) {
Convey("percent_diff_abs with only nulls", func() {
reducer := newSimpleReducer("percent_diff_abs")
series := &tsdb.TimeSeries{
series := plugins.DataTimeSeries{
Name: "test time series",
}
series.Points = append(series.Points, tsdb.NewTimePoint(null.FloatFromPtr(nil), 1))
series.Points = append(series.Points, tsdb.NewTimePoint(null.FloatFromPtr(nil), 2))
series.Points = append(series.Points, plugins.DataTimePoint{null.FloatFromPtr(nil), null.FloatFrom(1)})
series.Points = append(series.Points, plugins.DataTimePoint{null.FloatFromPtr(nil), null.FloatFrom(2)})
So(reducer.Reduce(series).Valid, ShouldEqual, false)
})
@@ -399,12 +399,12 @@ func TestSimpleReducer(t *testing.T) {
func testReducer(reducerType string, datapoints ...float64) float64 {
reducer := newSimpleReducer(reducerType)
series := &tsdb.TimeSeries{
series := plugins.DataTimeSeries{
Name: "test time series",
}
for idx := range datapoints {
series.Points = append(series.Points, tsdb.NewTimePoint(null.FloatFrom(datapoints[idx]), 1234134))
series.Points = append(series.Points, plugins.DataTimePoint{null.FloatFrom(datapoints[idx]), null.FloatFrom(1234134)})
}
return reducer.Reduce(series).Float64
+3 -1
View File
@@ -13,6 +13,7 @@ import (
"github.com/grafana/grafana/pkg/registry"
"github.com/grafana/grafana/pkg/services/rendering"
"github.com/grafana/grafana/pkg/setting"
"github.com/grafana/grafana/pkg/tsdb"
"github.com/opentracing/opentracing-go"
"github.com/opentracing/opentracing-go/ext"
tlog "github.com/opentracing/opentracing-go/log"
@@ -26,6 +27,7 @@ type AlertEngine struct {
RenderService rendering.Service `inject:""`
Bus bus.Bus `inject:""`
RequestValidator models.PluginRequestValidator `inject:""`
DataService *tsdb.Service `inject:""`
execQueue chan *Job
ticker *Ticker
@@ -50,7 +52,7 @@ func (e *AlertEngine) Init() error {
e.ticker = NewTicker(time.Now(), time.Second*0, clock.New(), 1)
e.execQueue = make(chan *Job, 1000)
e.scheduler = newScheduler()
e.evalHandler = NewEvalHandler()
e.evalHandler = NewEvalHandler(e.DataService)
e.ruleReader = newRuleReader()
e.log = log.New("alerting.engine")
e.resultHandler = newResultHandler(e.RenderService)
+5 -2
View File
@@ -7,19 +7,22 @@ import (
"github.com/grafana/grafana/pkg/infra/log"
"github.com/grafana/grafana/pkg/infra/metrics"
"github.com/grafana/grafana/pkg/tsdb/tsdbifaces"
)
// DefaultEvalHandler is responsible for evaluating the alert rule.
type DefaultEvalHandler struct {
log log.Logger
alertJobTimeout time.Duration
requestHandler tsdbifaces.RequestHandler
}
// NewEvalHandler is the `DefaultEvalHandler` constructor.
func NewEvalHandler() *DefaultEvalHandler {
func NewEvalHandler(requestHandler tsdbifaces.RequestHandler) *DefaultEvalHandler {
return &DefaultEvalHandler{
log: log.New("alerting.evalHandler"),
alertJobTimeout: time.Second * 5,
requestHandler: requestHandler,
}
}
@@ -31,7 +34,7 @@ func (e *DefaultEvalHandler) Eval(context *EvalContext) {
for i := 0; i < len(context.Rule.Conditions); i++ {
condition := context.Rule.Conditions[i]
cr, err := condition.Eval(context)
cr, err := condition.Eval(context, e.requestHandler)
if err != nil {
context.Error = err
}
+3 -2
View File
@@ -5,6 +5,7 @@ import (
"testing"
"github.com/grafana/grafana/pkg/services/validations"
"github.com/grafana/grafana/pkg/tsdb/tsdbifaces"
. "github.com/smartystreets/goconvey/convey"
)
@@ -16,13 +17,13 @@ type conditionStub struct {
noData bool
}
func (c *conditionStub) Eval(context *EvalContext) (*ConditionResult, error) {
func (c *conditionStub) Eval(context *EvalContext, reqHandler tsdbifaces.RequestHandler) (*ConditionResult, error) {
return &ConditionResult{Firing: c.firing, EvalMatches: c.matches, Operator: c.operator, NoDataFound: c.noData}, nil
}
func TestAlertingEvaluationHandler(t *testing.T) {
Convey("Test alert evaluation handler", t, func() {
handler := NewEvalHandler()
handler := NewEvalHandler(nil)
Convey("Show return triggered with single passing condition", func() {
context := NewEvalContext(context.TODO(), &Rule{
+2 -1
View File
@@ -5,6 +5,7 @@ import (
"time"
"github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/tsdb/tsdbifaces"
)
type evalHandler interface {
@@ -59,5 +60,5 @@ type ConditionResult struct {
// Condition is responsible for evaluating an alert condition.
type Condition interface {
Eval(result *EvalContext) (*ConditionResult, error)
Eval(result *EvalContext, requestHandler tsdbifaces.RequestHandler) (*ConditionResult, error)
}
+2 -1
View File
@@ -6,6 +6,7 @@ import (
"github.com/grafana/grafana/pkg/components/simplejson"
"github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/services/sqlstore"
"github.com/grafana/grafana/pkg/tsdb/tsdbifaces"
. "github.com/smartystreets/goconvey/convey"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
@@ -13,7 +14,7 @@ import (
type FakeCondition struct{}
func (f *FakeCondition) Eval(context *EvalContext) (*ConditionResult, error) {
func (f *FakeCondition) Eval(context *EvalContext, reqHandler tsdbifaces.RequestHandler) (*ConditionResult, error) {
return &ConditionResult{}, nil
}
+22 -42
View File
@@ -4,59 +4,39 @@ import (
"context"
"fmt"
"github.com/grafana/grafana/pkg/bus"
"github.com/grafana/grafana/pkg/components/simplejson"
"github.com/grafana/grafana/pkg/models"
)
// AlertTestCommand initiates an test evaluation
// of an alert rule.
type AlertTestCommand struct {
Dashboard *simplejson.Json
PanelID int64
OrgID int64
User *models.SignedInUser
// AlertTest makes a test alert.
func (e *AlertEngine) AlertTest(orgID int64, dashboard *simplejson.Json, panelID int64, user *models.SignedInUser) (*EvalContext, error) {
dash := models.NewDashboardFromJson(dashboard)
Result *EvalContext
}
func init() {
bus.AddHandler("alerting", handleAlertTestCommand)
}
func handleAlertTestCommand(cmd *AlertTestCommand) error {
dash := models.NewDashboardFromJson(cmd.Dashboard)
extractor := NewDashAlertExtractor(dash, cmd.OrgID, cmd.User)
extractor := NewDashAlertExtractor(dash, orgID, user)
alerts, err := extractor.GetAlerts()
if err != nil {
return err
return nil, err
}
for _, alert := range alerts {
if alert.PanelId == cmd.PanelID {
rule, err := NewRuleFromDBAlert(alert, true)
if err != nil {
return err
}
cmd.Result = testAlertRule(rule)
return nil
if alert.PanelId != panelID {
continue
}
rule, err := NewRuleFromDBAlert(alert, true)
if err != nil {
return nil, err
}
handler := NewEvalHandler(e.DataService)
context := NewEvalContext(context.Background(), rule, fakeRequestValidator{})
context.IsTestRun = true
context.IsDebug = true
handler.Eval(context)
context.Rule.State = context.GetNewState()
return context, nil
}
return fmt.Errorf("could not find alert with panel ID %d", cmd.PanelID)
}
func testAlertRule(rule *Rule) *EvalContext {
handler := NewEvalHandler()
context := NewEvalContext(context.Background(), rule, fakeRequestValidator{})
context.IsTestRun = true
context.IsDebug = true
handler.Eval(context)
context.Rule.State = context.GetNewState()
return context
return nil, fmt.Errorf("could not find alert with panel ID %d", panelID)
}