SQL: Migrate to use SDK contracts (#36635)

* convert SQLs to use sdk contracts

* make draft

* postgres

* intermedia

* get datasourceinfo filled at the beginning of the service

* move the interval into package because of cyclict  import and fix all postgres tests

* fix mysql test

* fix mssql

* fix the test for pr https://github.com/grafana/grafana/issues/35839

* fix some issue about intervalv2 package

* update sql test

* wire migration for SQLs

* add sqls to the background process

* make it register instead of register and start

* revert formatting

* fix tests

* fix linter

* remove integration test

* Postgres test fix

Co-authored-by: Marcus Efraimsson <marcus.efraimsson@gmail.com>
This commit is contained in:
ying-jeanne
2021-09-07 09:35:37 +02:00
committed by GitHub
co-authored by Marcus Efraimsson
parent 51aeb717a4
commit c989f0fbbe
40 changed files with 1632 additions and 1495 deletions
+134
View File
@@ -0,0 +1,134 @@
package intervalv2
import (
"testing"
"time"
"github.com/grafana/grafana-plugin-sdk-go/backend"
"github.com/grafana/grafana/pkg/models"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestIntervalCalculator_Calculate(t *testing.T) {
calculator := NewCalculator(CalculatorOptions{})
timeNow := time.Now()
testCases := []struct {
name string
timeRange backend.TimeRange
intervalMode IntervalMode
expected string
}{
{"from 5m to now", backend.TimeRange{From: timeNow, To: timeNow.Add(5 * time.Minute)}, Min, "200ms"},
{"from 5m to now", backend.TimeRange{From: timeNow, To: timeNow.Add(5 * time.Minute)}, Max, "1ms"},
{"from 5m to now", backend.TimeRange{From: timeNow, To: timeNow.Add(5 * time.Minute)}, Exact, "1ms"},
{"from 15m to now", backend.TimeRange{From: timeNow, To: timeNow.Add(15 * time.Minute)}, Min, "500ms"},
{"from 15m to now", backend.TimeRange{From: timeNow, To: timeNow.Add(15 * time.Minute)}, Max, "1ms"},
{"from 15m to now", backend.TimeRange{From: timeNow, To: timeNow.Add(15 * time.Minute)}, Exact, "1ms"},
{"from 30m to now", backend.TimeRange{From: timeNow, To: timeNow.Add(30 * time.Minute)}, Min, "1s"},
{"from 30m to now", backend.TimeRange{From: timeNow, To: timeNow.Add(30 * time.Minute)}, Max, "1ms"},
{"from 30m to now", backend.TimeRange{From: timeNow, To: timeNow.Add(30 * time.Minute)}, Exact, "1ms"},
{"from 1h to now", backend.TimeRange{From: timeNow, To: timeNow.Add(1440 * time.Minute)}, Min, "1m"},
{"from 1h to now", backend.TimeRange{From: timeNow, To: timeNow.Add(1440 * time.Minute)}, Max, "1ms"},
{"from 1h to now", backend.TimeRange{From: timeNow, To: timeNow.Add(1440 * time.Minute)}, Exact, "1ms"},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
interval, err := calculator.Calculate(tc.timeRange, time.Millisecond*1, tc.intervalMode)
require.Nil(t, err)
assert.Equal(t, tc.expected, interval.Text)
})
}
}
func TestIntervalCalculator_CalculateSafeInterval(t *testing.T) {
calculator := NewCalculator(CalculatorOptions{})
timeNow := time.Now()
testCases := []struct {
name string
timeRange backend.TimeRange
safeResolution int64
expected string
}{
{"from 5m to now", backend.TimeRange{From: timeNow, To: timeNow.Add(5 * time.Minute)}, 11000, "20ms"},
{"from 15m to now", backend.TimeRange{From: timeNow, To: timeNow.Add(15 * time.Minute)}, 11000, "100ms"},
{"from 30m to now", backend.TimeRange{From: timeNow, To: timeNow.Add(30 * time.Minute)}, 11000, "200ms"},
{"from 24h to now", backend.TimeRange{From: timeNow, To: timeNow.Add(1440 * time.Minute)}, 11000, "10s"},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
interval := calculator.CalculateSafeInterval(tc.timeRange, tc.safeResolution)
assert.Equal(t, tc.expected, interval.Text)
})
}
}
func TestRoundInterval(t *testing.T) {
testCases := []struct {
name string
interval time.Duration
expected time.Duration
}{
{"30ms", time.Millisecond * 30, time.Millisecond * 20},
{"45ms", time.Millisecond * 45, time.Millisecond * 50},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
assert.Equal(t, tc.expected, roundInterval(tc.interval))
})
}
}
func TestFormatDuration(t *testing.T) {
testCases := []struct {
name string
duration time.Duration
expected string
}{
{"61s", time.Second * 61, "1m"},
{"30ms", time.Millisecond * 30, "30ms"},
{"23h", time.Hour * 23, "23h"},
{"24h", time.Hour * 24, "1d"},
{"367d", time.Hour * 24 * 367, "1y"},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
assert.Equal(t, tc.expected, FormatDuration(tc.duration))
})
}
}
func TestGetIntervalFrom(t *testing.T) {
testCases := []struct {
name string
dsInfo *models.DataSource
queryInterval string
queryIntervalMs int64
defaultInterval time.Duration
expected time.Duration
}{
{"45s", nil, "45s", 0, time.Second * 15, time.Second * 45},
{"45", nil, "45", 0, time.Second * 15, time.Second * 45},
{"2m", nil, "2m", 0, time.Second * 15, time.Minute * 2},
{"intervalMs", nil, "", 45000, time.Second * 15, time.Second * 45},
{"intervalMs sub-seconds", nil, "", 45200, time.Second * 15, time.Millisecond * 45200},
{"defaultInterval when interval empty", nil, "", 0, time.Second * 15, time.Second * 15},
{"defaultInterval when intervalMs 0", nil, "", 0, time.Second * 15, time.Second * 15},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
actual, err := GetIntervalFrom(tc.queryInterval, "", tc.queryIntervalMs, tc.defaultInterval)
assert.Nil(t, err)
assert.Equal(t, tc.expected, actual)
})
}
}
+257
View File
@@ -0,0 +1,257 @@
package intervalv2
import (
"fmt"
"regexp"
"strings"
"time"
"github.com/grafana/grafana-plugin-sdk-go/backend"
"github.com/grafana/grafana/pkg/tsdb/interval"
)
var (
defaultRes int64 = 1500
defaultMinInterval = time.Millisecond * 1
year = time.Hour * 24 * 365
day = time.Hour * 24
)
type IntervalMode string
const (
Min IntervalMode = "min"
Max IntervalMode = "max"
Exact IntervalMode = "exact"
)
type Interval struct {
Text string
Value time.Duration
}
type intervalCalculator struct {
minInterval time.Duration
}
type Calculator interface {
Calculate(timerange backend.TimeRange, minInterval time.Duration, intervalMode IntervalMode) (Interval, error)
CalculateSafeInterval(timerange backend.TimeRange, resolution int64) Interval
}
type CalculatorOptions struct {
MinInterval time.Duration
}
func NewCalculator(opts ...CalculatorOptions) *intervalCalculator {
calc := &intervalCalculator{}
for _, o := range opts {
if o.MinInterval == 0 {
calc.minInterval = defaultMinInterval
} else {
calc.minInterval = o.MinInterval
}
}
return calc
}
func (i *Interval) Milliseconds() int64 {
return i.Value.Nanoseconds() / int64(time.Millisecond)
}
func (ic *intervalCalculator) Calculate(timerange backend.TimeRange, intrvl time.Duration, intervalMode IntervalMode) (Interval, error) {
to := timerange.To.UnixNano()
from := timerange.From.UnixNano()
calculatedIntrvl := time.Duration((to - from) / defaultRes)
switch intervalMode {
case Min:
if calculatedIntrvl < intrvl {
return Interval{Text: interval.FormatDuration(intrvl), Value: intrvl}, nil
}
case Max:
if calculatedIntrvl > intrvl {
return Interval{Text: interval.FormatDuration(intrvl), Value: intrvl}, nil
}
case Exact:
return Interval{Text: interval.FormatDuration(intrvl), Value: intrvl}, nil
default:
return Interval{}, fmt.Errorf("unrecognized intervalMode: %v", intervalMode)
}
rounded := roundInterval(calculatedIntrvl)
return Interval{Text: interval.FormatDuration(rounded), Value: rounded}, nil
}
func (ic *intervalCalculator) CalculateSafeInterval(timerange backend.TimeRange, safeRes int64) Interval {
to := timerange.To.UnixNano()
from := timerange.From.UnixNano()
safeInterval := time.Duration((to - from) / safeRes)
rounded := roundInterval(safeInterval)
return Interval{Text: interval.FormatDuration(rounded), Value: rounded}
}
// GetIntervalFrom returns the minimum interval.
// dsInterval is the string representation of data source min interval, if configured.
// queryInterval is the string representation of query interval (min interval), e.g. "10ms" or "10s".
// queryIntervalMS is a pre-calculated numeric representation of the query interval in milliseconds.
func GetIntervalFrom(dsInterval, queryInterval string, queryIntervalMS int64, defaultInterval time.Duration) (time.Duration, error) {
// Apparently we are setting default value of queryInterval to 0s now
if queryInterval == "0s" {
queryInterval = ""
}
if queryInterval == "" {
if queryIntervalMS != 0 {
return time.Duration(queryIntervalMS) * time.Millisecond, nil
}
}
interval := queryInterval
if queryInterval == "" && dsInterval != "" {
interval = dsInterval
}
if interval == "" {
return defaultInterval, nil
}
interval = strings.Replace(strings.Replace(interval, "<", "", 1), ">", "", 1)
isPureNum, err := regexp.MatchString(`^\d+$`, interval)
if err != nil {
return time.Duration(0), err
}
if isPureNum {
interval += "s"
}
parsedInterval, err := time.ParseDuration(interval)
if err != nil {
return time.Duration(0), err
}
return parsedInterval, nil
}
// FormatDuration converts a duration into the kbn format e.g. 1m 2h or 3d
func FormatDuration(inter time.Duration) string {
if inter >= year {
return fmt.Sprintf("%dy", inter/year)
}
if inter >= day {
return fmt.Sprintf("%dd", inter/day)
}
if inter >= time.Hour {
return fmt.Sprintf("%dh", inter/time.Hour)
}
if inter >= time.Minute {
return fmt.Sprintf("%dm", inter/time.Minute)
}
if inter >= time.Second {
return fmt.Sprintf("%ds", inter/time.Second)
}
if inter >= time.Millisecond {
return fmt.Sprintf("%dms", inter/time.Millisecond)
}
return "1ms"
}
//nolint: gocyclo
func roundInterval(interval time.Duration) time.Duration {
switch {
// 0.015s
case interval <= 15*time.Millisecond:
return time.Millisecond * 10 // 0.01s
// 0.035s
case interval <= 35*time.Millisecond:
return time.Millisecond * 20 // 0.02s
// 0.075s
case interval <= 75*time.Millisecond:
return time.Millisecond * 50 // 0.05s
// 0.15s
case interval <= 150*time.Millisecond:
return time.Millisecond * 100 // 0.1s
// 0.35s
case interval <= 350*time.Millisecond:
return time.Millisecond * 200 // 0.2s
// 0.75s
case interval <= 750*time.Millisecond:
return time.Millisecond * 500 // 0.5s
// 1.5s
case interval <= 1500*time.Millisecond:
return time.Millisecond * 1000 // 1s
// 3.5s
case interval <= 3500*time.Millisecond:
return time.Millisecond * 2000 // 2s
// 7.5s
case interval <= 7500*time.Millisecond:
return time.Millisecond * 5000 // 5s
// 12.5s
case interval <= 12500*time.Millisecond:
return time.Millisecond * 10000 // 10s
// 17.5s
case interval <= 17500*time.Millisecond:
return time.Millisecond * 15000 // 15s
// 25s
case interval <= 25000*time.Millisecond:
return time.Millisecond * 20000 // 20s
// 45s
case interval <= 45000*time.Millisecond:
return time.Millisecond * 30000 // 30s
// 1.5m
case interval <= 90000*time.Millisecond:
return time.Millisecond * 60000 // 1m
// 3.5m
case interval <= 210000*time.Millisecond:
return time.Millisecond * 120000 // 2m
// 7.5m
case interval <= 450000*time.Millisecond:
return time.Millisecond * 300000 // 5m
// 12.5m
case interval <= 750000*time.Millisecond:
return time.Millisecond * 600000 // 10m
// 12.5m
case interval <= 1050000*time.Millisecond:
return time.Millisecond * 900000 // 15m
// 25m
case interval <= 1500000*time.Millisecond:
return time.Millisecond * 1200000 // 20m
// 45m
case interval <= 2700000*time.Millisecond:
return time.Millisecond * 1800000 // 30m
// 1.5h
case interval <= 5400000*time.Millisecond:
return time.Millisecond * 3600000 // 1h
// 2.5h
case interval <= 9000000*time.Millisecond:
return time.Millisecond * 7200000 // 2h
// 4.5h
case interval <= 16200000*time.Millisecond:
return time.Millisecond * 10800000 // 3h
// 9h
case interval <= 32400000*time.Millisecond:
return time.Millisecond * 21600000 // 6h
// 24h
case interval <= 86400000*time.Millisecond:
return time.Millisecond * 43200000 // 12h
// 48h
case interval <= 172800000*time.Millisecond:
return time.Millisecond * 86400000 // 24h
// 1w
case interval <= 604800000*time.Millisecond:
return time.Millisecond * 86400000 // 24h
// 3w
case interval <= 1814400000*time.Millisecond:
return time.Millisecond * 604800000 // 1w
// 2y
case interval < 3628800000*time.Millisecond:
return time.Millisecond * 2592000000 // 30d
default:
return time.Millisecond * 31536000000 // 1y
}
}