github.com/m3db/m3@v1.5.1-0.20231129193456-75a402aa583b/src/metrics/matcher/ruleset_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  	"fmt"
    25  	"testing"
    26  	"time"
    27  
    28  	"github.com/m3db/m3/src/cluster/kv"
    29  	"github.com/m3db/m3/src/cluster/kv/mem"
    30  	"github.com/m3db/m3/src/metrics/aggregation"
    31  	"github.com/m3db/m3/src/metrics/generated/proto/rulepb"
    32  	"github.com/m3db/m3/src/metrics/matcher/cache"
    33  	"github.com/m3db/m3/src/metrics/matcher/namespace"
    34  	"github.com/m3db/m3/src/metrics/metric"
    35  	"github.com/m3db/m3/src/metrics/metric/id"
    36  	"github.com/m3db/m3/src/metrics/rules"
    37  	"github.com/m3db/m3/src/metrics/rules/view"
    38  
    39  	"github.com/stretchr/testify/require"
    40  )
    41  
    42  const (
    43  	testRuleSetKey = "testRuleSet"
    44  )
    45  
    46  var (
    47  	testNamespace = []byte("testNamespace")
    48  )
    49  
    50  func TestRuleSetProperties(t *testing.T) {
    51  	_, _, rs := testRuleSet()
    52  	rs.namespace = testNamespace
    53  	rs.version = 2
    54  	rs.cutoverNanos = 12345
    55  	rs.tombstoned = true
    56  
    57  	require.Equal(t, testNamespace, rs.Namespace())
    58  	require.Equal(t, 2, rs.Version())
    59  	require.Equal(t, int64(12345), rs.CutoverNanos())
    60  	require.Equal(t, true, rs.Tombstoned())
    61  }
    62  
    63  func TestRuleSetMatchNoMatcher(t *testing.T) {
    64  	_, _, rs := testRuleSet()
    65  	nowNanos := rs.nowFn().UnixNano()
    66  	res, err := rs.ForwardMatch(testID("foo"), nowNanos, nowNanos, rules.MatchOptions{})
    67  	require.NoError(t, err)
    68  	require.Equal(t, rules.EmptyMatchResult, res)
    69  }
    70  
    71  func TestRuleSetForwardMatchWithMatcher(t *testing.T) {
    72  	_, _, rs := testRuleSet()
    73  	mockMatcher := &mockMatcher{res: rules.EmptyMatchResult}
    74  	rs.activeSet = mockMatcher
    75  
    76  	var (
    77  		now       = rs.nowFn()
    78  		fromNanos = now.Add(-time.Second).UnixNano()
    79  		toNanos   = now.Add(time.Second).UnixNano()
    80  	)
    81  
    82  	res, err := rs.ForwardMatch(testID("foo"), fromNanos, toNanos, rules.MatchOptions{})
    83  	require.NoError(t, err)
    84  	require.Equal(t, mockMatcher.res, res)
    85  	require.Equal(t, []byte("foo"), mockMatcher.id)
    86  	require.Equal(t, fromNanos, mockMatcher.fromNanos)
    87  	require.Equal(t, toNanos, mockMatcher.toNanos)
    88  }
    89  
    90  func TestRuleSetReverseMatchWithMatcher(t *testing.T) {
    91  	_, _, rs := testRuleSet()
    92  	mockMatcher := &mockMatcher{res: rules.EmptyMatchResult}
    93  	rs.activeSet = mockMatcher
    94  
    95  	var (
    96  		now                            = rs.nowFn()
    97  		fromNanos                      = now.Add(-time.Second).UnixNano()
    98  		toNanos                        = now.Add(time.Second).UnixNano()
    99  		isMultiAggregationTypesAllowed = true
   100  		aggTypesOpts                   = aggregation.NewTypesOptions()
   101  	)
   102  
   103  	res, err := rs.ReverseMatch(testID("foo"), fromNanos, toNanos, metric.CounterType, aggregation.Sum,
   104  		isMultiAggregationTypesAllowed, aggTypesOpts)
   105  	require.NoError(t, err)
   106  	require.Equal(t, mockMatcher.res, res)
   107  	require.Equal(t, []byte("foo"), mockMatcher.id)
   108  	require.Equal(t, fromNanos, mockMatcher.fromNanos)
   109  	require.Equal(t, toNanos, mockMatcher.toNanos)
   110  	require.Equal(t, metric.CounterType, mockMatcher.metricType)
   111  	require.Equal(t, aggregation.Sum, mockMatcher.aggregationType)
   112  	require.Equal(t, isMultiAggregationTypesAllowed, mockMatcher.isMultiAggregationTypesAllowed)
   113  	require.Equal(t, aggTypesOpts, mockMatcher.aggTypesOpts)
   114  }
   115  
   116  func TestToRuleSetNilValue(t *testing.T) {
   117  	_, _, rs := testRuleSet()
   118  	_, err := rs.toRuleSet(nil)
   119  	require.Equal(t, errNilValue, err)
   120  }
   121  
   122  func TestToRuleSetUnmarshalError(t *testing.T) {
   123  	_, _, rs := testRuleSet()
   124  	_, err := rs.toRuleSet(&mockValue{})
   125  	require.Error(t, err)
   126  }
   127  
   128  func TestToRuleSetSuccess(t *testing.T) {
   129  	store, _, rs := testRuleSet()
   130  	proto := &rulepb.RuleSet{
   131  		Namespace:    string(testNamespace),
   132  		Tombstoned:   false,
   133  		CutoverNanos: 123456,
   134  	}
   135  	_, err := store.SetIfNotExists(testRuleSetKey, proto)
   136  	require.NoError(t, err)
   137  	v, err := store.Get(testRuleSetKey)
   138  	require.NoError(t, err)
   139  	res, err := rs.toRuleSet(v)
   140  	require.NoError(t, err)
   141  	actual := res.(rules.RuleSet)
   142  	require.Equal(t, testNamespace, actual.Namespace())
   143  	require.Equal(t, 1, actual.Version())
   144  	require.Equal(t, int64(123456), actual.CutoverNanos())
   145  	require.Equal(t, false, actual.Tombstoned())
   146  }
   147  
   148  func TestRuleSetProcessNamespaceNotRegistered(t *testing.T) {
   149  	var (
   150  		inputs = []rules.RuleSet{
   151  			&mockRuleSet{namespace: "ns1", version: 1, cutoverNanos: 1234, tombstoned: false, matcher: &mockMatcher{}},
   152  			&mockRuleSet{namespace: "ns2", version: 2, cutoverNanos: 1235, tombstoned: true, matcher: &mockMatcher{}},
   153  			&mockRuleSet{namespace: "ns3", version: 3, cutoverNanos: 1236, tombstoned: false, matcher: &mockMatcher{}},
   154  			&mockRuleSet{namespace: "ns4", version: 4, cutoverNanos: 1237, tombstoned: true, matcher: &mockMatcher{}},
   155  			&mockRuleSet{namespace: "ns5", version: 5, cutoverNanos: 1238, tombstoned: false, matcher: &mockMatcher{}},
   156  		}
   157  	)
   158  
   159  	_, cache, rs := testRuleSet()
   160  	memCache := cache.(*memCache)
   161  	for _, input := range inputs {
   162  		err := rs.process(input)
   163  		require.NoError(t, err)
   164  	}
   165  
   166  	require.Equal(t, 5, rs.Version())
   167  	require.Equal(t, int64(1238), rs.CutoverNanos())
   168  	require.Equal(t, false, rs.Tombstoned())
   169  	require.NotNil(t, rs.activeSet)
   170  	require.Equal(t, 0, len(memCache.namespaces))
   171  }
   172  
   173  func TestRuleSetProcessStaleUpdate(t *testing.T) {
   174  	var (
   175  		inputs = []rules.RuleSet{
   176  			&mockRuleSet{namespace: "ns1", version: 1, cutoverNanos: 1234, tombstoned: false, matcher: &mockMatcher{}},
   177  			&mockRuleSet{namespace: "ns2", version: 2, cutoverNanos: 1235, tombstoned: true, matcher: &mockMatcher{}},
   178  			&mockRuleSet{namespace: "ns3", version: 3, cutoverNanos: 1236, tombstoned: false, matcher: &mockMatcher{}},
   179  			&mockRuleSet{namespace: "ns4", version: 4, cutoverNanos: 1237, tombstoned: true, matcher: &mockMatcher{}},
   180  			&mockRuleSet{namespace: "ns5", version: 5, cutoverNanos: 1238, tombstoned: false, matcher: &mockMatcher{}},
   181  		}
   182  	)
   183  
   184  	_, cache, rs := testRuleSet()
   185  	src := newRuleSet(testNamespace, testNamespacesKey, NewOptions())
   186  	cache.Register([]byte("ns5"), src)
   187  
   188  	memCache := cache.(*memCache)
   189  	for _, input := range inputs {
   190  		err := rs.process(input)
   191  		require.NoError(t, err)
   192  	}
   193  
   194  	require.Equal(t, 5, rs.Version())
   195  	require.Equal(t, int64(1238), rs.CutoverNanos())
   196  	require.Equal(t, false, rs.Tombstoned())
   197  	require.NotNil(t, rs.activeSet)
   198  	require.Equal(t, 1, len(memCache.namespaces))
   199  	actual := memCache.namespaces["ns5"]
   200  	require.Equal(t, src, actual.source)
   201  }
   202  
   203  func TestRuleSetProcessSuccess(t *testing.T) {
   204  	var (
   205  		inputs = []rules.RuleSet{
   206  			&mockRuleSet{namespace: "ns1", version: 1, cutoverNanos: 1234, tombstoned: false, matcher: &mockMatcher{}},
   207  			&mockRuleSet{namespace: "ns2", version: 2, cutoverNanos: 1235, tombstoned: true, matcher: &mockMatcher{}},
   208  			&mockRuleSet{namespace: "ns3", version: 3, cutoverNanos: 1236, tombstoned: false, matcher: &mockMatcher{}},
   209  			&mockRuleSet{namespace: "ns4", version: 4, cutoverNanos: 1237, tombstoned: true, matcher: &mockMatcher{}},
   210  			&mockRuleSet{namespace: "ns5", version: 5, cutoverNanos: 1238, tombstoned: false, matcher: &mockMatcher{}},
   211  		}
   212  	)
   213  
   214  	_, cache, rs := testRuleSet()
   215  	cache.Register([]byte("ns5"), rs)
   216  
   217  	memCache := cache.(*memCache)
   218  	for _, input := range inputs {
   219  		err := rs.process(input)
   220  		require.NoError(t, err)
   221  	}
   222  
   223  	require.Equal(t, 5, rs.Version())
   224  	require.Equal(t, int64(1238), rs.CutoverNanos())
   225  	require.Equal(t, false, rs.Tombstoned())
   226  	require.NotNil(t, rs.activeSet)
   227  	require.Equal(t, 1, len(memCache.namespaces))
   228  	actual := memCache.namespaces["ns5"]
   229  	require.Equal(t, rs, actual.source)
   230  }
   231  
   232  func testID(id string) id.ID {
   233  	return namespace.NewTestID(id, string(testNamespace))
   234  }
   235  
   236  type mockMatcher struct {
   237  	id                             []byte
   238  	fromNanos                      int64
   239  	toNanos                        int64
   240  	res                            rules.MatchResult
   241  	metricType                     metric.Type
   242  	aggregationType                aggregation.Type
   243  	isMultiAggregationTypesAllowed bool
   244  	aggTypesOpts                   aggregation.TypesOptions
   245  }
   246  
   247  func (mm *mockMatcher) LatestRollupRules(_ []byte, _ int64) ([]view.RollupRule, error) {
   248  	return []view.RollupRule{}, nil
   249  }
   250  
   251  func (mm *mockMatcher) ForwardMatch(
   252  	id id.ID,
   253  	fromNanos, toNanos int64,
   254  	_ rules.MatchOptions,
   255  ) (rules.MatchResult, error) {
   256  	mm.id = id.Bytes()
   257  	mm.fromNanos = fromNanos
   258  	mm.toNanos = toNanos
   259  	return mm.res, nil
   260  }
   261  
   262  func (mm *mockMatcher) ReverseMatch(
   263  	id id.ID,
   264  	fromNanos, toNanos int64,
   265  	mt metric.Type,
   266  	at aggregation.Type,
   267  	isMultiAggregationTypesAllowed bool,
   268  	aggTypesOpts aggregation.TypesOptions,
   269  ) (rules.MatchResult, error) {
   270  	mm.id = id.Bytes()
   271  	mm.fromNanos = fromNanos
   272  	mm.toNanos = toNanos
   273  	mm.metricType = mt
   274  	mm.aggregationType = at
   275  	mm.isMultiAggregationTypesAllowed = isMultiAggregationTypesAllowed
   276  	mm.aggTypesOpts = aggTypesOpts
   277  	return mm.res, nil
   278  }
   279  
   280  type mockRuleSet struct {
   281  	namespace    string
   282  	version      int
   283  	cutoverNanos int64
   284  	tombstoned   bool
   285  	matcher      *mockMatcher
   286  }
   287  
   288  func (r *mockRuleSet) Namespace() []byte                        { return []byte(r.namespace) }
   289  func (r *mockRuleSet) Version() int                             { return r.version }
   290  func (r *mockRuleSet) CutoverNanos() int64                      { return r.cutoverNanos }
   291  func (r *mockRuleSet) LastUpdatedAtNanos() int64                { return 0 }
   292  func (r *mockRuleSet) CreatedAtNanos() int64                    { return 0 }
   293  func (r *mockRuleSet) Tombstoned() bool                         { return r.tombstoned }
   294  func (r *mockRuleSet) Proto() (*rulepb.RuleSet, error)          { return nil, nil }
   295  func (r *mockRuleSet) ActiveSet(_ int64) rules.ActiveSet        { return r.matcher }
   296  func (r *mockRuleSet) ToMutableRuleSet() rules.MutableRuleSet   { return nil }
   297  func (r *mockRuleSet) MappingRules() (view.MappingRules, error) { return nil, nil }
   298  func (r *mockRuleSet) RollupRules() (view.RollupRules, error)   { return nil, nil }
   299  func (r *mockRuleSet) Latest() (view.RuleSet, error)            { return view.RuleSet{}, nil }
   300  
   301  func testRuleSet() (kv.Store, cache.Cache, *ruleSet) {
   302  	store := mem.NewStore()
   303  	cache := newMemCache()
   304  	opts := NewOptions().
   305  		SetInitWatchTimeout(100 * time.Millisecond).
   306  		SetKVStore(store).
   307  		SetRuleSetKeyFn(func(ns []byte) string { return fmt.Sprintf("/rules/%s", ns) }).
   308  		SetOnRuleSetUpdatedFn(func(namespace []byte, ruleSet RuleSet) { cache.Refresh(namespace, ruleSet) }).
   309  		SetMatchRangePast(0)
   310  	return store, cache, newRuleSet(testNamespace, testNamespacesKey, opts).(*ruleSet)
   311  }