go.uber.org/cadence@v1.2.9/internal/common/metrics/scope_test.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 "io" 25 "sync" 26 "testing" 27 "time" 28 29 "github.com/stretchr/testify/require" 30 "github.com/uber-go/tally" 31 ) 32 33 func Test_Counter(t *testing.T) { 34 t.Parallel() 35 replayed, executed := withScope(t, func(scope tally.Scope) { 36 scope.Counter("test-name").Inc(3) 37 }) 38 require.Equal(t, 0, len(replayed.Counts())) 39 require.Equal(t, 1, len(executed.Counts())) 40 require.Equal(t, int64(3), executed.Counts()[0].Value()) 41 } 42 43 func Test_Gauge(t *testing.T) { 44 t.Parallel() 45 replayed, executed := withScope(t, func(scope tally.Scope) { 46 scope.Gauge("test-name").Update(3) 47 }) 48 require.Equal(t, 0, len(replayed.Gauges())) 49 require.Equal(t, 1, len(executed.Gauges())) 50 require.Equal(t, float64(3), executed.Gauges()[0].Value()) 51 } 52 53 func Test_Timer(t *testing.T) { 54 t.Parallel() 55 replayed, executed := withScope(t, func(scope tally.Scope) { 56 scope.Timer("test-name").Record(time.Second) 57 scope.Timer("test-stopwatch").Start().Stop() 58 }) 59 require.Equal(t, 0, len(replayed.Timers())) 60 require.Equal(t, 2, len(executed.Timers())) 61 require.Equal(t, time.Second, executed.Timers()[0].Value()) 62 } 63 64 func Test_Histogram(t *testing.T) { 65 t.Parallel() 66 t.Run("values", func(t *testing.T) { 67 t.Parallel() 68 replayed, executed := withScope(t, func(scope tally.Scope) { 69 valueBuckets := tally.MustMakeLinearValueBuckets(0, 10, 10) 70 scope.Histogram("test-hist-1", valueBuckets).RecordValue(5) 71 scope.Histogram("test-hist-2", valueBuckets).RecordValue(15) 72 }) 73 require.Equal(t, 0, len(replayed.HistogramValueSamples())) 74 require.Equal(t, 2, len(executed.HistogramValueSamples())) 75 }) 76 t.Run("durations", func(t *testing.T) { 77 t.Parallel() 78 replayed, executed := withScope(t, func(scope tally.Scope) { 79 durationBuckets := tally.MustMakeLinearDurationBuckets(0, time.Hour, 10) 80 scope.Histogram("test-hist-1", durationBuckets).RecordDuration(time.Minute) 81 scope.Histogram("test-hist-2", durationBuckets).RecordDuration(time.Minute * 61) 82 scope.Histogram("test-hist-3", durationBuckets).Start().Stop() 83 }) 84 require.Equal(t, 0, len(replayed.HistogramDurationSamples())) 85 require.Equal(t, 3, len(executed.HistogramDurationSamples())) 86 }) 87 } 88 89 func Test_ScopeCoverage(t *testing.T) { 90 isReplay := false 91 scope, closer, reporter := NewMetricsScope(&isReplay) 92 caps := scope.Capabilities() 93 require.Equal(t, true, caps.Reporting()) 94 require.Equal(t, true, caps.Tagging()) 95 subScope := scope.SubScope("test") 96 taggedScope := subScope.Tagged(make(map[string]string)) 97 taggedScope.Counter("test-counter").Inc(1) 98 closer.Close() 99 require.Equal(t, 1, len(reporter.Counts())) 100 } 101 102 func Test_TaggedScope(t *testing.T) { 103 taggedScope, closer, reporter := NewTaggedMetricsScope() 104 scope := taggedScope.GetTaggedScope("tag1", "val1") 105 scope.Counter("test-name").Inc(3) 106 closer.Close() 107 require.Equal(t, 1, len(reporter.Counts())) 108 require.Equal(t, int64(3), reporter.Counts()[0].Value()) 109 110 m := &sync.Map{} 111 taggedScope, closer, reporter = NewTaggedMetricsScope() 112 taggedScope.Map = m 113 scope = taggedScope.GetTaggedScope("tag2", "val1") 114 scope.Counter("test-name").Inc(2) 115 taggedScope, closer2, reporter2 := NewTaggedMetricsScope() 116 taggedScope.Map = m 117 scope = taggedScope.GetTaggedScope("tag2", "val1") 118 scope.Counter("test-name").Inc(1) 119 closer2.Close() 120 require.Equal(t, 0, len(reporter2.Counts())) 121 closer.Close() 122 require.Equal(t, 1, len(reporter.Counts())) 123 require.Equal(t, int64(3), reporter.Counts()[0].Value()) 124 } 125 126 func Test_TaggedScope_WithMultiTags(t *testing.T) { 127 taggedScope, closer, reporter := newTaggedMetricsScope() 128 scope := taggedScope.GetTaggedScope("tag1", "val1", "tag2", "val2") 129 scope.Counter("test-name").Inc(3) 130 closer.Close() 131 require.Equal(t, 1, len(reporter.counts)) 132 require.Equal(t, int64(3), reporter.counts[0].value) 133 134 m := &sync.Map{} 135 taggedScope, closer, reporter = newTaggedMetricsScope() 136 taggedScope.Map = m 137 scope = taggedScope.GetTaggedScope("tag2", "val1", "tag3", "val3") 138 scope.Counter("test-name").Inc(2) 139 taggedScope, closer2, reporter2 := newTaggedMetricsScope() 140 taggedScope.Map = m 141 scope = taggedScope.GetTaggedScope("tag2", "val1", "tag3", "val3") 142 scope.Counter("test-name").Inc(1) 143 closer2.Close() 144 require.Equal(t, 0, len(reporter2.counts)) 145 closer.Close() 146 require.Equal(t, 1, len(reporter.counts)) 147 require.Equal(t, int64(3), reporter.counts[0].value) 148 149 require.Panics(t, func() { taggedScope.GetTaggedScope("tag") }) 150 } 151 152 func newMetricsScope(isReplay *bool) (tally.Scope, io.Closer, *capturingStatsReporter) { 153 reporter := &capturingStatsReporter{} 154 opts := tally.ScopeOptions{Reporter: reporter} 155 scope, closer := tally.NewRootScope(opts, time.Second) 156 return WrapScope(isReplay, scope, &realClock{}), closer, reporter 157 } 158 159 func newTaggedMetricsScope() (*TaggedScope, io.Closer, *capturingStatsReporter) { 160 isReplay := false 161 scope, closer, reporter := newMetricsScope(&isReplay) 162 return &TaggedScope{Scope: scope}, closer, reporter 163 } 164 165 // withScope runs your callback twice, once for "during replay" and once for "after replay" / "executing". 166 // stats are captured, and the results are returned for your validation. 167 func withScope(t *testing.T, cb func(scope tally.Scope)) (replayed *CapturingStatsReporter, executed *CapturingStatsReporter) { 168 replaying, executing := true, false 169 170 replayingScope, replayingCloser, replayed := NewMetricsScope(&replaying) 171 executingScope, executingCloser, executed := NewMetricsScope(&executing) 172 173 defer func() { 174 require.NoError(t, replayingCloser.Close()) 175 require.NoError(t, executingCloser.Close()) 176 }() 177 178 cb(replayingScope) 179 cb(executingScope) 180 181 return replayed, executed 182 } 183 184 // capturingStatsReporter is a reporter used by tests to capture the metric so we can verify our tests. 185 type capturingStatsReporter struct { 186 counts []capturedCount 187 gauges []capturedGauge 188 timers []capturedTimer 189 histogramValueSamples []capturedHistogramValueSamples 190 histogramDurationSamples []capturedHistogramDurationSamples 191 capabilities int 192 flush int 193 } 194 195 type capturedCount struct { 196 name string 197 tags map[string]string 198 value int64 199 } 200 201 type capturedGauge struct { 202 name string 203 tags map[string]string 204 value float64 205 } 206 207 type capturedTimer struct { 208 name string 209 tags map[string]string 210 value time.Duration 211 } 212 213 type capturedHistogramValueSamples struct { 214 name string 215 tags map[string]string 216 bucketLowerBound float64 217 bucketUpperBound float64 218 samples int64 219 } 220 221 type capturedHistogramDurationSamples struct { 222 name string 223 tags map[string]string 224 bucketLowerBound time.Duration 225 bucketUpperBound time.Duration 226 samples int64 227 } 228 229 func (r *capturingStatsReporter) ReportCounter( 230 name string, 231 tags map[string]string, 232 value int64, 233 ) { 234 r.counts = append(r.counts, capturedCount{name, tags, value}) 235 } 236 237 func (r *capturingStatsReporter) ReportGauge( 238 name string, 239 tags map[string]string, 240 value float64, 241 ) { 242 r.gauges = append(r.gauges, capturedGauge{name, tags, value}) 243 } 244 245 func (r *capturingStatsReporter) ReportTimer( 246 name string, 247 tags map[string]string, 248 value time.Duration, 249 ) { 250 r.timers = append(r.timers, capturedTimer{name, tags, value}) 251 } 252 253 func (r *capturingStatsReporter) ReportHistogramValueSamples( 254 name string, 255 tags map[string]string, 256 buckets tally.Buckets, 257 bucketLowerBound, 258 bucketUpperBound float64, 259 samples int64, 260 ) { 261 elem := capturedHistogramValueSamples{name, tags, 262 bucketLowerBound, bucketUpperBound, samples} 263 r.histogramValueSamples = append(r.histogramValueSamples, elem) 264 } 265 266 func (r *capturingStatsReporter) ReportHistogramDurationSamples( 267 name string, 268 tags map[string]string, 269 buckets tally.Buckets, 270 bucketLowerBound, 271 bucketUpperBound time.Duration, 272 samples int64, 273 ) { 274 elem := capturedHistogramDurationSamples{name, tags, 275 bucketLowerBound, bucketUpperBound, samples} 276 r.histogramDurationSamples = append(r.histogramDurationSamples, elem) 277 } 278 279 func (r *capturingStatsReporter) Capabilities() tally.Capabilities { 280 r.capabilities++ 281 return r 282 } 283 284 func (r *capturingStatsReporter) Reporting() bool { 285 return true 286 } 287 288 func (r *capturingStatsReporter) Tagging() bool { 289 return true 290 } 291 292 func (r *capturingStatsReporter) Flush() { 293 r.flush++ 294 }