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 }