github.com/thanos-io/thanos@v0.32.5/pkg/receive/writer_test.go (about) 1 // Copyright (c) The Thanos Authors. 2 // Licensed under the Apache License 2.0. 3 4 package receive 5 6 import ( 7 "context" 8 "fmt" 9 "strings" 10 "testing" 11 "time" 12 13 "github.com/efficientgo/core/testutil" 14 "github.com/go-kit/log" 15 "github.com/pkg/errors" 16 "github.com/prometheus/client_golang/prometheus" 17 "github.com/prometheus/common/model" 18 "github.com/prometheus/prometheus/model/exemplar" 19 "github.com/prometheus/prometheus/model/labels" 20 "github.com/prometheus/prometheus/storage" 21 "github.com/prometheus/prometheus/tsdb" 22 "github.com/prometheus/prometheus/tsdb/tsdbutil" 23 24 "github.com/thanos-io/thanos/pkg/block/metadata" 25 "github.com/thanos-io/thanos/pkg/runutil" 26 "github.com/thanos-io/thanos/pkg/store/labelpb" 27 "github.com/thanos-io/thanos/pkg/store/storepb/prompb" 28 "github.com/thanos-io/thanos/pkg/tenancy" 29 ) 30 31 func TestWriter(t *testing.T) { 32 now := model.Now() 33 lbls := []labelpb.ZLabel{{Name: "__name__", Value: "test"}} 34 tests := map[string]struct { 35 reqs []*prompb.WriteRequest 36 expectedErr error 37 expectedIngested []prompb.TimeSeries 38 maxExemplars int64 39 opts *WriterOptions 40 }{ 41 "should error out on series with no labels": { 42 reqs: []*prompb.WriteRequest{ 43 { 44 Timeseries: []prompb.TimeSeries{ 45 { 46 Samples: []prompb.Sample{{Value: 1, Timestamp: 10}}, 47 }, 48 { 49 Labels: []labelpb.ZLabel{{Name: "__name__", Value: ""}}, 50 Samples: []prompb.Sample{{Value: 1, Timestamp: 10}}, 51 }, 52 }, 53 }, 54 }, 55 expectedErr: errors.Wrapf(labelpb.ErrEmptyLabels, "add 2 series"), 56 }, 57 "should succeed on series with valid labels": { 58 reqs: []*prompb.WriteRequest{ 59 { 60 Timeseries: []prompb.TimeSeries{ 61 { 62 Labels: append(lbls, labelpb.ZLabel{Name: "a", Value: "1"}, labelpb.ZLabel{Name: "b", Value: "2"}), 63 Samples: []prompb.Sample{{Value: 1, Timestamp: 10}}, 64 }, 65 }, 66 }, 67 }, 68 expectedErr: nil, 69 expectedIngested: []prompb.TimeSeries{ 70 { 71 Labels: append(lbls, labelpb.ZLabel{Name: "a", Value: "1"}, labelpb.ZLabel{Name: "b", Value: "2"}), 72 Samples: []prompb.Sample{{Value: 1, Timestamp: 10}}, 73 }, 74 }, 75 }, 76 "should error out and skip series with out-of-order labels": { 77 reqs: []*prompb.WriteRequest{ 78 { 79 Timeseries: []prompb.TimeSeries{ 80 { 81 Labels: append(lbls, labelpb.ZLabel{Name: "a", Value: "1"}, labelpb.ZLabel{Name: "b", Value: "1"}, labelpb.ZLabel{Name: "Z", Value: "1"}, labelpb.ZLabel{Name: "b", Value: "2"}), 82 Samples: []prompb.Sample{{Value: 1, Timestamp: 10}}, 83 }, 84 }, 85 }, 86 }, 87 expectedErr: errors.Wrapf(labelpb.ErrOutOfOrderLabels, "add 1 series"), 88 }, 89 "should error out and skip series with duplicate labels": { 90 reqs: []*prompb.WriteRequest{ 91 { 92 Timeseries: []prompb.TimeSeries{ 93 { 94 Labels: append(lbls, labelpb.ZLabel{Name: "a", Value: "1"}, labelpb.ZLabel{Name: "b", Value: "1"}, labelpb.ZLabel{Name: "b", Value: "2"}, labelpb.ZLabel{Name: "z", Value: "1"}), 95 Samples: []prompb.Sample{{Value: 1, Timestamp: 10}}, 96 }, 97 }, 98 }, 99 }, 100 expectedErr: errors.Wrapf(labelpb.ErrDuplicateLabels, "add 1 series"), 101 }, 102 "should error out and skip series with out-of-order labels; accept series with valid labels": { 103 reqs: []*prompb.WriteRequest{ 104 { 105 Timeseries: []prompb.TimeSeries{ 106 { 107 Labels: append(lbls, labelpb.ZLabel{Name: "A", Value: "1"}, labelpb.ZLabel{Name: "b", Value: "2"}), 108 Samples: []prompb.Sample{{Value: 1, Timestamp: 10}}, 109 }, 110 { 111 Labels: append(lbls, labelpb.ZLabel{Name: "c", Value: "1"}, labelpb.ZLabel{Name: "d", Value: "2"}), 112 Samples: []prompb.Sample{{Value: 1, Timestamp: 10}}, 113 }, 114 { 115 Labels: append(lbls, labelpb.ZLabel{Name: "E", Value: "1"}, labelpb.ZLabel{Name: "f", Value: "2"}), 116 Samples: []prompb.Sample{{Value: 1, Timestamp: 10}}, 117 }, 118 }, 119 }, 120 }, 121 expectedErr: errors.Wrapf(labelpb.ErrOutOfOrderLabels, "add 2 series"), 122 expectedIngested: []prompb.TimeSeries{ 123 { 124 Labels: append(lbls, labelpb.ZLabel{Name: "c", Value: "1"}, labelpb.ZLabel{Name: "d", Value: "2"}), 125 Samples: []prompb.Sample{{Value: 1, Timestamp: 10}}, 126 }, 127 }, 128 }, 129 "should succeed when sample timestamp is NOT too far in the future": { 130 reqs: []*prompb.WriteRequest{ 131 { 132 Timeseries: []prompb.TimeSeries{ 133 { 134 Labels: lbls, 135 Samples: []prompb.Sample{{Value: 1, Timestamp: int64(now)}}, 136 }, 137 }, 138 }, 139 }, 140 expectedErr: nil, 141 expectedIngested: []prompb.TimeSeries{ 142 { 143 Labels: lbls, 144 Samples: []prompb.Sample{{Value: 1, Timestamp: int64(now)}}, 145 }, 146 }, 147 opts: &WriterOptions{TooFarInFutureTimeWindow: 30 * int64(time.Second)}, 148 }, 149 "should error out when sample timestamp is too far in the future": { 150 reqs: []*prompb.WriteRequest{ 151 { 152 Timeseries: []prompb.TimeSeries{ 153 { 154 Labels: lbls, 155 // A sample with a very large timestamp in year 5138 (milliseconds) 156 Samples: []prompb.Sample{{Value: 1, Timestamp: 99999999999999}}, 157 }, 158 }, 159 }, 160 }, 161 expectedErr: errors.Wrapf(storage.ErrOutOfBounds, "add 1 samples"), 162 opts: &WriterOptions{TooFarInFutureTimeWindow: 10000}, 163 }, 164 "should succeed on valid series with exemplars": { 165 reqs: []*prompb.WriteRequest{{ 166 Timeseries: []prompb.TimeSeries{ 167 { 168 Labels: lbls, 169 // Ingesting an exemplar requires a sample to create the series first. 170 Samples: []prompb.Sample{{Value: 1, Timestamp: 10}}, 171 Exemplars: []prompb.Exemplar{ 172 { 173 Labels: []labelpb.ZLabel{{Name: "traceID", Value: "123"}}, 174 Value: 111, 175 Timestamp: 11, 176 }, 177 { 178 Labels: []labelpb.ZLabel{{Name: "traceID", Value: "234"}}, 179 Value: 112, 180 Timestamp: 12, 181 }, 182 }, 183 }, 184 }, 185 }}, 186 expectedErr: nil, 187 maxExemplars: 2, 188 }, 189 "should error out on valid series with out of order exemplars": { 190 reqs: []*prompb.WriteRequest{ 191 { 192 Timeseries: []prompb.TimeSeries{ 193 { 194 Labels: lbls, 195 // Ingesting an exemplar requires a sample to create the series first. 196 Samples: []prompb.Sample{{Value: 1, Timestamp: 10}}, 197 Exemplars: []prompb.Exemplar{ 198 { 199 Labels: []labelpb.ZLabel{{Name: "traceID", Value: "123"}}, 200 Value: 111, 201 Timestamp: 11, 202 }, 203 }, 204 }, 205 }, 206 }, 207 { 208 Timeseries: []prompb.TimeSeries{ 209 { 210 Labels: lbls, 211 Exemplars: []prompb.Exemplar{ 212 { 213 Labels: []labelpb.ZLabel{{Name: "traceID", Value: "1234"}}, 214 Value: 111, 215 Timestamp: 10, 216 }, 217 }, 218 }, 219 }, 220 }, 221 }, 222 expectedErr: errors.Wrapf(storage.ErrOutOfOrderExemplar, "add 1 exemplars"), 223 maxExemplars: 2, 224 }, 225 "should error out when exemplar label length exceeds the limit": { 226 reqs: []*prompb.WriteRequest{ 227 { 228 Timeseries: []prompb.TimeSeries{ 229 { 230 Labels: lbls, 231 // Ingesting an exemplar requires a sample to create the series first. 232 Samples: []prompb.Sample{{Value: 1, Timestamp: 10}}, 233 Exemplars: []prompb.Exemplar{ 234 { 235 Labels: []labelpb.ZLabel{{Name: strings.Repeat("a", exemplar.ExemplarMaxLabelSetLength), Value: "1"}}, 236 Value: 111, 237 Timestamp: 11, 238 }, 239 }, 240 }, 241 }, 242 }, 243 }, 244 expectedErr: errors.Wrapf(storage.ErrExemplarLabelLength, "add 1 exemplars"), 245 maxExemplars: 2, 246 }, 247 "should succeed on histogram with valid labels": { 248 reqs: []*prompb.WriteRequest{ 249 { 250 Timeseries: []prompb.TimeSeries{ 251 { 252 Labels: append(lbls, labelpb.ZLabel{Name: "a", Value: "1"}, labelpb.ZLabel{Name: "b", Value: "2"}), 253 Histograms: []prompb.Histogram{ 254 prompb.HistogramToHistogramProto(10, tsdbutil.GenerateTestHistogram(0)), 255 }, 256 }, 257 }, 258 }, 259 }, 260 expectedErr: nil, 261 expectedIngested: []prompb.TimeSeries{ 262 { 263 Labels: append(lbls, labelpb.ZLabel{Name: "a", Value: "1"}, labelpb.ZLabel{Name: "b", Value: "2"}), 264 Histograms: []prompb.Histogram{ 265 prompb.HistogramToHistogramProto(10, tsdbutil.GenerateTestHistogram(0)), 266 }, 267 }, 268 }, 269 }, 270 "should succeed on float histogram with valid labels": { 271 reqs: []*prompb.WriteRequest{ 272 { 273 Timeseries: []prompb.TimeSeries{ 274 { 275 Labels: append(lbls, labelpb.ZLabel{Name: "a", Value: "1"}, labelpb.ZLabel{Name: "b", Value: "2"}), 276 Histograms: []prompb.Histogram{ 277 prompb.FloatHistogramToHistogramProto(10, tsdbutil.GenerateTestFloatHistogram(1)), 278 }, 279 }, 280 }, 281 }, 282 }, 283 expectedErr: nil, 284 expectedIngested: []prompb.TimeSeries{ 285 { 286 Labels: append(lbls, labelpb.ZLabel{Name: "a", Value: "1"}, labelpb.ZLabel{Name: "b", Value: "2"}), 287 Histograms: []prompb.Histogram{ 288 prompb.FloatHistogramToHistogramProto(10, tsdbutil.GenerateTestFloatHistogram(1)), 289 }, 290 }, 291 }, 292 }, 293 "should error out on valid histograms with out of order histogram": { 294 reqs: []*prompb.WriteRequest{ 295 { 296 Timeseries: []prompb.TimeSeries{ 297 { 298 Labels: append(lbls, labelpb.ZLabel{Name: "a", Value: "1"}, labelpb.ZLabel{Name: "b", Value: "2"}), 299 Histograms: []prompb.Histogram{ 300 prompb.HistogramToHistogramProto(10, tsdbutil.GenerateTestHistogram(0)), 301 }, 302 }, 303 }, 304 }, 305 { 306 Timeseries: []prompb.TimeSeries{ 307 { 308 Labels: append(lbls, labelpb.ZLabel{Name: "a", Value: "1"}, labelpb.ZLabel{Name: "b", Value: "2"}), 309 Histograms: []prompb.Histogram{ 310 prompb.HistogramToHistogramProto(9, tsdbutil.GenerateTestHistogram(0)), 311 }, 312 }, 313 }, 314 }, 315 }, 316 expectedErr: errors.Wrapf(storage.ErrOutOfOrderSample, "add 1 samples"), 317 expectedIngested: []prompb.TimeSeries{ 318 { 319 Labels: append(lbls, labelpb.ZLabel{Name: "a", Value: "1"}, labelpb.ZLabel{Name: "b", Value: "2"}), 320 Histograms: []prompb.Histogram{ 321 prompb.HistogramToHistogramProto(10, tsdbutil.GenerateTestHistogram(0)), 322 }, 323 }, 324 }, 325 }, 326 } 327 328 for testName, testData := range tests { 329 t.Run(testName, func(t *testing.T) { 330 dir := t.TempDir() 331 logger := log.NewNopLogger() 332 333 m := NewMultiTSDB(dir, logger, prometheus.NewRegistry(), &tsdb.Options{ 334 MinBlockDuration: (2 * time.Hour).Milliseconds(), 335 MaxBlockDuration: (2 * time.Hour).Milliseconds(), 336 RetentionDuration: (6 * time.Hour).Milliseconds(), 337 NoLockfile: true, 338 MaxExemplars: testData.maxExemplars, 339 EnableExemplarStorage: true, 340 EnableNativeHistograms: true, 341 }, 342 labels.FromStrings("replica", "01"), 343 "tenant_id", 344 nil, 345 false, 346 metadata.NoneFunc, 347 ) 348 t.Cleanup(func() { testutil.Ok(t, m.Close()) }) 349 350 testutil.Ok(t, m.Flush()) 351 testutil.Ok(t, m.Open()) 352 353 ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) 354 defer cancel() 355 356 app, err := m.TenantAppendable(tenancy.DefaultTenant) 357 testutil.Ok(t, err) 358 359 testutil.Ok(t, runutil.Retry(1*time.Second, ctx.Done(), func() error { 360 _, err = app.Appender(context.Background()) 361 return err 362 })) 363 364 w := NewWriter(logger, m, testData.opts) 365 366 for idx, req := range testData.reqs { 367 err = w.Write(context.Background(), tenancy.DefaultTenant, req) 368 369 // We expect no error on any request except the last one 370 // which may error (and in that case we assert on it). 371 if testData.expectedErr == nil || idx < len(testData.reqs)-1 { 372 testutil.Ok(t, err) 373 } else { 374 testutil.NotOk(t, err) 375 testutil.Equals(t, testData.expectedErr.Error(), err.Error()) 376 } 377 } 378 379 // On each expected series, assert we have a ref available. 380 a, err := app.Appender(context.Background()) 381 testutil.Ok(t, err) 382 gr := a.(storage.GetRef) 383 384 for _, ts := range testData.expectedIngested { 385 l := labelpb.ZLabelsToPromLabels(ts.Labels) 386 ref, _ := gr.GetRef(l, l.Hash()) 387 testutil.Assert(t, ref != 0, fmt.Sprintf("appender should have reference to series %v", ts)) 388 } 389 }) 390 } 391 } 392 393 func BenchmarkWriterTimeSeriesWithSingleLabel_10(b *testing.B) { benchmarkWriter(b, 1, 10, false) } 394 func BenchmarkWriterTimeSeriesWithSingleLabel_100(b *testing.B) { benchmarkWriter(b, 1, 100, false) } 395 func BenchmarkWriterTimeSeriesWithSingleLabel_1000(b *testing.B) { benchmarkWriter(b, 1, 1000, false) } 396 397 func BenchmarkWriterTimeSeriesWith10Labels_10(b *testing.B) { benchmarkWriter(b, 10, 10, false) } 398 func BenchmarkWriterTimeSeriesWith10Labels_100(b *testing.B) { benchmarkWriter(b, 10, 100, false) } 399 func BenchmarkWriterTimeSeriesWith10Labels_1000(b *testing.B) { benchmarkWriter(b, 10, 1000, false) } 400 401 func BenchmarkWriterTimeSeriesWithHistogramsWithSingleLabel_10(b *testing.B) { 402 benchmarkWriter(b, 1, 10, true) 403 } 404 func BenchmarkWriterTimeSeriesWithHistogramsWithSingleLabel_100(b *testing.B) { 405 benchmarkWriter(b, 1, 100, true) 406 } 407 func BenchmarkWriterTimeSeriesWithHistogramsWithSingleLabel_1000(b *testing.B) { 408 benchmarkWriter(b, 1, 1000, true) 409 } 410 411 func BenchmarkWriterTimeSeriesWithHistogramsWith10Labels_10(b *testing.B) { 412 benchmarkWriter(b, 10, 10, true) 413 } 414 func BenchmarkWriterTimeSeriesWithHistogramsWith10Labels_100(b *testing.B) { 415 benchmarkWriter(b, 10, 100, true) 416 } 417 func BenchmarkWriterTimeSeriesWithHistogramsWith10Labels_1000(b *testing.B) { 418 benchmarkWriter(b, 10, 1000, true) 419 } 420 421 func benchmarkWriter(b *testing.B, labelsNum int, seriesNum int, generateHistograms bool) { 422 dir := b.TempDir() 423 logger := log.NewNopLogger() 424 425 m := NewMultiTSDB(dir, logger, prometheus.NewRegistry(), &tsdb.Options{ 426 MinBlockDuration: (2 * time.Hour).Milliseconds(), 427 MaxBlockDuration: (2 * time.Hour).Milliseconds(), 428 RetentionDuration: (6 * time.Hour).Milliseconds(), 429 NoLockfile: true, 430 MaxExemplars: 0, 431 EnableExemplarStorage: true, 432 EnableNativeHistograms: generateHistograms, 433 }, 434 labels.FromStrings("replica", "01"), 435 "tenant_id", 436 nil, 437 false, 438 metadata.NoneFunc, 439 ) 440 b.Cleanup(func() { testutil.Ok(b, m.Close()) }) 441 442 testutil.Ok(b, m.Flush()) 443 testutil.Ok(b, m.Open()) 444 445 app, err := m.TenantAppendable("foo") 446 testutil.Ok(b, err) 447 448 ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) 449 defer cancel() 450 451 testutil.Ok(b, runutil.Retry(1*time.Second, ctx.Done(), func() error { 452 _, err = app.Appender(context.Background()) 453 return err 454 })) 455 456 timeSeries := generateLabelsAndSeries(labelsNum, seriesNum, generateHistograms) 457 458 wreq := &prompb.WriteRequest{ 459 Timeseries: timeSeries, 460 } 461 462 b.Run("without interning", func(b *testing.B) { 463 w := NewWriter(logger, m, &WriterOptions{Intern: false}) 464 465 b.ReportAllocs() 466 b.ResetTimer() 467 468 for i := 0; i < b.N; i++ { 469 testutil.Ok(b, w.Write(ctx, "foo", wreq)) 470 } 471 }) 472 473 b.Run("with interning", func(b *testing.B) { 474 w := NewWriter(logger, m, &WriterOptions{Intern: true}) 475 476 b.ReportAllocs() 477 b.ResetTimer() 478 479 for i := 0; i < b.N; i++ { 480 testutil.Ok(b, w.Write(ctx, "foo", wreq)) 481 } 482 }) 483 484 } 485 486 // generateLabelsAndSeries generates time series for benchmark with specified number of labels. 487 // Although in this method we're reusing samples with same value and timestamp, Prometheus actually allows us to provide exact 488 // duplicates without error (see comment https://github.com/prometheus/prometheus/blob/release-2.37/tsdb/head_append.go#L316). 489 // This also means the sample won't be appended, which means the overhead of appending additional samples to head is not 490 // reflected in the benchmark, but should still capture the performance of receive writer. 491 func generateLabelsAndSeries(numLabels int, numSeries int, generateHistograms bool) []prompb.TimeSeries { 492 // Generate some labels first. 493 l := make([]labelpb.ZLabel, 0, numLabels) 494 l = append(l, labelpb.ZLabel{Name: "__name__", Value: "test"}) 495 for i := 0; i < numLabels; i++ { 496 l = append(l, labelpb.ZLabel{Name: fmt.Sprintf("label_%s", string(rune('a'+i))), Value: fmt.Sprintf("%d", i)}) 497 } 498 499 ts := make([]prompb.TimeSeries, numSeries) 500 for j := 0; j < numSeries; j++ { 501 ts[j] = prompb.TimeSeries{ 502 Labels: l, 503 } 504 505 if generateHistograms { 506 ts[j].Histograms = []prompb.Histogram{prompb.HistogramToHistogramProto(10, tsdbutil.GenerateTestHistogram(0))} 507 continue 508 } 509 510 ts[j].Samples = []prompb.Sample{{Value: 1, Timestamp: 10}} 511 } 512 513 return ts 514 }