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  }