Merge pull request #12753 from svenklemm/sql-fillmode-last

Add fill mode to fill in previously seen value when point is missing to SQL datasources
This commit is contained in:
Marcus Efraimsson
2018-08-08 10:29:56 +02:00
committed by GitHub
16 changed files with 748 additions and 135 deletions
+7 -3
View File
@@ -99,9 +99,13 @@ func (m *msSqlMacroEngine) evaluateMacro(name string, args []string) (string, er
if len(args) == 3 {
m.query.Model.Set("fill", true)
m.query.Model.Set("fillInterval", interval.Seconds())
if args[2] == "NULL" {
m.query.Model.Set("fillNull", true)
} else {
switch args[2] {
case "NULL":
m.query.Model.Set("fillMode", "null")
case "previous":
m.query.Model.Set("fillMode", "previous")
default:
m.query.Model.Set("fillMode", "value")
floatVal, err := strconv.ParseFloat(args[2], 64)
if err != nil {
return "", fmt.Errorf("error parsing fill value %v", args[2])
+15 -2
View File
@@ -76,12 +76,25 @@ func TestMacroEngine(t *testing.T) {
_, err := engine.Interpolate(query, timeRange, "GROUP BY $__timeGroup(time_column,'5m', NULL)")
fill := query.Model.Get("fill").MustBool()
fillNull := query.Model.Get("fillNull").MustBool()
fillMode := query.Model.Get("fillMode").MustString()
fillInterval := query.Model.Get("fillInterval").MustInt()
So(err, ShouldBeNil)
So(fill, ShouldBeTrue)
So(fillNull, ShouldBeTrue)
So(fillMode, ShouldEqual, "null")
So(fillInterval, ShouldEqual, 5*time.Minute.Seconds())
})
Convey("interpolate __timeGroup function with fill (value = previous)", func() {
_, err := engine.Interpolate(query, timeRange, "GROUP BY $__timeGroup(time_column,'5m', previous)")
fill := query.Model.Get("fill").MustBool()
fillMode := query.Model.Get("fillMode").MustString()
fillInterval := query.Model.Get("fillInterval").MustInt()
So(err, ShouldBeNil)
So(fill, ShouldBeTrue)
So(fillMode, ShouldEqual, "previous")
So(fillInterval, ShouldEqual, 5*time.Minute.Seconds())
})
+7 -3
View File
@@ -94,9 +94,13 @@ func (m *mySqlMacroEngine) evaluateMacro(name string, args []string) (string, er
if len(args) == 3 {
m.query.Model.Set("fill", true)
m.query.Model.Set("fillInterval", interval.Seconds())
if args[2] == "NULL" {
m.query.Model.Set("fillNull", true)
} else {
switch args[2] {
case "NULL":
m.query.Model.Set("fillMode", "null")
case "previous":
m.query.Model.Set("fillMode", "previous")
default:
m.query.Model.Set("fillMode", "value")
floatVal, err := strconv.ParseFloat(args[2], 64)
if err != nil {
return "", fmt.Errorf("error parsing fill value %v", args[2])
+30 -1
View File
@@ -295,7 +295,7 @@ func TestMySQL(t *testing.T) {
})
Convey("When doing a metric query using timeGroup with float fill enabled", func() {
Convey("When doing a metric query using timeGroup with value fill enabled", func() {
query := &tsdb.TsdbQuery{
Queries: []*tsdb.Query{
{
@@ -320,6 +320,35 @@ func TestMySQL(t *testing.T) {
points := queryResult.Series[0].Points
So(points[3][0].Float64, ShouldEqual, 1.5)
})
Convey("When doing a metric query using timeGroup with previous fill enabled", func() {
query := &tsdb.TsdbQuery{
Queries: []*tsdb.Query{
{
Model: simplejson.NewFromAny(map[string]interface{}{
"rawSql": "SELECT $__timeGroup(time, '5m', previous) as time_sec, avg(value) as value FROM metric GROUP BY 1 ORDER BY 1",
"format": "time_series",
}),
RefId: "A",
},
},
TimeRange: &tsdb.TimeRange{
From: fmt.Sprintf("%v", fromStart.Unix()*1000),
To: fmt.Sprintf("%v", fromStart.Add(34*time.Minute).Unix()*1000),
},
}
resp, err := endpoint.Query(nil, nil, query)
So(err, ShouldBeNil)
queryResult := resp.Results["A"]
So(queryResult.Error, ShouldBeNil)
points := queryResult.Series[0].Points
So(points[2][0].Float64, ShouldEqual, 15.0)
So(points[3][0].Float64, ShouldEqual, 15.0)
So(points[6][0].Float64, ShouldEqual, 20.0)
})
})
Convey("Given a table with metrics having multiple values and measurements", func() {
+7 -3
View File
@@ -116,9 +116,13 @@ func (m *postgresMacroEngine) evaluateMacro(name string, args []string) (string,
if len(args) == 3 {
m.query.Model.Set("fill", true)
m.query.Model.Set("fillInterval", interval.Seconds())
if args[2] == "NULL" {
m.query.Model.Set("fillNull", true)
} else {
switch args[2] {
case "NULL":
m.query.Model.Set("fillMode", "null")
case "previous":
m.query.Model.Set("fillMode", "previous")
default:
m.query.Model.Set("fillMode", "value")
floatVal, err := strconv.ParseFloat(args[2], 64)
if err != nil {
return "", fmt.Errorf("error parsing fill value %v", args[2])
+29 -1
View File
@@ -276,7 +276,7 @@ func TestPostgres(t *testing.T) {
})
Convey("When doing a metric query using timeGroup with float fill enabled", func() {
Convey("When doing a metric query using timeGroup with value fill enabled", func() {
query := &tsdb.TsdbQuery{
Queries: []*tsdb.Query{
{
@@ -303,6 +303,34 @@ func TestPostgres(t *testing.T) {
})
})
Convey("When doing a metric query using timeGroup with previous fill enabled", func() {
query := &tsdb.TsdbQuery{
Queries: []*tsdb.Query{
{
Model: simplejson.NewFromAny(map[string]interface{}{
"rawSql": "SELECT $__timeGroup(time, '5m', previous), avg(value) as value FROM metric GROUP BY 1 ORDER BY 1",
"format": "time_series",
}),
RefId: "A",
},
},
TimeRange: &tsdb.TimeRange{
From: fmt.Sprintf("%v", fromStart.Unix()*1000),
To: fmt.Sprintf("%v", fromStart.Add(34*time.Minute).Unix()*1000),
},
}
resp, err := endpoint.Query(nil, nil, query)
So(err, ShouldBeNil)
queryResult := resp.Results["A"]
So(queryResult.Error, ShouldBeNil)
points := queryResult.Series[0].Points
So(points[2][0].Float64, ShouldEqual, 15.0)
So(points[3][0].Float64, ShouldEqual, 15.0)
So(points[6][0].Float64, ShouldEqual, 20.0)
})
Convey("Given a table with metrics having multiple values and measurements", func() {
type metric_values struct {
Time time.Time
+23 -1
View File
@@ -274,9 +274,15 @@ func (e *sqlQueryEndpoint) transformToTimeSeries(query *Query, rows *core.Rows,
fillMissing := query.Model.Get("fill").MustBool(false)
var fillInterval float64
fillValue := null.Float{}
fillPrevious := false
if fillMissing {
fillInterval = query.Model.Get("fillInterval").MustFloat64() * 1000
if !query.Model.Get("fillNull").MustBool(false) {
switch query.Model.Get("fillMode").MustString() {
case "null":
case "previous":
fillPrevious = true
case "value":
fillValue.Float64 = query.Model.Get("fillValue").MustFloat64()
fillValue.Valid = true
}
@@ -352,6 +358,14 @@ func (e *sqlQueryEndpoint) transformToTimeSeries(query *Query, rows *core.Rows,
intervalStart = series.Points[len(series.Points)-1][1].Float64 + fillInterval
}
if fillPrevious {
if len(series.Points) > 0 {
fillValue = series.Points[len(series.Points)-1][0]
} else {
fillValue.Valid = false
}
}
// align interval start
intervalStart = math.Floor(intervalStart/fillInterval) * fillInterval
@@ -377,6 +391,14 @@ func (e *sqlQueryEndpoint) transformToTimeSeries(query *Query, rows *core.Rows,
intervalStart := series.Points[len(series.Points)-1][1].Float64
intervalEnd := float64(tsdbQuery.TimeRange.MustGetTo().UnixNano() / 1e6)
if fillPrevious {
if len(series.Points) > 0 {
fillValue = series.Points[len(series.Points)-1][0]
} else {
fillValue.Valid = false
}
}
// align interval start
intervalStart = math.Floor(intervalStart/fillInterval) * fillInterval
for i := intervalStart + fillInterval; i < intervalEnd; i += fillInterval {