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  }