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