github.com/m3db/m3@v1.5.0/src/metrics/matcher/match_test.go (about)

     1  // Copyright (c) 2017 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 matcher
    22  
    23  import (
    24  	"math"
    25  	"testing"
    26  	"time"
    27  
    28  	"github.com/golang/mock/gomock"
    29  	"github.com/stretchr/testify/require"
    30  	"github.com/uber-go/tally"
    31  
    32  	"github.com/m3db/m3/src/cluster/kv"
    33  	"github.com/m3db/m3/src/cluster/kv/mem"
    34  	"github.com/m3db/m3/src/metrics/aggregation"
    35  	"github.com/m3db/m3/src/metrics/filters"
    36  	"github.com/m3db/m3/src/metrics/generated/proto/aggregationpb"
    37  	"github.com/m3db/m3/src/metrics/generated/proto/policypb"
    38  	"github.com/m3db/m3/src/metrics/generated/proto/rulepb"
    39  	"github.com/m3db/m3/src/metrics/matcher/cache"
    40  	"github.com/m3db/m3/src/metrics/metadata"
    41  	"github.com/m3db/m3/src/metrics/metric/id"
    42  	"github.com/m3db/m3/src/metrics/policy"
    43  	"github.com/m3db/m3/src/metrics/rules"
    44  	"github.com/m3db/m3/src/query/models"
    45  	"github.com/m3db/m3/src/x/clock"
    46  	"github.com/m3db/m3/src/x/instrument"
    47  	xtest "github.com/m3db/m3/src/x/test"
    48  	"github.com/m3db/m3/src/x/watch"
    49  )
    50  
    51  func TestMatcherCreateWatchError(t *testing.T) {
    52  	ctrl := gomock.NewController(t)
    53  	defer ctrl.Finish()
    54  
    55  	kvStore := kv.NewMockStore(ctrl)
    56  	kvStore.EXPECT().Watch(testNamespacesKey).Return(nil, watch.CreateWatchError{})
    57  	opts := NewOptions().
    58  		SetInitWatchTimeout(10 * time.Millisecond).
    59  		SetNamespacesKey(testNamespacesKey).
    60  		SetKVStore(kvStore)
    61  
    62  	_, err := NewMatcher(newMemCache(), opts)
    63  	require.Error(t, err)
    64  	_, ok := err.(watch.CreateWatchError)
    65  	require.True(t, ok)
    66  }
    67  
    68  func TestMatcherInitializeValueError(t *testing.T) {
    69  	memStore := mem.NewStore()
    70  	opts := NewOptions().
    71  		SetInitWatchTimeout(10 * time.Millisecond).
    72  		SetNamespacesKey(testNamespacesKey).
    73  		SetKVStore(memStore)
    74  
    75  	matcher, err := NewMatcher(newMemCache(), opts)
    76  	require.NoError(t, err)
    77  	require.NotNil(t, matcher)
    78  }
    79  
    80  func TestMatcherMatchDoesNotExist(t *testing.T) {
    81  	id := &testMetricID{
    82  		id:         []byte("foo"),
    83  		tagValueFn: func(tagName []byte) ([]byte, bool) { return nil, false },
    84  	}
    85  	now := time.Now()
    86  	matcher, testScope := testMatcher(t, testMatcherOptions{
    87  		cache: newMemCache(),
    88  	})
    89  	res, err := matcher.ForwardMatch(id, now.UnixNano(), now.UnixNano(), rules.MatchOptions{})
    90  	require.NoError(t, err)
    91  	require.Equal(t, rules.EmptyMatchResult, res)
    92  
    93  	requireLatencyMetrics(t, "cached-matcher", testScope)
    94  }
    95  
    96  func TestMatcherMatchExists(t *testing.T) {
    97  	var (
    98  		ns = "ns/foo"
    99  		id = &testMetricID{
   100  			id:         []byte("foo"),
   101  			tagValueFn: func(tagName []byte) ([]byte, bool) { return []byte(ns), true },
   102  		}
   103  		now    = time.Now()
   104  		res    = rules.NewMatchResult(0, math.MaxInt64, nil, nil, true)
   105  		memRes = memResults{results: map[string]rules.MatchResult{"foo": res}}
   106  	)
   107  	cache := newMemCache()
   108  	matcher, _ := testMatcher(t, testMatcherOptions{
   109  		cache: cache,
   110  	})
   111  	c := cache.(*memCache)
   112  	c.namespaces[ns] = memRes
   113  	actual, err := matcher.ForwardMatch(id, now.UnixNano(), now.UnixNano(), rules.MatchOptions{})
   114  	require.NoError(t, err)
   115  	require.Equal(t, res, actual)
   116  }
   117  
   118  func TestMatcherMatchExistsNoCache(t *testing.T) {
   119  	ctrl := xtest.NewController(t)
   120  	defer ctrl.Finish()
   121  
   122  	var (
   123  		ns     = "fooNs"
   124  		metric = &testMetricID{
   125  			id: []byte("foo"),
   126  			tagValueFn: func(tagName []byte) ([]byte, bool) {
   127  				if string(tagName) == "fooTag" {
   128  					return []byte("fooValue"), true
   129  				}
   130  				return []byte(ns), true
   131  			},
   132  		}
   133  		now = time.Now()
   134  	)
   135  	matcher, testScope := testMatcher(t, testMatcherOptions{
   136  		tagFilterOptions: filters.TagsFilterOptions{},
   137  		storeSetup: func(t *testing.T, store kv.TxnStore) {
   138  			_, err := store.Set(testNamespacesKey, &rulepb.Namespaces{
   139  				Namespaces: []*rulepb.Namespace{
   140  					{
   141  						Name: ns,
   142  						Snapshots: []*rulepb.NamespaceSnapshot{
   143  							{
   144  								ForRulesetVersion: 1,
   145  								Tombstoned:        false,
   146  							},
   147  						},
   148  					},
   149  				},
   150  			})
   151  			require.NoError(t, err)
   152  
   153  			_, err = store.Set("/ruleset/fooNs", &rulepb.RuleSet{
   154  				Namespace: ns,
   155  				MappingRules: []*rulepb.MappingRule{
   156  					{
   157  						Snapshots: []*rulepb.MappingRuleSnapshot{
   158  							{
   159  								Filter: "fooTag:fooValue",
   160  								AggregationTypes: []aggregationpb.AggregationType{
   161  									aggregationpb.AggregationType_LAST,
   162  								},
   163  								StoragePolicies: []*policypb.StoragePolicy{
   164  									{
   165  										Resolution: policypb.Resolution{
   166  											WindowSize: int64(time.Minute),
   167  											Precision:  int64(time.Minute),
   168  										},
   169  										Retention: policypb.Retention{
   170  											Period: 24 * int64(time.Hour),
   171  										},
   172  									},
   173  								},
   174  							},
   175  						},
   176  					},
   177  				},
   178  			})
   179  			require.NoError(t, err)
   180  		},
   181  	})
   182  
   183  	forExistingID := metadata.StagedMetadatas{
   184  		metadata.StagedMetadata{
   185  			Metadata: metadata.Metadata{
   186  				Pipelines: metadata.PipelineMetadatas{
   187  					metadata.PipelineMetadata{
   188  						AggregationID: aggregation.MustCompressTypes(aggregation.Last),
   189  						StoragePolicies: policy.StoragePolicies{
   190  							policy.MustParseStoragePolicy("1m:1d"),
   191  						},
   192  						Tags: []models.Tag{},
   193  					},
   194  				},
   195  			},
   196  		},
   197  	}
   198  	forNewRollupIDs := []rules.IDWithMetadatas{}
   199  	keepOriginal := false
   200  	expected := rules.NewMatchResult(1, math.MaxInt64,
   201  		forExistingID, forNewRollupIDs, keepOriginal)
   202  
   203  	matchOptions := rules.MatchOptions{
   204  		NameAndTagsFn: func(id []byte) (name []byte, tags []byte, err error) {
   205  			name = metric.id
   206  			return
   207  		},
   208  		SortedTagIteratorFn: func(tagPairs []byte) id.SortedTagIterator {
   209  			iter := id.NewMockSortedTagIterator(ctrl)
   210  			iter.EXPECT().Next().Return(true)
   211  			iter.EXPECT().Current().Return([]byte("fooTag"), []byte("fooValue"))
   212  			iter.EXPECT().Next().Return(false)
   213  			iter.EXPECT().Err().Return(nil)
   214  			return iter
   215  		},
   216  	}
   217  
   218  	result, err := matcher.ForwardMatch(metric, now.UnixNano(), now.UnixNano(), matchOptions)
   219  
   220  	require.NoError(t, err)
   221  	require.Equal(t, expected, result)
   222  
   223  	// Check that latency was measured
   224  	requireLatencyMetrics(t, "matcher", testScope)
   225  }
   226  
   227  func TestMatcherClose(t *testing.T) {
   228  	matcher, _ := testMatcher(t, testMatcherOptions{
   229  		cache: newMemCache(),
   230  	})
   231  	require.NoError(t, matcher.Close())
   232  }
   233  
   234  type testMatcherOptions struct {
   235  	cache            cache.Cache
   236  	storeSetup       func(*testing.T, kv.TxnStore)
   237  	tagFilterOptions filters.TagsFilterOptions
   238  }
   239  
   240  func testMatcher(t *testing.T, opts testMatcherOptions) (Matcher, tally.TestScope) {
   241  	scope := tally.NewTestScope("", nil)
   242  	var (
   243  		store       = mem.NewStore()
   244  		matcherOpts = NewOptions().
   245  				SetClockOptions(clock.NewOptions()).
   246  				SetInstrumentOptions(instrument.NewOptions().SetMetricsScope(scope)).
   247  				SetInitWatchTimeout(100 * time.Millisecond).
   248  				SetKVStore(store).
   249  				SetNamespacesKey(testNamespacesKey).
   250  				SetRuleSetKeyFn(defaultRuleSetKeyFn).
   251  				SetRuleSetOptions(rules.NewOptions().
   252  					SetTagsFilterOptions(opts.tagFilterOptions)).
   253  				SetMatchRangePast(0)
   254  		proto = &rulepb.Namespaces{
   255  			Namespaces: []*rulepb.Namespace{
   256  				{
   257  					Name: "fooNs",
   258  					Snapshots: []*rulepb.NamespaceSnapshot{
   259  						{
   260  							ForRulesetVersion: 1,
   261  							Tombstoned:        true,
   262  						},
   263  					},
   264  				},
   265  			},
   266  		}
   267  	)
   268  
   269  	_, err := store.SetIfNotExists(testNamespacesKey, proto)
   270  	require.NoError(t, err)
   271  
   272  	if fn := opts.storeSetup; fn != nil {
   273  		fn(t, store)
   274  	}
   275  
   276  	m, err := NewMatcher(opts.cache, matcherOpts)
   277  	require.NoError(t, err)
   278  	return m, scope
   279  }
   280  
   281  func requireLatencyMetrics(t *testing.T, metricScope string, testScope tally.TestScope) {
   282  	// Check that latency was measured
   283  	values, found := testScope.Snapshot().Histograms()[metricScope+".match-latency+"]
   284  	require.True(t, found)
   285  	latencyMeasured := false
   286  	for _, valuesInBucket := range values.Durations() {
   287  		if valuesInBucket > 0 {
   288  			latencyMeasured = true
   289  			break
   290  		}
   291  	}
   292  	require.True(t, latencyMeasured)
   293  }
   294  
   295  type tagValueFn func(tagName []byte) ([]byte, bool)
   296  
   297  type testMetricID struct {
   298  	id         []byte
   299  	tagValueFn tagValueFn
   300  }
   301  
   302  func (id *testMetricID) Bytes() []byte                          { return id.id }
   303  func (id *testMetricID) TagValue(tagName []byte) ([]byte, bool) { return id.tagValueFn(tagName) }