Loki: alerting: adjust step-calculation to be the same as in frontend (#42033)

* loki: alerting: adjust step-calculation to be the same as in frontend

* loki: simplified test

* lint fix
This commit is contained in:
Gábor Farkas 2021-11-22 14:28:51 +01:00 committed by GitHub
parent d11ae6dc3f
commit 037fd9cc8c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 90 additions and 52 deletions

View File

@ -197,7 +197,7 @@ func parseQuery(dsInfo *datasourceInfo, queryContext *backend.QueryDataRequest)
resolution = model.Resolution
}
step := time.Duration(int64(query.Interval) * resolution)
step := calculateStep(query.Interval, query.TimeRange.To.Sub(query.TimeRange.From), resolution)
qs = append(qs, &lokiQuery{
Expr: model.Expr,

View File

@ -1,7 +1,6 @@
package loki
import (
"fmt"
"testing"
"time"
@ -41,7 +40,7 @@ func TestLoki(t *testing.T) {
require.Equal(t, `http_request_total{app="backend", device="mobile"}`, formatLegend(metric, query))
})
t.Run("parsing query model with step", func(t *testing.T) {
t.Run("parsing query model", func(t *testing.T) {
queryContext := &backend.QueryDataRequest{
Queries: []backend.DataQuery{
{
@ -65,55 +64,6 @@ func TestLoki(t *testing.T) {
require.NoError(t, err)
require.Equal(t, time.Second*30, models[0].Step)
})
t.Run("parsing query model without step parameter", func(t *testing.T) {
queryContext1 := &backend.QueryDataRequest{
Queries: []backend.DataQuery{
{
JSON: []byte(`
{
"expr": "go_goroutines",
"format": "time_series",
"refId": "A"
}`,
),
TimeRange: backend.TimeRange{
From: time.Now().Add(-48 * time.Hour),
To: time.Now(),
},
Interval: time.Minute * 2,
},
},
}
dsInfo := &datasourceInfo{}
models, err := parseQuery(dsInfo, queryContext1)
require.NoError(t, err)
require.Equal(t, time.Minute*2, models[0].Step)
queryContext2 := &backend.QueryDataRequest{
Queries: []backend.DataQuery{
{
JSON: []byte(`
{
"expr": "go_goroutines",
"format": "time_series",
"refId": "A"
}`,
),
TimeRange: backend.TimeRange{
From: time.Now().Add(-48 * time.Hour),
To: time.Now(),
},
Interval: time.Second * 2,
},
},
}
models, err = parseQuery(dsInfo, queryContext2)
require.NoError(t, err)
fmt.Println(models)
require.Equal(t, time.Second*2, models[0].Step)
})
}
func TestParseResponse(t *testing.T) {

31
pkg/tsdb/loki/step.go Normal file
View File

@ -0,0 +1,31 @@
package loki
import (
"math"
"time"
)
// round the duration to the nearest millisecond larger-or-equal-to the duration
func ceilMs(duration time.Duration) time.Duration {
floatMs := float64(duration.Nanoseconds()) / 1000.0 / 1000.0
ceilMs := math.Ceil(floatMs)
return time.Duration(ceilMs) * time.Millisecond
}
func durationMax(d1 time.Duration, d2 time.Duration) time.Duration {
if d1.Nanoseconds() >= d2.Nanoseconds() {
return d1
} else {
return d2
}
}
func calculateStep(baseInterval time.Duration, timeRange time.Duration, resolution int64) time.Duration {
step := time.Duration(baseInterval.Nanoseconds() * resolution)
safeStep := timeRange / 11000
chosenStep := durationMax(step, safeStep)
return ceilMs(chosenStep)
}

View File

@ -0,0 +1,57 @@
package loki
import (
"testing"
"time"
"github.com/stretchr/testify/require"
)
func TestLokiStep(t *testing.T) {
t.Run("base case", func(t *testing.T) {
require.Equal(t, time.Second*14, calculateStep(time.Second*7, time.Second, 2))
})
t.Run("step should be at least 1 millisecond", func(t *testing.T) {
require.Equal(t, time.Millisecond*1, calculateStep(time.Microsecond*500, time.Second, 1))
})
t.Run("safeInterval should happen", func(t *testing.T) {
// safeInterval
require.Equal(t, time.Second*3, calculateStep(time.Second*2, time.Second*33000, 1))
})
t.Run("step should math.Ceil in milliseconds", func(t *testing.T) {
require.Equal(t, time.Millisecond*2, calculateStep(time.Microsecond*1234, time.Second*1, 1))
})
t.Run("step should math.Ceil in milliseconds, even if safeInterval happens", func(t *testing.T) {
require.Equal(t, time.Millisecond*3001, calculateStep(time.Second*2, time.Second*33001, 1))
})
t.Run("resolution should happen", func(t *testing.T) {
require.Equal(t, time.Second*5, calculateStep(time.Second*1, time.Second*100, 5))
})
t.Run("safeInterval check should happen after resolution is used", func(t *testing.T) {
require.Equal(t, time.Second*4, calculateStep(time.Second*2, time.Second*33000, 2))
})
t.Run("survive interval=0", func(t *testing.T) {
// interval=0. this should never happen, but we make sure we return something sane
// (in this case safeInterval will take care of the problem)
require.Equal(t, time.Second*2, calculateStep(time.Second*0, time.Second*22000, 1))
})
t.Run("survive resolution=0", func(t *testing.T) {
// resolution=0. this should never happen, but we make sure we return something sane
// (in this case safeInterval will take care of the problem)
require.Equal(t, time.Second*2, calculateStep(time.Second*1, time.Second*22000, 0))
})
t.Run("survive interval=0 and resolution=0", func(t *testing.T) {
// resolution=0 and interval=0. this should never happen, but we make sure we return something sane
// (in this case safeInterval will take care of the problem)
require.Equal(t, time.Second*2, calculateStep(time.Second*0, time.Second*22000, 0))
})
}