github.com/muhammadn/cortex@v1.9.1-0.20220510110439-46bb7000d03d/tools/blocksconvert/builder/tsdb_test.go (about)

     1  package builder
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"io/ioutil"
     7  	"math/rand"
     8  	"os"
     9  	"path/filepath"
    10  	"sync"
    11  	"testing"
    12  	"time"
    13  
    14  	"github.com/oklog/ulid"
    15  	"github.com/prometheus/client_golang/prometheus"
    16  	"github.com/prometheus/common/model"
    17  	"github.com/prometheus/prometheus/pkg/labels"
    18  	"github.com/prometheus/prometheus/tsdb"
    19  	"github.com/prometheus/prometheus/tsdb/chunks"
    20  	"github.com/prometheus/prometheus/tsdb/index"
    21  	"github.com/stretchr/testify/require"
    22  	"github.com/thanos-io/thanos/pkg/block"
    23  	"github.com/thanos-io/thanos/pkg/block/metadata"
    24  	"github.com/thanos-io/thanos/pkg/extprom"
    25  	"go.uber.org/atomic"
    26  
    27  	"github.com/cortexproject/cortex/pkg/chunk"
    28  	"github.com/cortexproject/cortex/pkg/chunk/encoding"
    29  	"github.com/cortexproject/cortex/pkg/util"
    30  	util_log "github.com/cortexproject/cortex/pkg/util/log"
    31  )
    32  
    33  func TestTsdbBuilder(t *testing.T) {
    34  	dir, err := ioutil.TempDir("", "tsdb")
    35  	require.NoError(t, err)
    36  	t.Cleanup(func() {
    37  		_ = os.RemoveAll(dir)
    38  	})
    39  
    40  	yesterdayStart := time.Now().Add(-24 * time.Hour).Truncate(24 * time.Hour)
    41  	yesterdayEnd := yesterdayStart.Add(24 * time.Hour)
    42  
    43  	seriesCounter := prometheus.NewCounter(prometheus.CounterOpts{})
    44  	samplesCounter := prometheus.NewCounter(prometheus.CounterOpts{})
    45  	inMemory := prometheus.NewGauge(prometheus.GaugeOpts{})
    46  
    47  	b, err := newTsdbBuilder(dir, yesterdayStart, yesterdayEnd, 33, 0, util_log.Logger, seriesCounter, samplesCounter, inMemory)
    48  	require.NoError(t, err)
    49  
    50  	seriesCount := 200
    51  	totalSamples := atomic.NewInt64(0)
    52  	concurrency := 15
    53  
    54  	ch := make(chan int, seriesCount)
    55  	for i := 0; i < seriesCount; i++ {
    56  		ch <- i
    57  	}
    58  	close(ch)
    59  
    60  	// Test that we can add series concurrently.
    61  	wg := sync.WaitGroup{}
    62  	for i := 0; i < concurrency; i++ {
    63  		wg.Add(1)
    64  		go func() {
    65  			defer wg.Done()
    66  
    67  			for i := range ch {
    68  				lbls, samples := metricInfo(i)
    69  
    70  				err := b.buildSingleSeries(lbls, generateSingleSeriesWithOverlappingChunks(t, lbls, yesterdayStart, yesterdayEnd, samples))
    71  				require.NoError(t, err)
    72  
    73  				totalSamples.Add(int64(samples))
    74  			}
    75  		}()
    76  	}
    77  
    78  	// Wait until all series are added to the builder.
    79  	wg.Wait()
    80  
    81  	id, err := b.finishBlock("unit test", map[string]string{"ext_label": "12345"})
    82  	require.NoError(t, err)
    83  
    84  	db, err := tsdb.Open(dir, util_log.Logger, prometheus.NewRegistry(), tsdb.DefaultOptions(), nil)
    85  	require.NoError(t, err)
    86  
    87  	blocks := db.Blocks()
    88  
    89  	// Verify basic blocks details.
    90  	{
    91  		require.Equal(t, 1, len(blocks))
    92  		require.Equal(t, id, blocks[0].Meta().ULID)
    93  		require.Equal(t, id, blocks[0].Meta().Compaction.Sources[0])
    94  		require.Equal(t, uint64(seriesCount), blocks[0].Meta().Stats.NumSeries)
    95  		require.Equal(t, uint64(totalSamples.Load()), blocks[0].Meta().Stats.NumSamples)
    96  	}
    97  
    98  	// Make sure we can query expected number of samples back.
    99  	{
   100  		q, err := db.Querier(context.Background(), util.TimeToMillis(yesterdayStart), util.TimeToMillis(yesterdayEnd))
   101  		require.NoError(t, err)
   102  		res := q.Select(true, nil, labels.MustNewMatcher(labels.MatchNotEqual, labels.MetricName, "")) // Select all
   103  
   104  		for i := 0; i < seriesCount; i++ {
   105  			require.True(t, res.Next())
   106  			s := res.At()
   107  
   108  			lbls, samples := metricInfo(i)
   109  			require.True(t, labels.Equal(lbls, s.Labels()))
   110  
   111  			cnt := 0
   112  			it := s.Iterator()
   113  			for it.Next() {
   114  				cnt++
   115  			}
   116  
   117  			require.NoError(t, it.Err())
   118  			require.Equal(t, samples, cnt)
   119  		}
   120  		require.NoError(t, res.Err())
   121  		require.False(t, res.Next())
   122  		require.NoError(t, q.Close())
   123  	}
   124  
   125  	// Verify that chunks are stored in sorted order (based on series).
   126  	{
   127  		idx, err := blocks[0].Index()
   128  		require.NoError(t, err)
   129  
   130  		allPostings, err := idx.Postings(index.AllPostingsKey())
   131  		require.NoError(t, err)
   132  
   133  		lastChunkRef := uint64(0)
   134  		// Postings must be sorted wrt. series. Here we check if chunks are sorted too.
   135  		for allPostings.Next() {
   136  			seriesID := allPostings.At()
   137  			var lset labels.Labels
   138  			var chks []chunks.Meta
   139  
   140  			require.NoError(t, idx.Series(seriesID, &lset, &chks))
   141  
   142  			for _, c := range chks {
   143  				require.True(t, lastChunkRef < c.Ref, "lastChunkRef: %d, c.Ref: %d", lastChunkRef, c.Ref)
   144  				lastChunkRef = c.Ref
   145  			}
   146  		}
   147  		require.NoError(t, allPostings.Err())
   148  		require.NoError(t, idx.Close())
   149  	}
   150  
   151  	require.NoError(t, db.Close())
   152  
   153  	m, err := metadata.ReadFromDir(filepath.Join(dir, id.String()))
   154  	require.NoError(t, err)
   155  
   156  	otherID := ulid.MustNew(ulid.Now(), nil)
   157  
   158  	// Make sure that deduplicate filter doesn't remove this block (thanks to correct sources).
   159  	df := block.NewDeduplicateFilter()
   160  	inp := map[ulid.ULID]*metadata.Meta{
   161  		otherID: {
   162  			BlockMeta: tsdb.BlockMeta{
   163  				ULID:    otherID,
   164  				MinTime: 0,
   165  				MaxTime: 0,
   166  				Compaction: tsdb.BlockMetaCompaction{
   167  					Sources: []ulid.ULID{otherID},
   168  				},
   169  				Version: 0,
   170  			},
   171  		},
   172  
   173  		id: m,
   174  	}
   175  
   176  	err = df.Filter(context.Background(), inp, extprom.NewTxGaugeVec(nil, prometheus.GaugeOpts{}, []string{"state"}))
   177  	require.NoError(t, err)
   178  	require.NotNil(t, inp[id])
   179  }
   180  
   181  func metricInfo(ix int) (labels.Labels, int) {
   182  	return labels.Labels{{Name: labels.MetricName, Value: fmt.Sprintf("metric_%04d", ix)}}, (ix + 1) * 100
   183  }
   184  
   185  func generateSingleSeriesWithOverlappingChunks(t *testing.T, metric labels.Labels, start, end time.Time, samples int) []chunk.Chunk {
   186  	r := rand.New(rand.NewSource(time.Now().UnixNano()))
   187  
   188  	tsStep := end.Sub(start).Milliseconds() / int64(samples)
   189  
   190  	// We keep adding new chunks with samples until we reach required number.
   191  	// To make sure we do that quickly, we align timestamps on "tsStep",
   192  	// and also start with chunk at the beginning of time range.
   193  	samplesMap := make(map[int64]float64, samples)
   194  
   195  	var ecs []encoding.Chunk
   196  	for len(samplesMap) < samples {
   197  		var ts int64
   198  		if len(samplesMap) < samples/10 {
   199  			ts = util.TimeToMillis(start)
   200  		} else {
   201  			ts = util.TimeToMillis(start) + ((r.Int63n(end.Sub(start).Milliseconds()) / tsStep) * tsStep)
   202  		}
   203  
   204  		pc := encoding.New()
   205  		for sc := r.Intn(samples/10) * 10; sc > 0 && len(samplesMap) < samples; sc-- {
   206  			val, ok := samplesMap[ts]
   207  			if !ok {
   208  				val = r.Float64()
   209  				samplesMap[ts] = val
   210  			}
   211  
   212  			overflow, err := pc.Add(model.SamplePair{
   213  				Timestamp: model.Time(ts),
   214  				Value:     model.SampleValue(val),
   215  			})
   216  			require.NoError(t, err)
   217  
   218  			if overflow != nil {
   219  				ecs = append(ecs, pc)
   220  				pc = overflow
   221  			}
   222  
   223  			ts += tsStep
   224  			if ts >= util.TimeToMillis(end) {
   225  				break
   226  			}
   227  		}
   228  
   229  		ecs = append(ecs, pc)
   230  	}
   231  
   232  	r.Shuffle(len(ecs), func(i, j int) {
   233  		ecs[i], ecs[j] = ecs[j], ecs[i]
   234  	})
   235  
   236  	var cs []chunk.Chunk
   237  	for _, ec := range ecs {
   238  		c := chunk.NewChunk("test", 0, metric, ec, model.TimeFromUnixNano(start.UnixNano()), model.TimeFromUnixNano(end.UnixNano()))
   239  		cs = append(cs, c)
   240  	}
   241  
   242  	return cs
   243  }