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 }