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  }