diff --git a/pkg/tsdb/elasticsearch/models.go b/pkg/tsdb/elasticsearch/models.go index 9233ac11de1..bf8ed5c6736 100644 --- a/pkg/tsdb/elasticsearch/models.go +++ b/pkg/tsdb/elasticsearch/models.go @@ -6,14 +6,15 @@ import ( // Query represents the time series query model of the datasource type Query struct { - TimeField string `json:"timeField"` - RawQuery string `json:"query"` - BucketAggs []*BucketAgg `json:"bucketAggs"` - Metrics []*MetricAgg `json:"metrics"` - Alias string `json:"alias"` - Interval string - IntervalMs int64 - RefID string + TimeField string `json:"timeField"` + RawQuery string `json:"query"` + BucketAggs []*BucketAgg `json:"bucketAggs"` + Metrics []*MetricAgg `json:"metrics"` + Alias string `json:"alias"` + Interval string + IntervalMs int64 + RefID string + MaxDataPoints int64 } // BucketAgg represents a bucket aggregation of the time series query model of the datasource diff --git a/pkg/tsdb/elasticsearch/time_series_query.go b/pkg/tsdb/elasticsearch/time_series_query.go index 4b4927d54f3..345a52eed37 100644 --- a/pkg/tsdb/elasticsearch/time_series_query.go +++ b/pkg/tsdb/elasticsearch/time_series_query.go @@ -70,7 +70,7 @@ func (e *timeSeriesQuery) processQuery(q *Query, ms *es.MultiSearchRequestBuilde if err != nil { return err } - interval := e.intervalCalculator.Calculate(e.dataQueries[0].TimeRange, minInterval) + interval := e.intervalCalculator.Calculate(e.dataQueries[0].TimeRange, minInterval, q.MaxDataPoints) b := ms.Search(interval) b.Size(0) @@ -400,13 +400,14 @@ func (p *timeSeriesQueryParser) parse(tsdbQuery []backend.DataQuery) ([]*Query, interval := model.Get("interval").MustString("") queries = append(queries, &Query{ - TimeField: timeField, - RawQuery: rawQuery, - BucketAggs: bucketAggs, - Metrics: metrics, - Alias: alias, - Interval: interval, - RefID: q.RefID, + TimeField: timeField, + RawQuery: rawQuery, + BucketAggs: bucketAggs, + Metrics: metrics, + Alias: alias, + Interval: interval, + RefID: q.RefID, + MaxDataPoints: q.MaxDataPoints, }) } diff --git a/pkg/tsdb/intervalv2/intervalv2.go b/pkg/tsdb/intervalv2/intervalv2.go index f62139bdc29..14014cd1306 100644 --- a/pkg/tsdb/intervalv2/intervalv2.go +++ b/pkg/tsdb/intervalv2/intervalv2.go @@ -27,7 +27,7 @@ type intervalCalculator struct { } type Calculator interface { - Calculate(timerange backend.TimeRange, minInterval time.Duration) Interval + Calculate(timerange backend.TimeRange, minInterval time.Duration, maxDataPoints int64) Interval CalculateSafeInterval(timerange backend.TimeRange, resolution int64) Interval } @@ -53,16 +53,21 @@ func (i *Interval) Milliseconds() int64 { return i.Value.Nanoseconds() / int64(time.Millisecond) } -func (ic *intervalCalculator) Calculate(timerange backend.TimeRange, minInterval time.Duration) Interval { +func (ic *intervalCalculator) Calculate(timerange backend.TimeRange, minInterval time.Duration, maxDataPoints int64) Interval { to := timerange.To.UnixNano() from := timerange.From.UnixNano() - calculatedIntrvl := time.Duration((to - from) / defaultRes) + resolution := maxDataPoints + if resolution == 0 { + resolution = defaultRes + } - if calculatedIntrvl < minInterval { + calculatedInterval := time.Duration((to - from) / resolution) + + if calculatedInterval < minInterval { return Interval{Text: interval.FormatDuration(minInterval), Value: minInterval} } - rounded := roundInterval(calculatedIntrvl) + rounded := roundInterval(calculatedInterval) return Interval{Text: interval.FormatDuration(rounded), Value: rounded} } diff --git a/pkg/tsdb/intervalv2/intervalv2_test.go b/pkg/tsdb/intervalv2/intervalv2_test.go index a91bb28147e..c5c8f5db858 100644 --- a/pkg/tsdb/intervalv2/intervalv2_test.go +++ b/pkg/tsdb/intervalv2/intervalv2_test.go @@ -15,19 +15,24 @@ func TestIntervalCalculator_Calculate(t *testing.T) { timeNow := time.Now() testCases := []struct { - name string - timeRange backend.TimeRange - expected string + name string + timeRange backend.TimeRange + resolution int64 + expected string }{ - {"from 5m to now", backend.TimeRange{From: timeNow, To: timeNow.Add(5 * time.Minute)}, "200ms"}, - {"from 15m to now", backend.TimeRange{From: timeNow, To: timeNow.Add(15 * time.Minute)}, "500ms"}, - {"from 30m to now", backend.TimeRange{From: timeNow, To: timeNow.Add(30 * time.Minute)}, "1s"}, - {"from 1h to now", backend.TimeRange{From: timeNow, To: timeNow.Add(60 * time.Minute)}, "2s"}, + {"from 5m to now and default resolution", backend.TimeRange{From: timeNow, To: timeNow.Add(5 * time.Minute)}, 0, "200ms"}, + {"from 5m to now and 500 resolution", backend.TimeRange{From: timeNow, To: timeNow.Add(5 * time.Minute)}, 500, "500ms"}, + {"from 15m to now and default resolution", backend.TimeRange{From: timeNow, To: timeNow.Add(15 * time.Minute)}, 0, "500ms"}, + {"from 15m to now and 100 resolution", backend.TimeRange{From: timeNow, To: timeNow.Add(15 * time.Minute)}, 100, "10s"}, + {"from 30m to now and default resolution", backend.TimeRange{From: timeNow, To: timeNow.Add(30 * time.Minute)}, 0, "1s"}, + {"from 30m to now and 3000 resolution", backend.TimeRange{From: timeNow, To: timeNow.Add(30 * time.Minute)}, 3000, "500ms"}, + {"from 1h to now and default resolution", backend.TimeRange{From: timeNow, To: timeNow.Add(60 * time.Minute)}, 0, "2s"}, + {"from 1h to now and 1000 resoluion", backend.TimeRange{From: timeNow, To: timeNow.Add(60 * time.Minute)}, 1000, "5s"}, } for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { - interval := calculator.Calculate(tc.timeRange, time.Millisecond*1) + interval := calculator.Calculate(tc.timeRange, time.Millisecond*1, tc.resolution) assert.Equal(t, tc.expected, interval.Text) }) } diff --git a/pkg/tsdb/loki/loki.go b/pkg/tsdb/loki/loki.go index edb717deea1..24e9dd86212 100644 --- a/pkg/tsdb/loki/loki.go +++ b/pkg/tsdb/loki/loki.go @@ -200,7 +200,7 @@ func (s *Service) parseQuery(dsInfo *datasourceInfo, queryContext *backend.Query return nil, fmt.Errorf("failed to parse Interval: %v", err) } - interval := s.intervalCalculator.Calculate(query.TimeRange, dsInterval) + interval := s.intervalCalculator.Calculate(query.TimeRange, dsInterval, query.MaxDataPoints) var resolution int64 = 1 if model.Resolution >= 1 && model.Resolution <= 5 || model.Resolution == 10 { diff --git a/pkg/tsdb/loki/loki_test.go b/pkg/tsdb/loki/loki_test.go index da38e7fad03..d7632a29c11 100644 --- a/pkg/tsdb/loki/loki_test.go +++ b/pkg/tsdb/loki/loki_test.go @@ -178,7 +178,7 @@ type mockCalculator struct { interval intervalv2.Interval } -func (m mockCalculator) Calculate(timerange backend.TimeRange, minInterval time.Duration) intervalv2.Interval { +func (m mockCalculator) Calculate(timerange backend.TimeRange, minInterval time.Duration, maxDataPoints int64) intervalv2.Interval { return m.interval } diff --git a/pkg/tsdb/prometheus/prometheus.go b/pkg/tsdb/prometheus/prometheus.go index a8b00d92399..786c0a81631 100644 --- a/pkg/tsdb/prometheus/prometheus.go +++ b/pkg/tsdb/prometheus/prometheus.go @@ -257,7 +257,7 @@ func (s *Service) parseQuery(queryContext *backend.QueryDataRequest, dsInfo *Dat return nil, err } - calculatedInterval := s.intervalCalculator.Calculate(query.TimeRange, minInterval) + calculatedInterval := s.intervalCalculator.Calculate(query.TimeRange, minInterval, query.MaxDataPoints) safeInterval := s.intervalCalculator.CalculateSafeInterval(query.TimeRange, int64(safeRes)) adjustedInterval := safeInterval.Value diff --git a/pkg/tsdb/sqleng/sql_engine.go b/pkg/tsdb/sqleng/sql_engine.go index c54bf3060f0..5bfb5acbc87 100644 --- a/pkg/tsdb/sqleng/sql_engine.go +++ b/pkg/tsdb/sqleng/sql_engine.go @@ -377,7 +377,7 @@ var Interpolate = func(query backend.DataQuery, timeRange backend.TimeRange, tim if err != nil { return "", err } - interval := sqlIntervalCalculator.Calculate(timeRange, minInterval) + interval := sqlIntervalCalculator.Calculate(timeRange, minInterval, query.MaxDataPoints) sql = strings.ReplaceAll(sql, "$__interval_ms", strconv.FormatInt(interval.Milliseconds(), 10)) sql = strings.ReplaceAll(sql, "$__interval", interval.Text)