github.com/muhammadn/cortex@v1.9.1-0.20220510110439-46bb7000d03d/pkg/querier/block_test.go (about) 1 package querier 2 3 import ( 4 "math" 5 "sort" 6 "strconv" 7 "testing" 8 "time" 9 10 "github.com/prometheus/common/model" 11 "github.com/prometheus/prometheus/pkg/labels" 12 "github.com/prometheus/prometheus/promql" 13 "github.com/prometheus/prometheus/storage" 14 "github.com/prometheus/prometheus/tsdb/chunkenc" 15 "github.com/stretchr/testify/assert" 16 "github.com/stretchr/testify/require" 17 "github.com/thanos-io/thanos/pkg/store/labelpb" 18 "github.com/thanos-io/thanos/pkg/store/storepb" 19 20 "github.com/cortexproject/cortex/pkg/util" 21 ) 22 23 func TestBlockQuerierSeries(t *testing.T) { 24 t.Parallel() 25 26 // Init some test fixtures 27 minTimestamp := time.Unix(1, 0) 28 maxTimestamp := time.Unix(10, 0) 29 30 tests := map[string]struct { 31 series *storepb.Series 32 expectedMetric labels.Labels 33 expectedSamples []model.SamplePair 34 expectedErr string 35 }{ 36 "empty series": { 37 series: &storepb.Series{}, 38 expectedMetric: labels.Labels(nil), 39 expectedErr: "no chunks", 40 }, 41 "should return series on success": { 42 series: &storepb.Series{ 43 Labels: []labelpb.ZLabel{ 44 {Name: "foo", Value: "bar"}, 45 }, 46 Chunks: []storepb.AggrChunk{ 47 {MinTime: minTimestamp.Unix() * 1000, MaxTime: maxTimestamp.Unix() * 1000, Raw: &storepb.Chunk{Type: storepb.Chunk_XOR, Data: mockTSDBChunkData()}}, 48 }, 49 }, 50 expectedMetric: labels.Labels{ 51 {Name: "foo", Value: "bar"}, 52 }, 53 expectedSamples: []model.SamplePair{ 54 {Timestamp: model.TimeFromUnixNano(time.Unix(1, 0).UnixNano()), Value: model.SampleValue(1)}, 55 {Timestamp: model.TimeFromUnixNano(time.Unix(2, 0).UnixNano()), Value: model.SampleValue(2)}, 56 }, 57 }, 58 "should return error on failure while reading encoded chunk data": { 59 series: &storepb.Series{ 60 Labels: []labelpb.ZLabel{{Name: "foo", Value: "bar"}}, 61 Chunks: []storepb.AggrChunk{ 62 {MinTime: minTimestamp.Unix() * 1000, MaxTime: maxTimestamp.Unix() * 1000, Raw: &storepb.Chunk{Type: storepb.Chunk_XOR, Data: []byte{0, 1}}}, 63 }, 64 }, 65 expectedMetric: labels.Labels{labels.Label{Name: "foo", Value: "bar"}}, 66 expectedErr: `cannot iterate chunk for series: {foo="bar"}: EOF`, 67 }, 68 } 69 70 for testName, testData := range tests { 71 testData := testData 72 73 t.Run(testName, func(t *testing.T) { 74 series := newBlockQuerierSeries(labelpb.ZLabelsToPromLabels(testData.series.Labels), testData.series.Chunks) 75 76 assert.Equal(t, testData.expectedMetric, series.Labels()) 77 78 sampleIx := 0 79 80 it := series.Iterator() 81 for it.Next() { 82 ts, val := it.At() 83 require.True(t, sampleIx < len(testData.expectedSamples)) 84 assert.Equal(t, int64(testData.expectedSamples[sampleIx].Timestamp), ts) 85 assert.Equal(t, float64(testData.expectedSamples[sampleIx].Value), val) 86 sampleIx++ 87 } 88 // make sure we've got all expected samples 89 require.Equal(t, sampleIx, len(testData.expectedSamples)) 90 91 if testData.expectedErr != "" { 92 require.EqualError(t, it.Err(), testData.expectedErr) 93 } else { 94 require.NoError(t, it.Err()) 95 } 96 }) 97 } 98 } 99 100 func mockTSDBChunkData() []byte { 101 chunk := chunkenc.NewXORChunk() 102 appender, err := chunk.Appender() 103 if err != nil { 104 panic(err) 105 } 106 107 appender.Append(time.Unix(1, 0).Unix()*1000, 1) 108 appender.Append(time.Unix(2, 0).Unix()*1000, 2) 109 110 return chunk.Bytes() 111 } 112 113 func TestBlockQuerierSeriesSet(t *testing.T) { 114 now := time.Now() 115 116 // It would be possible to split this test into smaller parts, but I prefer to keep 117 // it as is, to also test transitions between series. 118 119 bss := &blockQuerierSeriesSet{ 120 series: []*storepb.Series{ 121 // first, with one chunk. 122 { 123 Labels: mkZLabels("__name__", "first", "a", "a"), 124 Chunks: []storepb.AggrChunk{ 125 createAggrChunkWithSineSamples(now, now.Add(100*time.Second), 3*time.Millisecond), // ceil(100 / 0.003) samples (= 33334) 126 }, 127 }, 128 129 // continuation of previous series. Must have exact same labels. 130 { 131 Labels: mkZLabels("__name__", "first", "a", "a"), 132 Chunks: []storepb.AggrChunk{ 133 createAggrChunkWithSineSamples(now.Add(100*time.Second), now.Add(200*time.Second), 3*time.Millisecond), // ceil(100 / 0.003) samples more, 66668 in total 134 }, 135 }, 136 137 // second, with multiple chunks 138 { 139 Labels: mkZLabels("__name__", "second"), 140 Chunks: []storepb.AggrChunk{ 141 // unordered chunks 142 createAggrChunkWithSineSamples(now.Add(400*time.Second), now.Add(600*time.Second), 5*time.Millisecond), // 200 / 0.005 (= 40000 samples, = 120000 in total) 143 createAggrChunkWithSineSamples(now.Add(200*time.Second), now.Add(400*time.Second), 5*time.Millisecond), // 200 / 0.005 (= 40000 samples) 144 createAggrChunkWithSineSamples(now, now.Add(200*time.Second), 5*time.Millisecond), // 200 / 0.005 (= 40000 samples) 145 }, 146 }, 147 148 // overlapping 149 { 150 Labels: mkZLabels("__name__", "overlapping"), 151 Chunks: []storepb.AggrChunk{ 152 createAggrChunkWithSineSamples(now, now.Add(10*time.Second), 5*time.Millisecond), // 10 / 0.005 = 2000 samples 153 }, 154 }, 155 { 156 Labels: mkZLabels("__name__", "overlapping"), 157 Chunks: []storepb.AggrChunk{ 158 // 10 / 0.005 = 2000 samples, but first 1000 are overlapping with previous series, so this chunk only contributes 1000 159 createAggrChunkWithSineSamples(now.Add(5*time.Second), now.Add(15*time.Second), 5*time.Millisecond), 160 }, 161 }, 162 163 // overlapping 2. Chunks here come in wrong order. 164 { 165 Labels: mkZLabels("__name__", "overlapping2"), 166 Chunks: []storepb.AggrChunk{ 167 // entire range overlaps with the next chunk, so this chunks contributes 0 samples (it will be sorted as second) 168 createAggrChunkWithSineSamples(now.Add(3*time.Second), now.Add(7*time.Second), 5*time.Millisecond), 169 }, 170 }, 171 { 172 Labels: mkZLabels("__name__", "overlapping2"), 173 Chunks: []storepb.AggrChunk{ 174 // this chunk has completely overlaps previous chunk. Since its minTime is lower, it will be sorted as first. 175 createAggrChunkWithSineSamples(now, now.Add(10*time.Second), 5*time.Millisecond), // 10 / 0.005 = 2000 samples 176 }, 177 }, 178 { 179 Labels: mkZLabels("__name__", "overlapping2"), 180 Chunks: []storepb.AggrChunk{ 181 // no samples 182 createAggrChunkWithSineSamples(now, now, 5*time.Millisecond), 183 }, 184 }, 185 186 { 187 Labels: mkZLabels("__name__", "overlapping2"), 188 Chunks: []storepb.AggrChunk{ 189 // 2000 samples more (10 / 0.005) 190 createAggrChunkWithSineSamples(now.Add(20*time.Second), now.Add(30*time.Second), 5*time.Millisecond), 191 }, 192 }, 193 }, 194 } 195 196 verifyNextSeries(t, bss, labels.FromStrings("__name__", "first", "a", "a"), 66668) 197 verifyNextSeries(t, bss, labels.FromStrings("__name__", "second"), 120000) 198 verifyNextSeries(t, bss, labels.FromStrings("__name__", "overlapping"), 3000) 199 verifyNextSeries(t, bss, labels.FromStrings("__name__", "overlapping2"), 4000) 200 require.False(t, bss.Next()) 201 } 202 203 func verifyNextSeries(t *testing.T, ss storage.SeriesSet, labels labels.Labels, samples int) { 204 require.True(t, ss.Next()) 205 206 s := ss.At() 207 require.Equal(t, labels, s.Labels()) 208 209 prevTS := int64(0) 210 count := 0 211 for it := s.Iterator(); it.Next(); { 212 count++ 213 ts, v := it.At() 214 require.Equal(t, math.Sin(float64(ts)), v) 215 require.Greater(t, ts, prevTS, "timestamps are increasing") 216 prevTS = ts 217 } 218 219 require.Equal(t, samples, count) 220 } 221 222 func createAggrChunkWithSineSamples(minTime, maxTime time.Time, step time.Duration) storepb.AggrChunk { 223 var samples []promql.Point 224 225 minT := minTime.Unix() * 1000 226 maxT := maxTime.Unix() * 1000 227 stepMillis := step.Milliseconds() 228 229 for t := minT; t < maxT; t += stepMillis { 230 samples = append(samples, promql.Point{T: t, V: math.Sin(float64(t))}) 231 } 232 233 return createAggrChunk(minT, maxT, samples...) 234 } 235 236 func createAggrChunkWithSamples(samples ...promql.Point) storepb.AggrChunk { 237 return createAggrChunk(samples[0].T, samples[len(samples)-1].T, samples...) 238 } 239 240 func createAggrChunk(minTime, maxTime int64, samples ...promql.Point) storepb.AggrChunk { 241 // Ensure samples are sorted by timestamp. 242 sort.Slice(samples, func(i, j int) bool { 243 return samples[i].T < samples[j].T 244 }) 245 246 chunk := chunkenc.NewXORChunk() 247 appender, err := chunk.Appender() 248 if err != nil { 249 panic(err) 250 } 251 252 for _, s := range samples { 253 appender.Append(s.T, s.V) 254 } 255 256 return storepb.AggrChunk{ 257 MinTime: minTime, 258 MaxTime: maxTime, 259 Raw: &storepb.Chunk{ 260 Type: storepb.Chunk_XOR, 261 Data: chunk.Bytes(), 262 }, 263 } 264 } 265 266 func mkZLabels(s ...string) []labelpb.ZLabel { 267 var result []labelpb.ZLabel 268 269 for i := 0; i+1 < len(s); i = i + 2 { 270 result = append(result, labelpb.ZLabel{ 271 Name: s[i], 272 Value: s[i+1], 273 }) 274 } 275 276 return result 277 } 278 279 func mkLabels(s ...string) []labels.Label { 280 return labelpb.ZLabelsToPromLabels(mkZLabels(s...)) 281 } 282 283 func Benchmark_newBlockQuerierSeries(b *testing.B) { 284 lbls := mkLabels( 285 "__name__", "test", 286 "label_1", "value_1", 287 "label_2", "value_2", 288 "label_3", "value_3", 289 "label_4", "value_4", 290 "label_5", "value_5", 291 "label_6", "value_6", 292 "label_7", "value_7", 293 "label_8", "value_8", 294 "label_9", "value_9") 295 296 chunks := []storepb.AggrChunk{ 297 createAggrChunkWithSineSamples(time.Now(), time.Now().Add(-time.Hour), time.Minute), 298 } 299 300 b.ResetTimer() 301 for i := 0; i < b.N; i++ { 302 newBlockQuerierSeries(lbls, chunks) 303 } 304 } 305 306 func Benchmark_blockQuerierSeriesSet_iteration(b *testing.B) { 307 const ( 308 numSeries = 8000 309 numSamplesPerChunk = 240 310 numChunksPerSeries = 24 311 ) 312 313 // Generate series. 314 series := make([]*storepb.Series, 0, numSeries) 315 for seriesID := 0; seriesID < numSeries; seriesID++ { 316 lbls := mkZLabels("__name__", "test", "series_id", strconv.Itoa(seriesID)) 317 chunks := make([]storepb.AggrChunk, 0, numChunksPerSeries) 318 319 // Create chunks with 1 sample per second. 320 for minT := int64(0); minT < numChunksPerSeries*numSamplesPerChunk; minT += numSamplesPerChunk { 321 chunks = append(chunks, createAggrChunkWithSineSamples(util.TimeFromMillis(minT), util.TimeFromMillis(minT+numSamplesPerChunk), time.Millisecond)) 322 } 323 324 series = append(series, &storepb.Series{ 325 Labels: lbls, 326 Chunks: chunks, 327 }) 328 } 329 330 b.ResetTimer() 331 332 for n := 0; n < b.N; n++ { 333 set := blockQuerierSeriesSet{series: series} 334 335 for set.Next() { 336 for t := set.At().Iterator(); t.Next(); { 337 t.At() 338 } 339 } 340 } 341 }