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 }