github.com/m3db/m3@v1.5.1-0.20231129193456-75a402aa583b/src/metrics/integration/match_rule_update_stress_test.go (about) 1 // +build integration 2 3 // Copyright (c) 2017 Uber Technologies, Inc. 4 // 5 // Permission is hereby granted, free of charge, to any person obtaining a copy 6 // of this software and associated documentation files (the "Software"), to deal 7 // in the Software without restriction, including without limitation the rights 8 // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 // copies of the Software, and to permit persons to whom the Software is 10 // furnished to do so, subject to the following conditions: 11 // 12 // The above copyright notice and this permission notice shall be included in 13 // all copies or substantial portions of the Software. 14 // 15 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 // THE SOFTWARE. 22 23 package integration 24 25 import ( 26 "fmt" 27 "math" 28 "sync" 29 "testing" 30 "time" 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/pipelinepb" 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" 40 "github.com/m3db/m3/src/metrics/matcher/cache" 41 "github.com/m3db/m3/src/metrics/matcher/namespace" 42 "github.com/m3db/m3/src/metrics/metadata" 43 "github.com/m3db/m3/src/metrics/metric/id" 44 "github.com/m3db/m3/src/metrics/metric/id/m3" 45 "github.com/m3db/m3/src/metrics/policy" 46 "github.com/m3db/m3/src/metrics/rules" 47 "github.com/m3db/m3/src/x/pool" 48 49 "github.com/golang/protobuf/proto" 50 "github.com/google/go-cmp/cmp" 51 "github.com/google/go-cmp/cmp/cmpopts" 52 "github.com/stretchr/testify/require" 53 ) 54 55 const ( 56 stressTestNamespacesKey = "namespaces" 57 stressTestRuleSetKeyFmt = "rulesets/%s" 58 stressTestNameTagKey = "name" 59 stressTestNamespaceName = "stress" 60 stressTestRuleSetKey = "rulesets/stress" 61 ) 62 63 var stressTestNamespaceTag = []byte("namespace") 64 65 func TestMatchWithRuleUpdatesStress(t *testing.T) { 66 if testing.Short() { 67 t.SkipNow() // Just skip if we're doing a short run 68 } 69 70 // Initialize the kv store with namespace and ruleset. 71 store := mem.NewStore() 72 namespaces := stressTestNamespaces() 73 ruleSet := stressTestRuleSet() 74 updateStore(t, store, stressTestNamespacesKey, namespaces) 75 updateStore(t, store, stressTestRuleSetKey, ruleSet) 76 77 // Create matcher. 78 cache := stressTestCache() 79 iterPool := stressTestSortedTagIteratorPool() 80 opts := stressTestMatcherOptions(store) 81 matcher, err := matcher.NewMatcher(cache, opts) 82 require.NoError(t, err) 83 84 inputs := []struct { 85 idFn func(int) id.ID 86 fromNanos int64 87 toNanos int64 88 expected rules.MatchResult 89 }{ 90 { 91 idFn: func(i int) id.ID { return m3.NewID([]byte(fmt.Sprintf("m3+nomatch%d+namespace=stress", i)), iterPool) }, 92 fromNanos: 2000, 93 toNanos: math.MaxInt64, 94 expected: rules.EmptyMatchResult, 95 }, 96 { 97 idFn: func(i int) id.ID { 98 return m3.NewID([]byte(fmt.Sprintf("m3+matchmapping%d+mtagName1=mtagValue1,namespace=stress", i)), iterPool) 99 }, 100 fromNanos: 2000, 101 toNanos: math.MaxInt64, 102 expected: rules.NewMatchResult( 103 1, 104 math.MaxInt64, 105 metadata.StagedMetadatas{ 106 { 107 CutoverNanos: 1000, 108 Tombstoned: false, 109 Metadata: metadata.Metadata{ 110 Pipelines: []metadata.PipelineMetadata{ 111 { 112 AggregationID: aggregation.DefaultID, 113 StoragePolicies: policy.StoragePolicies{ 114 policy.MustParseStoragePolicy("10s:1d"), 115 }, 116 }, 117 }, 118 }, 119 }, 120 }, 121 nil, 122 false, 123 ), 124 }, 125 { 126 idFn: func(i int) id.ID { 127 return m3.NewID([]byte(fmt.Sprintf("m3+matchrollup%d+namespace=stress,rtagName1=rtagValue1", i)), iterPool) 128 }, 129 fromNanos: 2000, 130 toNanos: math.MaxInt64, 131 expected: rules.NewMatchResult( 132 1, 133 math.MaxInt64, 134 metadata.StagedMetadatas{ 135 { 136 CutoverNanos: 500, 137 Tombstoned: false, 138 Metadata: metadata.DefaultMetadata, 139 }, 140 }, 141 []rules.IDWithMetadatas{ 142 { 143 ID: []byte("m3+newRollupName1+m3_rollup=true,namespace=stress,rtagName1=rtagValue1"), 144 Metadatas: metadata.StagedMetadatas{ 145 { 146 CutoverNanos: 500, 147 Tombstoned: false, 148 Metadata: metadata.Metadata{ 149 Pipelines: []metadata.PipelineMetadata{ 150 { 151 AggregationID: aggregation.DefaultID, 152 StoragePolicies: policy.StoragePolicies{ 153 policy.MustParseStoragePolicy("1m:2d"), 154 }, 155 }, 156 }, 157 }, 158 }, 159 }, 160 }, 161 }, 162 true, 163 ), 164 }, 165 { 166 idFn: func(i int) id.ID { 167 return m3.NewID([]byte(fmt.Sprintf("m3+matchmappingrollup%d+mtagName1=mtagValue1,namespace=stress,rtagName1=rtagValue1", i)), iterPool) 168 }, 169 fromNanos: 2000, 170 toNanos: math.MaxInt64, 171 expected: rules.NewMatchResult( 172 1, 173 math.MaxInt64, 174 metadata.StagedMetadatas{ 175 { 176 CutoverNanos: 1000, 177 Tombstoned: false, 178 Metadata: metadata.Metadata{ 179 Pipelines: []metadata.PipelineMetadata{ 180 { 181 AggregationID: aggregation.DefaultID, 182 StoragePolicies: policy.StoragePolicies{ 183 policy.MustParseStoragePolicy("10s:1d"), 184 }, 185 }, 186 }, 187 }, 188 }, 189 }, 190 []rules.IDWithMetadatas{ 191 { 192 ID: []byte("m3+newRollupName1+m3_rollup=true,namespace=stress,rtagName1=rtagValue1"), 193 Metadatas: metadata.StagedMetadatas{ 194 { 195 CutoverNanos: 500, 196 Tombstoned: false, 197 Metadata: metadata.Metadata{ 198 Pipelines: []metadata.PipelineMetadata{ 199 { 200 AggregationID: aggregation.DefaultID, 201 StoragePolicies: policy.StoragePolicies{ 202 policy.MustParseStoragePolicy("1m:2d"), 203 }, 204 }, 205 }, 206 }, 207 }, 208 }, 209 }, 210 }, 211 true, 212 ), 213 }, 214 } 215 216 for _, input := range inputs { 217 var ( 218 matchIter = 100000 219 updateIter = 10000 220 results []rules.MatchResult 221 expected []rules.MatchResult 222 wg sync.WaitGroup 223 ) 224 wg.Add(2) 225 go func() { 226 defer wg.Done() 227 it := iterPool.Get() 228 matchOpts := rules.MatchOptions{ 229 NameAndTagsFn: m3.NameAndTags, 230 SortedTagIteratorFn: func(tagPairs []byte) id.SortedTagIterator { 231 it.Reset(tagPairs) 232 return it 233 }, 234 } 235 236 for i := 0; i < matchIter; i++ { 237 res, err := matcher.ForwardMatch(input.idFn(i), input.fromNanos, input.toNanos, matchOpts) 238 require.NoError(t, err) 239 results = append(results, res) 240 expected = append(expected, input.expected) 241 } 242 iterPool.Put(it) 243 }() 244 245 go func() { 246 defer wg.Done() 247 248 for i := 0; i < updateIter; i++ { 249 updateStore(t, store, stressTestRuleSetKey, ruleSet) 250 } 251 }() 252 253 wg.Wait() 254 validateMatchResults(t, expected, results, true) 255 } 256 } 257 258 func validateMatchResults( 259 t *testing.T, 260 expected, actual []rules.MatchResult, 261 ignoreVersion bool, 262 ) { 263 require.Equal(t, len(expected), len(actual)) 264 for i := 0; i < len(expected); i++ { 265 validateMatchResult(t, expected[i], actual[i], ignoreVersion) 266 } 267 } 268 269 func validateMatchResult( 270 t *testing.T, 271 expected, actual rules.MatchResult, 272 ignoreVersion bool, 273 ) { 274 if ignoreVersion { 275 var ( 276 forExistingID metadata.StagedMetadatas 277 forNewRollupIDs []rules.IDWithMetadatas 278 ) 279 if m := actual.ForExistingIDAt(0); len(m) > 0 { 280 forExistingID = m 281 } 282 if numNewRollupIDs := actual.NumNewRollupIDs(); numNewRollupIDs > 0 { 283 forNewRollupIDs = make([]rules.IDWithMetadatas, numNewRollupIDs) 284 for i := range forNewRollupIDs { 285 forNewRollupIDs[i] = actual.ForNewRollupIDsAt(i, 0) 286 } 287 } 288 actual = rules.NewMatchResult( 289 expected.Version(), 290 actual.ExpireAtNanos(), 291 forExistingID, 292 forNewRollupIDs, 293 actual.KeepOriginal(), 294 ) 295 } 296 testMatchResultCmpOpts := []cmp.Option{ 297 cmp.AllowUnexported(rules.MatchResult{}), 298 cmpopts.EquateEmpty(), 299 } 300 301 require.True(t, cmp.Equal(expected, actual, testMatchResultCmpOpts...)) 302 } 303 304 func updateStore( 305 t *testing.T, 306 store kv.Store, 307 key string, 308 proto proto.Message, 309 ) { 310 _, err := store.Set(key, proto) 311 require.NoError(t, err) 312 } 313 314 func stressTestNamespaces() *rulepb.Namespaces { 315 return &rulepb.Namespaces{ 316 Namespaces: []*rulepb.Namespace{ 317 { 318 Name: stressTestNamespaceName, 319 Snapshots: []*rulepb.NamespaceSnapshot{ 320 { 321 ForRulesetVersion: 1, 322 Tombstoned: false, 323 }, 324 }, 325 }, 326 }, 327 } 328 } 329 330 func stressTestMappingRulesConfig() []*rulepb.MappingRule { 331 return []*rulepb.MappingRule{ 332 { 333 Uuid: "mappingRule1", 334 Snapshots: []*rulepb.MappingRuleSnapshot{ 335 { 336 Name: "mappingRule1.snapshot1", 337 Tombstoned: false, 338 CutoverNanos: 1000, 339 Filter: "mtagName1:mtagValue1", 340 StoragePolicies: []*policypb.StoragePolicy{ 341 { 342 Resolution: policypb.Resolution{ 343 WindowSize: int64(10 * time.Second), 344 Precision: int64(time.Second), 345 }, 346 Retention: policypb.Retention{ 347 Period: int64(24 * time.Hour), 348 }, 349 }, 350 }, 351 }, 352 }, 353 }, 354 } 355 } 356 357 func stressTestRollupRulesConfig() []*rulepb.RollupRule { 358 return []*rulepb.RollupRule{ 359 { 360 Uuid: "rollupRule1", 361 Snapshots: []*rulepb.RollupRuleSnapshot{ 362 { 363 Name: "rollupRule1.snapshot1", 364 Tombstoned: false, 365 CutoverNanos: 500, 366 Filter: "rtagName1:rtagValue1", 367 KeepOriginal: true, 368 TargetsV2: []*rulepb.RollupTargetV2{ 369 { 370 Pipeline: &pipelinepb.Pipeline{ 371 Ops: []pipelinepb.PipelineOp{ 372 { 373 Type: pipelinepb.PipelineOp_ROLLUP, 374 Rollup: &pipelinepb.RollupOp{ 375 NewName: "newRollupName1", 376 Tags: []string{"namespace", "rtagName1"}, 377 }, 378 }, 379 }, 380 }, 381 StoragePolicies: []*policypb.StoragePolicy{ 382 { 383 Resolution: policypb.Resolution{ 384 WindowSize: int64(time.Minute), 385 Precision: int64(time.Minute), 386 }, 387 Retention: policypb.Retention{ 388 Period: int64(48 * time.Hour), 389 }, 390 }, 391 }, 392 }, 393 }, 394 }, 395 }, 396 }, 397 } 398 } 399 400 func stressTestRuleSet() *rulepb.RuleSet { 401 return &rulepb.RuleSet{ 402 Uuid: "07592642-a105-40a5-a5c5-7c416ccb56c5", 403 Namespace: stressTestNamespaceName, 404 Tombstoned: false, 405 CutoverNanos: 1000, 406 MappingRules: stressTestMappingRulesConfig(), 407 RollupRules: stressTestRollupRulesConfig(), 408 } 409 } 410 411 func stressTestCache() cache.Cache { 412 return cache.NewCache(cache.NewOptions()) 413 } 414 415 func stressTestSortedTagIteratorPool() id.SortedTagIteratorPool { 416 poolOpts := pool.NewObjectPoolOptions() 417 sortedTagIteratorPool := id.NewSortedTagIteratorPool(poolOpts) 418 sortedTagIteratorPool.Init(func() id.SortedTagIterator { 419 return m3.NewPooledSortedTagIterator(nil, sortedTagIteratorPool) 420 }) 421 return sortedTagIteratorPool 422 } 423 424 func stressTestMatcherOptions(store kv.Store) matcher.Options { 425 tagsFilterOpts := filters.TagsFilterOptions{ 426 NameTagKey: []byte(stressTestNameTagKey), 427 } 428 ruleSetOpts := rules.NewOptions(). 429 SetTagsFilterOptions(tagsFilterOpts). 430 SetNewRollupIDFn(m3.NewRollupID) 431 return matcher.NewOptions(). 432 SetKVStore(store). 433 SetNamespacesKey(stressTestNamespacesKey). 434 SetRuleSetKeyFn(func(namespace []byte) string { 435 return fmt.Sprintf(stressTestRuleSetKeyFmt, namespace) 436 }). 437 SetNamespaceResolver(namespace.NewResolver(stressTestNamespaceTag, nil)). 438 SetRuleSetOptions(ruleSetOpts) 439 }