github.com/m3db/m3@v1.5.1-0.20231129193456-75a402aa583b/src/cmd/services/m3comparator/main/querier_test.go (about)

     1  // Copyright (c) 2020 Uber Technologies, Inc.
     2  //
     3  // Permission is hereby granted, free of charge, to any person obtaining a copy
     4  // of this software and associated documentation files (the "Software"), to deal
     5  // in the Software without restriction, including without limitation the rights
     6  // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
     7  // copies of the Software, and to permit persons to whom the Software is
     8  // furnished to do so, subject to the following conditions:
     9  //
    10  // The above copyright notice and this permission notice shall be included in
    11  // all copies or substantial portions of the Software.
    12  //
    13  // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    14  // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    15  // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
    16  // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
    17  // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
    18  // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
    19  // THE SOFTWARE.
    20  
    21  package main
    22  
    23  import (
    24  	"context"
    25  	"testing"
    26  	"time"
    27  
    28  	"github.com/m3db/m3/src/cmd/services/m3comparator/main/parser"
    29  	"github.com/m3db/m3/src/dbnode/encoding"
    30  	"github.com/m3db/m3/src/query/models"
    31  	"github.com/m3db/m3/src/query/storage"
    32  	"github.com/m3db/m3/src/x/ident"
    33  	xtest "github.com/m3db/m3/src/x/test"
    34  	xtime "github.com/m3db/m3/src/x/time"
    35  
    36  	"github.com/golang/mock/gomock"
    37  	"github.com/stretchr/testify/assert"
    38  	"github.com/stretchr/testify/require"
    39  )
    40  
    41  type testSeriesLoadHandler struct {
    42  	iters encoding.SeriesIterators
    43  }
    44  
    45  func (h *testSeriesLoadHandler) getSeriesIterators(
    46  	name string) (encoding.SeriesIterators, error) {
    47  	return h.iters, nil
    48  }
    49  
    50  var _ seriesLoadHandler = (*testSeriesLoadHandler)(nil)
    51  
    52  type tagMap map[string]string
    53  
    54  var (
    55  	iteratorOpts = parser.Options{
    56  		EncoderPool:       encoderPool,
    57  		IteratorPools:     iterPools,
    58  		TagOptions:        tagOptions,
    59  		InstrumentOptions: iOpts,
    60  	}
    61  	metricNameTag = string(iteratorOpts.TagOptions.MetricName())
    62  )
    63  
    64  const (
    65  	blockSize             = time.Hour * 12
    66  	defaultResolution     = time.Second * 30
    67  	metricsName           = "preloaded"
    68  	predefinedSeriesCount = 10
    69  	histogramBucketCount  = 4
    70  )
    71  
    72  func TestFetchCompressed(t *testing.T) {
    73  	tests := []struct {
    74  		name          string
    75  		queryTagName  string
    76  		queryTagValue string
    77  		expectedCount int
    78  	}{
    79  		{
    80  			name:          "querying by metric name returns preloaded data",
    81  			queryTagName:  metricNameTag,
    82  			queryTagValue: metricsName,
    83  			expectedCount: predefinedSeriesCount,
    84  		},
    85  		{
    86  			name:          "querying without metric name just by other tag returns preloaded data",
    87  			queryTagName:  "tag1",
    88  			queryTagValue: "test2",
    89  			expectedCount: 4,
    90  		},
    91  	}
    92  
    93  	for _, tt := range tests {
    94  		t.Run(tt.name, func(t *testing.T) {
    95  			ctrl := xtest.NewController(t)
    96  			defer ctrl.Finish()
    97  
    98  			query := matcherQuery(t, tt.queryTagName, tt.queryTagValue)
    99  			querier := setupQuerier(ctrl, query)
   100  
   101  			result, cleanup, err := querier.FetchCompressedResult(context.TODO(), query, nil)
   102  			assert.NoError(t, err)
   103  			defer cleanup()
   104  
   105  			assert.Equal(t, tt.expectedCount, len(result.SeriesIterators()))
   106  		})
   107  	}
   108  }
   109  
   110  func TestGenerateRandomSeries(t *testing.T) {
   111  	tests := []struct {
   112  		name       string
   113  		givenQuery *storage.FetchQuery
   114  		wantSeries []tagMap
   115  	}{
   116  		{
   117  			name:       "querying nonexistent_metric returns empty",
   118  			givenQuery: matcherQuery(t, metricNameTag, "nonexistent_metric"),
   119  			wantSeries: []tagMap{},
   120  		},
   121  		{
   122  			name:       "querying nonexistant returns empty",
   123  			givenQuery: matcherQuery(t, metricNameTag, "nonexistant"),
   124  			wantSeries: []tagMap{},
   125  		},
   126  		{
   127  			name:       "random data for known metrics",
   128  			givenQuery: matcherQuery(t, metricNameTag, "quail"),
   129  			wantSeries: []tagMap{
   130  				{
   131  					metricNameTag: "quail",
   132  					"foobar":      "qux",
   133  					"name":        "quail",
   134  				},
   135  			},
   136  		},
   137  		{
   138  			name:       "a hardcoded list of metrics",
   139  			givenQuery: matcherQuery(t, metricNameTag, "unknown"),
   140  			wantSeries: []tagMap{
   141  				{
   142  					metricNameTag: "foo",
   143  					"foobar":      "qux",
   144  					"name":        "foo",
   145  				},
   146  				{
   147  					metricNameTag: "bar",
   148  					"foobar":      "qux",
   149  					"name":        "bar",
   150  				},
   151  				{
   152  					metricNameTag: "quail",
   153  					"foobar":      "qux",
   154  					"name":        "quail",
   155  				},
   156  			},
   157  		},
   158  		{
   159  			name:       "a given number of single series metrics",
   160  			givenQuery: matcherQuery(t, "gen", "2"),
   161  			wantSeries: []tagMap{
   162  				{
   163  					metricNameTag: "foo_0",
   164  					"foobar":      "qux",
   165  					"name":        "foo_0",
   166  				},
   167  				{
   168  					metricNameTag: "foo_1",
   169  					"foobar":      "qux",
   170  					"name":        "foo_1",
   171  				},
   172  			},
   173  		},
   174  		{
   175  			name:       "single metrics with a given number of series",
   176  			givenQuery: matcherQuery(t, metricNameTag, "multi_4"),
   177  			wantSeries: []tagMap{
   178  				{
   179  					metricNameTag: "multi_4",
   180  					"const":       "x",
   181  					"id":          "0",
   182  					"parity":      "0",
   183  				},
   184  				{
   185  					metricNameTag: "multi_4",
   186  					"const":       "x",
   187  					"id":          "1",
   188  					"parity":      "1",
   189  				},
   190  				{
   191  					metricNameTag: "multi_4",
   192  					"const":       "x",
   193  					"id":          "2",
   194  					"parity":      "0",
   195  				},
   196  				{
   197  					metricNameTag: "multi_4",
   198  					"const":       "x",
   199  					"id":          "3",
   200  					"parity":      "1",
   201  				},
   202  			},
   203  		},
   204  		{
   205  			name:       "histogram metrics",
   206  			givenQuery: matcherQuery(t, metricNameTag, "histogram_2_bucket"),
   207  			wantSeries: []tagMap{
   208  				{
   209  					metricNameTag: "histogram_2_bucket",
   210  					"const":       "x",
   211  					"id":          "0",
   212  					"parity":      "0",
   213  					"le":          "1",
   214  				},
   215  				{
   216  					metricNameTag: "histogram_2_bucket",
   217  					"const":       "x",
   218  					"id":          "0",
   219  					"parity":      "0",
   220  					"le":          "10",
   221  				},
   222  				{
   223  					metricNameTag: "histogram_2_bucket",
   224  					"const":       "x",
   225  					"id":          "0",
   226  					"parity":      "0",
   227  					"le":          "100",
   228  				},
   229  				{
   230  					metricNameTag: "histogram_2_bucket",
   231  					"const":       "x",
   232  					"id":          "0",
   233  					"parity":      "0",
   234  					"le":          "+Inf",
   235  				},
   236  
   237  				{
   238  					metricNameTag: "histogram_2_bucket",
   239  					"const":       "x",
   240  					"id":          "1",
   241  					"parity":      "1",
   242  					"le":          "1",
   243  				},
   244  				{
   245  					metricNameTag: "histogram_2_bucket",
   246  					"const":       "x",
   247  					"id":          "1",
   248  					"parity":      "1",
   249  					"le":          "10",
   250  				},
   251  				{
   252  					metricNameTag: "histogram_2_bucket",
   253  					"const":       "x",
   254  					"id":          "1",
   255  					"parity":      "1",
   256  					"le":          "100",
   257  				},
   258  				{
   259  					metricNameTag: "histogram_2_bucket",
   260  					"const":       "x",
   261  					"id":          "1",
   262  					"parity":      "1",
   263  					"le":          "+Inf",
   264  				},
   265  			},
   266  		},
   267  		{
   268  			name: "apply tag filter",
   269  			givenQuery: and(
   270  				matcherQuery(t, metricNameTag, "multi_5"),
   271  				matcherQuery(t, "parity", "1")),
   272  			wantSeries: []tagMap{
   273  				{
   274  					metricNameTag: "multi_5",
   275  					"const":       "x",
   276  					"id":          "1",
   277  					"parity":      "1",
   278  				},
   279  				{
   280  					metricNameTag: "multi_5",
   281  					"const":       "x",
   282  					"id":          "3",
   283  					"parity":      "1",
   284  				},
   285  			},
   286  		},
   287  	}
   288  
   289  	for _, tt := range tests {
   290  		t.Run(tt.name, func(t *testing.T) {
   291  			ctrl := xtest.NewController(t)
   292  			defer ctrl.Finish()
   293  
   294  			querier, err := setupRandomGenQuerier(ctrl)
   295  			assert.NoError(t, err)
   296  
   297  			result, cleanup, err := querier.FetchCompressedResult(context.TODO(), tt.givenQuery, nil)
   298  			assert.NoError(t, err)
   299  			defer cleanup()
   300  
   301  			iters := result.SeriesIterators()
   302  			require.Equal(t, len(tt.wantSeries), len(iters))
   303  			for i, expectedTags := range tt.wantSeries {
   304  				iter := iters[i]
   305  				assert.Equal(t, expectedTags, extractTags(iter))
   306  				assert.True(t, iter.Next(), "Must have some datapoints generated.")
   307  			}
   308  		})
   309  	}
   310  }
   311  
   312  func TestHistogramBucketsAddUp(t *testing.T) {
   313  	ctrl := xtest.NewController(t)
   314  	defer ctrl.Finish()
   315  
   316  	querier, err := setupRandomGenQuerier(ctrl)
   317  	assert.NoError(t, err)
   318  
   319  	histogramQuery := matcherQuery(t, metricNameTag, "histogram_1_bucket")
   320  	result, cleanup, err := querier.FetchCompressedResult(context.TODO(), histogramQuery, nil)
   321  	assert.NoError(t, err)
   322  	defer cleanup()
   323  
   324  	iters := result.SeriesIterators()
   325  	require.Equal(t, histogramBucketCount,
   326  		len(iters), "number of histogram buckets")
   327  
   328  	iter0 := iters[0]
   329  	for iter0.Next() {
   330  		v0, t1, _ := iter0.Current()
   331  		for i := 1; i < histogramBucketCount; i++ {
   332  			iter := iters[i]
   333  			require.True(t, iter.Next(), "all buckets must have the same length")
   334  			vi, ti, _ := iter.Current()
   335  			assert.True(t, vi.Value >= v0.Value, "bucket values must be non decreasing")
   336  			assert.Equal(t, v0.TimestampNanos, vi.TimestampNanos, "bucket values timestamps must match")
   337  			assert.Equal(t, t1, ti)
   338  		}
   339  	}
   340  
   341  	for _, iter := range iters {
   342  		require.False(t, iter.Next(), "all buckets must have the same length")
   343  	}
   344  }
   345  
   346  func matcherQuery(t *testing.T, matcherName, matcherValue string) *storage.FetchQuery {
   347  	matcher, err := models.NewMatcher(models.MatchEqual, []byte(matcherName), []byte(matcherValue))
   348  	assert.NoError(t, err)
   349  
   350  	now := time.Now()
   351  
   352  	return &storage.FetchQuery{
   353  		TagMatchers: []models.Matcher{matcher},
   354  		Start:       now.Add(-time.Hour),
   355  		End:         now,
   356  	}
   357  }
   358  
   359  func and(query1, query2 *storage.FetchQuery) *storage.FetchQuery {
   360  	return &storage.FetchQuery{
   361  		TagMatchers: append(query1.TagMatchers, query2.TagMatchers...),
   362  		Start:       query1.Start,
   363  		End:         query1.End,
   364  	}
   365  }
   366  
   367  func extractTags(seriesIter encoding.SeriesIterator) tagMap {
   368  	tagsIter := seriesIter.Tags().Duplicate()
   369  	defer tagsIter.Close()
   370  
   371  	tags := make(tagMap)
   372  	for tagsIter.Next() {
   373  		tag := tagsIter.Current()
   374  		tags[tag.Name.String()] = tag.Value.String()
   375  	}
   376  
   377  	return tags
   378  }
   379  
   380  func setupQuerier(ctrl *gomock.Controller, query *storage.FetchQuery) *querier {
   381  	metricsTag := ident.NewTags(ident.Tag{
   382  		Name:  ident.BytesID(tagOptions.MetricName()),
   383  		Value: ident.BytesID(metricsName),
   384  	},
   385  		ident.Tag{
   386  			Name:  ident.BytesID("tag1"),
   387  			Value: ident.BytesID("test"),
   388  		},
   389  	)
   390  	metricsTag2 := ident.NewTags(ident.Tag{
   391  		Name:  ident.BytesID(tagOptions.MetricName()),
   392  		Value: ident.BytesID(metricsName),
   393  	},
   394  		ident.Tag{
   395  			Name:  ident.BytesID("tag1"),
   396  			Value: ident.BytesID("test2"),
   397  		},
   398  	)
   399  
   400  	iters := make([]encoding.SeriesIterator, 0, predefinedSeriesCount)
   401  	for i := 0; i < predefinedSeriesCount; i++ {
   402  		m := metricsTag
   403  		if i > 5 {
   404  			m = metricsTag2
   405  		}
   406  		iters = append(iters, encoding.NewSeriesIterator(
   407  			encoding.SeriesIteratorOptions{
   408  				Namespace:      ident.StringID("ns"),
   409  				Tags:           ident.NewTagsIterator(m),
   410  				StartInclusive: xtime.ToUnixNano(query.Start),
   411  				EndExclusive:   xtime.ToUnixNano(query.End),
   412  			}, nil))
   413  	}
   414  
   415  	seriesIterators := encoding.NewMockSeriesIterators(ctrl)
   416  	seriesIterators.EXPECT().Len().Return(predefinedSeriesCount).MinTimes(1)
   417  	seriesIterators.EXPECT().Iters().Return(iters).Times(1)
   418  	seriesIterators.EXPECT().Close()
   419  
   420  	seriesLoader := &testSeriesLoadHandler{seriesIterators}
   421  
   422  	return &querier{iteratorOpts: iteratorOpts, handler: seriesLoader}
   423  }
   424  
   425  func setupRandomGenQuerier(ctrl *gomock.Controller) (*querier, error) {
   426  	iters := encoding.NewMockSeriesIterators(ctrl)
   427  	iters.EXPECT().Len().Return(0).AnyTimes()
   428  
   429  	emptySeriesLoader := &testSeriesLoadHandler{iters}
   430  
   431  	return newQuerier(iteratorOpts, emptySeriesLoader, blockSize, defaultResolution, histogramBucketCount)
   432  }