diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index dd8e6988ebd..39fcdff146c 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -142,7 +142,6 @@ /pkg/tests/apis/ @grafana/grafana-app-platform-squad /pkg/tests/api/correlations/ @grafana/explore-squad /pkg/tsdb/grafanads/ @grafana/backend-platform -/pkg/tsdb/intervalv2/ @grafana/backend-platform /pkg/tsdb/legacydata/ @grafana/backend-platform /pkg/tsdb/opentsdb/ @grafana/backend-platform /pkg/tsdb/sqleng/ @grafana/partner-datasources @grafana/oss-big-tent diff --git a/pkg/services/alerting/conditions/query_interval_test.go b/pkg/services/alerting/conditions/query_interval_test.go index 443b0f74d83..bb841e0f9ad 100644 --- a/pkg/services/alerting/conditions/query_interval_test.go +++ b/pkg/services/alerting/conditions/query_interval_test.go @@ -15,10 +15,11 @@ import ( "github.com/grafana/grafana/pkg/services/featuremgmt" "github.com/grafana/grafana/pkg/services/validations" "github.com/grafana/grafana/pkg/setting" - "github.com/grafana/grafana/pkg/tsdb/intervalv2" "github.com/grafana/grafana/pkg/tsdb/legacydata" ) +const DefaultRes int64 = 1500 + func TestQueryInterval(t *testing.T) { t.Run("When evaluating query condition, regarding the interval value", func(t *testing.T) { t.Run("Can handle interval-calculation with no panel-min-interval and no datasource-min-interval", func(t *testing.T) { @@ -34,7 +35,7 @@ func TestQueryInterval(t *testing.T) { // 5minutes timerange = 300000milliseconds; default-resolution is 1500pixels, // so we should have 300000/1500 = 200milliseconds here require.Equal(t, int64(200), query.IntervalMS) - require.Equal(t, intervalv2.DefaultRes, query.MaxDataPoints) + require.Equal(t, DefaultRes, query.MaxDataPoints) } applyScenario(t, timeRange, dataSourceJson, queryModel, verifier) @@ -50,7 +51,7 @@ func TestQueryInterval(t *testing.T) { verifier := func(query legacydata.DataSubQuery) { require.Equal(t, int64(123000), query.IntervalMS) - require.Equal(t, intervalv2.DefaultRes, query.MaxDataPoints) + require.Equal(t, DefaultRes, query.MaxDataPoints) } applyScenario(t, timeRange, dataSourceJson, queryModel, verifier) @@ -69,7 +70,7 @@ func TestQueryInterval(t *testing.T) { verifier := func(query legacydata.DataSubQuery) { require.Equal(t, int64(71000), query.IntervalMS) - require.Equal(t, intervalv2.DefaultRes, query.MaxDataPoints) + require.Equal(t, DefaultRes, query.MaxDataPoints) } applyScenario(t, timeRange, dataSourceJson, queryModel, verifier) @@ -90,7 +91,7 @@ func TestQueryInterval(t *testing.T) { // when both panel-min-interval and datasource-min-interval exists, // panel-min-interval is used require.Equal(t, int64(19000), query.IntervalMS) - require.Equal(t, intervalv2.DefaultRes, query.MaxDataPoints) + require.Equal(t, DefaultRes, query.MaxDataPoints) } applyScenario(t, timeRange, dataSourceJson, queryModel, verifier) @@ -109,7 +110,7 @@ func TestQueryInterval(t *testing.T) { // no min-interval exists, the default-min-interval will be used, // and for such a short time-range this will cause the value to be 1millisecond. require.Equal(t, int64(1), query.IntervalMS) - require.Equal(t, intervalv2.DefaultRes, query.MaxDataPoints) + require.Equal(t, DefaultRes, query.MaxDataPoints) } applyScenario(t, timeRange, dataSourceJson, queryModel, verifier) diff --git a/pkg/services/publicdashboards/service/intervalv2/intervalv2.go b/pkg/services/publicdashboards/service/intervalv2/intervalv2.go new file mode 100644 index 00000000000..b20defc27f6 --- /dev/null +++ b/pkg/services/publicdashboards/service/intervalv2/intervalv2.go @@ -0,0 +1,77 @@ +package intervalv2 + +import ( + "time" + + "github.com/grafana/grafana-plugin-sdk-go/backend" + "github.com/grafana/grafana-plugin-sdk-go/backend/gtime" +) + +var ( + DefaultRes int64 = 1500 + defaultMinInterval = time.Millisecond * 1 +) + +type Interval struct { + Text string + Value time.Duration +} + +type intervalCalculator struct { + minInterval time.Duration +} + +type Calculator interface { + Calculate(timerange backend.TimeRange, minInterval time.Duration, maxDataPoints int64) Interval + 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, minInterval time.Duration, maxDataPoints int64) Interval { + to := timerange.To.UnixNano() + from := timerange.From.UnixNano() + resolution := maxDataPoints + if resolution == 0 { + resolution = DefaultRes + } + + calculatedInterval := time.Duration((to - from) / resolution) + + if calculatedInterval < minInterval { + return Interval{Text: gtime.FormatInterval(minInterval), Value: minInterval} + } + + rounded := gtime.RoundInterval(calculatedInterval) + + return Interval{Text: gtime.FormatInterval(rounded), Value: rounded} +} + +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 := gtime.RoundInterval(safeInterval) + return Interval{Text: gtime.FormatInterval(rounded), Value: rounded} +} diff --git a/pkg/tsdb/intervalv2/intervalv2_test.go b/pkg/services/publicdashboards/service/intervalv2/intervalv2_test.go similarity index 54% rename from pkg/tsdb/intervalv2/intervalv2_test.go rename to pkg/services/publicdashboards/service/intervalv2/intervalv2_test.go index 21b5a48abef..fe9900d90af 100644 --- a/pkg/tsdb/intervalv2/intervalv2_test.go +++ b/pkg/services/publicdashboards/service/intervalv2/intervalv2_test.go @@ -6,8 +6,6 @@ import ( "github.com/grafana/grafana-plugin-sdk-go/backend" "github.com/stretchr/testify/assert" - - "github.com/grafana/grafana/pkg/services/datasources" ) func TestIntervalCalculator_Calculate(t *testing.T) { @@ -63,70 +61,3 @@ func TestIntervalCalculator_CalculateSafeInterval(t *testing.T) { }) } } - -func TestRoundInterval(t *testing.T) { - testCases := []struct { - name string - interval time.Duration - expected time.Duration - }{ - {"10ms", time.Millisecond * 10, time.Millisecond * 1}, - {"15ms", time.Millisecond * 15, time.Millisecond * 10}, - {"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 *datasources.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}, - {"1d", nil, "1d", 0, time.Second * 15, time.Hour * 24}, - {"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) - }) - } -} diff --git a/pkg/services/publicdashboards/service/query_test.go b/pkg/services/publicdashboards/service/query_test.go index 46775f5822f..f33a3cfaf55 100644 --- a/pkg/services/publicdashboards/service/query_test.go +++ b/pkg/services/publicdashboards/service/query_test.go @@ -23,12 +23,12 @@ import ( "github.com/grafana/grafana/pkg/services/publicdashboards/database" "github.com/grafana/grafana/pkg/services/publicdashboards/internal" . "github.com/grafana/grafana/pkg/services/publicdashboards/models" + "github.com/grafana/grafana/pkg/services/publicdashboards/service/intervalv2" "github.com/grafana/grafana/pkg/services/query" "github.com/grafana/grafana/pkg/services/quota/quotatest" "github.com/grafana/grafana/pkg/services/sqlstore" "github.com/grafana/grafana/pkg/services/tag/tagimpl" "github.com/grafana/grafana/pkg/setting" - "github.com/grafana/grafana/pkg/tsdb/intervalv2" "github.com/grafana/grafana/pkg/tsdb/legacydata" "github.com/grafana/grafana/pkg/util" diff --git a/pkg/services/publicdashboards/service/service.go b/pkg/services/publicdashboards/service/service.go index c352e171a3f..eb8b3fcb0ad 100644 --- a/pkg/services/publicdashboards/service/service.go +++ b/pkg/services/publicdashboards/service/service.go @@ -8,6 +8,7 @@ import ( "github.com/google/uuid" "github.com/grafana/grafana-plugin-sdk-go/backend" + "github.com/grafana/grafana/pkg/api/dtos" "github.com/grafana/grafana/pkg/infra/log" "github.com/grafana/grafana/pkg/infra/metrics" @@ -16,11 +17,11 @@ import ( "github.com/grafana/grafana/pkg/services/dashboards" "github.com/grafana/grafana/pkg/services/publicdashboards" . "github.com/grafana/grafana/pkg/services/publicdashboards/models" + "github.com/grafana/grafana/pkg/services/publicdashboards/service/intervalv2" "github.com/grafana/grafana/pkg/services/publicdashboards/validation" "github.com/grafana/grafana/pkg/services/query" "github.com/grafana/grafana/pkg/services/user" "github.com/grafana/grafana/pkg/setting" - "github.com/grafana/grafana/pkg/tsdb/intervalv2" "github.com/grafana/grafana/pkg/tsdb/legacydata" "github.com/grafana/grafana/pkg/util" ) diff --git a/pkg/services/publicdashboards/service/service_test.go b/pkg/services/publicdashboards/service/service_test.go index 39f6f0af647..54f8f9cd32e 100644 --- a/pkg/services/publicdashboards/service/service_test.go +++ b/pkg/services/publicdashboards/service/service_test.go @@ -25,12 +25,12 @@ import ( . "github.com/grafana/grafana/pkg/services/publicdashboards" "github.com/grafana/grafana/pkg/services/publicdashboards/database" . "github.com/grafana/grafana/pkg/services/publicdashboards/models" + "github.com/grafana/grafana/pkg/services/publicdashboards/service/intervalv2" "github.com/grafana/grafana/pkg/services/publicdashboards/validation" "github.com/grafana/grafana/pkg/services/quota/quotatest" "github.com/grafana/grafana/pkg/services/tag/tagimpl" "github.com/grafana/grafana/pkg/services/user" "github.com/grafana/grafana/pkg/setting" - "github.com/grafana/grafana/pkg/tsdb/intervalv2" "github.com/grafana/grafana/pkg/util" "github.com/grafana/grafana/pkg/util/errutil" ) diff --git a/pkg/tsdb/azuremonitor/macros/macros.go b/pkg/tsdb/azuremonitor/macros/macros.go index 11304b7416d..953f5f5c914 100644 --- a/pkg/tsdb/azuremonitor/macros/macros.go +++ b/pkg/tsdb/azuremonitor/macros/macros.go @@ -8,9 +8,9 @@ import ( "time" "github.com/grafana/grafana-plugin-sdk-go/backend" + "github.com/grafana/grafana-plugin-sdk-go/backend/gtime" "github.com/grafana/grafana/pkg/tsdb/azuremonitor/kinds/dataquery" - azTime "github.com/grafana/grafana/pkg/tsdb/azuremonitor/time" "github.com/grafana/grafana/pkg/tsdb/azuremonitor/types" ) @@ -126,7 +126,7 @@ func (m *kqlMacroEngine) evaluateMacro(name string, defaultTimeField string, arg if dsInterval, ok = dsInfo.JSONData["interval"].(string); !ok { dsInterval = "" } - it, err = azTime.GetIntervalFrom(dsInterval, queryInterval.Interval, queryInterval.IntervalMs, defaultInterval) + it, err = gtime.GetIntervalFrom(dsInterval, queryInterval.Interval, queryInterval.IntervalMs, defaultInterval) if err != nil { it = defaultInterval } diff --git a/pkg/tsdb/azuremonitor/time/interval.go b/pkg/tsdb/azuremonitor/time/interval.go deleted file mode 100644 index ed8a7587c8c..00000000000 --- a/pkg/tsdb/azuremonitor/time/interval.go +++ /dev/null @@ -1,93 +0,0 @@ -// Copied from https://github.com/grafana/grafana/blob/main/pkg/tsdb/intervalv2/intervalv2.go -// We're copying this to not block ourselves from decoupling until the conversation here is resolved -// https://raintank-corp.slack.com/archives/C05QFJUHUQ6/p1700064431005089 -package time - -import ( - "fmt" - "regexp" - "strings" - "time" - - "github.com/grafana/grafana-plugin-sdk-go/backend/gtime" -) - -var ( - year = time.Hour * 24 * 365 - day = time.Hour * 24 -) - -// 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 - interval := queryInterval - if interval == "0s" { - interval = "" - } - if interval == "" { - if queryIntervalMS != 0 { - return time.Duration(queryIntervalMS) * time.Millisecond, nil - } - } - if interval == "" && dsInterval != "" { - interval = dsInterval - } - if interval == "" { - return defaultInterval, nil - } - - parsedInterval, err := ParseIntervalStringToTimeDuration(interval) - if err != nil { - return time.Duration(0), err - } - - return parsedInterval, nil -} - -func ParseIntervalStringToTimeDuration(interval string) (time.Duration, error) { - formattedInterval := strings.Replace(strings.Replace(interval, "<", "", 1), ">", "", 1) - isPureNum, err := regexp.MatchString(`^\d+$`, formattedInterval) - if err != nil { - return time.Duration(0), err - } - if isPureNum { - formattedInterval += "s" - } - parsedInterval, err := gtime.ParseDuration(formattedInterval) - 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" -} diff --git a/pkg/tsdb/azuremonitor/time/time-grain.go b/pkg/tsdb/azuremonitor/time/time-grain.go index f7c5b1e4d1c..dfc62d9c1ff 100644 --- a/pkg/tsdb/azuremonitor/time/time-grain.go +++ b/pkg/tsdb/azuremonitor/time/time-grain.go @@ -5,6 +5,8 @@ import ( "strconv" "strings" "time" + + "github.com/grafana/grafana-plugin-sdk-go/backend/gtime" ) // TimeGrain handles conversions between @@ -15,7 +17,7 @@ var ( ) func CreateISO8601DurationFromIntervalMS(it int64) (string, error) { - formatted := FormatDuration(time.Duration(it) * time.Millisecond) + formatted := gtime.FormatInterval(time.Duration(it) * time.Millisecond) if strings.Contains(formatted, "ms") { return "PT1M", nil diff --git a/pkg/tsdb/cloud-monitoring/time/interval.go b/pkg/tsdb/cloud-monitoring/time/interval.go index 3119d6fe624..c3a1c452f04 100644 --- a/pkg/tsdb/cloud-monitoring/time/interval.go +++ b/pkg/tsdb/cloud-monitoring/time/interval.go @@ -4,9 +4,6 @@ package time import ( - "fmt" - "regexp" - "strings" "time" "github.com/grafana/grafana-plugin-sdk-go/backend" @@ -16,8 +13,6 @@ import ( var ( DefaultRes int64 = 1500 defaultMinInterval = time.Millisecond * 1 - year = time.Hour * 24 * 365 - day = time.Hour * 24 ) type Interval struct { @@ -52,10 +47,6 @@ func NewCalculator(opts ...CalculatorOptions) *intervalCalculator { return calc } -func (i *Interval) Milliseconds() int64 { - return i.Value.Nanoseconds() / int64(time.Millisecond) -} - func (ic *intervalCalculator) Calculate(timerange backend.TimeRange, minInterval time.Duration, maxDataPoints int64) Interval { to := timerange.To.UnixNano() from := timerange.From.UnixNano() @@ -67,12 +58,12 @@ func (ic *intervalCalculator) Calculate(timerange backend.TimeRange, minInterval calculatedInterval := time.Duration((to - from) / resolution) if calculatedInterval < minInterval { - return Interval{Text: FormatDuration(minInterval), Value: minInterval} + return Interval{Text: gtime.FormatInterval(minInterval), Value: minInterval} } - rounded := roundInterval(calculatedInterval) + rounded := gtime.RoundInterval(calculatedInterval) - return Interval{Text: FormatDuration(rounded), Value: rounded} + return Interval{Text: gtime.FormatInterval(rounded), Value: rounded} } func (ic *intervalCalculator) CalculateSafeInterval(timerange backend.TimeRange, safeRes int64) Interval { @@ -80,179 +71,6 @@ func (ic *intervalCalculator) CalculateSafeInterval(timerange backend.TimeRange, from := timerange.From.UnixNano() safeInterval := time.Duration((to - from) / safeRes) - rounded := roundInterval(safeInterval) - return Interval{Text: 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 - interval := queryInterval - if interval == "0s" { - interval = "" - } - if interval == "" { - if queryIntervalMS != 0 { - return time.Duration(queryIntervalMS) * time.Millisecond, nil - } - } - if interval == "" && dsInterval != "" { - interval = dsInterval - } - if interval == "" { - return defaultInterval, nil - } - - parsedInterval, err := ParseIntervalStringToTimeDuration(interval) - if err != nil { - return time.Duration(0), err - } - - return parsedInterval, nil -} - -func ParseIntervalStringToTimeDuration(interval string) (time.Duration, error) { - formattedInterval := strings.Replace(strings.Replace(interval, "<", "", 1), ">", "", 1) - isPureNum, err := regexp.MatchString(`^\d+$`, formattedInterval) - if err != nil { - return time.Duration(0), err - } - if isPureNum { - formattedInterval += "s" - } - parsedInterval, err := gtime.ParseDuration(formattedInterval) - 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.01s - case interval <= 10*time.Millisecond: - return time.Millisecond * 1 // 0.001s - // 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 - // 17.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 - } + rounded := gtime.RoundInterval(safeInterval) + return Interval{Text: gtime.FormatInterval(rounded), Value: rounded} } diff --git a/pkg/tsdb/cloud-monitoring/utils.go b/pkg/tsdb/cloud-monitoring/utils.go index aa03c7fc226..3b719ff2c7e 100644 --- a/pkg/tsdb/cloud-monitoring/utils.go +++ b/pkg/tsdb/cloud-monitoring/utils.go @@ -14,17 +14,17 @@ import ( "time" "github.com/grafana/grafana-plugin-sdk-go/backend" + "github.com/grafana/grafana-plugin-sdk-go/backend/gtime" "github.com/grafana/grafana-plugin-sdk-go/backend/log" "github.com/grafana/grafana-plugin-sdk-go/backend/tracing" "github.com/grafana/grafana-plugin-sdk-go/data" - gcmTime "github.com/grafana/grafana/pkg/tsdb/cloud-monitoring/time" "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/trace" ) func addInterval(period string, field *data.Field) error { period = strings.TrimPrefix(period, "+") - p, err := gcmTime.ParseIntervalStringToTimeDuration(period) + p, err := gtime.ParseIntervalStringToTimeDuration(period) if err != nil { return err } diff --git a/pkg/tsdb/influxdb/flux/macros.go b/pkg/tsdb/influxdb/flux/macros.go index ed3a34d2d20..4dce9d76822 100644 --- a/pkg/tsdb/influxdb/flux/macros.go +++ b/pkg/tsdb/influxdb/flux/macros.go @@ -6,7 +6,7 @@ import ( "strings" "time" - "github.com/grafana/grafana/pkg/tsdb/intervalv2" + "github.com/grafana/grafana-plugin-sdk-go/backend/gtime" ) // $__interval_ms is the exact value in milliseconds @@ -15,7 +15,7 @@ import ( func interpolateInterval(flux string, interval time.Duration) string { intervalMs := int64(interval / time.Millisecond) - intervalText := intervalv2.FormatDuration(interval) + intervalText := gtime.FormatInterval(interval) flux = strings.ReplaceAll(flux, "$__interval_ms", strconv.FormatInt(intervalMs, 10)) flux = strings.ReplaceAll(flux, "$__interval", intervalText) diff --git a/pkg/tsdb/influxdb/models/query.go b/pkg/tsdb/influxdb/models/query.go index 64f3331a37f..fc6024fe074 100644 --- a/pkg/tsdb/influxdb/models/query.go +++ b/pkg/tsdb/influxdb/models/query.go @@ -8,8 +8,7 @@ import ( "time" "github.com/grafana/grafana-plugin-sdk-go/backend" - - "github.com/grafana/grafana/pkg/tsdb/intervalv2" + "github.com/grafana/grafana-plugin-sdk-go/backend/gtime" ) var ( @@ -36,7 +35,7 @@ func (query *Query) Build(queryContext *backend.QueryDataRequest) (string, error res += query.renderTz() } - intervalText := intervalv2.FormatDuration(query.Interval) + intervalText := gtime.FormatInterval(query.Interval) intervalMs := int64(query.Interval / time.Millisecond) res = strings.ReplaceAll(res, "$timeFilter", query.renderTimeFilter(queryContext)) diff --git a/pkg/tsdb/intervalv2/intervalv2.go b/pkg/tsdb/intervalv2/intervalv2.go deleted file mode 100644 index 9aadc011802..00000000000 --- a/pkg/tsdb/intervalv2/intervalv2.go +++ /dev/null @@ -1,228 +0,0 @@ -package intervalv2 - -import ( - "regexp" - "strings" - "time" - - "github.com/grafana/grafana-plugin-sdk-go/backend" - "github.com/grafana/grafana-plugin-sdk-go/backend/gtime" -) - -var ( - DefaultRes int64 = 1500 - defaultMinInterval = time.Millisecond * 1 -) - -type Interval struct { - Text string - Value time.Duration -} - -type intervalCalculator struct { - minInterval time.Duration -} - -type Calculator interface { - Calculate(timerange backend.TimeRange, minInterval time.Duration, maxDataPoints int64) Interval - 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, minInterval time.Duration, maxDataPoints int64) Interval { - to := timerange.To.UnixNano() - from := timerange.From.UnixNano() - resolution := maxDataPoints - if resolution == 0 { - resolution = DefaultRes - } - - calculatedInterval := time.Duration((to - from) / resolution) - - if calculatedInterval < minInterval { - return Interval{Text: FormatDuration(minInterval), Value: minInterval} - } - - rounded := roundInterval(calculatedInterval) - - return Interval{Text: FormatDuration(rounded), Value: rounded} -} - -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: 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 - interval := queryInterval - if interval == "0s" { - interval = "" - } - if interval == "" { - if queryIntervalMS != 0 { - return time.Duration(queryIntervalMS) * time.Millisecond, nil - } - } - if interval == "" && dsInterval != "" { - interval = dsInterval - } - if interval == "" { - return defaultInterval, nil - } - - parsedInterval, err := ParseIntervalStringToTimeDuration(interval) - if err != nil { - return time.Duration(0), err - } - - return parsedInterval, nil -} - -func ParseIntervalStringToTimeDuration(interval string) (time.Duration, error) { - formattedInterval := strings.Replace(strings.Replace(interval, "<", "", 1), ">", "", 1) - isPureNum, err := regexp.MatchString(`^\d+$`, formattedInterval) - if err != nil { - return time.Duration(0), err - } - if isPureNum { - formattedInterval += "s" - } - parsedInterval, err := gtime.ParseDuration(formattedInterval) - 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 { - return gtime.FormatInterval(inter) -} - -//nolint:gocyclo -func roundInterval(interval time.Duration) time.Duration { - switch { - // 0.01s - case interval <= 10*time.Millisecond: - return time.Millisecond * 1 // 0.001s - // 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 - // 17.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 - } -} diff --git a/pkg/tsdb/legacydata/interval/interval.go b/pkg/tsdb/legacydata/interval/interval.go index 2ab255a3c5f..7f9769f4717 100644 --- a/pkg/tsdb/legacydata/interval/interval.go +++ b/pkg/tsdb/legacydata/interval/interval.go @@ -1,7 +1,6 @@ package interval import ( - "fmt" "regexp" "strings" "time" @@ -16,8 +15,6 @@ import ( var ( DefaultRes int64 = 1500 defaultMinInterval = time.Millisecond * 1 - year = time.Hour * 24 * 365 - day = time.Hour * 24 ) type Interval struct { @@ -62,11 +59,11 @@ func (ic *intervalCalculator) Calculate(timerange legacydata.DataTimeRange, minI calculatedInterval := time.Duration((to - from) / DefaultRes) if calculatedInterval < minInterval { - return Interval{Text: FormatDuration(minInterval), Value: minInterval} + return Interval{Text: gtime.FormatInterval(minInterval), Value: minInterval} } rounded := roundInterval(calculatedInterval) - return Interval{Text: FormatDuration(rounded), Value: rounded} + return Interval{Text: gtime.FormatInterval(rounded), Value: rounded} } func (ic *intervalCalculator) CalculateSafeInterval(timerange legacydata.DataTimeRange, safeRes int64) Interval { @@ -75,7 +72,7 @@ func (ic *intervalCalculator) CalculateSafeInterval(timerange legacydata.DataTim safeInterval := time.Duration((to - from) / safeRes) rounded := roundInterval(safeInterval) - return Interval{Text: FormatDuration(rounded), Value: rounded} + return Interval{Text: gtime.FormatInterval(rounded), Value: rounded} } func GetIntervalFrom(dsInfo *datasources.DataSource, queryModel *simplejson.Json, defaultInterval time.Duration) (time.Duration, error) { @@ -117,126 +114,11 @@ func GetIntervalFrom(dsInfo *datasources.DataSource, queryModel *simplejson.Json 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: + if 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 } + return gtime.RoundInterval(interval) } diff --git a/pkg/tsdb/legacydata/interval/interval_test.go b/pkg/tsdb/legacydata/interval/interval_test.go index fdd5deb2519..b76c36a6774 100644 --- a/pkg/tsdb/legacydata/interval/interval_test.go +++ b/pkg/tsdb/legacydata/interval/interval_test.go @@ -74,26 +74,6 @@ func TestRoundInterval(t *testing.T) { } } -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) { dsJSON, err := simplejson.NewJson([]byte(`{"timeInterval": "60s"}`)) require.NoError(t, err) diff --git a/pkg/tsdb/loki/parse_query.go b/pkg/tsdb/loki/parse_query.go index 5e1c8e44d0b..60ace2430c8 100644 --- a/pkg/tsdb/loki/parse_query.go +++ b/pkg/tsdb/loki/parse_query.go @@ -8,8 +8,8 @@ import ( "time" "github.com/grafana/grafana-plugin-sdk-go/backend" + "github.com/grafana/grafana-plugin-sdk-go/backend/gtime" - "github.com/grafana/grafana/pkg/tsdb/intervalv2" "github.com/grafana/grafana/pkg/tsdb/loki/kinds/dataquery" ) @@ -32,8 +32,8 @@ const ( ) func interpolateVariables(expr string, interval time.Duration, timeRange time.Duration, queryType dataquery.LokiQueryType, step time.Duration) string { - intervalText := intervalv2.FormatDuration(interval) - stepText := intervalv2.FormatDuration(step) + intervalText := gtime.FormatInterval(interval) + stepText := gtime.FormatInterval(step) intervalMsText := strconv.FormatInt(int64(interval/time.Millisecond), 10) rangeMs := timeRange.Milliseconds() diff --git a/pkg/tsdb/loki/step.go b/pkg/tsdb/loki/step.go index df66ebef466..4023abfe1db 100644 --- a/pkg/tsdb/loki/step.go +++ b/pkg/tsdb/loki/step.go @@ -4,7 +4,7 @@ import ( "math" "time" - "github.com/grafana/grafana/pkg/tsdb/intervalv2" + "github.com/grafana/grafana-plugin-sdk-go/backend/gtime" ) // round the duration to the nearest millisecond larger-or-equal-to the duration @@ -31,7 +31,7 @@ func calculateStep(interval time.Duration, timeRange time.Duration, resolution i return ceilMs(chosenStep), nil } - step, err := intervalv2.ParseIntervalStringToTimeDuration(*queryStep) + step, err := gtime.ParseIntervalStringToTimeDuration(*queryStep) if err != nil { return step, err }