go.uber.org/cadence@v1.2.9/internal/common/metrics/scope.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 metrics 22 23 import ( 24 "sync" 25 "time" 26 27 "github.com/uber-go/tally" 28 ) 29 30 type ( 31 // Clock defines interface used as time source 32 Clock interface { 33 Now() time.Time 34 } 35 36 durationRecorder interface { 37 RecordDuration(duration time.Duration) 38 } 39 40 replayAwareScope struct { 41 isReplay *bool 42 scope tally.Scope 43 clock Clock 44 } 45 46 replayAwareCounter struct { 47 isReplay *bool 48 counter tally.Counter 49 } 50 51 replayAwareGauge struct { 52 isReplay *bool 53 gauge tally.Gauge 54 } 55 56 replayAwareTimer struct { 57 isReplay *bool 58 timer tally.Timer 59 clock Clock 60 } 61 62 replayAwareHistogram struct { 63 isReplay *bool 64 histogram tally.Histogram 65 clock Clock 66 } 67 68 replayAwareStopwatchRecorder struct { 69 isReplay *bool 70 recorder durationRecorder 71 clock Clock 72 } 73 74 // TaggedScope provides metricScope with tags 75 TaggedScope struct { 76 tally.Scope 77 *sync.Map 78 } 79 ) 80 81 // WrapScope wraps a scope and skip recording metrics when isReplay is true. 82 // This is designed to be used by only by workflowEnvironmentImpl so we suppress metrics while replaying history events. 83 // Parameter isReplay is a pointer to workflowEnvironmentImpl.isReplay which will be updated when replaying history events. 84 func WrapScope(isReplay *bool, scope tally.Scope, clock Clock) tally.Scope { 85 return &replayAwareScope{isReplay, scope, clock} 86 } 87 88 // Inc increments the counter by a delta. 89 func (c *replayAwareCounter) Inc(delta int64) { 90 if *c.isReplay { 91 return 92 } 93 c.counter.Inc(delta) 94 } 95 96 // Update sets the gauges absolute value. 97 func (g *replayAwareGauge) Update(value float64) { 98 if *g.isReplay { 99 return 100 } 101 g.gauge.Update(value) 102 } 103 104 // Record a specific duration. 105 func (t *replayAwareTimer) Record(value time.Duration) { 106 if *t.isReplay { 107 return 108 } 109 t.timer.Record(value) 110 } 111 112 // Record a specific duration. 113 func (t *replayAwareTimer) RecordDuration(duration time.Duration) { 114 t.Record(duration) 115 } 116 117 // Start gives you back a specific point in time to report via Stop. 118 func (t *replayAwareTimer) Start() tally.Stopwatch { 119 return tally.NewStopwatch(t.clock.Now(), &replayAwareStopwatchRecorder{t.isReplay, t, t.clock}) 120 } 121 122 // RecordValue records a specific value directly. Will use the configured value buckets for the histogram. 123 func (h *replayAwareHistogram) RecordValue(value float64) { 124 if *h.isReplay { 125 return 126 } 127 h.histogram.RecordValue(value) 128 } 129 130 // RecordDuration records a specific duration directly. 131 // Will use the configured duration buckets for the histogram. 132 func (h *replayAwareHistogram) RecordDuration(value time.Duration) { 133 if *h.isReplay { 134 return 135 } 136 h.histogram.RecordDuration(value) 137 } 138 139 // Start gives you a specific point in time to then record a duration. 140 // Will use the configured duration buckets for the histogram. 141 func (h *replayAwareHistogram) Start() tally.Stopwatch { 142 return tally.NewStopwatch(h.clock.Now(), &replayAwareStopwatchRecorder{h.isReplay, h, h.clock}) 143 } 144 145 // StopwatchRecorder is a recorder that is called when a stopwatch is stopped with Stop(). 146 func (r *replayAwareStopwatchRecorder) RecordStopwatch(stopwatchStart time.Time) { 147 if *r.isReplay { 148 return 149 } 150 d := r.clock.Now().Sub(stopwatchStart) 151 r.recorder.RecordDuration(d) 152 } 153 154 // Counter returns the Counter object corresponding to the name. 155 func (s *replayAwareScope) Counter(name string) tally.Counter { 156 return &replayAwareCounter{s.isReplay, s.scope.Counter(name)} 157 } 158 159 // Gauge returns the Gauge object corresponding to the name. 160 func (s *replayAwareScope) Gauge(name string) tally.Gauge { 161 return &replayAwareGauge{s.isReplay, s.scope.Gauge(name)} 162 } 163 164 // Timer returns the Timer object corresponding to the name. 165 func (s *replayAwareScope) Timer(name string) tally.Timer { 166 return &replayAwareTimer{s.isReplay, s.scope.Timer(name), s.clock} 167 } 168 169 // Histogram returns the Histogram object corresponding to the name. 170 // To use default value and duration buckets configured for the scope 171 // simply pass tally.DefaultBuckets or nil. 172 // You can use tally.ValueBuckets{x, y, ...} for value buckets. 173 // You can use tally.DurationBuckets{x, y, ...} for duration buckets. 174 // You can use tally.MustMakeLinearValueBuckets(start, width, count) for linear values. 175 // You can use tally.MustMakeLinearDurationBuckets(start, width, count) for linear durations. 176 // You can use tally.MustMakeExponentialValueBuckets(start, factor, count) for exponential values. 177 // You can use tally.MustMakeExponentialDurationBuckets(start, factor, count) for exponential durations. 178 func (s *replayAwareScope) Histogram(name string, buckets tally.Buckets) tally.Histogram { 179 return &replayAwareHistogram{s.isReplay, s.scope.Histogram(name, buckets), s.clock} 180 } 181 182 // Tagged returns a new child scope with the given tags and current tags. 183 func (s *replayAwareScope) Tagged(tags map[string]string) tally.Scope { 184 return &replayAwareScope{s.isReplay, s.scope.Tagged(tags), s.clock} 185 } 186 187 // SubScope returns a new child scope appending a further name prefix. 188 func (s *replayAwareScope) SubScope(name string) tally.Scope { 189 return &replayAwareScope{s.isReplay, s.scope.SubScope(name), s.clock} 190 } 191 192 // Capabilities returns a description of metrics reporting capabilities. 193 func (s *replayAwareScope) Capabilities() tally.Capabilities { 194 return s.scope.Capabilities() 195 } 196 197 // GetTaggedScope return a scope with one or multiple tags, 198 // input should be key value pairs like: GetTaggedScope(tag1, val1, tag2, val2). 199 func (ts *TaggedScope) GetTaggedScope(keyValueinPairs ...string) tally.Scope { 200 if len(keyValueinPairs)%2 != 0 { 201 panic("GetTaggedScope key value are not in pairs") 202 } 203 if ts.Map == nil { 204 ts.Map = &sync.Map{} 205 } 206 207 key := "" 208 tagsMap := map[string]string{} 209 for i := 0; i < len(keyValueinPairs); i += 2 { 210 tagName := keyValueinPairs[i] 211 tagValue := keyValueinPairs[i+1] 212 key = key + tagName + ":" + tagValue + "-" // used to prevent collision of tagValue (map key) for different tagName 213 tagsMap[tagName] = tagValue 214 } 215 216 taggedScope, ok := ts.Load(key) 217 if !ok { 218 ts.Store(key, ts.Scope.Tagged(tagsMap)) 219 taggedScope, _ = ts.Load(key) 220 } 221 if taggedScope == nil { 222 panic("metric scope cannot be tagged") // This should never happen 223 } 224 225 return taggedScope.(tally.Scope) 226 } 227 228 // NewTaggedScope create a new TaggedScope 229 func NewTaggedScope(scope tally.Scope) *TaggedScope { 230 if scope == nil { 231 scope = tally.NoopScope 232 } 233 return &TaggedScope{Scope: scope, Map: &sync.Map{}} 234 }