github.com/uber-go/tally/v4@v4.1.17/multi/reporter_test.go (about) 1 // Copyright (c) 2021 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 multi 22 23 import ( 24 "testing" 25 "time" 26 27 "github.com/stretchr/testify/assert" 28 "github.com/stretchr/testify/require" 29 tally "github.com/uber-go/tally/v4" 30 ) 31 32 func TestMultiReporter(t *testing.T) { 33 a, b, c := 34 newCapturingStatsReporter(), 35 newCapturingStatsReporter(), 36 newCapturingStatsReporter() 37 all := []*capturingStatsReporter{a, b, c} 38 39 r := NewMultiReporter(a, b, c) 40 41 tags := []map[string]string{ 42 {"foo": "bar"}, 43 {"foo": "baz"}, 44 {"foo": "qux"}, 45 {"foo": "bzz"}, 46 {"foo": "buz"}, 47 } 48 49 valueBuckets := tally.MustMakeLinearValueBuckets(0, 2, 5) 50 durationBuckets := tally.MustMakeLinearDurationBuckets(0, 2*time.Second, 5) 51 52 r.ReportCounter("foo", tags[0], 42) 53 r.ReportCounter("foo", tags[0], 84) 54 r.ReportGauge("baz", tags[1], 42.0) 55 r.ReportTimer("qux", tags[2], 126*time.Millisecond) 56 r.ReportHistogramValueSamples("bzz", tags[3], valueBuckets, 57 2.0, 4.0, 3) 58 r.ReportHistogramDurationSamples("buz", tags[4], durationBuckets, 59 2*time.Second, 4*time.Second, 3) 60 for _, r := range all { 61 require.Equal(t, 2, len(r.counts)) 62 63 assert.Equal(t, "foo", r.counts[0].name) 64 assert.Equal(t, tags[0], r.counts[0].tags) 65 assert.Equal(t, int64(42), r.counts[0].value) 66 67 assert.Equal(t, "foo", r.counts[1].name) 68 assert.Equal(t, tags[0], r.counts[1].tags) 69 assert.Equal(t, int64(84), r.counts[1].value) 70 71 assert.Equal(t, "baz", r.gauges[0].name) 72 assert.Equal(t, tags[1], r.gauges[0].tags) 73 assert.Equal(t, float64(42.0), r.gauges[0].value) 74 75 assert.Equal(t, "qux", r.timers[0].name) 76 assert.Equal(t, tags[2], r.timers[0].tags) 77 assert.Equal(t, 126*time.Millisecond, r.timers[0].value) 78 79 assert.Equal(t, "bzz", r.histogramValueSamples[0].name) 80 assert.Equal(t, tags[3], r.histogramValueSamples[0].tags) 81 assert.Equal(t, 2.0, r.histogramValueSamples[0].bucketLowerBound) 82 assert.Equal(t, 4.0, r.histogramValueSamples[0].bucketUpperBound) 83 assert.Equal(t, int64(3), r.histogramValueSamples[0].samples) 84 85 assert.Equal(t, "buz", r.histogramDurationSamples[0].name) 86 assert.Equal(t, tags[4], r.histogramDurationSamples[0].tags) 87 assert.Equal(t, 2*time.Second, r.histogramDurationSamples[0].bucketLowerBound) 88 assert.Equal(t, 4*time.Second, r.histogramDurationSamples[0].bucketUpperBound) 89 assert.Equal(t, int64(3), r.histogramDurationSamples[0].samples) 90 } 91 92 assert.NotNil(t, r.Capabilities()) 93 94 r.Flush() 95 for _, r := range all { 96 assert.Equal(t, 1, r.flush) 97 } 98 } 99 100 func TestMultiCachedReporter(t *testing.T) { 101 a, b, c := 102 newCapturingStatsReporter(), 103 newCapturingStatsReporter(), 104 newCapturingStatsReporter() 105 all := []*capturingStatsReporter{a, b, c} 106 107 r := NewMultiCachedReporter(a, b, c) 108 109 tags := []map[string]string{ 110 {"foo": "bar"}, 111 {"foo": "baz"}, 112 {"foo": "qux"}, 113 {"foo": "bzz"}, 114 {"foo": "buz"}, 115 } 116 117 valueBuckets := tally.MustMakeLinearValueBuckets(0, 2, 5) 118 durationBuckets := tally.MustMakeLinearDurationBuckets(0, 2*time.Second, 5) 119 120 ctr := r.AllocateCounter("foo", tags[0]) 121 ctr.ReportCount(42) 122 ctr.ReportCount(84) 123 124 gauge := r.AllocateGauge("baz", tags[1]) 125 gauge.ReportGauge(42.0) 126 127 tmr := r.AllocateTimer("qux", tags[2]) 128 tmr.ReportTimer(126 * time.Millisecond) 129 130 vhist := r.AllocateHistogram("bzz", tags[3], valueBuckets) 131 vhist.ValueBucket(2.0, 4.0).ReportSamples(3) 132 133 dhist := r.AllocateHistogram("buz", tags[4], durationBuckets) 134 dhist.DurationBucket(2*time.Second, 4*time.Second).ReportSamples(3) 135 136 for _, r := range all { 137 require.Equal(t, 2, len(r.counts)) 138 139 assert.Equal(t, "foo", r.counts[0].name) 140 assert.Equal(t, tags[0], r.counts[0].tags) 141 assert.Equal(t, int64(42), r.counts[0].value) 142 143 assert.Equal(t, "foo", r.counts[1].name) 144 assert.Equal(t, tags[0], r.counts[1].tags) 145 assert.Equal(t, int64(84), r.counts[1].value) 146 147 assert.Equal(t, "baz", r.gauges[0].name) 148 assert.Equal(t, tags[1], r.gauges[0].tags) 149 assert.Equal(t, float64(42.0), r.gauges[0].value) 150 151 assert.Equal(t, "qux", r.timers[0].name) 152 assert.Equal(t, tags[2], r.timers[0].tags) 153 assert.Equal(t, 126*time.Millisecond, r.timers[0].value) 154 155 assert.Equal(t, "bzz", r.histogramValueSamples[0].name) 156 assert.Equal(t, tags[3], r.histogramValueSamples[0].tags) 157 assert.Equal(t, 2.0, r.histogramValueSamples[0].bucketLowerBound) 158 assert.Equal(t, 4.0, r.histogramValueSamples[0].bucketUpperBound) 159 assert.Equal(t, int64(3), r.histogramValueSamples[0].samples) 160 161 assert.Equal(t, "buz", r.histogramDurationSamples[0].name) 162 assert.Equal(t, tags[4], r.histogramDurationSamples[0].tags) 163 assert.Equal(t, 2*time.Second, r.histogramDurationSamples[0].bucketLowerBound) 164 assert.Equal(t, 4*time.Second, r.histogramDurationSamples[0].bucketUpperBound) 165 assert.Equal(t, int64(3), r.histogramDurationSamples[0].samples) 166 } 167 168 assert.NotNil(t, r.Capabilities()) 169 170 r.Flush() 171 for _, r := range all { 172 assert.Equal(t, 1, r.flush) 173 } 174 } 175 176 type capturingStatsReporter struct { 177 counts []capturedCount 178 gauges []capturedGauge 179 timers []capturedTimer 180 histogramValueSamples []capturedHistogramValueSamples 181 histogramDurationSamples []capturedHistogramDurationSamples 182 capabilities int 183 flush int 184 } 185 186 type capturedCount struct { 187 name string 188 tags map[string]string 189 value int64 190 } 191 192 type capturedGauge struct { 193 name string 194 tags map[string]string 195 value float64 196 } 197 198 type capturedTimer struct { 199 name string 200 tags map[string]string 201 value time.Duration 202 } 203 204 type capturedHistogramValueSamples struct { 205 name string 206 tags map[string]string 207 bucketLowerBound float64 208 bucketUpperBound float64 209 samples int64 210 } 211 212 type capturedHistogramDurationSamples struct { 213 name string 214 tags map[string]string 215 bucketLowerBound time.Duration 216 bucketUpperBound time.Duration 217 samples int64 218 } 219 220 func newCapturingStatsReporter() *capturingStatsReporter { 221 return &capturingStatsReporter{} 222 } 223 224 func (r *capturingStatsReporter) ReportCounter( 225 name string, 226 tags map[string]string, 227 value int64, 228 ) { 229 r.counts = append(r.counts, capturedCount{name, tags, value}) 230 } 231 232 func (r *capturingStatsReporter) ReportGauge( 233 name string, 234 tags map[string]string, 235 value float64, 236 ) { 237 r.gauges = append(r.gauges, capturedGauge{name, tags, value}) 238 } 239 240 func (r *capturingStatsReporter) ReportTimer( 241 name string, 242 tags map[string]string, 243 value time.Duration, 244 ) { 245 r.timers = append(r.timers, capturedTimer{name, tags, value}) 246 } 247 248 func (r *capturingStatsReporter) ReportHistogramValueSamples( 249 name string, 250 tags map[string]string, 251 buckets tally.Buckets, 252 bucketLowerBound, 253 bucketUpperBound float64, 254 samples int64, 255 ) { 256 elem := capturedHistogramValueSamples{ 257 name, tags, 258 bucketLowerBound, bucketUpperBound, samples, 259 } 260 r.histogramValueSamples = append(r.histogramValueSamples, elem) 261 } 262 263 func (r *capturingStatsReporter) ReportHistogramDurationSamples( 264 name string, 265 tags map[string]string, 266 buckets tally.Buckets, 267 bucketLowerBound, 268 bucketUpperBound time.Duration, 269 samples int64, 270 ) { 271 elem := capturedHistogramDurationSamples{ 272 name, tags, 273 bucketLowerBound, bucketUpperBound, samples, 274 } 275 r.histogramDurationSamples = append(r.histogramDurationSamples, elem) 276 } 277 278 func (r *capturingStatsReporter) AllocateCounter( 279 name string, 280 tags map[string]string, 281 ) tally.CachedCount { 282 return cachedCount{fn: func(value int64) { 283 r.counts = append(r.counts, capturedCount{name, tags, value}) 284 }} 285 } 286 287 func (r *capturingStatsReporter) AllocateGauge( 288 name string, 289 tags map[string]string, 290 ) tally.CachedGauge { 291 return cachedGauge{fn: func(value float64) { 292 r.gauges = append(r.gauges, capturedGauge{name, tags, value}) 293 }} 294 } 295 296 func (r *capturingStatsReporter) AllocateTimer( 297 name string, 298 tags map[string]string, 299 ) tally.CachedTimer { 300 return cachedTimer{fn: func(value time.Duration) { 301 r.timers = append(r.timers, capturedTimer{name, tags, value}) 302 }} 303 } 304 305 func (r *capturingStatsReporter) AllocateHistogram( 306 name string, 307 tags map[string]string, 308 buckets tally.Buckets, 309 ) tally.CachedHistogram { 310 return cachedHistogram{ 311 valueFn: func(bucketLowerBound, bucketUpperBound float64, samples int64) { 312 elem := capturedHistogramValueSamples{ 313 name, tags, bucketLowerBound, bucketUpperBound, samples, 314 } 315 r.histogramValueSamples = append(r.histogramValueSamples, elem) 316 }, 317 durationFn: func(bucketLowerBound, bucketUpperBound time.Duration, samples int64) { 318 elem := capturedHistogramDurationSamples{ 319 name, tags, bucketLowerBound, bucketUpperBound, samples, 320 } 321 r.histogramDurationSamples = append(r.histogramDurationSamples, elem) 322 }, 323 } 324 } 325 326 func (r *capturingStatsReporter) Capabilities() tally.Capabilities { 327 r.capabilities++ 328 return r 329 } 330 331 func (r *capturingStatsReporter) Reporting() bool { 332 return true 333 } 334 335 func (r *capturingStatsReporter) Tagging() bool { 336 return true 337 } 338 339 func (r *capturingStatsReporter) Flush() { 340 r.flush++ 341 } 342 343 type cachedCount struct { 344 fn func(value int64) 345 } 346 347 func (c cachedCount) ReportCount(value int64) { 348 c.fn(value) 349 } 350 351 type cachedGauge struct { 352 fn func(value float64) 353 } 354 355 func (c cachedGauge) ReportGauge(value float64) { 356 c.fn(value) 357 } 358 359 type cachedTimer struct { 360 fn func(value time.Duration) 361 } 362 363 func (c cachedTimer) ReportTimer(value time.Duration) { 364 c.fn(value) 365 } 366 367 type cachedHistogram struct { 368 valueFn func(bucketLowerBound, bucketUpperBound float64, samples int64) 369 durationFn func(bucketLowerBound, bucketUpperBound time.Duration, samples int64) 370 } 371 372 func (h cachedHistogram) ValueBucket( 373 bucketLowerBound, bucketUpperBound float64, 374 ) tally.CachedHistogramBucket { 375 return cachedHistogramValueBucket{&h, bucketLowerBound, bucketUpperBound} 376 } 377 378 func (h cachedHistogram) DurationBucket( 379 bucketLowerBound, bucketUpperBound time.Duration, 380 ) tally.CachedHistogramBucket { 381 return cachedHistogramDurationBucket{&h, bucketLowerBound, bucketUpperBound} 382 } 383 384 type cachedHistogramValueBucket struct { 385 histogram *cachedHistogram 386 bucketLowerBound float64 387 bucketUpperBound float64 388 } 389 390 func (b cachedHistogramValueBucket) ReportSamples(v int64) { 391 b.histogram.valueFn(b.bucketLowerBound, b.bucketUpperBound, v) 392 } 393 394 type cachedHistogramDurationBucket struct { 395 histogram *cachedHistogram 396 bucketLowerBound time.Duration 397 bucketUpperBound time.Duration 398 } 399 400 func (b cachedHistogramDurationBucket) ReportSamples(v int64) { 401 b.histogram.durationFn(b.bucketLowerBound, b.bucketUpperBound, v) 402 }