diff --git a/pkg/tsdb/prometheus/time_series_query.go b/pkg/tsdb/prometheus/time_series_query.go index fbb2497cc8e..2b95756db07 100644 --- a/pkg/tsdb/prometheus/time_series_query.go +++ b/pkg/tsdb/prometheus/time_series_query.go @@ -177,17 +177,9 @@ func (s *Service) parseTimeSeriesQuery(queryContext *backend.QueryDataRequest, d interval = time.Duration(int64(adjustedInterval) * intervalFactor) } - intervalMs := int64(interval / time.Millisecond) - rangeS := query.TimeRange.To.Unix() - query.TimeRange.From.Unix() - // Interpolate variables in expr - expr := model.Expr - expr = strings.ReplaceAll(expr, varIntervalMs, strconv.FormatInt(intervalMs, 10)) - expr = strings.ReplaceAll(expr, varInterval, intervalv2.FormatDuration(interval)) - expr = strings.ReplaceAll(expr, varRangeMs, strconv.FormatInt(rangeS*1000, 10)) - expr = strings.ReplaceAll(expr, varRangeS, strconv.FormatInt(rangeS, 10)) - expr = strings.ReplaceAll(expr, varRange, strconv.FormatInt(rangeS, 10)+"s") - expr = strings.ReplaceAll(expr, varRateInterval, intervalv2.FormatDuration(calculateRateInterval(interval, dsInfo.TimeInterval, s.intervalCalculator))) + timeRange := query.TimeRange.To.Sub(query.TimeRange.From) + expr := interpolateVariables(model.Expr, interval, timeRange, s.intervalCalculator, dsInfo.TimeInterval) rangeQuery := model.RangeQuery if !model.InstantQuery && !model.RangeQuery { @@ -262,6 +254,19 @@ func calculateRateInterval(interval time.Duration, scrapeInterval string, interv return rateInterval } +func interpolateVariables(expr string, interval time.Duration, timeRange time.Duration, intervalCalculator intervalv2.Calculator, timeInterval string) string { + rangeMs := timeRange.Milliseconds() + rangeSRounded := int64(math.Round(float64(rangeMs) / 1000.0)) + + expr = strings.ReplaceAll(expr, varIntervalMs, strconv.FormatInt(int64(interval/time.Millisecond), 10)) + expr = strings.ReplaceAll(expr, varInterval, intervalv2.FormatDuration(interval)) + expr = strings.ReplaceAll(expr, varRangeMs, strconv.FormatInt(rangeMs, 10)) + expr = strings.ReplaceAll(expr, varRangeS, strconv.FormatInt(rangeSRounded, 10)) + expr = strings.ReplaceAll(expr, varRange, strconv.FormatInt(rangeSRounded, 10)+"s") + expr = strings.ReplaceAll(expr, varRateInterval, intervalv2.FormatDuration(calculateRateInterval(interval, timeInterval, intervalCalculator))) + return expr +} + func matrixToDataFrames(matrix model.Matrix, query *PrometheusQuery, frames data.Frames) data.Frames { for _, v := range matrix { tags := make(map[string]string, len(v.Metric)) diff --git a/pkg/tsdb/prometheus/time_series_query_test.go b/pkg/tsdb/prometheus/time_series_query_test.go index 30360f7ea17..10992a03841 100644 --- a/pkg/tsdb/prometheus/time_series_query_test.go +++ b/pkg/tsdb/prometheus/time_series_query_test.go @@ -281,6 +281,44 @@ func TestPrometheus_timeSeriesQuery_parseTimeSeriesQuery(t *testing.T) { require.Equal(t, "rate(ALERTS{job=\"test\" [172800]})", models[0].Expr) }) + t.Run("parsing query model with $__range_s variable below 0.5s", func(t *testing.T) { + timeRange := backend.TimeRange{ + From: now, + To: now.Add(40 * time.Millisecond), + } + + query := queryContext(`{ + "expr": "rate(ALERTS{job=\"test\" [$__range_s]})", + "format": "time_series", + "intervalFactor": 1, + "refId": "A" + }`, timeRange) + + dsInfo := &DatasourceInfo{} + models, err := service.parseTimeSeriesQuery(query, dsInfo) + require.NoError(t, err) + require.Equal(t, "rate(ALERTS{job=\"test\" [0]})", models[0].Expr) + }) + + t.Run("parsing query model with $__range_s variable between 1-0.5s", func(t *testing.T) { + timeRange := backend.TimeRange{ + From: now, + To: now.Add(800 * time.Millisecond), + } + + query := queryContext(`{ + "expr": "rate(ALERTS{job=\"test\" [$__range_s]})", + "format": "time_series", + "intervalFactor": 1, + "refId": "A" + }`, timeRange) + + dsInfo := &DatasourceInfo{} + models, err := service.parseTimeSeriesQuery(query, dsInfo) + require.NoError(t, err) + require.Equal(t, "rate(ALERTS{job=\"test\" [1]})", models[0].Expr) + }) + t.Run("parsing query model with $__range_ms variable", func(t *testing.T) { timeRange := backend.TimeRange{ From: now, @@ -300,6 +338,25 @@ func TestPrometheus_timeSeriesQuery_parseTimeSeriesQuery(t *testing.T) { require.Equal(t, "rate(ALERTS{job=\"test\" [172800000]})", models[0].Expr) }) + t.Run("parsing query model with $__range_ms variable below 1s", func(t *testing.T) { + timeRange := backend.TimeRange{ + From: now, + To: now.Add(20 * time.Millisecond), + } + + query := queryContext(`{ + "expr": "rate(ALERTS{job=\"test\" [$__range_ms]})", + "format": "time_series", + "intervalFactor": 1, + "refId": "A" + }`, timeRange) + + dsInfo := &DatasourceInfo{} + models, err := service.parseTimeSeriesQuery(query, dsInfo) + require.NoError(t, err) + require.Equal(t, "rate(ALERTS{job=\"test\" [20]})", models[0].Expr) + }) + t.Run("parsing query model with $__rate_interval variable", func(t *testing.T) { timeRange := backend.TimeRange{ From: now,