mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
add missing value fill code to mysql datasource
This commit is contained in:
parent
6cdd901ec6
commit
cb15d9cbdd
@ -3,6 +3,7 @@ package mysql
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"regexp"
|
"regexp"
|
||||||
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@ -15,6 +16,7 @@ const sExpr = `\$` + rsIdentifier + `\(([^\)]*)\)`
|
|||||||
|
|
||||||
type MySqlMacroEngine struct {
|
type MySqlMacroEngine struct {
|
||||||
TimeRange *tsdb.TimeRange
|
TimeRange *tsdb.TimeRange
|
||||||
|
Query *tsdb.Query
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewMysqlMacroEngine() tsdb.SqlMacroEngine {
|
func NewMysqlMacroEngine() tsdb.SqlMacroEngine {
|
||||||
@ -23,6 +25,7 @@ func NewMysqlMacroEngine() tsdb.SqlMacroEngine {
|
|||||||
|
|
||||||
func (m *MySqlMacroEngine) Interpolate(query *tsdb.Query, timeRange *tsdb.TimeRange, sql string) (string, error) {
|
func (m *MySqlMacroEngine) Interpolate(query *tsdb.Query, timeRange *tsdb.TimeRange, sql string) (string, error) {
|
||||||
m.TimeRange = timeRange
|
m.TimeRange = timeRange
|
||||||
|
m.Query = query
|
||||||
rExp, _ := regexp.Compile(sExpr)
|
rExp, _ := regexp.Compile(sExpr)
|
||||||
var macroError error
|
var macroError error
|
||||||
|
|
||||||
@ -76,13 +79,26 @@ func (m *MySqlMacroEngine) evaluateMacro(name string, args []string) (string, er
|
|||||||
case "__timeTo":
|
case "__timeTo":
|
||||||
return fmt.Sprintf("FROM_UNIXTIME(%d)", uint64(m.TimeRange.GetToAsMsEpoch()/1000)), nil
|
return fmt.Sprintf("FROM_UNIXTIME(%d)", uint64(m.TimeRange.GetToAsMsEpoch()/1000)), nil
|
||||||
case "__timeGroup":
|
case "__timeGroup":
|
||||||
if len(args) != 2 {
|
if len(args) < 2 {
|
||||||
return "", fmt.Errorf("macro %v needs time column and interval", name)
|
return "", fmt.Errorf("macro %v needs time column and interval", name)
|
||||||
}
|
}
|
||||||
interval, err := time.ParseDuration(strings.Trim(args[1], `'" `))
|
interval, err := time.ParseDuration(strings.Trim(args[1], `'" `))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", fmt.Errorf("error parsing interval %v", args[1])
|
return "", fmt.Errorf("error parsing interval %v", args[1])
|
||||||
}
|
}
|
||||||
|
if len(args) == 3 {
|
||||||
|
m.Query.Model.Set("fill", true)
|
||||||
|
m.Query.Model.Set("fillInterval", interval.Seconds())
|
||||||
|
if strings.Trim(args[2], " ") == "NULL" {
|
||||||
|
m.Query.Model.Set("fillNull", true)
|
||||||
|
} else {
|
||||||
|
floatVal, err := strconv.ParseFloat(args[2], 64)
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("error parsing fill value %v", args[2])
|
||||||
|
}
|
||||||
|
m.Query.Model.Set("fillValue", floatVal)
|
||||||
|
}
|
||||||
|
}
|
||||||
return fmt.Sprintf("cast(cast(UNIX_TIMESTAMP(%s)/(%.0f) as signed)*%.0f as signed)", args[0], interval.Seconds(), interval.Seconds()), nil
|
return fmt.Sprintf("cast(cast(UNIX_TIMESTAMP(%s)/(%.0f) as signed)*%.0f as signed)", args[0], interval.Seconds(), interval.Seconds()), nil
|
||||||
case "__unixEpochFilter":
|
case "__unixEpochFilter":
|
||||||
if len(args) == 0 {
|
if len(args) == 0 {
|
||||||
|
@ -5,6 +5,7 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"database/sql"
|
"database/sql"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"math"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
|
||||||
"time"
|
"time"
|
||||||
@ -176,6 +177,18 @@ func (e MysqlQueryEndpoint) transformToTimeSeries(query *tsdb.Query, rows *core.
|
|||||||
rowLimit := 1000000
|
rowLimit := 1000000
|
||||||
rowCount := 0
|
rowCount := 0
|
||||||
|
|
||||||
|
fillMissing := query.Model.Get("fill").MustBool(false)
|
||||||
|
var fillInterval float64
|
||||||
|
fillValue := null.Float{}
|
||||||
|
if fillMissing {
|
||||||
|
fillInterval = query.Model.Get("fillInterval").MustFloat64() * 1000
|
||||||
|
if query.Model.Get("fillNull").MustBool(false) == false {
|
||||||
|
fillValue.Float64 = query.Model.Get("fillValue").MustFloat64()
|
||||||
|
fillValue.Valid = true
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
for ; rows.Next(); rowCount++ {
|
for ; rows.Next(); rowCount++ {
|
||||||
if rowCount > rowLimit {
|
if rowCount > rowLimit {
|
||||||
return fmt.Errorf("MySQL query row limit exceeded, limit %d", rowLimit)
|
return fmt.Errorf("MySQL query row limit exceeded, limit %d", rowLimit)
|
||||||
@ -195,19 +208,50 @@ func (e MysqlQueryEndpoint) transformToTimeSeries(query *tsdb.Query, rows *core.
|
|||||||
return fmt.Errorf("Found row with no time value")
|
return fmt.Errorf("Found row with no time value")
|
||||||
}
|
}
|
||||||
|
|
||||||
if series, exist := pointsBySeries[rowData.metric]; exist {
|
series, exist := pointsBySeries[rowData.metric]
|
||||||
series.Points = append(series.Points, tsdb.TimePoint{rowData.value, rowData.time})
|
if exist == false {
|
||||||
} else {
|
series = &tsdb.TimeSeries{Name: rowData.metric}
|
||||||
series := &tsdb.TimeSeries{Name: rowData.metric}
|
|
||||||
series.Points = append(series.Points, tsdb.TimePoint{rowData.value, rowData.time})
|
|
||||||
pointsBySeries[rowData.metric] = series
|
pointsBySeries[rowData.metric] = series
|
||||||
seriesByQueryOrder.PushBack(rowData.metric)
|
seriesByQueryOrder.PushBack(rowData.metric)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if fillMissing {
|
||||||
|
var intervalStart float64
|
||||||
|
if exist == false {
|
||||||
|
intervalStart = float64(tsdbQuery.TimeRange.MustGetFrom().UnixNano() / 1e6)
|
||||||
|
} else {
|
||||||
|
intervalStart = series.Points[len(series.Points)-1][1].Float64 + fillInterval
|
||||||
|
}
|
||||||
|
|
||||||
|
// align interval start
|
||||||
|
intervalStart = math.Floor(intervalStart/fillInterval) * fillInterval
|
||||||
|
|
||||||
|
for i := intervalStart; i < rowData.time.Float64; i += fillInterval {
|
||||||
|
series.Points = append(series.Points, tsdb.TimePoint{fillValue, null.FloatFrom(i)})
|
||||||
|
rowCount++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
series.Points = append(series.Points, tsdb.TimePoint{rowData.value, rowData.time})
|
||||||
}
|
}
|
||||||
|
|
||||||
for elem := seriesByQueryOrder.Front(); elem != nil; elem = elem.Next() {
|
for elem := seriesByQueryOrder.Front(); elem != nil; elem = elem.Next() {
|
||||||
key := elem.Value.(string)
|
key := elem.Value.(string)
|
||||||
result.Series = append(result.Series, pointsBySeries[key])
|
result.Series = append(result.Series, pointsBySeries[key])
|
||||||
|
|
||||||
|
if fillMissing {
|
||||||
|
series := pointsBySeries[key]
|
||||||
|
// fill in values from last fetched value till interval end
|
||||||
|
intervalStart := series.Points[len(series.Points)-1][1].Float64
|
||||||
|
intervalEnd := float64(tsdbQuery.TimeRange.MustGetTo().UnixNano() / 1e6)
|
||||||
|
|
||||||
|
// align interval start
|
||||||
|
intervalStart = math.Floor(intervalStart/fillInterval) * fillInterval
|
||||||
|
for i := intervalStart + fillInterval; i < intervalEnd; i += fillInterval {
|
||||||
|
series.Points = append(series.Points, tsdb.TimePoint{fillValue, null.FloatFrom(i)})
|
||||||
|
rowCount++
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
result.Meta.Set("rowCount", rowCount)
|
result.Meta.Set("rowCount", rowCount)
|
||||||
|
Loading…
Reference in New Issue
Block a user