From 0b5d7067f9316961809aa8fcf8a3865f53cdfe44 Mon Sep 17 00:00:00 2001 From: Ivana Huckova <30407135+ivanahuckova@users.noreply.github.com> Date: Mon, 29 Nov 2021 12:41:23 +0100 Subject: [PATCH] Prometheus: Fix range variables interpolation for time ranges smaller than 1 second (#42242) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Prometheus: Fix precision for queries with < 1s timeRange * Update pkg/tsdb/prometheus/time_series_query.go Co-authored-by: Gábor Farkas Co-authored-by: Gábor Farkas --- pkg/tsdb/prometheus/time_series_query.go | 25 ++++---- pkg/tsdb/prometheus/time_series_query_test.go | 57 +++++++++++++++++++ 2 files changed, 72 insertions(+), 10 deletions(-) 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,