github.com/thanos-io/thanos@v0.32.5/test/e2e/query_frontend_test.go (about) 1 // Copyright (c) The Thanos Authors. 2 // Licensed under the Apache License 2.0. 3 4 package e2e_test 5 6 import ( 7 "context" 8 "fmt" 9 "os" 10 "reflect" 11 "sort" 12 "testing" 13 "time" 14 15 "github.com/efficientgo/e2e" 16 e2emon "github.com/efficientgo/e2e/monitoring" 17 "github.com/efficientgo/e2e/monitoring/matchers" 18 "github.com/pkg/errors" 19 "github.com/prometheus/common/model" 20 "github.com/prometheus/prometheus/model/labels" 21 "github.com/prometheus/prometheus/model/timestamp" 22 23 "github.com/efficientgo/core/testutil" 24 "github.com/thanos-io/thanos/pkg/block/metadata" 25 "github.com/thanos-io/thanos/pkg/cacheutil" 26 "github.com/thanos-io/thanos/pkg/promclient" 27 "github.com/thanos-io/thanos/pkg/queryfrontend" 28 "github.com/thanos-io/thanos/pkg/runutil" 29 "github.com/thanos-io/thanos/pkg/testutil/e2eutil" 30 "github.com/thanos-io/thanos/test/e2e/e2ethanos" 31 ) 32 33 func TestQFEEngineExplanation(t *testing.T) { 34 t.Parallel() 35 36 e, err := e2e.NewDockerEnvironment("qfe-opts") 37 testutil.Ok(t, err) 38 t.Cleanup(e2ethanos.CleanScenario(t, e)) 39 40 prom, sidecar := e2ethanos.NewPrometheusWithSidecar(e, "1", e2ethanos.DefaultPromConfig("test", 0, "", "", e2ethanos.LocalPrometheusTarget), "", e2ethanos.DefaultPrometheusImage(), "") 41 testutil.Ok(t, e2e.StartAndWaitReady(prom, sidecar)) 42 43 q := e2ethanos.NewQuerierBuilder(e, "1", sidecar.InternalEndpoint("grpc")).Init() 44 testutil.Ok(t, e2e.StartAndWaitReady(q)) 45 46 inMemoryCacheConfig := queryfrontend.CacheProviderConfig{ 47 Type: queryfrontend.INMEMORY, 48 Config: queryfrontend.InMemoryResponseCacheConfig{ 49 MaxSizeItems: 1000, 50 Validity: time.Hour, 51 }, 52 } 53 54 cfg := queryfrontend.Config{} 55 queryFrontend := e2ethanos.NewQueryFrontend(e, "1", "http://"+q.InternalEndpoint("http"), cfg, inMemoryCacheConfig) 56 testutil.Ok(t, e2e.StartAndWaitReady(queryFrontend)) 57 58 ctx, cancel := context.WithTimeout(context.Background(), time.Minute) 59 t.Cleanup(cancel) 60 61 testutil.Ok(t, q.WaitSumMetricsWithOptions(e2emon.Equals(1), []string{"thanos_store_nodes_grpc_connections"}, e2emon.WaitMissingMetrics())) 62 63 var queriesBeforeUp = 0 64 testutil.Ok(t, runutil.RetryWithLog(e2e.NewLogger(os.Stderr), 5*time.Second, ctx.Done(), func() error { 65 queriesBeforeUp++ 66 _, _, err := simpleInstantQuery(t, ctx, queryFrontend.Endpoint("http"), e2ethanos.QueryUpWithoutInstance, time.Now, promclient.QueryOptions{}, 1) 67 return err 68 })) 69 70 t.Logf("queried %d times before prom is up", queriesBeforeUp) 71 72 t.Run("passes query to Thanos engine", func(t *testing.T) { 73 queryAndAssertSeries(t, ctx, queryFrontend.Endpoint("http"), e2ethanos.QueryUpWithoutInstance, time.Now, promclient.QueryOptions{ 74 Deduplicate: false, 75 Engine: "thanos", 76 }, []model.Metric{ 77 { 78 "job": "myself", 79 "prometheus": "test", 80 "replica": "0", 81 }, 82 }) 83 84 testutil.Ok(t, q.WaitSumMetricsWithOptions(e2emon.GreaterOrEqual(1), []string{"thanos_engine_queries_total"}, e2emon.WaitMissingMetrics(), e2emon.WithLabelMatchers(matchers.MustNewMatcher(matchers.MatchEqual, "fallback", "false")))) 85 86 }) 87 88 now := time.Now() 89 t.Run("passes range query to Thanos engine and returns explanation", func(t *testing.T) { 90 explanation := rangeQuery(t, ctx, queryFrontend.Endpoint("http"), e2ethanos.QueryUpWithoutInstance, 91 timestamp.FromTime(now.Add(-5*time.Minute)), 92 timestamp.FromTime(now), 1, promclient.QueryOptions{ 93 Explain: true, 94 Engine: "thanos", 95 Deduplicate: true, 96 }, func(res model.Matrix) error { 97 if res.Len() == 0 { 98 return fmt.Errorf("expected results") 99 } 100 return nil 101 }) 102 testutil.Assert(t, explanation != nil, "expected to have an explanation") 103 testutil.Ok(t, q.WaitSumMetricsWithOptions(e2emon.GreaterOrEqual(2), []string{"thanos_engine_queries_total"}, e2emon.WaitMissingMetrics(), e2emon.WithLabelMatchers(matchers.MustNewMatcher(matchers.MatchEqual, "fallback", "false")))) 104 }) 105 106 t.Run("passes query to Prometheus engine", func(t *testing.T) { 107 queryAndAssertSeries(t, ctx, queryFrontend.Endpoint("http"), e2ethanos.QueryUpWithoutInstance, time.Now, promclient.QueryOptions{ 108 Deduplicate: false, 109 Engine: "prometheus", 110 }, []model.Metric{ 111 { 112 "job": "myself", 113 "prometheus": "test", 114 "replica": "0", 115 }, 116 }) 117 118 testutil.Ok(t, q.WaitSumMetricsWithOptions(e2emon.GreaterOrEqual(1), []string{"thanos_engine_queries_total"}, e2emon.WaitMissingMetrics(), e2emon.WithLabelMatchers(matchers.MustNewMatcher(matchers.MatchEqual, "fallback", "false")))) 119 testutil.Ok(t, q.WaitSumMetricsWithOptions(e2emon.GreaterOrEqual(float64(queriesBeforeUp)+2), []string{"thanos_query_concurrent_gate_queries_total"}, e2emon.WaitMissingMetrics())) 120 121 }) 122 123 t.Run("explanation works with instant query", func(t *testing.T) { 124 _, explanation := instantQuery(t, ctx, queryFrontend.Endpoint("http"), e2ethanos.QueryUpWithoutInstance, time.Now, promclient.QueryOptions{ 125 Deduplicate: true, 126 Engine: "thanos", 127 Explain: true, 128 }, 1) 129 testutil.Assert(t, explanation != nil, "expected to have an explanation") 130 }) 131 132 t.Run("does not return explanation if not needed", func(t *testing.T) { 133 _, explanation := instantQuery(t, ctx, queryFrontend.Endpoint("http"), e2ethanos.QueryUpWithoutInstance, time.Now, promclient.QueryOptions{ 134 Deduplicate: false, 135 Engine: "thanos", 136 Explain: false, 137 }, 1) 138 testutil.Assert(t, explanation == nil, "expected to have no explanation") 139 }) 140 } 141 142 func TestQueryFrontend(t *testing.T) { 143 t.Parallel() 144 145 e, err := e2e.NewDockerEnvironment("query-frontend") 146 testutil.Ok(t, err) 147 t.Cleanup(e2ethanos.CleanScenario(t, e)) 148 149 now := time.Now() 150 151 prom, sidecar := e2ethanos.NewPrometheusWithSidecar(e, "1", e2ethanos.DefaultPromConfig("test", 0, "", "", e2ethanos.LocalPrometheusTarget), "", e2ethanos.DefaultPrometheusImage(), "") 152 testutil.Ok(t, e2e.StartAndWaitReady(prom, sidecar)) 153 154 q := e2ethanos.NewQuerierBuilder(e, "1", sidecar.InternalEndpoint("grpc")).Init() 155 testutil.Ok(t, e2e.StartAndWaitReady(q)) 156 157 inMemoryCacheConfig := queryfrontend.CacheProviderConfig{ 158 Type: queryfrontend.INMEMORY, 159 Config: queryfrontend.InMemoryResponseCacheConfig{ 160 MaxSizeItems: 1000, 161 Validity: time.Hour, 162 }, 163 } 164 165 cfg := queryfrontend.Config{} 166 queryFrontend := e2ethanos.NewQueryFrontend(e, "1", "http://"+q.InternalEndpoint("http"), cfg, inMemoryCacheConfig) 167 testutil.Ok(t, e2e.StartAndWaitReady(queryFrontend)) 168 169 ctx, cancel := context.WithTimeout(context.Background(), time.Minute) 170 t.Cleanup(cancel) 171 172 testutil.Ok(t, q.WaitSumMetricsWithOptions(e2emon.Equals(1), []string{"thanos_store_nodes_grpc_connections"}, e2emon.WaitMissingMetrics())) 173 174 // Ensure we can get the result from Querier first so that it 175 // doesn't need to retry when we send queries to the frontend later. 176 queryAndAssertSeries(t, ctx, q.Endpoint("http"), e2ethanos.QueryUpWithoutInstance, time.Now, promclient.QueryOptions{ 177 Deduplicate: false, 178 }, []model.Metric{ 179 { 180 "job": "myself", 181 "prometheus": "test", 182 "replica": "0", 183 }, 184 }) 185 186 vals, err := q.SumMetrics([]string{"http_requests_total"}) 187 e2emon.WithLabelMatchers(matchers.MustNewMatcher(matchers.MatchEqual, "handler", "query")) 188 189 testutil.Ok(t, err) 190 testutil.Equals(t, 1, len(vals)) 191 queryTimes := vals[0] 192 193 t.Run("query frontend works for instant query", func(t *testing.T) { 194 queryAndAssertSeries(t, ctx, queryFrontend.Endpoint("http"), e2ethanos.QueryUpWithoutInstance, time.Now, promclient.QueryOptions{ 195 Deduplicate: false, 196 }, []model.Metric{ 197 { 198 "job": "myself", 199 "prometheus": "test", 200 "replica": "0", 201 }, 202 }) 203 204 testutil.Ok(t, queryFrontend.WaitSumMetricsWithOptions( 205 e2emon.Equals(1), 206 []string{"thanos_query_frontend_queries_total"}, 207 e2emon.WithLabelMatchers(matchers.MustNewMatcher(matchers.MatchEqual, "op", "query")), 208 )) 209 210 testutil.Ok(t, q.WaitSumMetricsWithOptions( 211 e2emon.Equals(queryTimes+1), 212 []string{"http_requests_total"}, 213 e2emon.WithLabelMatchers(matchers.MustNewMatcher(matchers.MatchEqual, "handler", "query")), 214 )) 215 }) 216 217 t.Run("query frontend works for range query and it can cache results", func(t *testing.T) { 218 rangeQuery( 219 t, 220 ctx, 221 queryFrontend.Endpoint("http"), 222 e2ethanos.QueryUpWithoutInstance, 223 timestamp.FromTime(now.Add(-time.Hour)), 224 timestamp.FromTime(now.Add(time.Hour)), 225 14, 226 promclient.QueryOptions{ 227 Deduplicate: true, 228 }, 229 func(res model.Matrix) error { 230 if len(res) == 0 { 231 return errors.Errorf("expected some results, got nothing") 232 } 233 return nil 234 }, 235 ) 236 237 testutil.Ok(t, queryFrontend.WaitSumMetricsWithOptions( 238 e2emon.Equals(1), 239 []string{"thanos_query_frontend_queries_total"}, 240 e2emon.WithLabelMatchers(matchers.MustNewMatcher(matchers.MatchEqual, "op", "query_range")), 241 )) 242 testutil.Ok(t, queryFrontend.WaitSumMetrics(e2emon.Equals(1), "cortex_cache_fetched_keys_total")) 243 testutil.Ok(t, queryFrontend.WaitSumMetrics(e2emon.Equals(0), "cortex_cache_hits_total")) 244 testutil.Ok(t, queryFrontend.WaitSumMetrics(e2emon.Equals(1), "querier_cache_added_new_total")) 245 testutil.Ok(t, queryFrontend.WaitSumMetrics(e2emon.Equals(1), "querier_cache_added_total")) 246 testutil.Ok(t, queryFrontend.WaitSumMetrics(e2emon.Equals(1), "querier_cache_entries")) 247 testutil.Ok(t, queryFrontend.WaitSumMetrics(e2emon.Equals(1), "querier_cache_gets_total")) 248 testutil.Ok(t, queryFrontend.WaitSumMetrics(e2emon.Equals(1), "querier_cache_misses_total")) 249 250 // Query is only 2h so it won't be split. 251 testutil.Ok(t, queryFrontend.WaitSumMetrics(e2emon.Equals(1), "thanos_frontend_split_queries_total")) 252 253 testutil.Ok(t, q.WaitSumMetricsWithOptions( 254 e2emon.Equals(1), 255 []string{"http_requests_total"}, 256 e2emon.WithLabelMatchers(matchers.MustNewMatcher(matchers.MatchEqual, "handler", "query_range")), 257 )) 258 }) 259 260 t.Run("same range query, cache hit.", func(t *testing.T) { 261 // Run the same range query again, the result can be retrieved from cache directly. 262 rangeQuery( 263 t, 264 ctx, 265 queryFrontend.Endpoint("http"), 266 e2ethanos.QueryUpWithoutInstance, 267 timestamp.FromTime(now.Add(-time.Hour)), 268 timestamp.FromTime(now.Add(time.Hour)), 269 14, 270 promclient.QueryOptions{ 271 Deduplicate: true, 272 }, 273 func(res model.Matrix) error { 274 if len(res) == 0 { 275 return errors.Errorf("expected some results, got nothing") 276 } 277 return nil 278 }, 279 ) 280 281 testutil.Ok(t, queryFrontend.WaitSumMetricsWithOptions( 282 e2emon.Equals(2), 283 []string{"thanos_query_frontend_queries_total"}, 284 e2emon.WithLabelMatchers(matchers.MustNewMatcher(matchers.MatchEqual, "op", "query_range"))), 285 ) 286 testutil.Ok(t, queryFrontend.WaitSumMetrics(e2emon.Equals(2), "cortex_cache_fetched_keys_total")) 287 testutil.Ok(t, queryFrontend.WaitSumMetrics(e2emon.Equals(1), "cortex_cache_hits_total")) 288 testutil.Ok(t, queryFrontend.WaitSumMetrics(e2emon.Equals(1), "querier_cache_added_new_total")) 289 testutil.Ok(t, queryFrontend.WaitSumMetrics(e2emon.Equals(2), "querier_cache_added_total")) 290 testutil.Ok(t, queryFrontend.WaitSumMetrics(e2emon.Equals(1), "querier_cache_entries")) 291 testutil.Ok(t, queryFrontend.WaitSumMetrics(e2emon.Equals(2), "querier_cache_gets_total")) 292 testutil.Ok(t, queryFrontend.WaitSumMetrics(e2emon.Equals(1), "querier_cache_misses_total")) 293 294 // Query is only 2h so it won't be split. 295 testutil.Ok(t, queryFrontend.WaitSumMetricsWithOptions( 296 e2emon.Equals(2), []string{"thanos_frontend_split_queries_total"}, 297 e2emon.WithLabelMatchers(matchers.MustNewMatcher(matchers.MatchEqual, "tripperware", "query_range"))), 298 ) 299 300 // One more request is needed in order to satisfy the req range. 301 testutil.Ok(t, q.WaitSumMetricsWithOptions( 302 e2emon.Equals(2), 303 []string{"http_requests_total"}, 304 e2emon.WithLabelMatchers(matchers.MustNewMatcher(matchers.MatchEqual, "handler", "query_range"))), 305 ) 306 }) 307 308 t.Run("range query > 24h should be split", func(t *testing.T) { 309 rangeQuery( 310 t, 311 ctx, 312 queryFrontend.Endpoint("http"), 313 e2ethanos.QueryUpWithoutInstance, 314 timestamp.FromTime(now.Add(-time.Hour)), 315 timestamp.FromTime(now.Add(24*time.Hour)), 316 14, 317 promclient.QueryOptions{ 318 Deduplicate: true, 319 }, 320 func(res model.Matrix) error { 321 if len(res) == 0 { 322 return errors.Errorf("expected some results, got nothing") 323 } 324 return nil 325 }, 326 ) 327 328 testutil.Ok(t, queryFrontend.WaitSumMetricsWithOptions( 329 e2emon.Equals(3), 330 []string{"thanos_query_frontend_queries_total"}, 331 e2emon.WithLabelMatchers(matchers.MustNewMatcher(matchers.MatchEqual, "op", "query_range"))), 332 ) 333 testutil.Ok(t, queryFrontend.WaitSumMetrics(e2emon.Equals(3), "cortex_cache_fetched_keys_total")) 334 testutil.Ok(t, queryFrontend.WaitSumMetrics(e2emon.Equals(2), "cortex_cache_hits_total")) 335 testutil.Ok(t, queryFrontend.WaitSumMetrics(e2emon.Equals(1), "querier_cache_added_new_total")) 336 testutil.Ok(t, queryFrontend.WaitSumMetrics(e2emon.Equals(3), "querier_cache_added_total")) 337 testutil.Ok(t, queryFrontend.WaitSumMetrics(e2emon.Equals(1), "querier_cache_entries")) 338 testutil.Ok(t, queryFrontend.WaitSumMetrics(e2emon.Equals(3), "querier_cache_gets_total")) 339 testutil.Ok(t, queryFrontend.WaitSumMetrics(e2emon.Equals(1), "querier_cache_misses_total")) 340 341 // Query is 25h so it will be split to 2 requests. 342 testutil.Ok(t, queryFrontend.WaitSumMetricsWithOptions( 343 e2emon.Equals(4), []string{"thanos_frontend_split_queries_total"}, 344 e2emon.WithLabelMatchers(matchers.MustNewMatcher(matchers.MatchEqual, "tripperware", "query_range"))), 345 ) 346 347 testutil.Ok(t, q.WaitSumMetricsWithOptions( 348 e2emon.Equals(4), 349 []string{"http_requests_total"}, 350 e2emon.WithLabelMatchers(matchers.MustNewMatcher(matchers.MatchEqual, "handler", "query_range"))), 351 ) 352 }) 353 354 t.Run("query frontend splitting works for labels names API", func(t *testing.T) { 355 // LabelNames and LabelValues API should still work via query frontend. 356 labelNames(t, ctx, queryFrontend.Endpoint("http"), nil, timestamp.FromTime(now.Add(-time.Hour)), timestamp.FromTime(now.Add(time.Hour)), func(res []string) bool { 357 return len(res) > 0 358 }) 359 testutil.Ok(t, q.WaitSumMetricsWithOptions( 360 e2emon.Equals(1), 361 []string{"http_requests_total"}, 362 e2emon.WithLabelMatchers(matchers.MustNewMatcher(matchers.MatchEqual, "handler", "label_names"))), 363 ) 364 testutil.Ok(t, queryFrontend.WaitSumMetricsWithOptions( 365 e2emon.Equals(1), 366 []string{"thanos_query_frontend_queries_total"}, 367 e2emon.WithLabelMatchers(matchers.MustNewMatcher(matchers.MatchEqual, "op", "label_names"))), 368 ) 369 // Query is only 2h so it won't be split. 370 testutil.Ok(t, queryFrontend.WaitSumMetricsWithOptions( 371 e2emon.Equals(1), []string{"thanos_frontend_split_queries_total"}, 372 e2emon.WithLabelMatchers(matchers.MustNewMatcher(matchers.MatchEqual, "tripperware", "labels"))), 373 ) 374 375 labelNames(t, ctx, queryFrontend.Endpoint("http"), nil, timestamp.FromTime(now.Add(-24*time.Hour)), timestamp.FromTime(now.Add(time.Hour)), func(res []string) bool { 376 return len(res) > 0 377 }) 378 testutil.Ok(t, q.WaitSumMetricsWithOptions( 379 e2emon.Equals(3), 380 []string{"http_requests_total"}, 381 e2emon.WithLabelMatchers(matchers.MustNewMatcher(matchers.MatchEqual, "handler", "label_names"))), 382 ) 383 testutil.Ok(t, queryFrontend.WaitSumMetricsWithOptions( 384 e2emon.Equals(2), 385 []string{"thanos_query_frontend_queries_total"}, 386 e2emon.WithLabelMatchers(matchers.MustNewMatcher(matchers.MatchEqual, "op", "label_names"))), 387 ) 388 // Query is 25h so split to 2 requests. 389 testutil.Ok(t, queryFrontend.WaitSumMetricsWithOptions( 390 e2emon.Equals(3), []string{"thanos_frontend_split_queries_total"}, 391 e2emon.WithLabelMatchers(matchers.MustNewMatcher(matchers.MatchEqual, "tripperware", "labels"))), 392 ) 393 }) 394 395 t.Run("query frontend splitting works for labels values API", func(t *testing.T) { 396 labelValues(t, ctx, queryFrontend.Endpoint("http"), "instance", nil, timestamp.FromTime(now.Add(-time.Hour)), timestamp.FromTime(now.Add(time.Hour)), func(res []string) bool { 397 return len(res) == 1 && res[0] == "localhost:9090" 398 }) 399 testutil.Ok(t, q.WaitSumMetricsWithOptions( 400 e2emon.Equals(1), 401 []string{"http_requests_total"}, 402 e2emon.WithLabelMatchers(matchers.MustNewMatcher(matchers.MatchEqual, "handler", "label_values"))), 403 ) 404 testutil.Ok(t, queryFrontend.WaitSumMetricsWithOptions( 405 e2emon.Equals(1), 406 []string{"thanos_query_frontend_queries_total"}, 407 e2emon.WithLabelMatchers(matchers.MustNewMatcher(matchers.MatchEqual, "op", "label_values"))), 408 ) 409 // Query is only 2h so it won't be split. 410 testutil.Ok(t, queryFrontend.WaitSumMetricsWithOptions( 411 e2emon.Equals(4), []string{"thanos_frontend_split_queries_total"}, 412 e2emon.WithLabelMatchers(matchers.MustNewMatcher(matchers.MatchEqual, "tripperware", "labels"))), 413 ) 414 415 labelValues(t, ctx, queryFrontend.Endpoint("http"), "instance", nil, timestamp.FromTime(now.Add(-24*time.Hour)), timestamp.FromTime(now.Add(time.Hour)), func(res []string) bool { 416 return len(res) == 1 && res[0] == "localhost:9090" 417 }) 418 testutil.Ok(t, q.WaitSumMetricsWithOptions( 419 e2emon.Equals(3), 420 []string{"http_requests_total"}, 421 e2emon.WithLabelMatchers(matchers.MustNewMatcher(matchers.MatchEqual, "handler", "label_values"))), 422 ) 423 testutil.Ok(t, queryFrontend.WaitSumMetricsWithOptions( 424 e2emon.Equals(2), 425 []string{"thanos_query_frontend_queries_total"}, 426 e2emon.WithLabelMatchers(matchers.MustNewMatcher(matchers.MatchEqual, "op", "label_values"))), 427 ) 428 // Query is 25h so split to 2 requests. 429 testutil.Ok(t, queryFrontend.WaitSumMetricsWithOptions( 430 e2emon.Equals(6), []string{"thanos_frontend_split_queries_total"}, 431 e2emon.WithLabelMatchers(matchers.MustNewMatcher(matchers.MatchEqual, "tripperware", "labels"))), 432 ) 433 }) 434 435 t.Run("query frontend splitting works for series API", func(t *testing.T) { 436 series( 437 t, 438 ctx, 439 queryFrontend.Endpoint("http"), 440 []*labels.Matcher{labels.MustNewMatcher(labels.MatchEqual, "__name__", "up")}, 441 timestamp.FromTime(now.Add(-time.Hour)), 442 timestamp.FromTime(now.Add(time.Hour)), 443 func(res []map[string]string) bool { 444 if len(res) != 1 { 445 return false 446 } 447 448 return reflect.DeepEqual(res[0], map[string]string{ 449 "__name__": "up", 450 "instance": "localhost:9090", 451 "job": "myself", 452 "prometheus": "test", 453 }) 454 }, 455 ) 456 testutil.Ok(t, q.WaitSumMetricsWithOptions( 457 e2emon.Equals(1), 458 []string{"http_requests_total"}, 459 e2emon.WithLabelMatchers(matchers.MustNewMatcher(matchers.MatchEqual, "handler", "series"))), 460 ) 461 testutil.Ok(t, queryFrontend.WaitSumMetricsWithOptions( 462 e2emon.Equals(1), 463 []string{"thanos_query_frontend_queries_total"}, 464 e2emon.WithLabelMatchers(matchers.MustNewMatcher(matchers.MatchEqual, "op", "series"))), 465 ) 466 // Query is only 2h so it won't be split. 467 testutil.Ok(t, queryFrontend.WaitSumMetricsWithOptions( 468 e2emon.Equals(7), []string{"thanos_frontend_split_queries_total"}, 469 e2emon.WithLabelMatchers(matchers.MustNewMatcher(matchers.MatchEqual, "tripperware", "labels"))), 470 ) 471 472 series( 473 t, 474 ctx, 475 queryFrontend.Endpoint("http"), 476 []*labels.Matcher{labels.MustNewMatcher(labels.MatchEqual, "__name__", "up")}, 477 timestamp.FromTime(now.Add(-24*time.Hour)), 478 timestamp.FromTime(now.Add(time.Hour)), 479 func(res []map[string]string) bool { 480 if len(res) != 1 { 481 return false 482 } 483 484 return reflect.DeepEqual(res[0], map[string]string{ 485 "__name__": "up", 486 "instance": "localhost:9090", 487 "job": "myself", 488 "prometheus": "test", 489 }) 490 }, 491 ) 492 testutil.Ok(t, q.WaitSumMetricsWithOptions( 493 e2emon.Equals(3), 494 []string{"http_requests_total"}, 495 e2emon.WithLabelMatchers(matchers.MustNewMatcher(matchers.MatchEqual, "handler", "series"))), 496 ) 497 testutil.Ok(t, queryFrontend.WaitSumMetricsWithOptions( 498 e2emon.Equals(2), 499 []string{"thanos_query_frontend_queries_total"}, 500 e2emon.WithLabelMatchers(matchers.MustNewMatcher(matchers.MatchEqual, "op", "series"))), 501 ) 502 // Query is only 2h so it won't be split. 503 testutil.Ok(t, queryFrontend.WaitSumMetricsWithOptions( 504 e2emon.Equals(9), []string{"thanos_frontend_split_queries_total"}, 505 e2emon.WithLabelMatchers(matchers.MustNewMatcher(matchers.MatchEqual, "tripperware", "labels"))), 506 ) 507 }) 508 } 509 510 func TestQueryFrontendMemcachedCache(t *testing.T) { 511 t.Parallel() 512 513 e, err := e2e.NewDockerEnvironment("qf-memcached") 514 testutil.Ok(t, err) 515 t.Cleanup(e2ethanos.CleanScenario(t, e)) 516 517 now := time.Now() 518 519 prom, sidecar := e2ethanos.NewPrometheusWithSidecar(e, "1", e2ethanos.DefaultPromConfig("test", 0, "", "", e2ethanos.LocalPrometheusTarget), "", e2ethanos.DefaultPrometheusImage(), "") 520 testutil.Ok(t, e2e.StartAndWaitReady(prom, sidecar)) 521 522 q := e2ethanos.NewQuerierBuilder(e, "1", sidecar.InternalEndpoint("grpc")).Init() 523 testutil.Ok(t, e2e.StartAndWaitReady(q)) 524 525 memcached := e2ethanos.NewMemcached(e, "1") 526 testutil.Ok(t, e2e.StartAndWaitReady(memcached)) 527 528 memCachedConfig := queryfrontend.CacheProviderConfig{ 529 Type: queryfrontend.MEMCACHED, 530 Config: queryfrontend.MemcachedResponseCacheConfig{ 531 Memcached: cacheutil.MemcachedClientConfig{ 532 Addresses: []string{memcached.InternalEndpoint("memcached")}, 533 MaxIdleConnections: 100, 534 MaxAsyncConcurrency: 20, 535 MaxGetMultiConcurrency: 100, 536 MaxGetMultiBatchSize: 0, 537 Timeout: time.Minute, 538 MaxAsyncBufferSize: 10000, 539 DNSProviderUpdateInterval: 10 * time.Second, 540 }, 541 }, 542 } 543 544 cfg := queryfrontend.Config{} 545 queryFrontend := e2ethanos.NewQueryFrontend(e, "1", "http://"+q.InternalEndpoint("http"), cfg, memCachedConfig) 546 testutil.Ok(t, e2e.StartAndWaitReady(queryFrontend)) 547 548 ctx, cancel := context.WithTimeout(context.Background(), time.Minute) 549 t.Cleanup(cancel) 550 551 testutil.Ok(t, q.WaitSumMetricsWithOptions(e2emon.Equals(1), []string{"thanos_store_nodes_grpc_connections"}, e2emon.WaitMissingMetrics())) 552 553 testutil.Ok(t, queryFrontend.WaitSumMetrics(e2emon.Equals(1), "cortex_memcache_client_servers")) 554 555 // Ensure we can get the result from Querier first so that it 556 // doesn't need to retry when we send queries to the frontend later. 557 queryAndAssertSeries(t, ctx, q.Endpoint("http"), e2ethanos.QueryUpWithoutInstance, time.Now, promclient.QueryOptions{ 558 Deduplicate: false, 559 }, []model.Metric{ 560 { 561 "job": "myself", 562 "prometheus": "test", 563 "replica": "0", 564 }, 565 }) 566 567 vals, err := q.SumMetrics([]string{"http_requests_total"}, e2emon.WithLabelMatchers( 568 matchers.MustNewMatcher(matchers.MatchEqual, "handler", "query"))) 569 testutil.Ok(t, err) 570 testutil.Equals(t, 1, len(vals)) 571 572 rangeQuery( 573 t, 574 ctx, 575 queryFrontend.Endpoint("http"), 576 e2ethanos.QueryUpWithoutInstance, 577 timestamp.FromTime(now.Add(-time.Hour)), 578 timestamp.FromTime(now.Add(time.Hour)), 579 14, 580 promclient.QueryOptions{ 581 Deduplicate: true, 582 }, 583 func(res model.Matrix) error { 584 if len(res) == 0 { 585 return errors.Errorf("expected some results, got nothing") 586 } 587 return nil 588 }, 589 ) 590 591 testutil.Ok(t, queryFrontend.WaitSumMetricsWithOptions( 592 e2emon.Equals(1), 593 []string{"thanos_query_frontend_queries_total"}, 594 e2emon.WithLabelMatchers(matchers.MustNewMatcher(matchers.MatchEqual, "op", "query_range"))), 595 ) 596 597 testutil.Ok(t, queryFrontend.WaitSumMetrics(e2emon.Equals(1), "cortex_cache_fetched_keys_total")) 598 testutil.Ok(t, queryFrontend.WaitSumMetrics(e2emon.Equals(0), "cortex_cache_hits_total")) 599 600 // Query is only 2h so it won't be split. 601 testutil.Ok(t, queryFrontend.WaitSumMetrics(e2emon.Equals(1), "thanos_frontend_split_queries_total")) 602 603 // Run the same range query again, the result can be retrieved from cache directly. 604 rangeQuery( 605 t, 606 ctx, 607 queryFrontend.Endpoint("http"), 608 e2ethanos.QueryUpWithoutInstance, 609 timestamp.FromTime(now.Add(-time.Hour)), 610 timestamp.FromTime(now.Add(time.Hour)), 611 14, 612 promclient.QueryOptions{ 613 Deduplicate: true, 614 }, 615 func(res model.Matrix) error { 616 if len(res) == 0 { 617 return errors.Errorf("expected some results, got nothing") 618 } 619 return nil 620 }, 621 ) 622 623 testutil.Ok(t, queryFrontend.WaitSumMetricsWithOptions( 624 e2emon.Equals(2), 625 []string{"thanos_query_frontend_queries_total"}, 626 e2emon.WithLabelMatchers(matchers.MustNewMatcher(matchers.MatchEqual, "op", "query_range"))), 627 ) 628 629 // Query is only 2h so it won't be split. 630 // If it was split this would be increase by more then 1. 631 testutil.Ok(t, queryFrontend.WaitSumMetrics(e2emon.Equals(2), "thanos_frontend_split_queries_total")) 632 633 testutil.Ok(t, queryFrontend.WaitSumMetrics(e2emon.Equals(2), "cortex_cache_fetched_keys_total")) 634 testutil.Ok(t, queryFrontend.WaitSumMetrics(e2emon.Equals(1), "cortex_cache_hits_total")) 635 } 636 637 func TestRangeQueryShardingWithRandomData(t *testing.T) { 638 t.Parallel() 639 640 e, err := e2e.NewDockerEnvironment("rq-sharding") 641 testutil.Ok(t, err) 642 t.Cleanup(e2ethanos.CleanScenario(t, e)) 643 644 promConfig := e2ethanos.DefaultPromConfig("p1", 0, "", "") 645 prom, sidecar := e2ethanos.NewPrometheusWithSidecar(e, "p1", promConfig, "", e2ethanos.DefaultPrometheusImage(), "", "remote-write-receiver") 646 647 now := model.Now() 648 ctx := context.Background() 649 timeSeries := []labels.Labels{ 650 {{Name: labels.MetricName, Value: "http_requests_total"}, {Name: "pod", Value: "1"}, {Name: "handler", Value: "/"}}, 651 {{Name: labels.MetricName, Value: "http_requests_total"}, {Name: "pod", Value: "1"}, {Name: "handler", Value: "/metrics"}}, 652 {{Name: labels.MetricName, Value: "http_requests_total"}, {Name: "pod", Value: "2"}, {Name: "handler", Value: "/"}}, 653 {{Name: labels.MetricName, Value: "http_requests_total"}, {Name: "pod", Value: "2"}, {Name: "handler", Value: "/metrics"}}, 654 {{Name: labels.MetricName, Value: "http_requests_total"}, {Name: "pod", Value: "3"}, {Name: "handler", Value: "/"}}, 655 {{Name: labels.MetricName, Value: "http_requests_total"}, {Name: "pod", Value: "3"}, {Name: "handler", Value: "/metrics"}}, 656 {{Name: labels.MetricName, Value: "http_requests_total"}, {Name: "pod", Value: "4"}, {Name: "handler", Value: "/"}}, 657 {{Name: labels.MetricName, Value: "http_requests_total"}, {Name: "pod", Value: "4"}, {Name: "handler", Value: "/metrics"}}, 658 {{Name: labels.MetricName, Value: "http_requests_total"}, {Name: "pod", Value: "5"}, {Name: "handler", Value: "/"}}, 659 {{Name: labels.MetricName, Value: "http_requests_total"}, {Name: "pod", Value: "5"}, {Name: "handler", Value: "/metrics"}}, 660 {{Name: labels.MetricName, Value: "http_requests_total"}, {Name: "pod", Value: "6"}, {Name: "handler", Value: "/"}}, 661 {{Name: labels.MetricName, Value: "http_requests_total"}, {Name: "pod", Value: "6"}, {Name: "handler", Value: "/metrics"}}, 662 } 663 664 startTime := now.Time().Add(-1 * time.Hour) 665 endTime := now.Time().Add(1 * time.Hour) 666 _, err = e2eutil.CreateBlock(ctx, prom.Dir(), timeSeries, 20, timestamp.FromTime(startTime), timestamp.FromTime(endTime), nil, 0, metadata.NoneFunc) 667 testutil.Ok(t, err) 668 testutil.Ok(t, e2e.StartAndWaitReady(prom, sidecar)) 669 670 stores := []string{sidecar.InternalEndpoint("grpc")} 671 q1 := e2ethanos.NewQuerierBuilder(e, "q1", stores...).Init() 672 testutil.Ok(t, e2e.StartAndWaitReady(q1)) 673 674 inMemoryCacheConfig := queryfrontend.CacheProviderConfig{ 675 Type: queryfrontend.INMEMORY, 676 Config: queryfrontend.InMemoryResponseCacheConfig{ 677 MaxSizeItems: 1000, 678 Validity: time.Hour, 679 }, 680 } 681 config := queryfrontend.Config{ 682 QueryRangeConfig: queryfrontend.QueryRangeConfig{ 683 AlignRangeWithStep: false, 684 }, 685 NumShards: 2, 686 } 687 qfe := e2ethanos.NewQueryFrontend(e, "query-frontend", "http://"+q1.InternalEndpoint("http"), config, inMemoryCacheConfig) 688 testutil.Ok(t, e2e.StartAndWaitReady(qfe)) 689 690 qryFunc := func() string { return `sum by (pod) (http_requests_total)` } 691 queryOpts := promclient.QueryOptions{Deduplicate: true} 692 693 var resultWithoutSharding model.Matrix 694 rangeQuery(t, ctx, q1.Endpoint("http"), qryFunc, timestamp.FromTime(startTime), timestamp.FromTime(endTime), 30, queryOpts, func(res model.Matrix) error { 695 resultWithoutSharding = res 696 return nil 697 }) 698 var resultWithSharding model.Matrix 699 rangeQuery(t, ctx, qfe.Endpoint("http"), qryFunc, timestamp.FromTime(startTime), timestamp.FromTime(endTime), 30, queryOpts, func(res model.Matrix) error { 700 resultWithSharding = res 701 return nil 702 }) 703 704 testutil.Equals(t, resultWithoutSharding, resultWithSharding) 705 } 706 707 func TestRangeQueryDynamicHorizontalSharding(t *testing.T) { 708 t.Parallel() 709 710 e, err := e2e.New(e2e.WithName("qfe-dyn-sharding")) 711 testutil.Ok(t, err) 712 t.Cleanup(e2ethanos.CleanScenario(t, e)) 713 714 now := time.Now() 715 716 prom, sidecar := e2ethanos.NewPrometheusWithSidecar(e, "1", e2ethanos.DefaultPromConfig("test", 0, "", "", e2ethanos.LocalPrometheusTarget), "", e2ethanos.DefaultPrometheusImage(), "") 717 testutil.Ok(t, e2e.StartAndWaitReady(prom, sidecar)) 718 719 querier := e2ethanos.NewQuerierBuilder(e, "1", sidecar.InternalEndpoint("grpc")).Init() 720 testutil.Ok(t, e2e.StartAndWaitReady(querier)) 721 722 inMemoryCacheConfig := queryfrontend.CacheProviderConfig{ 723 Type: queryfrontend.INMEMORY, 724 Config: queryfrontend.InMemoryResponseCacheConfig{ 725 MaxSizeItems: 1000, 726 Validity: time.Hour, 727 }, 728 } 729 730 cfg := queryfrontend.Config{ 731 QueryRangeConfig: queryfrontend.QueryRangeConfig{ 732 MinQuerySplitInterval: time.Hour, 733 MaxQuerySplitInterval: 12 * time.Hour, 734 HorizontalShards: 4, 735 SplitQueriesByInterval: 0, 736 }, 737 } 738 queryFrontend := e2ethanos.NewQueryFrontend(e, "1", "http://"+querier.InternalEndpoint("http"), cfg, inMemoryCacheConfig) 739 testutil.Ok(t, e2e.StartAndWaitReady(queryFrontend)) 740 741 ctx, cancel := context.WithTimeout(context.Background(), time.Minute) 742 t.Cleanup(cancel) 743 744 testutil.Ok(t, querier.WaitSumMetricsWithOptions(e2emon.Equals(1), []string{"thanos_store_nodes_grpc_connections"}, e2emon.WaitMissingMetrics())) 745 746 // Ensure we can get the result from Querier first so that it 747 // doesn't need to retry when we send queries to the frontend later. 748 queryAndAssertSeries(t, ctx, querier.Endpoint("http"), e2ethanos.QueryUpWithoutInstance, time.Now, promclient.QueryOptions{ 749 Deduplicate: false, 750 }, []model.Metric{ 751 { 752 "job": "myself", 753 "prometheus": "test", 754 "replica": "0", 755 }, 756 }) 757 758 // -- test starts here -- 759 rangeQuery( 760 t, 761 ctx, 762 queryFrontend.Endpoint("http"), 763 e2ethanos.QueryUpWithoutInstance, 764 timestamp.FromTime(now.Add(-time.Hour)), 765 timestamp.FromTime(now.Add(time.Hour)), 766 14, 767 promclient.QueryOptions{ 768 Deduplicate: true, 769 }, 770 func(res model.Matrix) error { 771 if len(res) == 0 { 772 return errors.Errorf("expected some results, got nothing") 773 } 774 return nil 775 }, 776 ) 777 778 testutil.Ok(t, queryFrontend.WaitSumMetricsWithOptions( 779 e2emon.Equals(1), 780 []string{"thanos_query_frontend_queries_total"}, 781 e2emon.WithLabelMatchers(matchers.MustNewMatcher(matchers.MatchEqual, "op", "query_range")), 782 )) 783 784 // make sure that we don't break cortex cache code. 785 testutil.Ok(t, queryFrontend.WaitSumMetrics(e2emon.Equals(3), "cortex_cache_fetched_keys_total")) 786 testutil.Ok(t, queryFrontend.WaitSumMetrics(e2emon.Equals(0), "cortex_cache_hits_total")) 787 testutil.Ok(t, queryFrontend.WaitSumMetrics(e2emon.Equals(2), "querier_cache_added_new_total")) 788 testutil.Ok(t, queryFrontend.WaitSumMetrics(e2emon.Equals(3), "querier_cache_added_total")) 789 testutil.Ok(t, queryFrontend.WaitSumMetrics(e2emon.Equals(3), "querier_cache_misses_total")) 790 791 // Query interval is 2 hours, which is greater than min-slit-interval, query will be broken down into 4 parts 792 // + rest (of interval) 793 testutil.Ok(t, queryFrontend.WaitSumMetrics(e2emon.Equals(5), "thanos_frontend_split_queries_total")) 794 795 testutil.Ok(t, querier.WaitSumMetricsWithOptions( 796 e2emon.Equals(5), 797 []string{"http_requests_total"}, 798 e2emon.WithLabelMatchers(matchers.MustNewMatcher(matchers.MatchEqual, "handler", "query_range")), 799 )) 800 } 801 802 func TestInstantQueryShardingWithRandomData(t *testing.T) { 803 t.Parallel() 804 805 e, err := e2e.NewDockerEnvironment("query-sharding") 806 testutil.Ok(t, err) 807 t.Cleanup(e2ethanos.CleanScenario(t, e)) 808 809 promConfig := e2ethanos.DefaultPromConfig("p1", 0, "", "") 810 prom, sidecar := e2ethanos.NewPrometheusWithSidecar(e, "p1", promConfig, "", e2ethanos.DefaultPrometheusImage(), "", "remote-write-receiver") 811 812 now := model.Now() 813 ctx := context.Background() 814 timeSeries := []labels.Labels{ 815 {{Name: labels.MetricName, Value: "http_requests_total"}, {Name: "pod", Value: "1"}, {Name: "handler", Value: "/"}}, 816 {{Name: labels.MetricName, Value: "http_requests_total"}, {Name: "pod", Value: "1"}, {Name: "handler", Value: "/metrics"}}, 817 {{Name: labels.MetricName, Value: "http_requests_total"}, {Name: "pod", Value: "2"}, {Name: "handler", Value: "/"}}, 818 {{Name: labels.MetricName, Value: "http_requests_total"}, {Name: "pod", Value: "2"}, {Name: "handler", Value: "/metrics"}}, 819 {{Name: labels.MetricName, Value: "http_requests_total"}, {Name: "pod", Value: "3"}, {Name: "handler", Value: "/"}}, 820 {{Name: labels.MetricName, Value: "http_requests_total"}, {Name: "pod", Value: "3"}, {Name: "handler", Value: "/metrics"}}, 821 {{Name: labels.MetricName, Value: "http_requests_total"}, {Name: "pod", Value: "4"}, {Name: "handler", Value: "/"}}, 822 {{Name: labels.MetricName, Value: "http_requests_total"}, {Name: "pod", Value: "4"}, {Name: "handler", Value: "/metrics"}}, 823 {{Name: labels.MetricName, Value: "http_requests_total"}, {Name: "pod", Value: "5"}, {Name: "handler", Value: "/"}}, 824 {{Name: labels.MetricName, Value: "http_requests_total"}, {Name: "pod", Value: "5"}, {Name: "handler", Value: "/metrics"}}, 825 {{Name: labels.MetricName, Value: "http_requests_total"}, {Name: "pod", Value: "6"}, {Name: "handler", Value: "/"}}, 826 {{Name: labels.MetricName, Value: "http_requests_total"}, {Name: "pod", Value: "6"}, {Name: "handler", Value: "/metrics"}}, 827 } 828 829 // Ensure labels are ordered. 830 for _, ts := range timeSeries { 831 sort.Slice(ts, func(i, j int) bool { 832 return ts[i].Name < ts[j].Name 833 }) 834 } 835 836 startTime := now.Time().Add(-1 * time.Hour) 837 endTime := now.Time().Add(1 * time.Hour) 838 _, err = e2eutil.CreateBlock(ctx, prom.Dir(), timeSeries, 20, timestamp.FromTime(startTime), timestamp.FromTime(endTime), nil, 0, metadata.NoneFunc) 839 testutil.Ok(t, err) 840 testutil.Ok(t, e2e.StartAndWaitReady(prom, sidecar)) 841 842 stores := []string{sidecar.InternalEndpoint("grpc")} 843 q1 := e2ethanos.NewQuerierBuilder(e, "q1", stores...).Init() 844 testutil.Ok(t, e2e.StartAndWaitReady(q1)) 845 846 inMemoryCacheConfig := queryfrontend.CacheProviderConfig{ 847 Type: queryfrontend.INMEMORY, 848 Config: queryfrontend.InMemoryResponseCacheConfig{ 849 MaxSizeItems: 1000, 850 Validity: time.Hour, 851 }, 852 } 853 config := queryfrontend.Config{ 854 QueryRangeConfig: queryfrontend.QueryRangeConfig{ 855 AlignRangeWithStep: false, 856 }, 857 NumShards: 2, 858 } 859 qfe := e2ethanos.NewQueryFrontend(e, "query-frontend", "http://"+q1.InternalEndpoint("http"), config, inMemoryCacheConfig) 860 testutil.Ok(t, e2e.StartAndWaitReady(qfe)) 861 862 queryOpts := promclient.QueryOptions{Deduplicate: true} 863 for _, tc := range []struct { 864 name string 865 qryFunc func() string 866 expectedSeries int 867 }{ 868 { 869 name: "aggregation", 870 qryFunc: func() string { return `sum(http_requests_total)` }, 871 expectedSeries: 1, 872 }, 873 { 874 name: "outer aggregation with no grouping", 875 qryFunc: func() string { return `count(sum by (pod) (http_requests_total))` }, 876 expectedSeries: 1, 877 }, 878 { 879 name: "scalar", 880 qryFunc: func() string { return `1 + 1` }, 881 expectedSeries: 1, 882 }, 883 { 884 name: "binary expression", 885 qryFunc: func() string { return `http_requests_total{pod="1"} / http_requests_total` }, 886 expectedSeries: 2, 887 }, 888 { 889 name: "binary expression with constant", 890 qryFunc: func() string { return `http_requests_total / 2` }, 891 expectedSeries: 12, 892 }, 893 { 894 name: "vector selector", 895 qryFunc: func() string { return `http_requests_total` }, 896 expectedSeries: 12, 897 }, 898 { 899 name: "aggregation with grouping", 900 qryFunc: func() string { return `sum by (pod) (http_requests_total)` }, 901 expectedSeries: 6, 902 }, 903 { 904 name: "aggregate without grouping", 905 qryFunc: func() string { return `sum without (pod) (http_requests_total)` }, 906 expectedSeries: 2, 907 }, 908 { 909 name: "multiple aggregations with grouping", 910 qryFunc: func() string { return `max by (handler) (sum(http_requests_total) by (pod, handler))` }, 911 expectedSeries: 2, 912 }, 913 } { 914 t.Run(tc.name, func(t *testing.T) { 915 resultWithoutSharding, _ := instantQuery(t, ctx, q1.Endpoint("http"), tc.qryFunc, func() time.Time { 916 return now.Time() 917 }, queryOpts, tc.expectedSeries) 918 resultWithSharding, _ := instantQuery(t, ctx, qfe.Endpoint("http"), tc.qryFunc, func() time.Time { 919 return now.Time() 920 }, queryOpts, tc.expectedSeries) 921 testutil.Equals(t, resultWithoutSharding, resultWithSharding) 922 }) 923 } 924 }