github.com/muhammadn/cortex@v1.9.1-0.20220510110439-46bb7000d03d/pkg/querier/queryrange/split_by_interval_test.go (about)

     1  package queryrange
     2  
     3  import (
     4  	"context"
     5  	"io/ioutil"
     6  	"net/http"
     7  	"net/http/httptest"
     8  	"net/url"
     9  	"strconv"
    10  	"testing"
    11  	"time"
    12  
    13  	"github.com/weaveworks/common/httpgrpc"
    14  
    15  	"github.com/prometheus/prometheus/promql/parser"
    16  	"github.com/stretchr/testify/require"
    17  	"github.com/weaveworks/common/middleware"
    18  	"github.com/weaveworks/common/user"
    19  	"go.uber.org/atomic"
    20  )
    21  
    22  const seconds = 1e3 // 1e3 milliseconds per second.
    23  
    24  func TestNextIntervalBoundary(t *testing.T) {
    25  	for i, tc := range []struct {
    26  		in, step, out int64
    27  		interval      time.Duration
    28  	}{
    29  		// Smallest possible period is 1 millisecond
    30  		{0, 1, toMs(day) - 1, day},
    31  		{0, 1, toMs(time.Hour) - 1, time.Hour},
    32  		// A more standard example
    33  		{0, 15 * seconds, toMs(day) - 15*seconds, day},
    34  		{0, 15 * seconds, toMs(time.Hour) - 15*seconds, time.Hour},
    35  		// Move start time forward 1 second; end time moves the same
    36  		{1 * seconds, 15 * seconds, toMs(day) - (15-1)*seconds, day},
    37  		{1 * seconds, 15 * seconds, toMs(time.Hour) - (15-1)*seconds, time.Hour},
    38  		// Move start time forward 14 seconds; end time moves the same
    39  		{14 * seconds, 15 * seconds, toMs(day) - (15-14)*seconds, day},
    40  		{14 * seconds, 15 * seconds, toMs(time.Hour) - (15-14)*seconds, time.Hour},
    41  		// Now some examples where the period does not divide evenly into a day:
    42  		// 1 day modulus 35 seconds = 20 seconds
    43  		{0, 35 * seconds, toMs(day) - 20*seconds, day},
    44  		// 1 hour modulus 35 sec = 30  (3600 mod 35 = 30)
    45  		{0, 35 * seconds, toMs(time.Hour) - 30*seconds, time.Hour},
    46  		// Move start time forward 1 second; end time moves the same
    47  		{1 * seconds, 35 * seconds, toMs(day) - (20-1)*seconds, day},
    48  		{1 * seconds, 35 * seconds, toMs(time.Hour) - (30-1)*seconds, time.Hour},
    49  		// If the end time lands exactly on midnight we stop one period before that
    50  		{20 * seconds, 35 * seconds, toMs(day) - 35*seconds, day},
    51  		{30 * seconds, 35 * seconds, toMs(time.Hour) - 35*seconds, time.Hour},
    52  		// This example starts 35 seconds after the 5th one ends
    53  		{toMs(day) + 15*seconds, 35 * seconds, 2*toMs(day) - 5*seconds, day},
    54  		{toMs(time.Hour) + 15*seconds, 35 * seconds, 2*toMs(time.Hour) - 15*seconds, time.Hour},
    55  	} {
    56  		t.Run(strconv.Itoa(i), func(t *testing.T) {
    57  			require.Equal(t, tc.out, nextIntervalBoundary(tc.in, tc.step, tc.interval))
    58  		})
    59  	}
    60  }
    61  
    62  func TestSplitQuery(t *testing.T) {
    63  	for i, tc := range []struct {
    64  		input    Request
    65  		expected []Request
    66  		interval time.Duration
    67  	}{
    68  		{
    69  			input: &PrometheusRequest{
    70  				Start: 0,
    71  				End:   60 * 60 * seconds,
    72  				Step:  15 * seconds,
    73  				Query: "foo",
    74  			},
    75  			expected: []Request{
    76  				&PrometheusRequest{
    77  					Start: 0,
    78  					End:   60 * 60 * seconds,
    79  					Step:  15 * seconds,
    80  					Query: "foo",
    81  				},
    82  			},
    83  			interval: day,
    84  		},
    85  		{
    86  			input: &PrometheusRequest{
    87  				Start: 0,
    88  				End:   60 * 60 * seconds,
    89  				Step:  15 * seconds,
    90  				Query: "foo",
    91  			},
    92  			expected: []Request{
    93  				&PrometheusRequest{
    94  					Start: 0,
    95  					End:   60 * 60 * seconds,
    96  					Step:  15 * seconds,
    97  					Query: "foo",
    98  				},
    99  			},
   100  			interval: 3 * time.Hour,
   101  		},
   102  		{
   103  			input: &PrometheusRequest{
   104  				Start: 0,
   105  				End:   24 * 3600 * seconds,
   106  				Step:  15 * seconds,
   107  				Query: "foo",
   108  			},
   109  			expected: []Request{
   110  				&PrometheusRequest{
   111  					Start: 0,
   112  					End:   24 * 3600 * seconds,
   113  					Step:  15 * seconds,
   114  					Query: "foo",
   115  				},
   116  			},
   117  			interval: day,
   118  		},
   119  		{
   120  			input: &PrometheusRequest{
   121  				Start: 0,
   122  				End:   3 * 3600 * seconds,
   123  				Step:  15 * seconds,
   124  				Query: "foo",
   125  			},
   126  			expected: []Request{
   127  				&PrometheusRequest{
   128  					Start: 0,
   129  					End:   3 * 3600 * seconds,
   130  					Step:  15 * seconds,
   131  					Query: "foo",
   132  				},
   133  			},
   134  			interval: 3 * time.Hour,
   135  		},
   136  		{
   137  			input: &PrometheusRequest{
   138  				Start: 0,
   139  				End:   2 * 24 * 3600 * seconds,
   140  				Step:  15 * seconds,
   141  				Query: "foo @ start()",
   142  			},
   143  			expected: []Request{
   144  				&PrometheusRequest{
   145  					Start: 0,
   146  					End:   (24 * 3600 * seconds) - (15 * seconds),
   147  					Step:  15 * seconds,
   148  					Query: "foo @ 0.000",
   149  				},
   150  				&PrometheusRequest{
   151  					Start: 24 * 3600 * seconds,
   152  					End:   2 * 24 * 3600 * seconds,
   153  					Step:  15 * seconds,
   154  					Query: "foo @ 0.000",
   155  				},
   156  			},
   157  			interval: day,
   158  		},
   159  		{
   160  			input: &PrometheusRequest{
   161  				Start: 0,
   162  				End:   2 * 3 * 3600 * seconds,
   163  				Step:  15 * seconds,
   164  				Query: "foo",
   165  			},
   166  			expected: []Request{
   167  				&PrometheusRequest{
   168  					Start: 0,
   169  					End:   (3 * 3600 * seconds) - (15 * seconds),
   170  					Step:  15 * seconds,
   171  					Query: "foo",
   172  				},
   173  				&PrometheusRequest{
   174  					Start: 3 * 3600 * seconds,
   175  					End:   2 * 3 * 3600 * seconds,
   176  					Step:  15 * seconds,
   177  					Query: "foo",
   178  				},
   179  			},
   180  			interval: 3 * time.Hour,
   181  		},
   182  		{
   183  			input: &PrometheusRequest{
   184  				Start: 3 * 3600 * seconds,
   185  				End:   3 * 24 * 3600 * seconds,
   186  				Step:  15 * seconds,
   187  				Query: "foo",
   188  			},
   189  			expected: []Request{
   190  				&PrometheusRequest{
   191  					Start: 3 * 3600 * seconds,
   192  					End:   (24 * 3600 * seconds) - (15 * seconds),
   193  					Step:  15 * seconds,
   194  					Query: "foo",
   195  				},
   196  				&PrometheusRequest{
   197  					Start: 24 * 3600 * seconds,
   198  					End:   (2 * 24 * 3600 * seconds) - (15 * seconds),
   199  					Step:  15 * seconds,
   200  					Query: "foo",
   201  				},
   202  				&PrometheusRequest{
   203  					Start: 2 * 24 * 3600 * seconds,
   204  					End:   3 * 24 * 3600 * seconds,
   205  					Step:  15 * seconds,
   206  					Query: "foo",
   207  				},
   208  			},
   209  			interval: day,
   210  		},
   211  		{
   212  			input: &PrometheusRequest{
   213  				Start: 2 * 3600 * seconds,
   214  				End:   3 * 3 * 3600 * seconds,
   215  				Step:  15 * seconds,
   216  				Query: "foo",
   217  			},
   218  			expected: []Request{
   219  				&PrometheusRequest{
   220  					Start: 2 * 3600 * seconds,
   221  					End:   (3 * 3600 * seconds) - (15 * seconds),
   222  					Step:  15 * seconds,
   223  					Query: "foo",
   224  				},
   225  				&PrometheusRequest{
   226  					Start: 3 * 3600 * seconds,
   227  					End:   (2 * 3 * 3600 * seconds) - (15 * seconds),
   228  					Step:  15 * seconds,
   229  					Query: "foo",
   230  				},
   231  				&PrometheusRequest{
   232  					Start: 2 * 3 * 3600 * seconds,
   233  					End:   3 * 3 * 3600 * seconds,
   234  					Step:  15 * seconds,
   235  					Query: "foo",
   236  				},
   237  			},
   238  			interval: 3 * time.Hour,
   239  		},
   240  	} {
   241  		t.Run(strconv.Itoa(i), func(t *testing.T) {
   242  			days, err := splitQuery(tc.input, tc.interval)
   243  			require.NoError(t, err)
   244  			require.Equal(t, tc.expected, days)
   245  		})
   246  	}
   247  }
   248  
   249  func TestSplitByDay(t *testing.T) {
   250  	mergedResponse, err := PrometheusCodec.MergeResponse(parsedResponse, parsedResponse)
   251  	require.NoError(t, err)
   252  
   253  	mergedHTTPResponse, err := PrometheusCodec.EncodeResponse(context.Background(), mergedResponse)
   254  	require.NoError(t, err)
   255  
   256  	mergedHTTPResponseBody, err := ioutil.ReadAll(mergedHTTPResponse.Body)
   257  	require.NoError(t, err)
   258  
   259  	for i, tc := range []struct {
   260  		path, expectedBody string
   261  		expectedQueryCount int32
   262  	}{
   263  		{query, string(mergedHTTPResponseBody), 2},
   264  	} {
   265  		t.Run(strconv.Itoa(i), func(t *testing.T) {
   266  			var actualCount atomic.Int32
   267  			s := httptest.NewServer(
   268  				middleware.AuthenticateUser.Wrap(
   269  					http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
   270  						actualCount.Inc()
   271  						_, _ = w.Write([]byte(responseBody))
   272  					}),
   273  				),
   274  			)
   275  			defer s.Close()
   276  
   277  			u, err := url.Parse(s.URL)
   278  			require.NoError(t, err)
   279  
   280  			interval := func(_ Request) time.Duration { return 24 * time.Hour }
   281  			roundtripper := NewRoundTripper(singleHostRoundTripper{
   282  				host: u.Host,
   283  				next: http.DefaultTransport,
   284  			}, PrometheusCodec, NewLimitsMiddleware(mockLimits{}), SplitByIntervalMiddleware(interval, mockLimits{}, PrometheusCodec, nil))
   285  
   286  			req, err := http.NewRequest("GET", tc.path, http.NoBody)
   287  			require.NoError(t, err)
   288  
   289  			ctx := user.InjectOrgID(context.Background(), "1")
   290  			req = req.WithContext(ctx)
   291  
   292  			resp, err := roundtripper.RoundTrip(req)
   293  			require.NoError(t, err)
   294  			require.Equal(t, 200, resp.StatusCode)
   295  
   296  			bs, err := ioutil.ReadAll(resp.Body)
   297  			require.NoError(t, err)
   298  			require.Equal(t, tc.expectedBody, string(bs))
   299  			require.Equal(t, tc.expectedQueryCount, actualCount.Load())
   300  		})
   301  	}
   302  }
   303  
   304  func Test_evaluateAtModifier(t *testing.T) {
   305  	const (
   306  		start, end = int64(1546300800), int64(1646300800)
   307  	)
   308  	for _, tt := range []struct {
   309  		in, expected      string
   310  		expectedErrorCode int
   311  	}{
   312  		{
   313  			in:       "topk(5, rate(http_requests_total[1h] @ start()))",
   314  			expected: "topk(5, rate(http_requests_total[1h] @ 1546300.800))",
   315  		},
   316  		{
   317  			in:       "topk(5, rate(http_requests_total[1h] @ 0))",
   318  			expected: "topk(5, rate(http_requests_total[1h] @ 0.000))",
   319  		},
   320  		{
   321  			in:       "http_requests_total[1h] @ 10.001",
   322  			expected: "http_requests_total[1h] @ 10.001",
   323  		},
   324  		{
   325  			in: `min_over_time(
   326  				sum by(cluster) (
   327  					rate(http_requests_total[5m] @ end())
   328  				)[10m:]
   329  			)
   330  			or
   331  			max_over_time(
   332  				stddev_over_time(
   333  					deriv(
   334  						rate(http_requests_total[10m] @ start())
   335  					[5m:1m])
   336  				[2m:])
   337  			[10m:])`,
   338  			expected: `min_over_time(
   339  				sum by(cluster) (
   340  					rate(http_requests_total[5m] @ 1646300.800)
   341  				)[10m:]
   342  			)
   343  			or
   344  			max_over_time(
   345  				stddev_over_time(
   346  					deriv(
   347  						rate(http_requests_total[10m] @ 1546300.800)
   348  					[5m:1m])
   349  				[2m:])
   350  			[10m:])`,
   351  		},
   352  		{
   353  			// parse error: missing unit character in duration
   354  			in:                "http_requests_total[5] @ 10.001",
   355  			expectedErrorCode: http.StatusBadRequest,
   356  		},
   357  		{
   358  			// parse error: @ modifier must be preceded by an instant vector selector or range vector selector or a subquery
   359  			in:                "sum(http_requests_total[5m]) @ 10.001",
   360  			expectedErrorCode: http.StatusBadRequest,
   361  		},
   362  	} {
   363  		tt := tt
   364  		t.Run(tt.in, func(t *testing.T) {
   365  			t.Parallel()
   366  			out, err := evaluateAtModifierFunction(tt.in, start, end)
   367  			if tt.expectedErrorCode != 0 {
   368  				require.Error(t, err)
   369  				httpResp, ok := httpgrpc.HTTPResponseFromError(err)
   370  				require.True(t, ok, "returned error is not an httpgrpc response")
   371  				require.Equal(t, tt.expectedErrorCode, int(httpResp.Code))
   372  			} else {
   373  				require.NoError(t, err)
   374  				expectedExpr, err := parser.ParseExpr(tt.expected)
   375  				require.NoError(t, err)
   376  				require.Equal(t, expectedExpr.String(), out)
   377  			}
   378  		})
   379  	}
   380  }