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) }