Like Prometheus, but for logs.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 
loki/pkg/querier/queryrange/queryrangebase/split_by_interval_test.go

383 lines
9.8 KiB

package queryrangebase
import (
"context"
"io/ioutil"
"net/http"
"net/http/httptest"
"net/url"
"strconv"
"testing"
"time"
"github.com/prometheus/prometheus/promql/parser"
"github.com/stretchr/testify/require"
"github.com/weaveworks/common/httpgrpc"
"github.com/weaveworks/common/middleware"
"github.com/weaveworks/common/user"
"go.uber.org/atomic"
)
const seconds = 1e3 // 1e3 milliseconds per second.
func TestNextIntervalBoundary(t *testing.T) {
for i, tc := range []struct {
in, step, out int64
interval time.Duration
}{
// Smallest possible period is 1 millisecond
{0, 1, toMs(day) - 1, day},
{0, 1, toMs(time.Hour) - 1, time.Hour},
// A more standard example
{0, 15 * seconds, toMs(day) - 15*seconds, day},
{0, 15 * seconds, toMs(time.Hour) - 15*seconds, time.Hour},
// Move start time forward 1 second; end time moves the same
{1 * seconds, 15 * seconds, toMs(day) - (15-1)*seconds, day},
{1 * seconds, 15 * seconds, toMs(time.Hour) - (15-1)*seconds, time.Hour},
// Move start time forward 14 seconds; end time moves the same
{14 * seconds, 15 * seconds, toMs(day) - (15-14)*seconds, day},
{14 * seconds, 15 * seconds, toMs(time.Hour) - (15-14)*seconds, time.Hour},
// Now some examples where the period does not divide evenly into a day:
// 1 day modulus 35 seconds = 20 seconds
{0, 35 * seconds, toMs(day) - 20*seconds, day},
// 1 hour modulus 35 sec = 30 (3600 mod 35 = 30)
{0, 35 * seconds, toMs(time.Hour) - 30*seconds, time.Hour},
// Move start time forward 1 second; end time moves the same
{1 * seconds, 35 * seconds, toMs(day) - (20-1)*seconds, day},
{1 * seconds, 35 * seconds, toMs(time.Hour) - (30-1)*seconds, time.Hour},
// If the end time lands exactly on midnight we stop one period before that
{20 * seconds, 35 * seconds, toMs(day) - 35*seconds, day},
{30 * seconds, 35 * seconds, toMs(time.Hour) - 35*seconds, time.Hour},
// This example starts 35 seconds after the 5th one ends
{toMs(day) + 15*seconds, 35 * seconds, 2*toMs(day) - 5*seconds, day},
{toMs(time.Hour) + 15*seconds, 35 * seconds, 2*toMs(time.Hour) - 15*seconds, time.Hour},
} {
t.Run(strconv.Itoa(i), func(t *testing.T) {
require.Equal(t, tc.out, nextIntervalBoundary(tc.in, tc.step, tc.interval))
})
}
}
func TestSplitQuery(t *testing.T) {
for i, tc := range []struct {
input Request
expected []Request
interval time.Duration
}{
{
input: &PrometheusRequest{
Start: 0,
End: 60 * 60 * seconds,
Step: 15 * seconds,
Query: "foo",
},
expected: []Request{
&PrometheusRequest{
Start: 0,
End: 60 * 60 * seconds,
Step: 15 * seconds,
Query: "foo",
},
},
interval: day,
},
{
input: &PrometheusRequest{
Start: 0,
End: 60 * 60 * seconds,
Step: 15 * seconds,
Query: "foo",
},
expected: []Request{
&PrometheusRequest{
Start: 0,
End: 60 * 60 * seconds,
Step: 15 * seconds,
Query: "foo",
},
},
interval: 3 * time.Hour,
},
{
input: &PrometheusRequest{
Start: 0,
End: 24 * 3600 * seconds,
Step: 15 * seconds,
Query: "foo",
},
expected: []Request{
&PrometheusRequest{
Start: 0,
End: 24 * 3600 * seconds,
Step: 15 * seconds,
Query: "foo",
},
},
interval: day,
},
{
input: &PrometheusRequest{
Start: 0,
End: 3 * 3600 * seconds,
Step: 15 * seconds,
Query: "foo",
},
expected: []Request{
&PrometheusRequest{
Start: 0,
End: 3 * 3600 * seconds,
Step: 15 * seconds,
Query: "foo",
},
},
interval: 3 * time.Hour,
},
{
input: &PrometheusRequest{
Start: 0,
End: 2 * 24 * 3600 * seconds,
Step: 15 * seconds,
Query: "foo @ start()",
},
expected: []Request{
&PrometheusRequest{
Start: 0,
End: (24 * 3600 * seconds) - (15 * seconds),
Step: 15 * seconds,
Query: "foo @ 0.000",
},
&PrometheusRequest{
Start: 24 * 3600 * seconds,
End: 2 * 24 * 3600 * seconds,
Step: 15 * seconds,
Query: "foo @ 0.000",
},
},
interval: day,
},
{
input: &PrometheusRequest{
Start: 0,
End: 2 * 3 * 3600 * seconds,
Step: 15 * seconds,
Query: "foo",
},
expected: []Request{
&PrometheusRequest{
Start: 0,
End: (3 * 3600 * seconds) - (15 * seconds),
Step: 15 * seconds,
Query: "foo",
},
&PrometheusRequest{
Start: 3 * 3600 * seconds,
End: 2 * 3 * 3600 * seconds,
Step: 15 * seconds,
Query: "foo",
},
},
interval: 3 * time.Hour,
},
{
input: &PrometheusRequest{
Start: 3 * 3600 * seconds,
End: 3 * 24 * 3600 * seconds,
Step: 15 * seconds,
Query: "foo",
},
expected: []Request{
&PrometheusRequest{
Start: 3 * 3600 * seconds,
End: (24 * 3600 * seconds) - (15 * seconds),
Step: 15 * seconds,
Query: "foo",
},
&PrometheusRequest{
Start: 24 * 3600 * seconds,
End: (2 * 24 * 3600 * seconds) - (15 * seconds),
Step: 15 * seconds,
Query: "foo",
},
&PrometheusRequest{
Start: 2 * 24 * 3600 * seconds,
End: 3 * 24 * 3600 * seconds,
Step: 15 * seconds,
Query: "foo",
},
},
interval: day,
},
{
input: &PrometheusRequest{
Start: 2 * 3600 * seconds,
End: 3 * 3 * 3600 * seconds,
Step: 15 * seconds,
Query: "foo",
},
expected: []Request{
&PrometheusRequest{
Start: 2 * 3600 * seconds,
End: (3 * 3600 * seconds) - (15 * seconds),
Step: 15 * seconds,
Query: "foo",
},
&PrometheusRequest{
Start: 3 * 3600 * seconds,
End: (2 * 3 * 3600 * seconds) - (15 * seconds),
Step: 15 * seconds,
Query: "foo",
},
&PrometheusRequest{
Start: 2 * 3 * 3600 * seconds,
End: 3 * 3 * 3600 * seconds,
Step: 15 * seconds,
Query: "foo",
},
},
interval: 3 * time.Hour,
},
} {
t.Run(strconv.Itoa(i), func(t *testing.T) {
days, err := splitQuery(tc.input, tc.interval)
require.NoError(t, err)
require.Equal(t, tc.expected, days)
})
}
}
func TestSplitByDay(t *testing.T) {
mergedResponse, err := PrometheusCodec.MergeResponse(parsedResponse, parsedResponse)
require.NoError(t, err)
mergedHTTPResponse, err := PrometheusCodec.EncodeResponse(context.Background(), mergedResponse)
require.NoError(t, err)
mergedHTTPResponseBody, err := ioutil.ReadAll(mergedHTTPResponse.Body)
require.NoError(t, err)
for i, tc := range []struct {
path, expectedBody string
expectedQueryCount int32
}{
{query, string(mergedHTTPResponseBody), 2},
} {
t.Run(strconv.Itoa(i), func(t *testing.T) {
var actualCount atomic.Int32
s := httptest.NewServer(
middleware.AuthenticateUser.Wrap(
http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
actualCount.Inc()
_, _ = w.Write([]byte(responseBody))
}),
),
)
defer s.Close()
u, err := url.Parse(s.URL)
require.NoError(t, err)
interval := func(_ Request) time.Duration { return 24 * time.Hour }
roundtripper := NewRoundTripper(singleHostRoundTripper{
host: u.Host,
next: http.DefaultTransport,
}, PrometheusCodec, nil, NewLimitsMiddleware(mockLimits{}), SplitByIntervalMiddleware(interval, mockLimits{}, PrometheusCodec, nil))
req, err := http.NewRequest("GET", tc.path, http.NoBody)
require.NoError(t, err)
ctx := user.InjectOrgID(context.Background(), "1")
req = req.WithContext(ctx)
resp, err := roundtripper.RoundTrip(req)
require.NoError(t, err)
require.Equal(t, 200, resp.StatusCode)
bs, err := ioutil.ReadAll(resp.Body)
require.NoError(t, err)
require.Equal(t, tc.expectedBody, string(bs))
require.Equal(t, tc.expectedQueryCount, actualCount.Load())
})
}
}
func Test_evaluateAtModifier(t *testing.T) {
const (
start, end = int64(1546300800), int64(1646300800)
)
for _, tt := range []struct {
in, expected string
expectedErrorCode int
}{
{
in: "topk(5, rate(http_requests_total[1h] @ start()))",
expected: "topk(5, rate(http_requests_total[1h] @ 1546300.800))",
},
{
in: "topk(5, rate(http_requests_total[1h] @ 0))",
expected: "topk(5, rate(http_requests_total[1h] @ 0.000))",
},
{
in: "http_requests_total[1h] @ 10.001",
expected: "http_requests_total[1h] @ 10.001",
},
{
in: `min_over_time(
sum by(cluster) (
rate(http_requests_total[5m] @ end())
)[10m:]
)
or
max_over_time(
stddev_over_time(
deriv(
rate(http_requests_total[10m] @ start())
[5m:1m])
[2m:])
[10m:])`,
expected: `min_over_time(
sum by(cluster) (
rate(http_requests_total[5m] @ 1646300.800)
)[10m:]
)
or
max_over_time(
stddev_over_time(
deriv(
rate(http_requests_total[10m] @ 1546300.800)
[5m:1m])
[2m:])
[10m:])`,
},
{
// parse error: missing unit character in duration
in: "http_requests_total[5] @ 10.001",
expectedErrorCode: http.StatusBadRequest,
},
{
// parse error: @ modifier must be preceded by an instant vector selector or range vector selector or a subquery
in: "sum(http_requests_total[5m]) @ 10.001",
expectedErrorCode: http.StatusBadRequest,
},
} {
tt := tt
t.Run(tt.in, func(t *testing.T) {
t.Parallel()
out, err := evaluateAtModifierFunction(tt.in, start, end)
if tt.expectedErrorCode != 0 {
require.Error(t, err)
httpResp, ok := httpgrpc.HTTPResponseFromError(err)
require.True(t, ok, "returned error is not an httpgrpc response")
require.Equal(t, tt.expectedErrorCode, int(httpResp.Code))
} else {
require.NoError(t, err)
expectedExpr, err := parser.ParseExpr(tt.expected)
require.NoError(t, err)
require.Equal(t, expectedExpr.String(), out)
}
})
}
}
func toMs(t time.Duration) int64 {
return int64(t / time.Millisecond)
}