diff --git a/pkg/tsdb/prometheus/models/query.go b/pkg/tsdb/prometheus/models/query.go index 91c7caf60bc..3f5a282aadb 100644 --- a/pkg/tsdb/prometheus/models/query.go +++ b/pkg/tsdb/prometheus/models/query.go @@ -8,6 +8,7 @@ import ( "time" "github.com/grafana/grafana-plugin-sdk-go/backend" + "github.com/grafana/grafana/pkg/tsdb/intervalv2" "github.com/grafana/grafana/pkg/tsdb/prometheus/kinds/dataquery" ) @@ -76,21 +77,27 @@ type Query struct { UtcOffsetSec int64 } -func Parse(query backend.DataQuery, timeInterval string, intervalCalculator intervalv2.Calculator, fromAlert bool) (*Query, error) { +func Parse(query backend.DataQuery, dsScrapeInterval string, intervalCalculator intervalv2.Calculator, fromAlert bool) (*Query, error) { model := &QueryModel{} if err := json.Unmarshal(query.JSON, model); err != nil { return nil, err } - // Final interval value - interval, err := calculatePrometheusInterval(model.Interval, timeInterval, model.IntervalMs, model.IntervalFactor, query, intervalCalculator) + // Final step value for prometheus + calculatedMinStep, err := calculatePrometheusInterval(model.Interval, dsScrapeInterval, model.IntervalMs, model.IntervalFactor, query, intervalCalculator) if err != nil { return nil, err } // Interpolate variables in expr timeRange := query.TimeRange.To.Sub(query.TimeRange.From) - expr := interpolateVariables(model.Expr, model.Interval, interval, timeRange, intervalCalculator, timeInterval) + expr := interpolateVariables( + model.Expr, + query.Interval, + calculatedMinStep, + model.Interval, + timeRange, + ) var rangeQuery, instantQuery bool if model.Instant == nil { instantQuery = false @@ -118,7 +125,7 @@ func Parse(query backend.DataQuery, timeInterval string, intervalCalculator inte return &Query{ Expr: expr, - Step: interval, + Step: calculatedMinStep, LegendFormat: model.LegendFormat, Start: query.TimeRange.From, End: query.TimeRange.To, @@ -153,7 +160,7 @@ func (query *Query) TimeRange() TimeRange { } func calculatePrometheusInterval( - queryInterval, timeInterval string, + queryInterval, dsScrapeInterval string, intervalMs, intervalFactor int64, query backend.DataQuery, intervalCalculator intervalv2.Calculator, @@ -167,7 +174,7 @@ func calculatePrometheusInterval( queryInterval = "" } - minInterval, err := intervalv2.GetIntervalFrom(timeInterval, queryInterval, intervalMs, 15*time.Second) + minInterval, err := intervalv2.GetIntervalFrom(dsScrapeInterval, queryInterval, intervalMs, 15*time.Second) if err != nil { return time.Duration(0), err } @@ -182,7 +189,7 @@ func calculatePrometheusInterval( // here is where we compare for $__rate_interval or ${__rate_interval} if originalQueryInterval == varRateInterval || originalQueryInterval == varRateIntervalAlt { // Rate interval is final and is not affected by resolution - return calculateRateInterval(adjustedInterval, timeInterval, intervalCalculator), nil + return calculateRateInterval(adjustedInterval, dsScrapeInterval), nil } else { queryIntervalFactor := intervalFactor if queryIntervalFactor == 0 { @@ -192,12 +199,16 @@ func calculatePrometheusInterval( } } +// calculateRateInterval calculates the $__rate_interval value +// queryInterval is the value calculated range / maxDataPoints on the frontend +// queryInterval is shown on the Query Options Panel above the query editor +// requestedMinStep is the data source scrape interval (default 15s) +// requestedMinStep can be changed by setting "Min Step" value in Options panel below the code editor func calculateRateInterval( - interval time.Duration, - scrapeInterval string, - intervalCalculator intervalv2.Calculator, + queryInterval time.Duration, + requestedMinStep string, ) time.Duration { - scrape := scrapeInterval + scrape := requestedMinStep if scrape == "" { scrape = "15s" } @@ -207,25 +218,38 @@ func calculateRateInterval( return time.Duration(0) } - rateInterval := time.Duration(int64(math.Max(float64(interval+scrapeIntervalDuration), float64(4)*float64(scrapeIntervalDuration)))) + rateInterval := time.Duration(int64(math.Max(float64(queryInterval+scrapeIntervalDuration), float64(4)*float64(scrapeIntervalDuration)))) return rateInterval } -func interpolateVariables(expr, queryInterval string, interval time.Duration, +// interpolateVariables interpolates built-in variables +// expr PromQL query +// queryInterval Requested interval in milliseconds. This value may be overridden by MinStep in query options +// calculatedMinStep Calculated final step value. It was calculated in calculatePrometheusInterval +// requestedMinStep Requested minimum step value. QueryModel.interval +// timeRange Requested time range for query +func interpolateVariables( + expr string, + queryInterval time.Duration, + calculatedMinStep time.Duration, + requestedMinStep string, timeRange time.Duration, - intervalCalculator intervalv2.Calculator, timeInterval string) string { +) string { rangeMs := timeRange.Milliseconds() rangeSRounded := int64(math.Round(float64(rangeMs) / 1000.0)) var rateInterval time.Duration - if queryInterval == varRateInterval || queryInterval == varRateIntervalAlt { - rateInterval = interval + if requestedMinStep == varRateInterval || requestedMinStep == varRateIntervalAlt { + rateInterval = calculatedMinStep } else { - rateInterval = calculateRateInterval(interval, timeInterval, intervalCalculator) + if requestedMinStep == varInterval || requestedMinStep == varIntervalAlt { + requestedMinStep = calculatedMinStep.String() + } + rateInterval = calculateRateInterval(queryInterval, requestedMinStep) } - expr = strings.ReplaceAll(expr, varIntervalMs, strconv.FormatInt(int64(interval/time.Millisecond), 10)) - expr = strings.ReplaceAll(expr, varInterval, intervalv2.FormatDuration(interval)) + expr = strings.ReplaceAll(expr, varIntervalMs, strconv.FormatInt(int64(queryInterval/time.Millisecond), 10)) + expr = strings.ReplaceAll(expr, varInterval, intervalv2.FormatDuration(queryInterval)) 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") @@ -233,8 +257,8 @@ func interpolateVariables(expr, queryInterval string, interval time.Duration, expr = strings.ReplaceAll(expr, varRateInterval, rateInterval.String()) // Repetitive code, we should have functionality to unify these - expr = strings.ReplaceAll(expr, varIntervalMsAlt, strconv.FormatInt(int64(interval/time.Millisecond), 10)) - expr = strings.ReplaceAll(expr, varIntervalAlt, intervalv2.FormatDuration(interval)) + expr = strings.ReplaceAll(expr, varIntervalMsAlt, strconv.FormatInt(int64(queryInterval/time.Millisecond), 10)) + expr = strings.ReplaceAll(expr, varIntervalAlt, intervalv2.FormatDuration(queryInterval)) expr = strings.ReplaceAll(expr, varRangeMsAlt, strconv.FormatInt(rangeMs, 10)) expr = strings.ReplaceAll(expr, varRangeSAlt, strconv.FormatInt(rangeSRounded, 10)) expr = strings.ReplaceAll(expr, varRangeAlt, strconv.FormatInt(rangeSRounded, 10)+"s") diff --git a/pkg/tsdb/prometheus/models/query_test.go b/pkg/tsdb/prometheus/models/query_test.go index 4322ed802a1..5c86ab17e7b 100644 --- a/pkg/tsdb/prometheus/models/query_test.go +++ b/pkg/tsdb/prometheus/models/query_test.go @@ -1,6 +1,7 @@ package models_test import ( + "fmt" "reflect" "testing" "time" @@ -51,7 +52,7 @@ func TestParse(t *testing.T) { "expr": "go_goroutines", "format": "time_series", "refId": "A" - }`, timeRange) + }`, timeRange, time.Duration(1)*time.Minute) res, err := models.Parse(q, "15s", intervalCalculator, false) require.NoError(t, err) @@ -69,7 +70,7 @@ func TestParse(t *testing.T) { "format": "time_series", "intervalFactor": 1, "refId": "A" - }`, timeRange) + }`, timeRange, time.Duration(1)*time.Minute) res, err := models.Parse(q, "15s", intervalCalculator, false) require.NoError(t, err) @@ -87,7 +88,7 @@ func TestParse(t *testing.T) { "format": "time_series", "intervalFactor": 10, "refId": "A" - }`, timeRange) + }`, timeRange, time.Duration(1)*time.Minute) res, err := models.Parse(q, "15s", intervalCalculator, false) require.NoError(t, err) @@ -105,7 +106,7 @@ func TestParse(t *testing.T) { "format": "time_series", "intervalFactor": 1, "refId": "A" - }`, timeRange) + }`, timeRange, time.Duration(1)*time.Minute) res, err := models.Parse(q, "15s", intervalCalculator, false) require.NoError(t, err) @@ -123,7 +124,7 @@ func TestParse(t *testing.T) { "format": "time_series", "intervalFactor": 1, "refId": "A" - }`, timeRange) + }`, timeRange, time.Duration(1)*time.Minute) res, err := models.Parse(q, "240s", intervalCalculator, false) require.NoError(t, err) @@ -140,12 +141,14 @@ func TestParse(t *testing.T) { "expr": "rate(ALERTS{job=\"test\" [$__interval]})", "format": "time_series", "intervalFactor": 1, + "intervalMs": 60000, "refId": "A" - }`, timeRange) + }`, timeRange, time.Duration(1)*time.Minute) res, err := models.Parse(q, "15s", intervalCalculator, false) require.NoError(t, err) - require.Equal(t, "rate(ALERTS{job=\"test\" [2m]})", res.Expr) + require.Equal(t, "rate(ALERTS{job=\"test\" [1m]})", res.Expr) + require.Equal(t, 120*time.Second, res.Step) }) t.Run("parsing query model with ${__interval} variable", func(t *testing.T) { @@ -158,12 +161,14 @@ func TestParse(t *testing.T) { "expr": "rate(ALERTS{job=\"test\" [${__interval}]})", "format": "time_series", "intervalFactor": 1, + "interval": "1m", + "intervalMs": 60000, "refId": "A" - }`, timeRange) + }`, timeRange, time.Duration(1)*time.Minute) res, err := models.Parse(q, "15s", intervalCalculator, false) require.NoError(t, err) - require.Equal(t, "rate(ALERTS{job=\"test\" [2m]})", res.Expr) + require.Equal(t, "rate(ALERTS{job=\"test\" [1m]})", res.Expr) }) t.Run("parsing query model with $__interval_ms variable", func(t *testing.T) { @@ -176,12 +181,13 @@ func TestParse(t *testing.T) { "expr": "rate(ALERTS{job=\"test\" [$__interval_ms]})", "format": "time_series", "intervalFactor": 1, + "intervalMs": 60000, "refId": "A" - }`, timeRange) + }`, timeRange, time.Duration(1)*time.Minute) res, err := models.Parse(q, "15s", intervalCalculator, false) require.NoError(t, err) - require.Equal(t, "rate(ALERTS{job=\"test\" [120000]})", res.Expr) + require.Equal(t, "rate(ALERTS{job=\"test\" [60000]})", res.Expr) }) t.Run("parsing query model with $__interval_ms and $__interval variable", func(t *testing.T) { @@ -194,12 +200,13 @@ func TestParse(t *testing.T) { "expr": "rate(ALERTS{job=\"test\" [$__interval_ms]}) + rate(ALERTS{job=\"test\" [$__interval]})", "format": "time_series", "intervalFactor": 1, + "intervalMs": 60000, "refId": "A" - }`, timeRange) + }`, timeRange, time.Duration(1)*time.Minute) res, err := models.Parse(q, "15s", intervalCalculator, false) require.NoError(t, err) - require.Equal(t, "rate(ALERTS{job=\"test\" [120000]}) + rate(ALERTS{job=\"test\" [2m]})", res.Expr) + require.Equal(t, "rate(ALERTS{job=\"test\" [60000]}) + rate(ALERTS{job=\"test\" [1m]})", res.Expr) }) t.Run("parsing query model with ${__interval_ms} and ${__interval} variable", func(t *testing.T) { @@ -212,12 +219,13 @@ func TestParse(t *testing.T) { "expr": "rate(ALERTS{job=\"test\" [${__interval_ms}]}) + rate(ALERTS{job=\"test\" [${__interval}]})", "format": "time_series", "intervalFactor": 1, + "intervalMs": 60000, "refId": "A" - }`, timeRange) + }`, timeRange, time.Duration(1)*time.Minute) res, err := models.Parse(q, "15s", intervalCalculator, false) require.NoError(t, err) - require.Equal(t, "rate(ALERTS{job=\"test\" [120000]}) + rate(ALERTS{job=\"test\" [2m]})", res.Expr) + require.Equal(t, "rate(ALERTS{job=\"test\" [60000]}) + rate(ALERTS{job=\"test\" [1m]})", res.Expr) }) t.Run("parsing query model with $__range variable", func(t *testing.T) { @@ -231,7 +239,7 @@ func TestParse(t *testing.T) { "format": "time_series", "intervalFactor": 1, "refId": "A" - }`, timeRange) + }`, timeRange, time.Duration(1)*time.Minute) res, err := models.Parse(q, "15s", intervalCalculator, false) require.NoError(t, err) @@ -249,7 +257,7 @@ func TestParse(t *testing.T) { "format": "time_series", "intervalFactor": 1, "refId": "A" - }`, timeRange) + }`, timeRange, time.Duration(1)*time.Minute) res, err := models.Parse(q, "15s", intervalCalculator, false) require.NoError(t, err) @@ -267,7 +275,7 @@ func TestParse(t *testing.T) { "format": "time_series", "intervalFactor": 1, "refId": "A" - }`, timeRange) + }`, timeRange, time.Duration(1)*time.Minute) res, err := models.Parse(q, "15s", intervalCalculator, false) require.NoError(t, err) @@ -285,7 +293,7 @@ func TestParse(t *testing.T) { "format": "time_series", "intervalFactor": 1, "refId": "A" - }`, timeRange) + }`, timeRange, time.Duration(1)*time.Minute) res, err := models.Parse(q, "15s", intervalCalculator, false) require.NoError(t, err) @@ -303,7 +311,7 @@ func TestParse(t *testing.T) { "format": "time_series", "intervalFactor": 1, "refId": "A" - }`, timeRange) + }`, timeRange, time.Duration(1)*time.Minute) res, err := models.Parse(q, "15s", intervalCalculator, false) require.NoError(t, err) @@ -321,7 +329,7 @@ func TestParse(t *testing.T) { "format": "time_series", "intervalFactor": 1, "refId": "A" - }`, timeRange) + }`, timeRange, time.Duration(1)*time.Minute) res, err := models.Parse(q, "15s", intervalCalculator, false) require.NoError(t, err) @@ -339,7 +347,7 @@ func TestParse(t *testing.T) { "format": "time_series", "intervalFactor": 1, "refId": "A" - }`, timeRange) + }`, timeRange, time.Duration(1)*time.Minute) res, err := models.Parse(q, "15s", intervalCalculator, false) require.NoError(t, err) @@ -358,11 +366,11 @@ func TestParse(t *testing.T) { "intervalFactor": 1, "interval": "5m", "refId": "A" - }`, timeRange) + }`, timeRange, time.Duration(1)*time.Minute) res, err := models.Parse(q, "15s", intervalCalculator, false) require.NoError(t, err) - require.Equal(t, "rate(ALERTS{job=\"test\" [5m15s]})", res.Expr) + require.Equal(t, "rate(ALERTS{job=\"test\" [20m0s]})", res.Expr) }) t.Run("parsing query model with $__rate_interval variable in expr and interval", func(t *testing.T) { @@ -377,7 +385,7 @@ func TestParse(t *testing.T) { "intervalFactor": 1, "interval": "$__rate_interval", "refId": "A" - }`, timeRange) + }`, timeRange, 1*time.Minute) res, err := models.Parse(q, "15s", intervalCalculator, false) require.NoError(t, err) @@ -396,7 +404,7 @@ func TestParse(t *testing.T) { "format": "time_series", "intervalFactor": 1, "refId": "A" - }`, timeRange) + }`, timeRange, 2*time.Minute) res, err := models.Parse(q, "15s", intervalCalculator, false) require.NoError(t, err) @@ -414,7 +422,7 @@ func TestParse(t *testing.T) { "format": "time_series", "intervalFactor": 1, "refId": "A" - }`, timeRange) + }`, timeRange, 2*time.Minute) res, err := models.Parse(q, "15s", intervalCalculator, false) require.NoError(t, err) @@ -432,7 +440,7 @@ func TestParse(t *testing.T) { "format": "time_series", "intervalFactor": 1, "refId": "A" - }`, timeRange) + }`, timeRange, 2*time.Minute) res, err := models.Parse(q, "15s", intervalCalculator, false) require.NoError(t, err) @@ -451,7 +459,7 @@ func TestParse(t *testing.T) { "intervalFactor": 1, "refId": "A", "range": true - }`, timeRange) + }`, timeRange, time.Duration(1)*time.Minute) res, err := models.Parse(q, "15s", intervalCalculator, false) require.NoError(t, err) @@ -471,7 +479,7 @@ func TestParse(t *testing.T) { "refId": "A", "range": true, "instant": true - }`, timeRange) + }`, timeRange, time.Duration(1)*time.Minute) res, err := models.Parse(q, "15s", intervalCalculator, false) require.NoError(t, err) @@ -490,7 +498,7 @@ func TestParse(t *testing.T) { "format": "time_series", "intervalFactor": 1, "refId": "A" - }`, timeRange) + }`, timeRange, time.Duration(1)*time.Minute) res, err := models.Parse(q, "15s", intervalCalculator, false) require.NoError(t, err) @@ -498,8 +506,164 @@ func TestParse(t *testing.T) { }) } -func queryContext(json string, timeRange backend.TimeRange) backend.DataQuery { +func TestRateInterval(t *testing.T) { + type args struct { + expr string + interval string + intervalMs int64 + dsScrapeInterval string + timeRange *backend.TimeRange + } + tests := []struct { + name string + args args + want *models.Query + }{ + { + name: "intervalMs 100s, minStep override 150s and scrape interval 30s", + args: args{ + expr: "rate(rpc_durations_seconds_count[$__rate_interval])", + interval: "150s", + intervalMs: 100000, + dsScrapeInterval: "30s", + }, + want: &models.Query{ + Expr: "rate(rpc_durations_seconds_count[10m0s])", + Step: time.Second * 150, + }, + }, + { + name: "intervalMs 120s, minStep override 150s and ds scrape interval 30s", + args: args{ + expr: "rate(rpc_durations_seconds_count[$__rate_interval])", + interval: "150s", + intervalMs: 120000, + dsScrapeInterval: "30s", + }, + want: &models.Query{ + Expr: "rate(rpc_durations_seconds_count[10m0s])", + Step: time.Second * 150, + }, + }, + { + name: "intervalMs 120s, minStep auto (interval not overridden) and ds scrape interval 30s", + args: args{ + expr: "rate(rpc_durations_seconds_count[$__rate_interval])", + interval: "120s", + intervalMs: 120000, + dsScrapeInterval: "30s", + }, + want: &models.Query{ + Expr: "rate(rpc_durations_seconds_count[8m0s])", + Step: time.Second * 120, + }, + }, + { + name: "interval and minStep are automatically calculated and ds scrape interval 30s and time range 1 hour", + args: args{ + expr: "rate(rpc_durations_seconds_count[$__rate_interval])", + interval: "30s", + intervalMs: 30000, + dsScrapeInterval: "30s", + timeRange: &backend.TimeRange{ + From: now, + To: now.Add(1 * time.Hour), + }, + }, + want: &models.Query{ + Expr: "rate(rpc_durations_seconds_count[2m30s])", + Step: time.Second * 30, + }, + }, + { + name: "minStep is $__rate_interval and ds scrape interval 30s and time range 1 hour", + args: args{ + expr: "rate(rpc_durations_seconds_count[$__rate_interval])", + interval: "$__rate_interval", + intervalMs: 30000, + dsScrapeInterval: "30s", + timeRange: &backend.TimeRange{ + From: now, + To: now.Add(1 * time.Hour), + }, + }, + want: &models.Query{ + Expr: "rate(rpc_durations_seconds_count[2m0s])", + Step: time.Minute * 2, + }, + }, + { + name: "minStep is $__rate_interval and ds scrape interval 30s and time range 2 days", + args: args{ + expr: "rate(rpc_durations_seconds_count[$__rate_interval])", + interval: "$__rate_interval", + intervalMs: 120000, + dsScrapeInterval: "30s", + timeRange: &backend.TimeRange{ + From: now, + To: now.Add(2 * 24 * time.Hour), + }, + }, + want: &models.Query{ + Expr: "rate(rpc_durations_seconds_count[2m30s])", + Step: time.Second * 150, + }, + }, + { + name: "minStep is $__rate_interval and ds scrape interval 15s and time range 2 days", + args: args{ + expr: "rate(rpc_durations_seconds_count[$__rate_interval])", + interval: "$__interval", + intervalMs: 120000, + dsScrapeInterval: "15s", + timeRange: &backend.TimeRange{ + From: now, + To: now.Add(2 * 24 * time.Hour), + }, + }, + want: &models.Query{ + Expr: "rate(rpc_durations_seconds_count[8m0s])", + Step: time.Second * 120, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + q := mockQuery(tt.args.expr, tt.args.interval, tt.args.intervalMs, tt.args.timeRange) + q.MaxDataPoints = 12384 + res, err := models.Parse(q, tt.args.dsScrapeInterval, intervalCalculator, false) + require.NoError(t, err) + require.Equal(t, tt.want.Expr, res.Expr) + require.Equal(t, tt.want.Step, res.Step) + }) + } +} + +func mockQuery(expr string, interval string, intervalMs int64, timeRange *backend.TimeRange) backend.DataQuery { + if timeRange == nil { + timeRange = &backend.TimeRange{ + From: now, + To: now.Add(1 * time.Hour), + } + } return backend.DataQuery{ + Interval: 2 * time.Minute, + JSON: []byte(fmt.Sprintf(`{ + "expr": "%s", + "format": "time_series", + "interval": "%s", + "intervalMs": %v, + "intervalFactor": 1, + "refId": "A" + }`, expr, interval, intervalMs)), + TimeRange: *timeRange, + RefID: "A", + } +} + +func queryContext(json string, timeRange backend.TimeRange, queryInterval time.Duration) backend.DataQuery { + return backend.DataQuery{ + Interval: queryInterval, JSON: []byte(json), TimeRange: timeRange, RefID: "A", diff --git a/pkg/tsdb/prometheus/testdata/range_auto.result.golden.jsonc b/pkg/tsdb/prometheus/testdata/range_auto.result.golden.jsonc index 6aaba0bdfd1..13bc995348a 100644 --- a/pkg/tsdb/prometheus/testdata/range_auto.result.golden.jsonc +++ b/pkg/tsdb/prometheus/testdata/range_auto.result.golden.jsonc @@ -9,9 +9,9 @@ // "custom": { // "resultType": "matrix" // }, -// "executedQueryString": "Expr: histogram_quantile(0.95, sum(rate(tns_request_duration_seconds_bucket[1m0s])) by (le))\nStep: 1s" +// "executedQueryString": "Expr: histogram_quantile(0.95, sum(rate(tns_request_duration_seconds_bucket[4s])) by (le))\nStep: 1s" // } -// Name: histogram_quantile(0.95, sum(rate(tns_request_duration_seconds_bucket[1m0s])) by (le)) +// Name: histogram_quantile(0.95, sum(rate(tns_request_duration_seconds_bucket[4s])) by (le)) // Dimensions: 2 Fields by 301 Rows // +-----------------------------------+----------------------+ // | Name: Time | Name: Value | @@ -37,7 +37,7 @@ "frames": [ { "schema": { - "name": "histogram_quantile(0.95, sum(rate(tns_request_duration_seconds_bucket[1m0s])) by (le))", + "name": "histogram_quantile(0.95, sum(rate(tns_request_duration_seconds_bucket[4s])) by (le))", "meta": { "type": "timeseries-multi", "typeVersion": [ @@ -47,7 +47,7 @@ "custom": { "resultType": "matrix" }, - "executedQueryString": "Expr: histogram_quantile(0.95, sum(rate(tns_request_duration_seconds_bucket[1m0s])) by (le))\nStep: 1s" + "executedQueryString": "Expr: histogram_quantile(0.95, sum(rate(tns_request_duration_seconds_bucket[4s])) by (le))\nStep: 1s" }, "fields": [ { @@ -68,7 +68,7 @@ }, "labels": {}, "config": { - "displayNameFromDS": "histogram_quantile(0.95, sum(rate(tns_request_duration_seconds_bucket[1m0s])) by (le))" + "displayNameFromDS": "histogram_quantile(0.95, sum(rate(tns_request_duration_seconds_bucket[4s])) by (le))" } } ]