Prometheus: Fix $__rate_interval calculation (#77234)

* Remove unused param

* simple unit test

* rename

* rename

* add some comments

* Update values

* refactor

* rename

* always calculate rate interval

* fix unit tests

* Fix indentation

* linter fix

* update test

* Fixing issues with the calculation

* new test

* fix $__interval interpolation

* fix test

* add comment
This commit is contained in:
ismail simsek
2023-11-13 10:13:07 +01:00
committed by GitHub
parent 4bc322ca1d
commit b607a4e0a2
3 changed files with 247 additions and 59 deletions
+196 -32
View File
@@ -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",