* set minStep value as final step value when set explicitly. * enhance it with tests * improve function readability * a bit more improvement for readability
603 lines
15 KiB
Go
603 lines
15 KiB
Go
package models
|
|
|
|
import (
|
|
"context"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/grafana/grafana-plugin-sdk-go/backend"
|
|
"github.com/stretchr/testify/require"
|
|
"go.opentelemetry.io/otel"
|
|
|
|
"github.com/grafana/grafana/pkg/promlib/intervalv2"
|
|
)
|
|
|
|
var (
|
|
testNow = time.Now()
|
|
testIntervalCalculator = intervalv2.NewCalculator()
|
|
testTracer = otel.Tracer("test/interval")
|
|
)
|
|
|
|
func TestCalculatePrometheusInterval(t *testing.T) {
|
|
_, span := testTracer.Start(context.Background(), "test")
|
|
defer span.End()
|
|
|
|
tests := []struct {
|
|
name string
|
|
queryInterval string
|
|
dsScrapeInterval string
|
|
intervalMs int64
|
|
intervalFactor int64
|
|
query backend.DataQuery
|
|
want time.Duration
|
|
wantErr bool
|
|
}{
|
|
{
|
|
name: "min step 2m with 300000 intervalMs",
|
|
queryInterval: "2m",
|
|
dsScrapeInterval: "",
|
|
intervalMs: 300000,
|
|
intervalFactor: 1,
|
|
query: backend.DataQuery{
|
|
TimeRange: backend.TimeRange{
|
|
From: testNow,
|
|
To: testNow.Add(48 * time.Hour),
|
|
},
|
|
Interval: 5 * time.Minute,
|
|
MaxDataPoints: 761,
|
|
},
|
|
want: 2 * time.Minute,
|
|
wantErr: false,
|
|
},
|
|
{
|
|
name: "min step 2m with 900000 intervalMs",
|
|
queryInterval: "2m",
|
|
dsScrapeInterval: "",
|
|
intervalMs: 900000,
|
|
intervalFactor: 1,
|
|
query: backend.DataQuery{
|
|
TimeRange: backend.TimeRange{
|
|
From: testNow,
|
|
To: testNow.Add(48 * time.Hour),
|
|
},
|
|
Interval: 15 * time.Minute,
|
|
MaxDataPoints: 175,
|
|
},
|
|
want: 2 * time.Minute,
|
|
wantErr: false,
|
|
},
|
|
{
|
|
name: "with step parameter",
|
|
queryInterval: "",
|
|
dsScrapeInterval: "15s",
|
|
intervalMs: 0,
|
|
intervalFactor: 1,
|
|
query: backend.DataQuery{
|
|
TimeRange: backend.TimeRange{
|
|
From: testNow,
|
|
To: testNow.Add(12 * time.Hour),
|
|
},
|
|
Interval: 1 * time.Minute,
|
|
},
|
|
want: 30 * time.Second,
|
|
wantErr: false,
|
|
},
|
|
{
|
|
name: "without step parameter",
|
|
queryInterval: "",
|
|
dsScrapeInterval: "15s",
|
|
intervalMs: 0,
|
|
intervalFactor: 1,
|
|
query: backend.DataQuery{
|
|
TimeRange: backend.TimeRange{
|
|
From: testNow,
|
|
To: testNow.Add(1 * time.Hour),
|
|
},
|
|
Interval: 1 * time.Minute,
|
|
},
|
|
want: 15 * time.Second,
|
|
wantErr: false,
|
|
},
|
|
{
|
|
name: "with high intervalFactor",
|
|
queryInterval: "",
|
|
dsScrapeInterval: "15s",
|
|
intervalMs: 0,
|
|
intervalFactor: 10,
|
|
query: backend.DataQuery{
|
|
TimeRange: backend.TimeRange{
|
|
From: testNow,
|
|
To: testNow.Add(48 * time.Hour),
|
|
},
|
|
Interval: 1 * time.Minute,
|
|
},
|
|
want: 20 * time.Minute,
|
|
wantErr: false,
|
|
},
|
|
{
|
|
name: "with low intervalFactor",
|
|
queryInterval: "",
|
|
dsScrapeInterval: "15s",
|
|
intervalMs: 0,
|
|
intervalFactor: 1,
|
|
query: backend.DataQuery{
|
|
TimeRange: backend.TimeRange{
|
|
From: testNow,
|
|
To: testNow.Add(48 * time.Hour),
|
|
},
|
|
Interval: 1 * time.Minute,
|
|
},
|
|
want: 2 * time.Minute,
|
|
wantErr: false,
|
|
},
|
|
{
|
|
name: "with specified scrape-interval in data source",
|
|
queryInterval: "",
|
|
dsScrapeInterval: "240s",
|
|
intervalMs: 0,
|
|
intervalFactor: 1,
|
|
query: backend.DataQuery{
|
|
TimeRange: backend.TimeRange{
|
|
From: testNow,
|
|
To: testNow.Add(48 * time.Hour),
|
|
},
|
|
Interval: 1 * time.Minute,
|
|
},
|
|
want: 4 * time.Minute,
|
|
wantErr: false,
|
|
},
|
|
{
|
|
name: "with zero intervalFactor defaults to 1",
|
|
queryInterval: "",
|
|
dsScrapeInterval: "15s",
|
|
intervalMs: 0,
|
|
intervalFactor: 0,
|
|
query: backend.DataQuery{
|
|
TimeRange: backend.TimeRange{
|
|
From: testNow,
|
|
To: testNow.Add(1 * time.Hour),
|
|
},
|
|
Interval: 1 * time.Minute,
|
|
},
|
|
want: 15 * time.Second,
|
|
wantErr: false,
|
|
},
|
|
{
|
|
name: "with $__interval variable",
|
|
queryInterval: "$__interval",
|
|
dsScrapeInterval: "15s",
|
|
intervalMs: 60000,
|
|
intervalFactor: 1,
|
|
query: backend.DataQuery{
|
|
TimeRange: backend.TimeRange{
|
|
From: testNow,
|
|
To: testNow.Add(48 * time.Hour),
|
|
},
|
|
Interval: 1 * time.Minute,
|
|
},
|
|
want: 120 * time.Second,
|
|
wantErr: false,
|
|
},
|
|
{
|
|
name: "with ${__interval} variable",
|
|
queryInterval: "${__interval}",
|
|
dsScrapeInterval: "15s",
|
|
intervalMs: 60000,
|
|
intervalFactor: 1,
|
|
query: backend.DataQuery{
|
|
TimeRange: backend.TimeRange{
|
|
From: testNow,
|
|
To: testNow.Add(48 * time.Hour),
|
|
},
|
|
Interval: 1 * time.Minute,
|
|
},
|
|
want: 120 * time.Second,
|
|
wantErr: false,
|
|
},
|
|
{
|
|
name: "with ${__interval} variable and explicit interval",
|
|
queryInterval: "1m",
|
|
dsScrapeInterval: "15s",
|
|
intervalMs: 60000,
|
|
intervalFactor: 1,
|
|
query: backend.DataQuery{
|
|
TimeRange: backend.TimeRange{
|
|
From: testNow,
|
|
To: testNow.Add(48 * time.Hour),
|
|
},
|
|
Interval: 1 * time.Minute,
|
|
},
|
|
want: 1 * time.Minute,
|
|
wantErr: false,
|
|
},
|
|
{
|
|
name: "with $__rate_interval variable",
|
|
queryInterval: "$__rate_interval",
|
|
dsScrapeInterval: "30s",
|
|
intervalMs: 100000,
|
|
intervalFactor: 1,
|
|
query: backend.DataQuery{
|
|
TimeRange: backend.TimeRange{
|
|
From: testNow,
|
|
To: testNow.Add(2 * 24 * time.Hour),
|
|
},
|
|
Interval: 100 * time.Second,
|
|
MaxDataPoints: 12384,
|
|
},
|
|
want: 130 * time.Second,
|
|
wantErr: false,
|
|
},
|
|
{
|
|
name: "with ${__rate_interval} variable",
|
|
queryInterval: "${__rate_interval}",
|
|
dsScrapeInterval: "30s",
|
|
intervalMs: 100000,
|
|
intervalFactor: 1,
|
|
query: backend.DataQuery{
|
|
TimeRange: backend.TimeRange{
|
|
From: testNow,
|
|
To: testNow.Add(2 * 24 * time.Hour),
|
|
},
|
|
Interval: 100 * time.Second,
|
|
MaxDataPoints: 12384,
|
|
},
|
|
want: 130 * time.Second,
|
|
wantErr: false,
|
|
},
|
|
{
|
|
name: "intervalMs 100s, minStep override 150s and scrape interval 30s",
|
|
queryInterval: "150s",
|
|
dsScrapeInterval: "30s",
|
|
intervalMs: 100000,
|
|
intervalFactor: 1,
|
|
query: backend.DataQuery{
|
|
TimeRange: backend.TimeRange{
|
|
From: testNow,
|
|
To: testNow.Add(2 * 24 * time.Hour),
|
|
},
|
|
Interval: 100 * time.Second,
|
|
MaxDataPoints: 12384,
|
|
},
|
|
want: 150 * time.Second,
|
|
wantErr: false,
|
|
},
|
|
{
|
|
name: "intervalMs 120s, minStep override 150s and ds scrape interval 30s",
|
|
queryInterval: "150s",
|
|
dsScrapeInterval: "30s",
|
|
intervalMs: 120000,
|
|
intervalFactor: 1,
|
|
query: backend.DataQuery{
|
|
TimeRange: backend.TimeRange{
|
|
From: testNow,
|
|
To: testNow.Add(2 * 24 * time.Hour),
|
|
},
|
|
Interval: 120 * time.Second,
|
|
MaxDataPoints: 12384,
|
|
},
|
|
want: 150 * time.Second,
|
|
wantErr: false,
|
|
},
|
|
{
|
|
name: "intervalMs 120s, minStep auto (interval not overridden) and ds scrape interval 30s",
|
|
queryInterval: "120s",
|
|
dsScrapeInterval: "30s",
|
|
intervalMs: 120000,
|
|
intervalFactor: 1,
|
|
query: backend.DataQuery{
|
|
TimeRange: backend.TimeRange{
|
|
From: testNow,
|
|
To: testNow.Add(2 * 24 * time.Hour),
|
|
},
|
|
Interval: 120 * time.Second,
|
|
MaxDataPoints: 12384,
|
|
},
|
|
want: 120 * time.Second,
|
|
wantErr: false,
|
|
},
|
|
{
|
|
name: "interval and minStep are automatically calculated and ds scrape interval 30s and time range 1 hour",
|
|
queryInterval: "30s",
|
|
dsScrapeInterval: "30s",
|
|
intervalMs: 30000,
|
|
intervalFactor: 1,
|
|
query: backend.DataQuery{
|
|
TimeRange: backend.TimeRange{
|
|
From: testNow,
|
|
To: testNow.Add(1 * time.Hour),
|
|
},
|
|
Interval: 30 * time.Second,
|
|
MaxDataPoints: 12384,
|
|
},
|
|
want: 30 * time.Second,
|
|
wantErr: false,
|
|
},
|
|
{
|
|
name: "minStep is $__rate_interval and ds scrape interval 30s and time range 1 hour",
|
|
queryInterval: "$__rate_interval",
|
|
dsScrapeInterval: "30s",
|
|
intervalMs: 30000,
|
|
intervalFactor: 1,
|
|
query: backend.DataQuery{
|
|
TimeRange: backend.TimeRange{
|
|
From: testNow,
|
|
To: testNow.Add(1 * time.Hour),
|
|
},
|
|
Interval: 30 * time.Second,
|
|
MaxDataPoints: 12384,
|
|
},
|
|
want: 2 * time.Minute,
|
|
wantErr: false,
|
|
},
|
|
{
|
|
name: "minStep is $__rate_interval and ds scrape interval 30s and time range 2 days",
|
|
queryInterval: "$__rate_interval",
|
|
dsScrapeInterval: "30s",
|
|
intervalMs: 120000,
|
|
intervalFactor: 1,
|
|
query: backend.DataQuery{
|
|
TimeRange: backend.TimeRange{
|
|
From: testNow,
|
|
To: testNow.Add(2 * 24 * time.Hour),
|
|
},
|
|
Interval: 120 * time.Second,
|
|
MaxDataPoints: 12384,
|
|
},
|
|
want: 150 * time.Second,
|
|
wantErr: false,
|
|
},
|
|
{
|
|
name: "minStep is $__interval and ds scrape interval 15s and time range 2 days",
|
|
queryInterval: "$__interval",
|
|
dsScrapeInterval: "15s",
|
|
intervalMs: 120000,
|
|
intervalFactor: 1,
|
|
query: backend.DataQuery{
|
|
TimeRange: backend.TimeRange{
|
|
From: testNow,
|
|
To: testNow.Add(2 * 24 * time.Hour),
|
|
},
|
|
Interval: 120 * time.Second,
|
|
MaxDataPoints: 12384,
|
|
},
|
|
want: 120 * time.Second,
|
|
wantErr: false,
|
|
},
|
|
{
|
|
name: "with empty dsScrapeInterval defaults to 15s",
|
|
queryInterval: "",
|
|
dsScrapeInterval: "",
|
|
intervalMs: 0,
|
|
intervalFactor: 1,
|
|
query: backend.DataQuery{
|
|
TimeRange: backend.TimeRange{
|
|
From: testNow,
|
|
To: testNow.Add(1 * time.Hour),
|
|
},
|
|
Interval: 1 * time.Minute,
|
|
},
|
|
want: 15 * time.Second,
|
|
wantErr: false,
|
|
},
|
|
{
|
|
name: "with very short time range",
|
|
queryInterval: "",
|
|
dsScrapeInterval: "15s",
|
|
intervalMs: 0,
|
|
intervalFactor: 1,
|
|
query: backend.DataQuery{
|
|
TimeRange: backend.TimeRange{
|
|
From: testNow,
|
|
To: testNow.Add(1 * time.Minute),
|
|
},
|
|
Interval: 1 * time.Minute,
|
|
},
|
|
want: 15 * time.Second,
|
|
wantErr: false,
|
|
},
|
|
{
|
|
name: "with very long time range",
|
|
queryInterval: "",
|
|
dsScrapeInterval: "15s",
|
|
intervalMs: 0,
|
|
intervalFactor: 1,
|
|
query: backend.DataQuery{
|
|
TimeRange: backend.TimeRange{
|
|
From: testNow,
|
|
To: testNow.Add(30 * 24 * time.Hour),
|
|
},
|
|
Interval: 1 * time.Minute,
|
|
},
|
|
want: 30 * time.Minute,
|
|
wantErr: false,
|
|
},
|
|
{
|
|
name: "with manual interval override",
|
|
queryInterval: "5m",
|
|
dsScrapeInterval: "15s",
|
|
intervalMs: 0,
|
|
intervalFactor: 1,
|
|
query: backend.DataQuery{
|
|
TimeRange: backend.TimeRange{
|
|
From: testNow,
|
|
To: testNow.Add(48 * time.Hour),
|
|
},
|
|
Interval: 1 * time.Minute,
|
|
},
|
|
want: 5 * time.Minute,
|
|
wantErr: false,
|
|
},
|
|
{
|
|
name: "minStep is auto and ds scrape interval 30s and time range 1 hour",
|
|
queryInterval: "",
|
|
dsScrapeInterval: "30s",
|
|
intervalMs: 30000,
|
|
intervalFactor: 1,
|
|
query: backend.DataQuery{
|
|
TimeRange: backend.TimeRange{
|
|
From: testNow,
|
|
To: testNow.Add(1 * time.Hour),
|
|
},
|
|
Interval: 30 * time.Second,
|
|
MaxDataPoints: 1613,
|
|
},
|
|
want: 30 * time.Second,
|
|
wantErr: false,
|
|
},
|
|
{
|
|
name: "minStep is auto and ds scrape interval 15s and time range 5 minutes",
|
|
queryInterval: "",
|
|
dsScrapeInterval: "15s",
|
|
intervalMs: 15000,
|
|
intervalFactor: 1,
|
|
query: backend.DataQuery{
|
|
TimeRange: backend.TimeRange{
|
|
From: testNow,
|
|
To: testNow.Add(5 * time.Minute),
|
|
},
|
|
Interval: 15 * time.Second,
|
|
MaxDataPoints: 1055,
|
|
},
|
|
want: 15 * time.Second,
|
|
wantErr: false,
|
|
},
|
|
// Additional test cases for better coverage
|
|
{
|
|
name: "with $__interval_ms variable",
|
|
queryInterval: "$__interval_ms",
|
|
dsScrapeInterval: "15s",
|
|
intervalMs: 60000,
|
|
intervalFactor: 1,
|
|
query: backend.DataQuery{
|
|
TimeRange: backend.TimeRange{
|
|
From: testNow,
|
|
To: testNow.Add(48 * time.Hour),
|
|
},
|
|
Interval: 1 * time.Minute,
|
|
},
|
|
want: 120 * time.Second,
|
|
wantErr: false,
|
|
},
|
|
{
|
|
name: "with ${__interval_ms} variable",
|
|
queryInterval: "${__interval_ms}",
|
|
dsScrapeInterval: "15s",
|
|
intervalMs: 60000,
|
|
intervalFactor: 1,
|
|
query: backend.DataQuery{
|
|
TimeRange: backend.TimeRange{
|
|
From: testNow,
|
|
To: testNow.Add(48 * time.Hour),
|
|
},
|
|
Interval: 1 * time.Minute,
|
|
},
|
|
want: 120 * time.Second,
|
|
wantErr: false,
|
|
},
|
|
{
|
|
name: "with MaxDataPoints zero",
|
|
queryInterval: "",
|
|
dsScrapeInterval: "15s",
|
|
intervalMs: 0,
|
|
intervalFactor: 1,
|
|
query: backend.DataQuery{
|
|
TimeRange: backend.TimeRange{
|
|
From: testNow,
|
|
To: testNow.Add(1 * time.Hour),
|
|
},
|
|
Interval: 1 * time.Minute,
|
|
MaxDataPoints: 0,
|
|
},
|
|
want: 15 * time.Second,
|
|
wantErr: false,
|
|
},
|
|
{
|
|
name: "with negative intervalFactor",
|
|
queryInterval: "",
|
|
dsScrapeInterval: "15s",
|
|
intervalMs: 0,
|
|
intervalFactor: -5,
|
|
query: backend.DataQuery{
|
|
TimeRange: backend.TimeRange{
|
|
From: testNow,
|
|
To: testNow.Add(48 * time.Hour),
|
|
},
|
|
Interval: 1 * time.Minute,
|
|
},
|
|
want: -10 * time.Minute,
|
|
wantErr: false,
|
|
},
|
|
{
|
|
name: "with invalid interval string that fails parsing",
|
|
queryInterval: "invalid-interval",
|
|
dsScrapeInterval: "15s",
|
|
intervalMs: 0,
|
|
intervalFactor: 1,
|
|
query: backend.DataQuery{
|
|
TimeRange: backend.TimeRange{
|
|
From: testNow,
|
|
To: testNow.Add(48 * time.Hour),
|
|
},
|
|
Interval: 1 * time.Minute,
|
|
},
|
|
want: time.Duration(0),
|
|
wantErr: true,
|
|
},
|
|
{
|
|
name: "with very small MaxDataPoints",
|
|
queryInterval: "",
|
|
dsScrapeInterval: "15s",
|
|
intervalMs: 0,
|
|
intervalFactor: 1,
|
|
query: backend.DataQuery{
|
|
TimeRange: backend.TimeRange{
|
|
From: testNow,
|
|
To: testNow.Add(1 * time.Hour),
|
|
},
|
|
Interval: 1 * time.Minute,
|
|
MaxDataPoints: 10,
|
|
},
|
|
want: 5 * time.Minute,
|
|
wantErr: false,
|
|
},
|
|
{
|
|
name: "when safeInterval is larger than calculatedInterval",
|
|
queryInterval: "",
|
|
dsScrapeInterval: "15s",
|
|
intervalMs: 0,
|
|
intervalFactor: 1,
|
|
query: backend.DataQuery{
|
|
TimeRange: backend.TimeRange{
|
|
From: testNow,
|
|
To: testNow.Add(1 * time.Hour),
|
|
},
|
|
Interval: 1 * time.Minute,
|
|
MaxDataPoints: 10000,
|
|
},
|
|
want: 15 * time.Second,
|
|
wantErr: false,
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
got, err := calculatePrometheusInterval(
|
|
tt.queryInterval,
|
|
tt.dsScrapeInterval,
|
|
tt.intervalMs,
|
|
tt.intervalFactor,
|
|
tt.query,
|
|
testIntervalCalculator,
|
|
)
|
|
|
|
if tt.wantErr {
|
|
require.Error(t, err)
|
|
return
|
|
}
|
|
|
|
require.NoError(t, err)
|
|
require.Equal(t, tt.want, got)
|
|
})
|
|
}
|
|
}
|