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 }