github.com/thanos-io/thanos@v0.32.5/pkg/compact/compact_test.go (about) 1 // Copyright (c) The Thanos Authors. 2 // Licensed under the Apache License 2.0. 3 4 package compact 5 6 import ( 7 "bytes" 8 "context" 9 "encoding/json" 10 "fmt" 11 "io" 12 "path" 13 "testing" 14 "time" 15 16 "github.com/go-kit/log" 17 "github.com/oklog/run" 18 "github.com/oklog/ulid" 19 "github.com/pkg/errors" 20 "github.com/prometheus/client_golang/prometheus" 21 "github.com/prometheus/client_golang/prometheus/promauto" 22 promtestutil "github.com/prometheus/client_golang/prometheus/testutil" 23 "github.com/prometheus/prometheus/tsdb" 24 "github.com/thanos-io/objstore" 25 26 "github.com/efficientgo/core/testutil" 27 "github.com/thanos-io/thanos/pkg/block" 28 "github.com/thanos-io/thanos/pkg/block/metadata" 29 "github.com/thanos-io/thanos/pkg/compact/downsample" 30 "github.com/thanos-io/thanos/pkg/errutil" 31 "github.com/thanos-io/thanos/pkg/extprom" 32 ) 33 34 func TestHaltError(t *testing.T) { 35 err := errors.New("test") 36 testutil.Assert(t, !IsHaltError(err), "halt error") 37 38 err = halt(errors.New("test")) 39 testutil.Assert(t, IsHaltError(err), "not a halt error") 40 41 err = errors.Wrap(halt(errors.New("test")), "something") 42 testutil.Assert(t, IsHaltError(err), "not a halt error") 43 44 err = errors.Wrap(errors.Wrap(halt(errors.New("test")), "something"), "something2") 45 testutil.Assert(t, IsHaltError(err), "not a halt error") 46 } 47 48 func TestHaltMultiError(t *testing.T) { 49 haltErr := halt(errors.New("halt error")) 50 nonHaltErr := errors.New("not a halt error") 51 52 errs := errutil.MultiError{nonHaltErr} 53 testutil.Assert(t, !IsHaltError(errs.Err()), "should not be a halt error") 54 55 errs.Add(haltErr) 56 testutil.Assert(t, IsHaltError(errs.Err()), "if any halt errors are present this should return true") 57 testutil.Assert(t, IsHaltError(errors.Wrap(errs.Err(), "wrap")), "halt error with wrap") 58 59 } 60 61 func TestRetryMultiError(t *testing.T) { 62 retryErr := retry(errors.New("retry error")) 63 nonRetryErr := errors.New("not a retry error") 64 65 errs := errutil.MultiError{nonRetryErr} 66 testutil.Assert(t, !IsRetryError(errs.Err()), "should not be a retry error") 67 68 errs = errutil.MultiError{retryErr} 69 testutil.Assert(t, IsRetryError(errs.Err()), "if all errors are retriable this should return true") 70 71 testutil.Assert(t, IsRetryError(errors.Wrap(errs.Err(), "wrap")), "retry error with wrap") 72 73 errs = errutil.MultiError{nonRetryErr, retryErr} 74 testutil.Assert(t, !IsRetryError(errs.Err()), "mixed errors should return false") 75 } 76 77 func TestRetryError(t *testing.T) { 78 err := errors.New("test") 79 testutil.Assert(t, !IsRetryError(err), "retry error") 80 81 err = retry(errors.New("test")) 82 testutil.Assert(t, IsRetryError(err), "not a retry error") 83 84 err = errors.Wrap(retry(errors.New("test")), "something") 85 testutil.Assert(t, IsRetryError(err), "not a retry error") 86 87 err = errors.Wrap(errors.Wrap(retry(errors.New("test")), "something"), "something2") 88 testutil.Assert(t, IsRetryError(err), "not a retry error") 89 90 err = errors.Wrap(retry(errors.Wrap(halt(errors.New("test")), "something")), "something2") 91 testutil.Assert(t, IsHaltError(err), "not a halt error. Retry should not hide halt error") 92 } 93 94 func TestGroupKey(t *testing.T) { 95 for _, tcase := range []struct { 96 input metadata.Thanos 97 expected string 98 }{ 99 { 100 input: metadata.Thanos{}, 101 expected: "0@17241709254077376921", 102 }, 103 { 104 input: metadata.Thanos{ 105 Labels: map[string]string{}, 106 Downsample: metadata.ThanosDownsample{Resolution: 0}, 107 }, 108 expected: "0@17241709254077376921", 109 }, 110 { 111 input: metadata.Thanos{ 112 Labels: map[string]string{"foo": "bar", "foo1": "bar2"}, 113 Downsample: metadata.ThanosDownsample{Resolution: 0}, 114 }, 115 expected: "0@2124638872457683483", 116 }, 117 { 118 input: metadata.Thanos{ 119 Labels: map[string]string{`foo/some..thing/some.thing/../`: `a_b_c/bar-something-a\metric/a\x`}, 120 Downsample: metadata.ThanosDownsample{Resolution: 0}, 121 }, 122 expected: "0@16590761456214576373", 123 }, 124 } { 125 if ok := t.Run("", func(t *testing.T) { 126 testutil.Equals(t, tcase.expected, tcase.input.GroupKey()) 127 }); !ok { 128 return 129 } 130 } 131 } 132 133 func TestGroupMaxMinTime(t *testing.T) { 134 g := &Group{ 135 metasByMinTime: []*metadata.Meta{ 136 {BlockMeta: tsdb.BlockMeta{MinTime: 0, MaxTime: 10}}, 137 {BlockMeta: tsdb.BlockMeta{MinTime: 1, MaxTime: 20}}, 138 {BlockMeta: tsdb.BlockMeta{MinTime: 2, MaxTime: 30}}, 139 }, 140 } 141 142 testutil.Equals(t, int64(0), g.MinTime()) 143 testutil.Equals(t, int64(30), g.MaxTime()) 144 } 145 146 func BenchmarkGatherNoCompactionMarkFilter_Filter(b *testing.B) { 147 ctx := context.TODO() 148 logger := log.NewLogfmtLogger(io.Discard) 149 150 m := extprom.NewTxGaugeVec(nil, prometheus.GaugeOpts{}, []string{"state"}) 151 152 for blocksNum := 10; blocksNum <= 10000; blocksNum *= 10 { 153 bkt := objstore.NewInMemBucket() 154 155 metas := make(map[ulid.ULID]*metadata.Meta, blocksNum) 156 157 for i := 0; i < blocksNum; i++ { 158 var meta metadata.Meta 159 meta.Version = 1 160 meta.ULID = ulid.MustNew(uint64(i), nil) 161 metas[meta.ULID] = &meta 162 163 var buf bytes.Buffer 164 testutil.Ok(b, json.NewEncoder(&buf).Encode(&meta)) 165 testutil.Ok(b, bkt.Upload(ctx, path.Join(meta.ULID.String(), metadata.MetaFilename), &buf)) 166 } 167 168 for i := 10; i <= 60; i += 10 { 169 b.Run(fmt.Sprintf("Bench-%d-%d", blocksNum, i), func(b *testing.B) { 170 b.ResetTimer() 171 172 for n := 0; n <= b.N; n++ { 173 slowBucket := objstore.WithNoopInstr(objstore.WithDelay(bkt, time.Millisecond*2)) 174 f := NewGatherNoCompactionMarkFilter(logger, slowBucket, i) 175 testutil.Ok(b, f.Filter(ctx, metas, m, nil)) 176 } 177 }) 178 } 179 } 180 181 } 182 183 func createBlockMeta(id uint64, minTime, maxTime int64, labels map[string]string, resolution int64, sources []uint64) *metadata.Meta { 184 sourceBlocks := make([]ulid.ULID, len(sources)) 185 for ind, source := range sources { 186 sourceBlocks[ind] = ulid.MustNew(source, nil) 187 } 188 189 m := &metadata.Meta{ 190 BlockMeta: tsdb.BlockMeta{ 191 ULID: ulid.MustNew(id, nil), 192 MinTime: minTime, 193 MaxTime: maxTime, 194 Compaction: tsdb.BlockMetaCompaction{ 195 Sources: sourceBlocks, 196 }, 197 }, 198 Thanos: metadata.Thanos{ 199 Labels: labels, 200 Downsample: metadata.ThanosDownsample{ 201 Resolution: resolution, 202 }, 203 }, 204 } 205 206 return m 207 } 208 209 func TestRetentionProgressCalculate(t *testing.T) { 210 logger := log.NewNopLogger() 211 reg := prometheus.NewRegistry() 212 213 var bkt objstore.Bucket 214 temp := promauto.With(reg).NewCounter(prometheus.CounterOpts{Name: "test_metric_for_group", Help: "this is a test metric for compact progress tests"}) 215 grouper := NewDefaultGrouper(logger, bkt, false, false, reg, temp, temp, temp, "", 1, 1) 216 217 type retInput struct { 218 meta []*metadata.Meta 219 resMap map[ResolutionLevel]time.Duration 220 } 221 222 keys := make([]string, 3) 223 m := make([]metadata.Meta, 3) 224 m[0].Thanos.Labels = map[string]string{"a": "1"} 225 m[0].Thanos.Downsample.Resolution = downsample.ResLevel0 226 m[1].Thanos.Labels = map[string]string{"b": "2"} 227 m[1].Thanos.Downsample.Resolution = downsample.ResLevel1 228 m[2].Thanos.Labels = map[string]string{"a": "1", "b": "2"} 229 m[2].Thanos.Downsample.Resolution = downsample.ResLevel2 230 for ind, meta := range m { 231 keys[ind] = meta.Thanos.GroupKey() 232 } 233 234 ps := NewRetentionProgressCalculator(reg, nil) 235 236 for _, tcase := range []struct { 237 testName string 238 input retInput 239 expected float64 240 }{ 241 { 242 // In this test case, blocks belonging to multiple groups are tested. All blocks in the first group and the first block in the second group are beyond their retention period. In the second group, the second block still has some time before its retention period and hence, is not marked to be deleted. 243 testName: "multi_group_test", 244 input: retInput{ 245 meta: []*metadata.Meta{ 246 createBlockMeta(6, 1, int64(time.Now().Add(-6*30*24*time.Hour).Unix()*1000), map[string]string{"a": "1"}, downsample.ResLevel0, []uint64{}), 247 createBlockMeta(9, 1, int64(time.Now().Add(-9*30*24*time.Hour).Unix()*1000), map[string]string{"a": "1"}, downsample.ResLevel0, []uint64{}), 248 createBlockMeta(7, 1, int64(time.Now().Add(-4*30*24*time.Hour).Unix()*1000), map[string]string{"b": "2"}, downsample.ResLevel1, []uint64{}), 249 createBlockMeta(8, 1, int64(time.Now().Add(-1*30*24*time.Hour).Unix()*1000), map[string]string{"b": "2"}, downsample.ResLevel1, []uint64{}), 250 createBlockMeta(10, 1, int64(time.Now().Add(-4*30*24*time.Hour).Unix()*1000), map[string]string{"a": "1", "b": "2"}, downsample.ResLevel2, []uint64{}), 251 }, 252 resMap: map[ResolutionLevel]time.Duration{ 253 ResolutionLevel(downsample.ResLevel0): 5 * 30 * 24 * time.Hour, // 5 months retention. 254 ResolutionLevel(downsample.ResLevel1): 3 * 30 * 24 * time.Hour, // 3 months retention. 255 ResolutionLevel(downsample.ResLevel2): 6 * 30 * 24 * time.Hour, // 6 months retention. 256 }, 257 }, 258 expected: 3.0, 259 }, { 260 // In this test case, all the blocks are retained since they have not yet crossed their retention period. 261 testName: "retain_test", 262 input: retInput{ 263 meta: []*metadata.Meta{ 264 createBlockMeta(6, 1, int64(time.Now().Add(-6*30*24*time.Hour).Unix()*1000), map[string]string{"a": "1"}, downsample.ResLevel0, []uint64{}), 265 createBlockMeta(7, 1, int64(time.Now().Add(-4*30*24*time.Hour).Unix()*1000), map[string]string{"b": "2"}, downsample.ResLevel1, []uint64{}), 266 createBlockMeta(8, 1, int64(time.Now().Add(-7*30*24*time.Hour).Unix()*1000), map[string]string{"a": "1", "b": "2"}, downsample.ResLevel2, []uint64{}), 267 }, 268 resMap: map[ResolutionLevel]time.Duration{ 269 ResolutionLevel(downsample.ResLevel0): 10 * 30 * 24 * time.Hour, // 10 months retention. 270 ResolutionLevel(downsample.ResLevel1): 12 * 30 * 24 * time.Hour, // 12 months retention. 271 ResolutionLevel(downsample.ResLevel2): 16 * 30 * 24 * time.Hour, // 6 months retention. 272 }, 273 }, 274 expected: 0.0, 275 }, 276 { 277 // In this test case, all the blocks are deleted since they are past their retention period. 278 testName: "delete_test", 279 input: retInput{ 280 meta: []*metadata.Meta{ 281 createBlockMeta(6, 1, int64(time.Now().Add(-6*30*24*time.Hour).Unix()*1000), map[string]string{"a": "1"}, downsample.ResLevel0, []uint64{}), 282 createBlockMeta(7, 1, int64(time.Now().Add(-4*30*24*time.Hour).Unix()*1000), map[string]string{"b": "2"}, downsample.ResLevel1, []uint64{}), 283 createBlockMeta(8, 1, int64(time.Now().Add(-7*30*24*time.Hour).Unix()*1000), map[string]string{"a": "1", "b": "2"}, downsample.ResLevel2, []uint64{}), 284 }, 285 resMap: map[ResolutionLevel]time.Duration{ 286 ResolutionLevel(downsample.ResLevel0): 3 * 30 * 24 * time.Hour, // 3 months retention. 287 ResolutionLevel(downsample.ResLevel1): 1 * 30 * 24 * time.Hour, // 1 months retention. 288 ResolutionLevel(downsample.ResLevel2): 6 * 30 * 24 * time.Hour, // 6 months retention. 289 }, 290 }, 291 expected: 3.0, 292 }, 293 { 294 // In this test case, none of the blocks are marked for deletion since the retention period is 0d i.e. indefinitely long retention. 295 testName: "zero_day_test", 296 input: retInput{ 297 meta: []*metadata.Meta{ 298 createBlockMeta(6, 1, int64(time.Now().Add(-6*30*24*time.Hour).Unix()*1000), map[string]string{"a": "1"}, downsample.ResLevel0, []uint64{}), 299 createBlockMeta(7, 1, int64(time.Now().Add(-4*30*24*time.Hour).Unix()*1000), map[string]string{"b": "2"}, downsample.ResLevel1, []uint64{}), 300 createBlockMeta(8, 1, int64(time.Now().Add(-7*30*24*time.Hour).Unix()*1000), map[string]string{"a": "1", "b": "2"}, downsample.ResLevel2, []uint64{}), 301 }, 302 resMap: map[ResolutionLevel]time.Duration{ 303 ResolutionLevel(downsample.ResLevel0): 0, 304 ResolutionLevel(downsample.ResLevel1): 0, 305 ResolutionLevel(downsample.ResLevel2): 0, 306 }, 307 }, 308 expected: 0.0, 309 }, 310 } { 311 if ok := t.Run(tcase.testName, func(t *testing.T) { 312 blocks := make(map[ulid.ULID]*metadata.Meta, len(tcase.input.meta)) 313 for _, meta := range tcase.input.meta { 314 blocks[meta.ULID] = meta 315 } 316 groups, err := grouper.Groups(blocks) 317 testutil.Ok(t, err) 318 ps.retentionByResolution = tcase.input.resMap 319 err = ps.ProgressCalculate(context.Background(), groups) 320 testutil.Ok(t, err) 321 metrics := ps.RetentionProgressMetrics 322 testutil.Ok(t, err) 323 testutil.Equals(t, tcase.expected, promtestutil.ToFloat64(metrics.NumberOfBlocksToDelete)) 324 }); !ok { 325 return 326 } 327 } 328 } 329 330 func TestCompactProgressCalculate(t *testing.T) { 331 type planResult struct { 332 compactionBlocks, compactionRuns float64 333 } 334 335 logger := log.NewNopLogger() 336 reg := prometheus.NewRegistry() 337 planner := NewTSDBBasedPlanner(logger, []int64{ 338 int64(1 * time.Hour / time.Millisecond), 339 int64(2 * time.Hour / time.Millisecond), 340 int64(4 * time.Hour / time.Millisecond), 341 int64(8 * time.Hour / time.Millisecond), 342 }) 343 344 keys := make([]string, 3) 345 m := make([]metadata.Meta, 3) 346 m[0].Thanos.Labels = map[string]string{"a": "1"} 347 m[1].Thanos.Labels = map[string]string{"b": "2"} 348 m[2].Thanos.Labels = map[string]string{"a": "1", "b": "2"} 349 m[2].Thanos.Downsample.Resolution = 1 350 for ind, meta := range m { 351 keys[ind] = meta.Thanos.GroupKey() 352 } 353 354 ps := NewCompactionProgressCalculator(reg, planner) 355 356 var bkt objstore.Bucket 357 temp := promauto.With(reg).NewCounter(prometheus.CounterOpts{Name: "test_metric_for_group", Help: "this is a test metric for compact progress tests"}) 358 grouper := NewDefaultGrouper(logger, bkt, false, false, reg, temp, temp, temp, "", 1, 1) 359 360 for _, tcase := range []struct { 361 testName string 362 input []*metadata.Meta 363 expected planResult 364 }{ 365 { 366 // This test has a single compaction run with two blocks from the second group compacted. 367 testName: "single_run_test", 368 input: []*metadata.Meta{ 369 createBlockMeta(0, 0, int64(time.Duration(2)*time.Hour/time.Millisecond), map[string]string{"a": "1"}, 0, []uint64{}), 370 createBlockMeta(1, int64(time.Duration(2)*time.Hour/time.Millisecond), int64(time.Duration(4)*time.Hour/time.Millisecond), map[string]string{"a": "1"}, 0, []uint64{}), 371 createBlockMeta(2, int64(time.Duration(4)*time.Hour/time.Millisecond), int64(time.Duration(6)*time.Hour/time.Millisecond), map[string]string{"b": "2"}, 0, []uint64{}), 372 createBlockMeta(3, int64(time.Duration(6)*time.Hour/time.Millisecond), int64(time.Duration(8)*time.Hour/time.Millisecond), map[string]string{"b": "2"}, 0, []uint64{}), 373 createBlockMeta(4, int64(time.Duration(8)*time.Hour/time.Millisecond), int64(time.Duration(10)*time.Hour/time.Millisecond), map[string]string{"b": "2"}, 0, []uint64{}), 374 createBlockMeta(5, int64(time.Duration(10)*time.Hour/time.Millisecond), int64(time.Duration(12)*time.Hour/time.Millisecond), map[string]string{"a": "1", "b": "2"}, 1, []uint64{}), 375 createBlockMeta(6, int64(time.Duration(12)*time.Hour/time.Millisecond), int64(time.Duration(20)*time.Hour/time.Millisecond), map[string]string{"a": "1", "b": "2"}, 1, []uint64{}), 376 createBlockMeta(7, int64(time.Duration(20)*time.Hour/time.Millisecond), int64(time.Duration(28)*time.Hour/time.Millisecond), map[string]string{"a": "1", "b": "2"}, 1, []uint64{}), 377 }, 378 expected: planResult{ 379 compactionRuns: 1.0, 380 compactionBlocks: 2.0, 381 }, 382 }, 383 { 384 // This test has three compaction runs, with blocks from the first group getting compacted. 385 testName: "three_runs_test", 386 input: []*metadata.Meta{ 387 createBlockMeta(0, 0, int64(time.Duration(2)*time.Hour/time.Millisecond), map[string]string{"a": "1"}, 0, []uint64{}), 388 createBlockMeta(3, int64(time.Duration(2)*time.Hour/time.Millisecond), int64(time.Duration(4)*time.Hour/time.Millisecond), map[string]string{"a": "1"}, 0, []uint64{}), 389 createBlockMeta(4, int64(time.Duration(4)*time.Hour/time.Millisecond), int64(time.Duration(6)*time.Hour/time.Millisecond), map[string]string{"a": "1"}, 0, []uint64{}), 390 createBlockMeta(5, int64(time.Duration(6)*time.Hour/time.Millisecond), int64(time.Duration(8)*time.Hour/time.Millisecond), map[string]string{"a": "1"}, 0, []uint64{}), 391 createBlockMeta(6, int64(time.Duration(8)*time.Hour/time.Millisecond), int64(time.Duration(10)*time.Hour/time.Millisecond), map[string]string{"a": "1"}, 0, []uint64{}), 392 createBlockMeta(1, int64(time.Duration(2)*time.Hour/time.Millisecond), int64(time.Duration(4)*time.Hour/time.Millisecond), map[string]string{"b": "2"}, 0, []uint64{}), 393 createBlockMeta(2, int64(time.Duration(4)*time.Hour/time.Millisecond), int64(time.Duration(6)*time.Hour/time.Millisecond), map[string]string{"b": "2"}, 0, []uint64{}), 394 }, 395 expected: planResult{ 396 compactionRuns: 3.0, 397 compactionBlocks: 6.0, 398 }, 399 }, 400 { 401 // This test case has 4 2-hour blocks, which are non consecutive. 402 // Hence, only the first two blocks are compacted. 403 testName: "non_consecutive_blocks_test", 404 input: []*metadata.Meta{ 405 createBlockMeta(1, int64(time.Duration(2)*time.Hour/time.Millisecond), int64(time.Duration(4)*time.Hour/time.Millisecond), map[string]string{"a": "1", "b": "2"}, 1, []uint64{}), 406 createBlockMeta(2, int64(time.Duration(4)*time.Hour/time.Millisecond), int64(time.Duration(6)*time.Hour/time.Millisecond), map[string]string{"a": "1", "b": "2"}, 1, []uint64{}), 407 createBlockMeta(3, int64(time.Duration(6)*time.Hour/time.Millisecond), int64(time.Duration(8)*time.Hour/time.Millisecond), map[string]string{"a": "1", "b": "2"}, 1, []uint64{}), 408 createBlockMeta(4, int64(time.Duration(10)*time.Hour/time.Millisecond), int64(time.Duration(12)*time.Hour/time.Millisecond), map[string]string{"a": "1", "b": "2"}, 1, []uint64{}), 409 }, 410 expected: planResult{ 411 compactionRuns: 1.0, 412 compactionBlocks: 2.0, 413 }, 414 }, 415 } { 416 if ok := t.Run(tcase.testName, func(t *testing.T) { 417 blocks := make(map[ulid.ULID]*metadata.Meta, len(tcase.input)) 418 for _, meta := range tcase.input { 419 blocks[meta.ULID] = meta 420 } 421 groups, err := grouper.Groups(blocks) 422 testutil.Ok(t, err) 423 err = ps.ProgressCalculate(context.Background(), groups) 424 testutil.Ok(t, err) 425 metrics := ps.CompactProgressMetrics 426 testutil.Ok(t, err) 427 testutil.Equals(t, tcase.expected.compactionBlocks, promtestutil.ToFloat64(metrics.NumberOfCompactionBlocks)) 428 testutil.Equals(t, tcase.expected.compactionRuns, promtestutil.ToFloat64(metrics.NumberOfCompactionRuns)) 429 }); !ok { 430 return 431 } 432 } 433 } 434 435 func TestDownsampleProgressCalculate(t *testing.T) { 436 reg := prometheus.NewRegistry() 437 logger := log.NewNopLogger() 438 439 keys := make([]string, 3) 440 m := make([]metadata.Meta, 3) 441 m[0].Thanos.Labels = map[string]string{"a": "1"} 442 m[0].Thanos.Downsample.Resolution = downsample.ResLevel0 443 m[1].Thanos.Labels = map[string]string{"b": "2"} 444 m[1].Thanos.Downsample.Resolution = downsample.ResLevel1 445 m[2].Thanos.Labels = map[string]string{"a": "1", "b": "2"} 446 m[2].Thanos.Downsample.Resolution = downsample.ResLevel2 447 for ind, meta := range m { 448 keys[ind] = meta.Thanos.GroupKey() 449 } 450 451 ds := NewDownsampleProgressCalculator(reg) 452 453 var bkt objstore.Bucket 454 temp := promauto.With(reg).NewCounter(prometheus.CounterOpts{Name: "test_metric_for_group", Help: "this is a test metric for downsample progress tests"}) 455 grouper := NewDefaultGrouper(logger, bkt, false, false, reg, temp, temp, temp, "", 1, 1) 456 457 for _, tcase := range []struct { 458 testName string 459 input []*metadata.Meta 460 expected float64 461 }{ 462 { 463 // This test case has blocks from multiple groups and resolution levels. Only the blocks in the second group should be downsampled since the others either have time differences not in the range for their resolution, or a resolution which should not be downsampled. 464 testName: "multi_group_test", 465 input: []*metadata.Meta{ 466 createBlockMeta(6, 1, downsample.ResLevel1DownsampleRange, map[string]string{"a": "1"}, downsample.ResLevel0, []uint64{7, 8}), 467 createBlockMeta(7, 0, downsample.ResLevel2DownsampleRange, map[string]string{"b": "2"}, downsample.ResLevel1, []uint64{8, 9}), 468 createBlockMeta(9, 0, downsample.ResLevel2DownsampleRange, map[string]string{"b": "2"}, downsample.ResLevel1, []uint64{8, 11}), 469 createBlockMeta(8, 0, downsample.ResLevel2DownsampleRange, map[string]string{"a": "1", "b": "2"}, downsample.ResLevel2, []uint64{9, 10}), 470 }, 471 expected: 2.0, 472 }, { 473 // This is a test case for resLevel0, with the correct time difference threshold. 474 // This block should be downsampled. 475 testName: "res_level0_test", 476 input: []*metadata.Meta{ 477 createBlockMeta(9, 0, downsample.ResLevel1DownsampleRange, map[string]string{"a": "1"}, downsample.ResLevel0, []uint64{10, 11}), 478 }, 479 expected: 1.0, 480 }, { 481 // This is a test case for resLevel1, with the correct time difference threshold. 482 // This block should be downsampled. 483 testName: "res_level1_test", 484 input: []*metadata.Meta{ 485 createBlockMeta(9, 0, downsample.ResLevel2DownsampleRange, map[string]string{"b": "2"}, downsample.ResLevel1, []uint64{10, 11}), 486 }, 487 expected: 1.0, 488 }, 489 { 490 // This is a test case for resLevel2. 491 // Blocks with this resolution should not be downsampled. 492 testName: "res_level2_test", 493 input: []*metadata.Meta{ 494 createBlockMeta(10, 0, downsample.ResLevel2DownsampleRange, map[string]string{"a": "1", "b": "2"}, downsample.ResLevel2, []uint64{11, 12}), 495 }, 496 expected: 0.0, 497 }, { 498 // This is a test case for resLevel0, with incorrect time difference, below the threshold. 499 // This block should be downsampled. 500 testName: "res_level0_test_incorrect", 501 input: []*metadata.Meta{ 502 createBlockMeta(9, 1, downsample.ResLevel1DownsampleRange, map[string]string{"a": "1"}, downsample.ResLevel0, []uint64{10, 11}), 503 }, 504 expected: 0.0, 505 }, 506 { 507 // This is a test case for resLevel1, with incorrect time difference, below the threshold. 508 // This block should be downsampled. 509 testName: "res_level1_test", 510 input: []*metadata.Meta{ 511 createBlockMeta(9, 1, downsample.ResLevel2DownsampleRange, map[string]string{"b": "2"}, downsample.ResLevel1, []uint64{10, 11}), 512 }, 513 expected: 0.0, 514 }, 515 } { 516 if ok := t.Run(tcase.testName, func(t *testing.T) { 517 blocks := make(map[ulid.ULID]*metadata.Meta, len(tcase.input)) 518 for _, meta := range tcase.input { 519 blocks[meta.ULID] = meta 520 } 521 groups, err := grouper.Groups(blocks) 522 testutil.Ok(t, err) 523 524 err = ds.ProgressCalculate(context.Background(), groups) 525 testutil.Ok(t, err) 526 metrics := ds.DownsampleProgressMetrics 527 testutil.Equals(t, tcase.expected, promtestutil.ToFloat64(metrics.NumberOfBlocksDownsampled)) 528 }); !ok { 529 return 530 } 531 } 532 } 533 534 func TestNoMarkFilterAtomic(t *testing.T) { 535 ctx := context.TODO() 536 logger := log.NewLogfmtLogger(io.Discard) 537 538 m := extprom.NewTxGaugeVec(nil, prometheus.GaugeOpts{}, []string{"state"}) 539 540 blocksNum := 200 541 bkt := objstore.NewInMemBucket() 542 543 metas := make(map[ulid.ULID]*metadata.Meta, blocksNum) 544 545 noMarkCounter := promauto.NewCounter(prometheus.CounterOpts{ 546 Name: "coolcounter", 547 }) 548 549 for i := 0; i < blocksNum; i++ { 550 var meta metadata.Meta 551 meta.Version = 1 552 meta.ULID = ulid.MustNew(uint64(i), nil) 553 metas[meta.ULID] = &meta 554 555 var buf bytes.Buffer 556 testutil.Ok(t, json.NewEncoder(&buf).Encode(&meta)) 557 testutil.Ok(t, bkt.Upload(ctx, path.Join(meta.ULID.String(), metadata.MetaFilename), &buf)) 558 if i%2 == 0 { 559 testutil.Ok( 560 t, 561 block.MarkForNoCompact(ctx, logger, bkt, meta.ULID, metadata.NoCompactReason("test"), "nodetails", noMarkCounter), 562 ) 563 } 564 } 565 566 slowBucket := objstore.WithNoopInstr(objstore.WithDelay(bkt, time.Millisecond*200)) 567 f := NewGatherNoCompactionMarkFilter(logger, slowBucket, 10) 568 569 ctx, cancel := context.WithCancel(ctx) 570 571 g := &run.Group{} 572 573 // Fill the map initially. 574 testutil.Ok(t, f.Filter(ctx, metas, m, nil)) 575 testutil.Assert(t, len(f.NoCompactMarkedBlocks()) > 0, "expected to always have not compacted blocks") 576 577 g.Add(func() error { 578 for { 579 if ctx.Err() != nil { 580 return nil 581 } 582 if err := f.Filter(ctx, metas, m, nil); err != nil && !errors.Is(err, context.Canceled) { 583 testutil.Ok(t, err) 584 } 585 } 586 }, func(err error) { 587 cancel() 588 }) 589 590 g.Add(func() error { 591 for { 592 if ctx.Err() != nil { 593 return nil 594 } 595 596 if len(f.NoCompactMarkedBlocks()) == 0 { 597 return fmt.Errorf("expected to always have not compacted blocks") 598 } 599 } 600 }, func(err error) { 601 cancel() 602 }) 603 604 time.AfterFunc(10*time.Second, func() { 605 cancel() 606 }) 607 testutil.Ok(t, g.Run()) 608 }