github.com/m3db/m3@v1.5.1-0.20231129193456-75a402aa583b/src/metrics/matcher/ruleset.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  	"bytes"
    25  	"fmt"
    26  	"sync"
    27  	"time"
    28  
    29  	"github.com/m3db/m3/src/cluster/kv"
    30  	"github.com/m3db/m3/src/cluster/kv/util/runtime"
    31  	"github.com/m3db/m3/src/metrics/aggregation"
    32  	"github.com/m3db/m3/src/metrics/generated/proto/rulepb"
    33  	"github.com/m3db/m3/src/metrics/metric"
    34  	"github.com/m3db/m3/src/metrics/metric/id"
    35  	"github.com/m3db/m3/src/metrics/rules"
    36  	"github.com/m3db/m3/src/metrics/rules/view"
    37  	"github.com/m3db/m3/src/x/clock"
    38  	"github.com/m3db/m3/src/x/instrument"
    39  
    40  	"github.com/uber-go/tally"
    41  )
    42  
    43  // RuleSet manages runtime updates to registered rules and provides
    44  // API to match metic ids against rules in the corresponding ruleset.
    45  type RuleSet interface {
    46  	runtime.Value
    47  	rules.ActiveSet
    48  
    49  	// Namespace returns the namespace of the ruleset.
    50  	Namespace() []byte
    51  
    52  	// Version returns the current version of the ruleset.
    53  	Version() int
    54  
    55  	// CutoverNanos returns the cutover time of the ruleset.
    56  	CutoverNanos() int64
    57  
    58  	// Tombstoned returns whether the ruleset is tombstoned.
    59  	Tombstoned() bool
    60  }
    61  
    62  type ruleSetMetrics struct {
    63  	match      instrument.MethodMetrics
    64  	nilMatcher tally.Counter
    65  	updated    tally.Counter
    66  }
    67  
    68  func newRuleSetMetrics(scope tally.Scope, opts instrument.TimerOptions) ruleSetMetrics {
    69  	return ruleSetMetrics{
    70  		match:      instrument.NewMethodMetrics(scope, "match", opts),
    71  		nilMatcher: scope.Counter("nil-matcher"),
    72  		updated:    scope.Counter("updated"),
    73  	}
    74  }
    75  
    76  // ruleSet contains the list of rules for a namespace.
    77  type ruleSet struct {
    78  	sync.RWMutex
    79  	runtime.Value
    80  
    81  	namespace          []byte
    82  	key                string
    83  	store              kv.Store
    84  	opts               Options
    85  	nowFn              clock.NowFn
    86  	matchRangePast     time.Duration
    87  	ruleSetOpts        rules.Options
    88  	onRuleSetUpdatedFn OnRuleSetUpdatedFn
    89  
    90  	proto        *rulepb.RuleSet
    91  	version      int
    92  	cutoverNanos int64
    93  	tombstoned   bool
    94  	activeSet    rules.ActiveSet
    95  	metrics      ruleSetMetrics
    96  }
    97  
    98  func newRuleSet(
    99  	namespace []byte,
   100  	key string,
   101  	opts Options,
   102  ) RuleSet {
   103  	instrumentOpts := opts.InstrumentOptions()
   104  	r := &ruleSet{
   105  		namespace:          namespace,
   106  		key:                key,
   107  		opts:               opts,
   108  		store:              opts.KVStore(),
   109  		nowFn:              opts.ClockOptions().NowFn(),
   110  		matchRangePast:     opts.MatchRangePast(),
   111  		ruleSetOpts:        opts.RuleSetOptions(),
   112  		onRuleSetUpdatedFn: opts.OnRuleSetUpdatedFn(),
   113  		proto:              &rulepb.RuleSet{},
   114  		version:            kv.UninitializedVersion,
   115  		metrics: newRuleSetMetrics(instrumentOpts.MetricsScope(),
   116  			instrumentOpts.TimerOptions()),
   117  	}
   118  	valueOpts := runtime.NewOptions().
   119  		SetInstrumentOptions(opts.InstrumentOptions()).
   120  		SetInitWatchTimeout(opts.InitWatchTimeout()).
   121  		SetKVStore(r.store).
   122  		SetUnmarshalFn(r.toRuleSet).
   123  		SetProcessFn(r.process)
   124  	r.Value = runtime.NewValue(key, valueOpts)
   125  	return r
   126  }
   127  
   128  func (r *ruleSet) LatestRollupRules(namespace []byte, timeNanos int64) ([]view.RollupRule, error) {
   129  	r.RLock()
   130  	if !bytes.Equal(namespace, r.Namespace()) {
   131  		return nil, fmt.Errorf("namespaces do not match: %s %s", namespace, r.Namespace())
   132  	}
   133  	rollupRules, err := r.activeSet.LatestRollupRules(namespace, timeNanos)
   134  	r.RUnlock()
   135  	return rollupRules, err
   136  }
   137  
   138  func (r *ruleSet) Namespace() []byte {
   139  	r.RLock()
   140  	namespace := r.namespace
   141  	r.RUnlock()
   142  	return namespace
   143  }
   144  
   145  func (r *ruleSet) Version() int {
   146  	r.RLock()
   147  	version := r.version
   148  	r.RUnlock()
   149  	return version
   150  }
   151  
   152  func (r *ruleSet) CutoverNanos() int64 {
   153  	r.RLock()
   154  	cutoverNanos := r.cutoverNanos
   155  	r.RUnlock()
   156  	return cutoverNanos
   157  }
   158  
   159  func (r *ruleSet) Tombstoned() bool {
   160  	r.RLock()
   161  	tombstoned := r.tombstoned
   162  	r.RUnlock()
   163  	return tombstoned
   164  }
   165  
   166  func (r *ruleSet) ForwardMatch(id id.ID, fromNanos, toNanos int64, opts rules.MatchOptions) (
   167  	rules.MatchResult, error) {
   168  	callStart := r.nowFn()
   169  	r.RLock()
   170  	if r.activeSet == nil {
   171  		r.RUnlock()
   172  		r.metrics.nilMatcher.Inc(1)
   173  		return rules.EmptyMatchResult, nil
   174  	}
   175  	res, err := r.activeSet.ForwardMatch(id, fromNanos, toNanos, opts)
   176  	r.RUnlock()
   177  	if err != nil {
   178  		return rules.EmptyMatchResult, err
   179  	}
   180  	r.metrics.match.ReportSuccess(r.nowFn().Sub(callStart))
   181  	return res, nil
   182  }
   183  
   184  func (r *ruleSet) ReverseMatch(
   185  	id id.ID,
   186  	fromNanos, toNanos int64,
   187  	mt metric.Type,
   188  	at aggregation.Type,
   189  	isMultiAggregationTypesAllowed bool,
   190  	aggTypesOpts aggregation.TypesOptions,
   191  ) (rules.MatchResult, error) {
   192  	callStart := r.nowFn()
   193  	r.RLock()
   194  	if r.activeSet == nil {
   195  		r.RUnlock()
   196  		r.metrics.nilMatcher.Inc(1)
   197  		return rules.EmptyMatchResult, nil
   198  	}
   199  	res, err := r.activeSet.ReverseMatch(id, fromNanos, toNanos, mt, at, isMultiAggregationTypesAllowed, aggTypesOpts)
   200  	r.RUnlock()
   201  	if err != nil {
   202  		return rules.MatchResult{}, err
   203  	}
   204  	r.metrics.match.ReportSuccess(r.nowFn().Sub(callStart))
   205  	return res, nil
   206  }
   207  
   208  func (r *ruleSet) toRuleSet(value kv.Value) (interface{}, error) {
   209  	r.Lock()
   210  	defer r.Unlock()
   211  
   212  	if value == nil {
   213  		return nil, errNilValue
   214  	}
   215  	r.proto.Reset()
   216  	if err := value.Unmarshal(r.proto); err != nil {
   217  		return nil, err
   218  	}
   219  	return rules.NewRuleSetFromProto(value.Version(), r.proto, r.ruleSetOpts)
   220  }
   221  
   222  // process processes an ruleset update.
   223  func (r *ruleSet) process(value interface{}) error {
   224  	r.Lock()
   225  	ruleSet := value.(rules.RuleSet)
   226  	r.version = ruleSet.Version()
   227  	r.cutoverNanos = ruleSet.CutoverNanos()
   228  	r.tombstoned = ruleSet.Tombstoned()
   229  	r.activeSet = ruleSet.ActiveSet(r.nowFn().Add(-r.matchRangePast).UnixNano())
   230  	r.Unlock()
   231  
   232  	// NB: calling the update callback outside the ruleset lock to avoid circular
   233  	// lock dependency causing a deadlock.
   234  	if r.onRuleSetUpdatedFn != nil {
   235  		r.onRuleSetUpdatedFn(r.namespace, r)
   236  	}
   237  	r.metrics.updated.Inc(1)
   238  	return nil
   239  }