github.com/muhammadn/cortex@v1.9.1-0.20220510110439-46bb7000d03d/pkg/ingester/ingester_test.go (about) 1 package ingester 2 3 import ( 4 "context" 5 "fmt" 6 "io/ioutil" 7 "math" 8 "math/rand" 9 "net/http" 10 "os" 11 "path/filepath" 12 "sort" 13 "strconv" 14 "strings" 15 "sync" 16 "testing" 17 "time" 18 19 "github.com/go-kit/log" 20 "github.com/grafana/dskit/ring" 21 "github.com/grafana/dskit/services" 22 "github.com/prometheus/client_golang/prometheus" 23 "github.com/prometheus/client_golang/prometheus/testutil" 24 "github.com/prometheus/common/model" 25 "github.com/prometheus/prometheus/pkg/labels" 26 "github.com/stretchr/testify/assert" 27 "github.com/stretchr/testify/require" 28 "github.com/weaveworks/common/httpgrpc" 29 "github.com/weaveworks/common/user" 30 "google.golang.org/grpc" 31 32 "github.com/cortexproject/cortex/pkg/chunk" 33 promchunk "github.com/cortexproject/cortex/pkg/chunk/encoding" 34 "github.com/cortexproject/cortex/pkg/cortexpb" 35 "github.com/cortexproject/cortex/pkg/ingester/client" 36 "github.com/cortexproject/cortex/pkg/util/chunkcompat" 37 "github.com/cortexproject/cortex/pkg/util/test" 38 "github.com/cortexproject/cortex/pkg/util/validation" 39 ) 40 41 type testStore struct { 42 mtx sync.Mutex 43 // Chunks keyed by userID. 44 chunks map[string][]chunk.Chunk 45 } 46 47 func newTestStore(t require.TestingT, cfg Config, clientConfig client.Config, limits validation.Limits, reg prometheus.Registerer) (*testStore, *Ingester) { 48 store := &testStore{ 49 chunks: map[string][]chunk.Chunk{}, 50 } 51 overrides, err := validation.NewOverrides(limits, nil) 52 require.NoError(t, err) 53 54 ing, err := New(cfg, clientConfig, overrides, store, reg, log.NewNopLogger()) 55 require.NoError(t, err) 56 require.NoError(t, services.StartAndAwaitRunning(context.Background(), ing)) 57 58 return store, ing 59 } 60 61 func newDefaultTestStore(t testing.TB) (*testStore, *Ingester) { 62 t.Helper() 63 64 return newTestStore(t, 65 defaultIngesterTestConfig(t), 66 defaultClientTestConfig(), 67 defaultLimitsTestConfig(), nil) 68 } 69 70 func (s *testStore) Put(ctx context.Context, chunks []chunk.Chunk) error { 71 if len(chunks) == 0 { 72 return nil 73 } 74 s.mtx.Lock() 75 defer s.mtx.Unlock() 76 77 for _, chunk := range chunks { 78 for _, v := range chunk.Metric { 79 if v.Value == "" { 80 return fmt.Errorf("Chunk has blank label %q", v.Name) 81 } 82 } 83 } 84 userID := chunks[0].UserID 85 s.chunks[userID] = append(s.chunks[userID], chunks...) 86 return nil 87 } 88 89 func (s *testStore) Stop() {} 90 91 // check that the store is holding data equivalent to what we expect 92 func (s *testStore) checkData(t *testing.T, userIDs []string, testData map[string]model.Matrix) { 93 s.mtx.Lock() 94 defer s.mtx.Unlock() 95 for _, userID := range userIDs { 96 res, err := chunk.ChunksToMatrix(context.Background(), s.chunks[userID], model.Time(0), model.Time(math.MaxInt64)) 97 require.NoError(t, err) 98 sort.Sort(res) 99 assert.Equal(t, testData[userID], res, "userID %s", userID) 100 } 101 } 102 103 func buildTestMatrix(numSeries int, samplesPerSeries int, offset int) model.Matrix { 104 m := make(model.Matrix, 0, numSeries) 105 for i := 0; i < numSeries; i++ { 106 ss := model.SampleStream{ 107 Metric: model.Metric{ 108 model.MetricNameLabel: model.LabelValue(fmt.Sprintf("testmetric_%d", i)), 109 model.JobLabel: model.LabelValue(fmt.Sprintf("testjob%d", i%2)), 110 }, 111 Values: make([]model.SamplePair, 0, samplesPerSeries), 112 } 113 for j := 0; j < samplesPerSeries; j++ { 114 ss.Values = append(ss.Values, model.SamplePair{ 115 Timestamp: model.Time(i + j + offset), 116 Value: model.SampleValue(i + j + offset), 117 }) 118 } 119 m = append(m, &ss) 120 } 121 sort.Sort(m) 122 return m 123 } 124 125 func matrixToSamples(m model.Matrix) []cortexpb.Sample { 126 var samples []cortexpb.Sample 127 for _, ss := range m { 128 for _, sp := range ss.Values { 129 samples = append(samples, cortexpb.Sample{ 130 TimestampMs: int64(sp.Timestamp), 131 Value: float64(sp.Value), 132 }) 133 } 134 } 135 return samples 136 } 137 138 // Return one copy of the labels per sample 139 func matrixToLables(m model.Matrix) []labels.Labels { 140 var labels []labels.Labels 141 for _, ss := range m { 142 for range ss.Values { 143 labels = append(labels, cortexpb.FromLabelAdaptersToLabels(cortexpb.FromMetricsToLabelAdapters(ss.Metric))) 144 } 145 } 146 return labels 147 } 148 149 func runTestQuery(ctx context.Context, t *testing.T, ing *Ingester, ty labels.MatchType, n, v string) (model.Matrix, *client.QueryRequest, error) { 150 return runTestQueryTimes(ctx, t, ing, ty, n, v, model.Earliest, model.Latest) 151 } 152 153 func runTestQueryTimes(ctx context.Context, t *testing.T, ing *Ingester, ty labels.MatchType, n, v string, start, end model.Time) (model.Matrix, *client.QueryRequest, error) { 154 matcher, err := labels.NewMatcher(ty, n, v) 155 if err != nil { 156 return nil, nil, err 157 } 158 req, err := client.ToQueryRequest(start, end, []*labels.Matcher{matcher}) 159 if err != nil { 160 return nil, nil, err 161 } 162 resp, err := ing.Query(ctx, req) 163 if err != nil { 164 return nil, nil, err 165 } 166 res := client.FromQueryResponse(resp) 167 sort.Sort(res) 168 return res, req, nil 169 } 170 171 func pushTestMetadata(t *testing.T, ing *Ingester, numMetadata, metadataPerMetric int) ([]string, map[string][]*cortexpb.MetricMetadata) { 172 userIDs := []string{"1", "2", "3"} 173 174 // Create test metadata. 175 // Map of userIDs, to map of metric => metadataSet 176 testData := map[string][]*cortexpb.MetricMetadata{} 177 for _, userID := range userIDs { 178 metadata := make([]*cortexpb.MetricMetadata, 0, metadataPerMetric) 179 for i := 0; i < numMetadata; i++ { 180 metricName := fmt.Sprintf("testmetric_%d", i) 181 for j := 0; j < metadataPerMetric; j++ { 182 m := &cortexpb.MetricMetadata{MetricFamilyName: metricName, Help: fmt.Sprintf("a help for %d", j), Unit: "", Type: cortexpb.COUNTER} 183 metadata = append(metadata, m) 184 } 185 } 186 testData[userID] = metadata 187 } 188 189 // Append metadata. 190 for _, userID := range userIDs { 191 ctx := user.InjectOrgID(context.Background(), userID) 192 _, err := ing.Push(ctx, cortexpb.ToWriteRequest(nil, nil, testData[userID], cortexpb.API)) 193 require.NoError(t, err) 194 } 195 196 return userIDs, testData 197 } 198 199 func pushTestSamples(t testing.TB, ing *Ingester, numSeries, samplesPerSeries, offset int) ([]string, map[string]model.Matrix) { 200 userIDs := []string{"1", "2", "3"} 201 202 // Create test samples. 203 testData := map[string]model.Matrix{} 204 for i, userID := range userIDs { 205 testData[userID] = buildTestMatrix(numSeries, samplesPerSeries, i+offset) 206 } 207 208 // Append samples. 209 for _, userID := range userIDs { 210 ctx := user.InjectOrgID(context.Background(), userID) 211 _, err := ing.Push(ctx, cortexpb.ToWriteRequest(matrixToLables(testData[userID]), matrixToSamples(testData[userID]), nil, cortexpb.API)) 212 require.NoError(t, err) 213 } 214 215 return userIDs, testData 216 } 217 218 func retrieveTestSamples(t *testing.T, ing *Ingester, userIDs []string, testData map[string]model.Matrix) { 219 // Read samples back via ingester queries. 220 for _, userID := range userIDs { 221 ctx := user.InjectOrgID(context.Background(), userID) 222 res, req, err := runTestQuery(ctx, t, ing, labels.MatchRegexp, model.JobLabel, ".+") 223 require.NoError(t, err) 224 assert.Equal(t, testData[userID], res) 225 226 s := stream{ 227 ctx: ctx, 228 } 229 err = ing.QueryStream(req, &s) 230 require.NoError(t, err) 231 232 res, err = chunkcompat.StreamsToMatrix(model.Earliest, model.Latest, s.responses) 233 require.NoError(t, err) 234 assert.Equal(t, testData[userID].String(), res.String()) 235 } 236 } 237 238 func TestIngesterAppend(t *testing.T) { 239 store, ing := newDefaultTestStore(t) 240 userIDs, testData := pushTestSamples(t, ing, 10, 1000, 0) 241 retrieveTestSamples(t, ing, userIDs, testData) 242 243 // Read samples back via chunk store. 244 require.NoError(t, services.StopAndAwaitTerminated(context.Background(), ing)) 245 store.checkData(t, userIDs, testData) 246 } 247 248 func TestIngesterMetadataAppend(t *testing.T) { 249 for _, tc := range []struct { 250 desc string 251 numMetadata int 252 metadataPerMetric int 253 expectedMetrics int 254 expectedMetadata int 255 err error 256 }{ 257 {"with no metadata", 0, 0, 0, 0, nil}, 258 {"with one metadata per metric", 10, 1, 10, 10, nil}, 259 {"with multiple metadata per metric", 10, 3, 10, 30, nil}, 260 } { 261 t.Run(tc.desc, func(t *testing.T) { 262 limits := defaultLimitsTestConfig() 263 limits.MaxLocalMetadataPerMetric = 50 264 _, ing := newTestStore(t, defaultIngesterTestConfig(t), defaultClientTestConfig(), limits, nil) 265 userIDs, _ := pushTestMetadata(t, ing, tc.numMetadata, tc.metadataPerMetric) 266 267 for _, userID := range userIDs { 268 ctx := user.InjectOrgID(context.Background(), userID) 269 resp, err := ing.MetricsMetadata(ctx, nil) 270 271 if tc.err != nil { 272 require.Equal(t, tc.err, err) 273 } else { 274 require.NoError(t, err) 275 require.NotNil(t, resp) 276 277 metricTracker := map[string]bool{} 278 for _, m := range resp.Metadata { 279 _, ok := metricTracker[m.GetMetricFamilyName()] 280 if !ok { 281 metricTracker[m.GetMetricFamilyName()] = true 282 } 283 } 284 285 require.Equal(t, tc.expectedMetrics, len(metricTracker)) 286 require.Equal(t, tc.expectedMetadata, len(resp.Metadata)) 287 } 288 } 289 }) 290 } 291 } 292 293 func TestIngesterPurgeMetadata(t *testing.T) { 294 cfg := defaultIngesterTestConfig(t) 295 cfg.MetadataRetainPeriod = 20 * time.Millisecond 296 _, ing := newTestStore(t, cfg, defaultClientTestConfig(), defaultLimitsTestConfig(), nil) 297 userIDs, _ := pushTestMetadata(t, ing, 10, 3) 298 299 time.Sleep(40 * time.Millisecond) 300 for _, userID := range userIDs { 301 ctx := user.InjectOrgID(context.Background(), userID) 302 ing.purgeUserMetricsMetadata() 303 304 resp, err := ing.MetricsMetadata(ctx, nil) 305 require.NoError(t, err) 306 assert.Equal(t, 0, len(resp.GetMetadata())) 307 } 308 } 309 310 func TestIngesterMetadataMetrics(t *testing.T) { 311 reg := prometheus.NewPedanticRegistry() 312 cfg := defaultIngesterTestConfig(t) 313 cfg.MetadataRetainPeriod = 20 * time.Millisecond 314 _, ing := newTestStore(t, cfg, defaultClientTestConfig(), defaultLimitsTestConfig(), reg) 315 _, _ = pushTestMetadata(t, ing, 10, 3) 316 317 pushTestMetadata(t, ing, 10, 3) 318 pushTestMetadata(t, ing, 10, 3) // We push the _exact_ same metrics again to ensure idempotency. Metadata is kept as a set so there shouldn't be a change of metrics. 319 320 metricNames := []string{ 321 "cortex_ingester_memory_metadata_created_total", 322 "cortex_ingester_memory_metadata_removed_total", 323 "cortex_ingester_memory_metadata", 324 } 325 326 assert.NoError(t, testutil.GatherAndCompare(reg, strings.NewReader(` 327 # HELP cortex_ingester_memory_metadata The current number of metadata in memory. 328 # TYPE cortex_ingester_memory_metadata gauge 329 cortex_ingester_memory_metadata 90 330 # HELP cortex_ingester_memory_metadata_created_total The total number of metadata that were created per user 331 # TYPE cortex_ingester_memory_metadata_created_total counter 332 cortex_ingester_memory_metadata_created_total{user="1"} 30 333 cortex_ingester_memory_metadata_created_total{user="2"} 30 334 cortex_ingester_memory_metadata_created_total{user="3"} 30 335 `), metricNames...)) 336 337 time.Sleep(40 * time.Millisecond) 338 ing.purgeUserMetricsMetadata() 339 assert.NoError(t, testutil.GatherAndCompare(reg, strings.NewReader(` 340 # HELP cortex_ingester_memory_metadata The current number of metadata in memory. 341 # TYPE cortex_ingester_memory_metadata gauge 342 cortex_ingester_memory_metadata 0 343 # HELP cortex_ingester_memory_metadata_created_total The total number of metadata that were created per user 344 # TYPE cortex_ingester_memory_metadata_created_total counter 345 cortex_ingester_memory_metadata_created_total{user="1"} 30 346 cortex_ingester_memory_metadata_created_total{user="2"} 30 347 cortex_ingester_memory_metadata_created_total{user="3"} 30 348 # HELP cortex_ingester_memory_metadata_removed_total The total number of metadata that were removed per user. 349 # TYPE cortex_ingester_memory_metadata_removed_total counter 350 cortex_ingester_memory_metadata_removed_total{user="1"} 30 351 cortex_ingester_memory_metadata_removed_total{user="2"} 30 352 cortex_ingester_memory_metadata_removed_total{user="3"} 30 353 `), metricNames...)) 354 355 } 356 357 func TestIngesterSendsOnlySeriesWithData(t *testing.T) { 358 _, ing := newDefaultTestStore(t) 359 360 userIDs, _ := pushTestSamples(t, ing, 10, 1000, 0) 361 362 // Read samples back via ingester queries. 363 for _, userID := range userIDs { 364 ctx := user.InjectOrgID(context.Background(), userID) 365 _, req, err := runTestQueryTimes(ctx, t, ing, labels.MatchRegexp, model.JobLabel, ".+", model.Latest.Add(-15*time.Second), model.Latest) 366 require.NoError(t, err) 367 368 s := stream{ 369 ctx: ctx, 370 } 371 err = ing.QueryStream(req, &s) 372 require.NoError(t, err) 373 374 // Nothing should be selected. 375 require.Equal(t, 0, len(s.responses)) 376 } 377 378 // Read samples back via chunk store. 379 require.NoError(t, services.StopAndAwaitTerminated(context.Background(), ing)) 380 } 381 382 func TestIngesterIdleFlush(t *testing.T) { 383 // Create test ingester with short flush cycle 384 cfg := defaultIngesterTestConfig(t) 385 cfg.FlushCheckPeriod = 20 * time.Millisecond 386 cfg.MaxChunkIdle = 100 * time.Millisecond 387 cfg.RetainPeriod = 500 * time.Millisecond 388 store, ing := newTestStore(t, cfg, defaultClientTestConfig(), defaultLimitsTestConfig(), nil) 389 390 userIDs, testData := pushTestSamples(t, ing, 4, 100, 0) 391 392 // wait beyond idle time so samples flush 393 time.Sleep(cfg.MaxChunkIdle * 3) 394 395 store.checkData(t, userIDs, testData) 396 397 // Check data is still retained by ingester 398 for _, userID := range userIDs { 399 ctx := user.InjectOrgID(context.Background(), userID) 400 res, _, err := runTestQuery(ctx, t, ing, labels.MatchRegexp, model.JobLabel, ".+") 401 require.NoError(t, err) 402 assert.Equal(t, testData[userID], res) 403 } 404 405 // now wait beyond retain time so chunks are removed from memory 406 time.Sleep(cfg.RetainPeriod) 407 408 // Check data has gone from ingester 409 for _, userID := range userIDs { 410 ctx := user.InjectOrgID(context.Background(), userID) 411 res, _, err := runTestQuery(ctx, t, ing, labels.MatchRegexp, model.JobLabel, ".+") 412 require.NoError(t, err) 413 assert.Equal(t, model.Matrix{}, res) 414 } 415 416 require.NoError(t, services.StopAndAwaitTerminated(context.Background(), ing)) 417 } 418 419 func TestIngesterSpreadFlush(t *testing.T) { 420 // Create test ingester with short flush cycle 421 cfg := defaultIngesterTestConfig(t) 422 cfg.SpreadFlushes = true 423 cfg.FlushCheckPeriod = 20 * time.Millisecond 424 store, ing := newTestStore(t, cfg, defaultClientTestConfig(), defaultLimitsTestConfig(), nil) 425 426 userIDs, testData := pushTestSamples(t, ing, 4, 100, 0) 427 428 // add another sample with timestamp at the end of the cycle to trigger 429 // head closes and get an extra chunk so we will flush the first one 430 _, _ = pushTestSamples(t, ing, 4, 1, int(cfg.MaxChunkAge.Seconds()-1)*1000) 431 432 // wait beyond flush time so first set of samples should be sent to store 433 // (you'd think a shorter wait, like period*2, would work, but Go timers are not reliable enough for that) 434 time.Sleep(cfg.FlushCheckPeriod * 10) 435 436 // check the first set of samples has been sent to the store 437 store.checkData(t, userIDs, testData) 438 439 require.NoError(t, services.StopAndAwaitTerminated(context.Background(), ing)) 440 } 441 442 type stream struct { 443 grpc.ServerStream 444 ctx context.Context 445 responses []*client.QueryStreamResponse 446 } 447 448 func (s *stream) Context() context.Context { 449 return s.ctx 450 } 451 452 func (s *stream) Send(response *client.QueryStreamResponse) error { 453 s.responses = append(s.responses, response) 454 return nil 455 } 456 457 func TestIngesterAppendOutOfOrderAndDuplicate(t *testing.T) { 458 _, ing := newDefaultTestStore(t) 459 defer services.StopAndAwaitTerminated(context.Background(), ing) //nolint:errcheck 460 461 m := labelPairs{ 462 {Name: model.MetricNameLabel, Value: "testmetric"}, 463 } 464 ctx := context.Background() 465 err := ing.append(ctx, userID, m, 1, 0, cortexpb.API, nil) 466 require.NoError(t, err) 467 468 // Two times exactly the same sample (noop). 469 err = ing.append(ctx, userID, m, 1, 0, cortexpb.API, nil) 470 require.NoError(t, err) 471 472 // Earlier sample than previous one. 473 err = ing.append(ctx, userID, m, 0, 0, cortexpb.API, nil) 474 require.Contains(t, err.Error(), "sample timestamp out of order") 475 errResp, ok := err.(*validationError) 476 require.True(t, ok) 477 require.Equal(t, errResp.code, 400) 478 479 // Same timestamp as previous sample, but different value. 480 err = ing.append(ctx, userID, m, 1, 1, cortexpb.API, nil) 481 require.Contains(t, err.Error(), "sample with repeated timestamp but different value") 482 errResp, ok = err.(*validationError) 483 require.True(t, ok) 484 require.Equal(t, errResp.code, 400) 485 } 486 487 // Test that blank labels are removed by the ingester 488 func TestIngesterAppendBlankLabel(t *testing.T) { 489 _, ing := newDefaultTestStore(t) 490 defer services.StopAndAwaitTerminated(context.Background(), ing) //nolint:errcheck 491 492 lp := labelPairs{ 493 {Name: model.MetricNameLabel, Value: "testmetric"}, 494 {Name: "foo", Value: ""}, 495 {Name: "bar", Value: ""}, 496 } 497 ctx := user.InjectOrgID(context.Background(), userID) 498 err := ing.append(ctx, userID, lp, 1, 0, cortexpb.API, nil) 499 require.NoError(t, err) 500 501 res, _, err := runTestQuery(ctx, t, ing, labels.MatchEqual, labels.MetricName, "testmetric") 502 require.NoError(t, err) 503 504 expected := model.Matrix{ 505 { 506 Metric: model.Metric{labels.MetricName: "testmetric"}, 507 Values: []model.SamplePair{ 508 {Timestamp: 1, Value: 0}, 509 }, 510 }, 511 } 512 513 assert.Equal(t, expected, res) 514 } 515 516 func TestIngesterUserLimitExceeded(t *testing.T) { 517 limits := defaultLimitsTestConfig() 518 limits.MaxLocalSeriesPerUser = 1 519 limits.MaxLocalMetricsWithMetadataPerUser = 1 520 521 dir, err := ioutil.TempDir("", "limits") 522 require.NoError(t, err) 523 defer func() { 524 require.NoError(t, os.RemoveAll(dir)) 525 }() 526 527 chunksDir := filepath.Join(dir, "chunks") 528 blocksDir := filepath.Join(dir, "blocks") 529 require.NoError(t, os.Mkdir(chunksDir, os.ModePerm)) 530 require.NoError(t, os.Mkdir(blocksDir, os.ModePerm)) 531 532 chunksIngesterGenerator := func() *Ingester { 533 cfg := defaultIngesterTestConfig(t) 534 cfg.WALConfig.WALEnabled = true 535 cfg.WALConfig.Recover = true 536 cfg.WALConfig.Dir = chunksDir 537 cfg.WALConfig.CheckpointDuration = 100 * time.Minute 538 539 _, ing := newTestStore(t, cfg, defaultClientTestConfig(), limits, nil) 540 return ing 541 } 542 543 blocksIngesterGenerator := func() *Ingester { 544 ing, err := prepareIngesterWithBlocksStorageAndLimits(t, defaultIngesterTestConfig(t), limits, blocksDir, nil) 545 require.NoError(t, err) 546 require.NoError(t, services.StartAndAwaitRunning(context.Background(), ing)) 547 // Wait until it's ACTIVE 548 test.Poll(t, time.Second, ring.ACTIVE, func() interface{} { 549 return ing.lifecycler.GetState() 550 }) 551 552 return ing 553 } 554 555 tests := []string{"chunks", "blocks"} 556 for i, ingGenerator := range []func() *Ingester{chunksIngesterGenerator, blocksIngesterGenerator} { 557 t.Run(tests[i], func(t *testing.T) { 558 ing := ingGenerator() 559 560 userID := "1" 561 // Series 562 labels1 := labels.Labels{{Name: labels.MetricName, Value: "testmetric"}, {Name: "foo", Value: "bar"}} 563 sample1 := cortexpb.Sample{ 564 TimestampMs: 0, 565 Value: 1, 566 } 567 sample2 := cortexpb.Sample{ 568 TimestampMs: 1, 569 Value: 2, 570 } 571 labels3 := labels.Labels{{Name: labels.MetricName, Value: "testmetric"}, {Name: "foo", Value: "biz"}} 572 sample3 := cortexpb.Sample{ 573 TimestampMs: 1, 574 Value: 3, 575 } 576 // Metadata 577 metadata1 := &cortexpb.MetricMetadata{MetricFamilyName: "testmetric", Help: "a help for testmetric", Type: cortexpb.COUNTER} 578 metadata2 := &cortexpb.MetricMetadata{MetricFamilyName: "testmetric2", Help: "a help for testmetric2", Type: cortexpb.COUNTER} 579 580 // Append only one series and one metadata first, expect no error. 581 ctx := user.InjectOrgID(context.Background(), userID) 582 _, err = ing.Push(ctx, cortexpb.ToWriteRequest([]labels.Labels{labels1}, []cortexpb.Sample{sample1}, []*cortexpb.MetricMetadata{metadata1}, cortexpb.API)) 583 require.NoError(t, err) 584 585 testLimits := func() { 586 // Append to two series, expect series-exceeded error. 587 _, err = ing.Push(ctx, cortexpb.ToWriteRequest([]labels.Labels{labels1, labels3}, []cortexpb.Sample{sample2, sample3}, nil, cortexpb.API)) 588 httpResp, ok := httpgrpc.HTTPResponseFromError(err) 589 require.True(t, ok, "returned error is not an httpgrpc response") 590 assert.Equal(t, http.StatusBadRequest, int(httpResp.Code)) 591 assert.Equal(t, wrapWithUser(makeLimitError(perUserSeriesLimit, ing.limiter.FormatError(userID, errMaxSeriesPerUserLimitExceeded)), userID).Error(), string(httpResp.Body)) 592 593 // Append two metadata, expect no error since metadata is a best effort approach. 594 _, err = ing.Push(ctx, cortexpb.ToWriteRequest(nil, nil, []*cortexpb.MetricMetadata{metadata1, metadata2}, cortexpb.API)) 595 require.NoError(t, err) 596 597 // Read samples back via ingester queries. 598 res, _, err := runTestQuery(ctx, t, ing, labels.MatchEqual, model.MetricNameLabel, "testmetric") 599 require.NoError(t, err) 600 601 expected := model.Matrix{ 602 { 603 Metric: cortexpb.FromLabelAdaptersToMetric(cortexpb.FromLabelsToLabelAdapters(labels1)), 604 Values: []model.SamplePair{ 605 { 606 Timestamp: model.Time(sample1.TimestampMs), 607 Value: model.SampleValue(sample1.Value), 608 }, 609 { 610 Timestamp: model.Time(sample2.TimestampMs), 611 Value: model.SampleValue(sample2.Value), 612 }, 613 }, 614 }, 615 } 616 617 // Verify samples 618 require.Equal(t, expected, res) 619 620 // Verify metadata 621 m, err := ing.MetricsMetadata(ctx, nil) 622 require.NoError(t, err) 623 assert.Equal(t, []*cortexpb.MetricMetadata{metadata1}, m.Metadata) 624 } 625 626 testLimits() 627 628 // Limits should hold after restart. 629 services.StopAndAwaitTerminated(context.Background(), ing) //nolint:errcheck 630 ing = ingGenerator() 631 defer services.StopAndAwaitTerminated(context.Background(), ing) //nolint:errcheck 632 633 testLimits() 634 }) 635 } 636 637 } 638 639 func TestIngesterMetricLimitExceeded(t *testing.T) { 640 limits := defaultLimitsTestConfig() 641 limits.MaxLocalSeriesPerMetric = 1 642 limits.MaxLocalMetadataPerMetric = 1 643 644 dir, err := ioutil.TempDir("", "limits") 645 require.NoError(t, err) 646 defer func() { 647 require.NoError(t, os.RemoveAll(dir)) 648 }() 649 650 chunksDir := filepath.Join(dir, "chunks") 651 blocksDir := filepath.Join(dir, "blocks") 652 require.NoError(t, os.Mkdir(chunksDir, os.ModePerm)) 653 require.NoError(t, os.Mkdir(blocksDir, os.ModePerm)) 654 655 chunksIngesterGenerator := func() *Ingester { 656 cfg := defaultIngesterTestConfig(t) 657 cfg.WALConfig.WALEnabled = true 658 cfg.WALConfig.Recover = true 659 cfg.WALConfig.Dir = chunksDir 660 cfg.WALConfig.CheckpointDuration = 100 * time.Minute 661 662 _, ing := newTestStore(t, cfg, defaultClientTestConfig(), limits, nil) 663 return ing 664 } 665 666 blocksIngesterGenerator := func() *Ingester { 667 ing, err := prepareIngesterWithBlocksStorageAndLimits(t, defaultIngesterTestConfig(t), limits, blocksDir, nil) 668 require.NoError(t, err) 669 require.NoError(t, services.StartAndAwaitRunning(context.Background(), ing)) 670 // Wait until it's ACTIVE 671 test.Poll(t, time.Second, ring.ACTIVE, func() interface{} { 672 return ing.lifecycler.GetState() 673 }) 674 675 return ing 676 } 677 678 tests := []string{"chunks", "blocks"} 679 for i, ingGenerator := range []func() *Ingester{chunksIngesterGenerator, blocksIngesterGenerator} { 680 t.Run(tests[i], func(t *testing.T) { 681 ing := ingGenerator() 682 683 userID := "1" 684 labels1 := labels.Labels{{Name: labels.MetricName, Value: "testmetric"}, {Name: "foo", Value: "bar"}} 685 sample1 := cortexpb.Sample{ 686 TimestampMs: 0, 687 Value: 1, 688 } 689 sample2 := cortexpb.Sample{ 690 TimestampMs: 1, 691 Value: 2, 692 } 693 labels3 := labels.Labels{{Name: labels.MetricName, Value: "testmetric"}, {Name: "foo", Value: "biz"}} 694 sample3 := cortexpb.Sample{ 695 TimestampMs: 1, 696 Value: 3, 697 } 698 699 // Metadata 700 metadata1 := &cortexpb.MetricMetadata{MetricFamilyName: "testmetric", Help: "a help for testmetric", Type: cortexpb.COUNTER} 701 metadata2 := &cortexpb.MetricMetadata{MetricFamilyName: "testmetric", Help: "a help for testmetric2", Type: cortexpb.COUNTER} 702 703 // Append only one series and one metadata first, expect no error. 704 ctx := user.InjectOrgID(context.Background(), userID) 705 _, err = ing.Push(ctx, cortexpb.ToWriteRequest([]labels.Labels{labels1}, []cortexpb.Sample{sample1}, []*cortexpb.MetricMetadata{metadata1}, cortexpb.API)) 706 require.NoError(t, err) 707 708 testLimits := func() { 709 // Append two series, expect series-exceeded error. 710 _, err = ing.Push(ctx, cortexpb.ToWriteRequest([]labels.Labels{labels1, labels3}, []cortexpb.Sample{sample2, sample3}, nil, cortexpb.API)) 711 httpResp, ok := httpgrpc.HTTPResponseFromError(err) 712 require.True(t, ok, "returned error is not an httpgrpc response") 713 assert.Equal(t, http.StatusBadRequest, int(httpResp.Code)) 714 assert.Equal(t, wrapWithUser(makeMetricLimitError(perMetricSeriesLimit, labels3, ing.limiter.FormatError(userID, errMaxSeriesPerMetricLimitExceeded)), userID).Error(), string(httpResp.Body)) 715 716 // Append two metadata for the same metric. Drop the second one, and expect no error since metadata is a best effort approach. 717 _, err = ing.Push(ctx, cortexpb.ToWriteRequest(nil, nil, []*cortexpb.MetricMetadata{metadata1, metadata2}, cortexpb.API)) 718 require.NoError(t, err) 719 720 // Read samples back via ingester queries. 721 res, _, err := runTestQuery(ctx, t, ing, labels.MatchEqual, model.MetricNameLabel, "testmetric") 722 require.NoError(t, err) 723 724 // Verify Series 725 expected := model.Matrix{ 726 { 727 Metric: cortexpb.FromLabelAdaptersToMetric(cortexpb.FromLabelsToLabelAdapters(labels1)), 728 Values: []model.SamplePair{ 729 { 730 Timestamp: model.Time(sample1.TimestampMs), 731 Value: model.SampleValue(sample1.Value), 732 }, 733 { 734 Timestamp: model.Time(sample2.TimestampMs), 735 Value: model.SampleValue(sample2.Value), 736 }, 737 }, 738 }, 739 } 740 741 assert.Equal(t, expected, res) 742 743 // Verify metadata 744 m, err := ing.MetricsMetadata(ctx, nil) 745 require.NoError(t, err) 746 assert.Equal(t, []*cortexpb.MetricMetadata{metadata1}, m.Metadata) 747 } 748 749 testLimits() 750 751 // Limits should hold after restart. 752 services.StopAndAwaitTerminated(context.Background(), ing) //nolint:errcheck 753 ing = ingGenerator() 754 defer services.StopAndAwaitTerminated(context.Background(), ing) //nolint:errcheck 755 756 testLimits() 757 }) 758 } 759 } 760 761 func TestIngesterValidation(t *testing.T) { 762 _, ing := newDefaultTestStore(t) 763 defer services.StopAndAwaitTerminated(context.Background(), ing) //nolint:errcheck 764 userID := "1" 765 ctx := user.InjectOrgID(context.Background(), userID) 766 m := labelPairs{{Name: labels.MetricName, Value: "testmetric"}} 767 768 // As a setup, let's append samples. 769 err := ing.append(context.Background(), userID, m, 1, 0, cortexpb.API, nil) 770 require.NoError(t, err) 771 772 for _, tc := range []struct { 773 desc string 774 lbls []labels.Labels 775 samples []cortexpb.Sample 776 err error 777 }{ 778 { 779 desc: "With multiple append failures, only return the first error.", 780 lbls: []labels.Labels{ 781 {{Name: labels.MetricName, Value: "testmetric"}}, 782 {{Name: labels.MetricName, Value: "testmetric"}}, 783 }, 784 samples: []cortexpb.Sample{ 785 {TimestampMs: 0, Value: 0}, // earlier timestamp, out of order. 786 {TimestampMs: 1, Value: 2}, // same timestamp different value. 787 }, 788 err: httpgrpc.Errorf(http.StatusBadRequest, `user=1: sample timestamp out of order; last timestamp: 0.001, incoming timestamp: 0 for series {__name__="testmetric"}`), 789 }, 790 } { 791 t.Run(tc.desc, func(t *testing.T) { 792 _, err := ing.Push(ctx, cortexpb.ToWriteRequest(tc.lbls, tc.samples, nil, cortexpb.API)) 793 require.Equal(t, tc.err, err) 794 }) 795 } 796 } 797 798 func BenchmarkIngesterSeriesCreationLocking(b *testing.B) { 799 for i := 1; i <= 32; i++ { 800 b.Run(strconv.Itoa(i), func(b *testing.B) { 801 for n := 0; n < b.N; n++ { 802 benchmarkIngesterSeriesCreationLocking(b, i) 803 } 804 }) 805 } 806 } 807 808 func benchmarkIngesterSeriesCreationLocking(b *testing.B, parallelism int) { 809 _, ing := newDefaultTestStore(b) 810 defer services.StopAndAwaitTerminated(context.Background(), ing) //nolint:errcheck 811 812 var ( 813 wg sync.WaitGroup 814 series = int(1e4) 815 ctx = context.Background() 816 ) 817 wg.Add(parallelism) 818 ctx = user.InjectOrgID(ctx, "1") 819 for i := 0; i < parallelism; i++ { 820 seriesPerGoroutine := series / parallelism 821 go func(from, through int) { 822 defer wg.Done() 823 824 for j := from; j < through; j++ { 825 _, err := ing.Push(ctx, &cortexpb.WriteRequest{ 826 Timeseries: []cortexpb.PreallocTimeseries{ 827 { 828 TimeSeries: &cortexpb.TimeSeries{ 829 Labels: []cortexpb.LabelAdapter{ 830 {Name: model.MetricNameLabel, Value: fmt.Sprintf("metric_%d", j)}, 831 }, 832 Samples: []cortexpb.Sample{ 833 {TimestampMs: int64(j), Value: float64(j)}, 834 }, 835 }, 836 }, 837 }, 838 }) 839 require.NoError(b, err) 840 } 841 842 }(i*seriesPerGoroutine, (i+1)*seriesPerGoroutine) 843 } 844 845 wg.Wait() 846 } 847 848 func BenchmarkIngesterPush(b *testing.B) { 849 limits := defaultLimitsTestConfig() 850 benchmarkIngesterPush(b, limits, false) 851 } 852 853 func BenchmarkIngesterPushErrors(b *testing.B) { 854 limits := defaultLimitsTestConfig() 855 limits.MaxLocalSeriesPerMetric = 1 856 benchmarkIngesterPush(b, limits, true) 857 } 858 859 // Construct a set of realistic-looking samples, all with slightly different label sets 860 func benchmarkData(nSeries int) (allLabels []labels.Labels, allSamples []cortexpb.Sample) { 861 for j := 0; j < nSeries; j++ { 862 labels := chunk.BenchmarkLabels.Copy() 863 for i := range labels { 864 if labels[i].Name == "cpu" { 865 labels[i].Value = fmt.Sprintf("cpu%02d", j) 866 } 867 } 868 allLabels = append(allLabels, labels) 869 allSamples = append(allSamples, cortexpb.Sample{TimestampMs: 0, Value: float64(j)}) 870 } 871 return 872 } 873 874 func benchmarkIngesterPush(b *testing.B, limits validation.Limits, errorsExpected bool) { 875 cfg := defaultIngesterTestConfig(b) 876 clientCfg := defaultClientTestConfig() 877 878 const ( 879 series = 100 880 samples = 100 881 ) 882 883 allLabels, allSamples := benchmarkData(series) 884 ctx := user.InjectOrgID(context.Background(), "1") 885 886 encodings := []struct { 887 name string 888 e promchunk.Encoding 889 }{ 890 {"DoubleDelta", promchunk.DoubleDelta}, 891 {"Varbit", promchunk.Varbit}, 892 {"Bigchunk", promchunk.Bigchunk}, 893 } 894 895 for _, enc := range encodings { 896 b.Run(fmt.Sprintf("encoding=%s", enc.name), func(b *testing.B) { 897 b.ResetTimer() 898 for iter := 0; iter < b.N; iter++ { 899 _, ing := newTestStore(b, cfg, clientCfg, limits, nil) 900 // Bump the timestamp on each of our test samples each time round the loop 901 for j := 0; j < samples; j++ { 902 for i := range allSamples { 903 allSamples[i].TimestampMs = int64(j + 1) 904 } 905 _, err := ing.Push(ctx, cortexpb.ToWriteRequest(allLabels, allSamples, nil, cortexpb.API)) 906 if !errorsExpected { 907 require.NoError(b, err) 908 } 909 } 910 _ = services.StopAndAwaitTerminated(context.Background(), ing) 911 } 912 }) 913 } 914 915 } 916 917 func BenchmarkIngester_QueryStream(b *testing.B) { 918 cfg := defaultIngesterTestConfig(b) 919 clientCfg := defaultClientTestConfig() 920 limits := defaultLimitsTestConfig() 921 _, ing := newTestStore(b, cfg, clientCfg, limits, nil) 922 ctx := user.InjectOrgID(context.Background(), "1") 923 924 const ( 925 series = 2000 926 samples = 1000 927 ) 928 929 allLabels, allSamples := benchmarkData(series) 930 931 // Bump the timestamp and set a random value on each of our test samples each time round the loop 932 for j := 0; j < samples; j++ { 933 for i := range allSamples { 934 allSamples[i].TimestampMs = int64(j + 1) 935 allSamples[i].Value = rand.Float64() 936 } 937 _, err := ing.Push(ctx, cortexpb.ToWriteRequest(allLabels, allSamples, nil, cortexpb.API)) 938 require.NoError(b, err) 939 } 940 941 req := &client.QueryRequest{ 942 StartTimestampMs: 0, 943 EndTimestampMs: samples + 1, 944 945 Matchers: []*client.LabelMatcher{{ 946 Type: client.EQUAL, 947 Name: model.MetricNameLabel, 948 Value: "container_cpu_usage_seconds_total", 949 }}, 950 } 951 952 mockStream := &mockQueryStreamServer{ctx: ctx} 953 954 b.ResetTimer() 955 956 for ix := 0; ix < b.N; ix++ { 957 err := ing.QueryStream(req, mockStream) 958 require.NoError(b, err) 959 } 960 } 961 962 func TestIngesterActiveSeries(t *testing.T) { 963 metricLabelAdapters := []cortexpb.LabelAdapter{{Name: labels.MetricName, Value: "test"}} 964 metricLabels := cortexpb.FromLabelAdaptersToLabels(metricLabelAdapters) 965 metricNames := []string{ 966 "cortex_ingester_active_series", 967 } 968 userID := "test" 969 970 tests := map[string]struct { 971 reqs []*cortexpb.WriteRequest 972 expectedMetrics string 973 disableActiveSeries bool 974 }{ 975 "should succeed on valid series and metadata": { 976 reqs: []*cortexpb.WriteRequest{ 977 cortexpb.ToWriteRequest( 978 []labels.Labels{metricLabels}, 979 []cortexpb.Sample{{Value: 1, TimestampMs: 9}}, 980 nil, 981 cortexpb.API), 982 cortexpb.ToWriteRequest( 983 []labels.Labels{metricLabels}, 984 []cortexpb.Sample{{Value: 2, TimestampMs: 10}}, 985 nil, 986 cortexpb.API), 987 }, 988 expectedMetrics: ` 989 # HELP cortex_ingester_active_series Number of currently active series per user. 990 # TYPE cortex_ingester_active_series gauge 991 cortex_ingester_active_series{user="test"} 1 992 `, 993 }, 994 "successful push, active series disabled": { 995 disableActiveSeries: true, 996 reqs: []*cortexpb.WriteRequest{ 997 cortexpb.ToWriteRequest( 998 []labels.Labels{metricLabels}, 999 []cortexpb.Sample{{Value: 1, TimestampMs: 9}}, 1000 nil, 1001 cortexpb.API), 1002 cortexpb.ToWriteRequest( 1003 []labels.Labels{metricLabels}, 1004 []cortexpb.Sample{{Value: 2, TimestampMs: 10}}, 1005 nil, 1006 cortexpb.API), 1007 }, 1008 expectedMetrics: ``, 1009 }, 1010 } 1011 1012 for testName, testData := range tests { 1013 t.Run(testName, func(t *testing.T) { 1014 registry := prometheus.NewRegistry() 1015 1016 // Create a mocked ingester 1017 cfg := defaultIngesterTestConfig(t) 1018 cfg.LifecyclerConfig.JoinAfter = 0 1019 cfg.ActiveSeriesMetricsEnabled = !testData.disableActiveSeries 1020 1021 _, i := newTestStore(t, 1022 cfg, 1023 defaultClientTestConfig(), 1024 defaultLimitsTestConfig(), registry) 1025 1026 defer services.StopAndAwaitTerminated(context.Background(), i) //nolint:errcheck 1027 1028 ctx := user.InjectOrgID(context.Background(), userID) 1029 1030 // Wait until the ingester is ACTIVE 1031 test.Poll(t, 100*time.Millisecond, ring.ACTIVE, func() interface{} { 1032 return i.lifecycler.GetState() 1033 }) 1034 1035 // Push timeseries 1036 for _, req := range testData.reqs { 1037 _, err := i.Push(ctx, req) 1038 assert.NoError(t, err) 1039 } 1040 1041 // Update active series for metrics check. 1042 if !testData.disableActiveSeries { 1043 i.userStatesMtx.Lock() 1044 i.userStates.purgeAndUpdateActiveSeries(time.Now().Add(-i.cfg.ActiveSeriesMetricsIdleTimeout)) 1045 i.userStatesMtx.Unlock() 1046 } 1047 1048 // Check tracked Prometheus metrics 1049 err := testutil.GatherAndCompare(registry, strings.NewReader(testData.expectedMetrics), metricNames...) 1050 assert.NoError(t, err) 1051 }) 1052 } 1053 } 1054 1055 func TestGetIgnoreSeriesLimitForMetricNamesMap(t *testing.T) { 1056 cfg := Config{} 1057 1058 require.Nil(t, cfg.getIgnoreSeriesLimitForMetricNamesMap()) 1059 1060 cfg.IgnoreSeriesLimitForMetricNames = ", ,,," 1061 require.Nil(t, cfg.getIgnoreSeriesLimitForMetricNamesMap()) 1062 1063 cfg.IgnoreSeriesLimitForMetricNames = "foo, bar, ," 1064 require.Equal(t, map[string]struct{}{"foo": {}, "bar": {}}, cfg.getIgnoreSeriesLimitForMetricNamesMap()) 1065 }