github.com/uber-go/tally/v4@v4.1.16/scope_test.go (about)

     1  // Copyright (c) 2023 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 tally
    22  
    23  import (
    24  	"fmt"
    25  	"math"
    26  	"math/rand"
    27  	"strconv"
    28  	"strings"
    29  	"sync"
    30  	"sync/atomic"
    31  	"testing"
    32  	"time"
    33  
    34  	"github.com/stretchr/testify/assert"
    35  	"github.com/stretchr/testify/require"
    36  	"go.uber.org/goleak"
    37  )
    38  
    39  func TestMain(m *testing.M) {
    40  	goleak.VerifyTestMain(m)
    41  }
    42  
    43  var (
    44  	// alphanumericSanitizerOpts is the options to create a sanitizer which uses
    45  	// the alphanumeric SanitizeFn.
    46  	alphanumericSanitizerOpts = SanitizeOptions{
    47  		NameCharacters: ValidCharacters{
    48  			Ranges:     AlphanumericRange,
    49  			Characters: UnderscoreDashCharacters,
    50  		},
    51  		KeyCharacters: ValidCharacters{
    52  			Ranges:     AlphanumericRange,
    53  			Characters: UnderscoreDashCharacters,
    54  		},
    55  		ValueCharacters: ValidCharacters{
    56  			Ranges:     AlphanumericRange,
    57  			Characters: UnderscoreDashCharacters,
    58  		},
    59  		ReplacementCharacter: DefaultReplacementCharacter,
    60  	}
    61  )
    62  
    63  type testIntValue struct {
    64  	val      int64
    65  	tags     map[string]string
    66  	reporter *testStatsReporter
    67  }
    68  
    69  func (m *testIntValue) ReportCount(value int64) {
    70  	m.val = value
    71  	m.reporter.cg.Done()
    72  }
    73  
    74  func (m *testIntValue) ReportTimer(interval time.Duration) {
    75  	m.val = int64(interval)
    76  	m.reporter.tg.Done()
    77  }
    78  
    79  type testFloatValue struct {
    80  	val      float64
    81  	tags     map[string]string
    82  	reporter *testStatsReporter
    83  }
    84  
    85  func (m *testFloatValue) ReportGauge(value float64) {
    86  	m.val = value
    87  	m.reporter.gg.Done()
    88  }
    89  
    90  type testHistogramValue struct {
    91  	tags            map[string]string
    92  	valueSamples    map[float64]int
    93  	durationSamples map[time.Duration]int
    94  }
    95  
    96  func newTestHistogramValue() *testHistogramValue {
    97  	return &testHistogramValue{
    98  		valueSamples:    make(map[float64]int),
    99  		durationSamples: make(map[time.Duration]int),
   100  	}
   101  }
   102  
   103  type testStatsReporter struct {
   104  	cg sync.WaitGroup
   105  	gg sync.WaitGroup
   106  	tg sync.WaitGroup
   107  	hg sync.WaitGroup
   108  
   109  	counters   map[string]*testIntValue
   110  	gauges     map[string]*testFloatValue
   111  	timers     map[string]*testIntValue
   112  	histograms map[string]*testHistogramValue
   113  
   114  	flushes int32
   115  }
   116  
   117  // newTestStatsReporter returns a new TestStatsReporter
   118  func newTestStatsReporter() *testStatsReporter {
   119  	return &testStatsReporter{
   120  		counters:   make(map[string]*testIntValue),
   121  		gauges:     make(map[string]*testFloatValue),
   122  		timers:     make(map[string]*testIntValue),
   123  		histograms: make(map[string]*testHistogramValue),
   124  	}
   125  }
   126  
   127  func (r *testStatsReporter) getCounters() map[string]*testIntValue {
   128  	dst := make(map[string]*testIntValue, len(r.counters))
   129  	for k, v := range r.counters {
   130  		var (
   131  			parts = strings.Split(k, "+")
   132  			name  string
   133  		)
   134  		if len(parts) > 0 {
   135  			name = parts[0]
   136  		}
   137  
   138  		dst[name] = v
   139  	}
   140  
   141  	return dst
   142  }
   143  
   144  func (r *testStatsReporter) getGauges() map[string]*testFloatValue {
   145  	dst := make(map[string]*testFloatValue, len(r.gauges))
   146  	for k, v := range r.gauges {
   147  		var (
   148  			parts = strings.Split(k, "+")
   149  			name  string
   150  		)
   151  		if len(parts) > 0 {
   152  			name = parts[0]
   153  		}
   154  
   155  		dst[name] = v
   156  	}
   157  
   158  	return dst
   159  }
   160  
   161  func (r *testStatsReporter) getTimers() map[string]*testIntValue {
   162  	dst := make(map[string]*testIntValue, len(r.timers))
   163  	for k, v := range r.timers {
   164  		var (
   165  			parts = strings.Split(k, "+")
   166  			name  string
   167  		)
   168  		if len(parts) > 0 {
   169  			name = parts[0]
   170  		}
   171  
   172  		dst[name] = v
   173  	}
   174  
   175  	return dst
   176  }
   177  
   178  func (r *testStatsReporter) getHistograms() map[string]*testHistogramValue {
   179  	dst := make(map[string]*testHistogramValue, len(r.histograms))
   180  	for k, v := range r.histograms {
   181  		var (
   182  			parts = strings.Split(k, "+")
   183  			name  string
   184  		)
   185  		if len(parts) > 0 {
   186  			name = parts[0]
   187  		}
   188  
   189  		dst[name] = v
   190  	}
   191  
   192  	return dst
   193  }
   194  
   195  func (r *testStatsReporter) WaitAll() {
   196  	r.cg.Wait()
   197  	r.gg.Wait()
   198  	r.tg.Wait()
   199  	r.hg.Wait()
   200  }
   201  
   202  func (r *testStatsReporter) AllocateCounter(
   203  	name string, tags map[string]string,
   204  ) CachedCount {
   205  	counter := &testIntValue{
   206  		val:      0,
   207  		tags:     tags,
   208  		reporter: r,
   209  	}
   210  	r.counters[name] = counter
   211  	return counter
   212  }
   213  
   214  func (r *testStatsReporter) ReportCounter(name string, tags map[string]string, value int64) {
   215  	r.counters[name] = &testIntValue{
   216  		val:  value,
   217  		tags: tags,
   218  	}
   219  	r.cg.Done()
   220  }
   221  
   222  func (r *testStatsReporter) AllocateGauge(
   223  	name string, tags map[string]string,
   224  ) CachedGauge {
   225  	gauge := &testFloatValue{
   226  		val:      0,
   227  		tags:     tags,
   228  		reporter: r,
   229  	}
   230  	r.gauges[name] = gauge
   231  	return gauge
   232  }
   233  
   234  func (r *testStatsReporter) ReportGauge(name string, tags map[string]string, value float64) {
   235  	r.gauges[name] = &testFloatValue{
   236  		val:  value,
   237  		tags: tags,
   238  	}
   239  	r.gg.Done()
   240  }
   241  
   242  func (r *testStatsReporter) AllocateTimer(
   243  	name string, tags map[string]string,
   244  ) CachedTimer {
   245  	timer := &testIntValue{
   246  		val:      0,
   247  		tags:     tags,
   248  		reporter: r,
   249  	}
   250  	r.timers[name] = timer
   251  	return timer
   252  }
   253  
   254  func (r *testStatsReporter) ReportTimer(name string, tags map[string]string, interval time.Duration) {
   255  	r.timers[name] = &testIntValue{
   256  		val:  int64(interval),
   257  		tags: tags,
   258  	}
   259  	r.tg.Done()
   260  }
   261  
   262  func (r *testStatsReporter) AllocateHistogram(
   263  	name string,
   264  	tags map[string]string,
   265  	buckets Buckets,
   266  ) CachedHistogram {
   267  	return testStatsReporterCachedHistogram{r, name, tags, buckets}
   268  }
   269  
   270  type testStatsReporterCachedHistogram struct {
   271  	r       *testStatsReporter
   272  	name    string
   273  	tags    map[string]string
   274  	buckets Buckets
   275  }
   276  
   277  func (h testStatsReporterCachedHistogram) ValueBucket(
   278  	bucketLowerBound, bucketUpperBound float64,
   279  ) CachedHistogramBucket {
   280  	return testStatsReporterCachedHistogramValueBucket{h, bucketLowerBound, bucketUpperBound}
   281  }
   282  
   283  func (h testStatsReporterCachedHistogram) DurationBucket(
   284  	bucketLowerBound, bucketUpperBound time.Duration,
   285  ) CachedHistogramBucket {
   286  	return testStatsReporterCachedHistogramDurationBucket{h, bucketLowerBound, bucketUpperBound}
   287  }
   288  
   289  type testStatsReporterCachedHistogramValueBucket struct {
   290  	histogram        testStatsReporterCachedHistogram
   291  	bucketLowerBound float64
   292  	bucketUpperBound float64
   293  }
   294  
   295  func (b testStatsReporterCachedHistogramValueBucket) ReportSamples(v int64) {
   296  	b.histogram.r.ReportHistogramValueSamples(
   297  		b.histogram.name, b.histogram.tags,
   298  		b.histogram.buckets, b.bucketLowerBound, b.bucketUpperBound, v,
   299  	)
   300  }
   301  
   302  type testStatsReporterCachedHistogramDurationBucket struct {
   303  	histogram        testStatsReporterCachedHistogram
   304  	bucketLowerBound time.Duration
   305  	bucketUpperBound time.Duration
   306  }
   307  
   308  func (b testStatsReporterCachedHistogramDurationBucket) ReportSamples(v int64) {
   309  	b.histogram.r.ReportHistogramDurationSamples(
   310  		b.histogram.name, b.histogram.tags,
   311  		b.histogram.buckets, b.bucketLowerBound, b.bucketUpperBound, v,
   312  	)
   313  }
   314  
   315  func (r *testStatsReporter) ReportHistogramValueSamples(
   316  	name string,
   317  	tags map[string]string,
   318  	buckets Buckets,
   319  	bucketLowerBound float64,
   320  	bucketUpperBound float64,
   321  	samples int64,
   322  ) {
   323  	key := KeyForPrefixedStringMap(name, tags)
   324  	value, ok := r.histograms[key]
   325  	if !ok {
   326  		value = newTestHistogramValue()
   327  		value.tags = tags
   328  		r.histograms[key] = value
   329  	}
   330  	value.valueSamples[bucketUpperBound] = int(samples)
   331  	r.hg.Done()
   332  }
   333  
   334  func (r *testStatsReporter) ReportHistogramDurationSamples(
   335  	name string,
   336  	tags map[string]string,
   337  	buckets Buckets,
   338  	bucketLowerBound time.Duration,
   339  	bucketUpperBound time.Duration,
   340  	samples int64,
   341  ) {
   342  	key := KeyForPrefixedStringMap(name, tags)
   343  	value, ok := r.histograms[key]
   344  	if !ok {
   345  		value = newTestHistogramValue()
   346  		value.tags = tags
   347  		r.histograms[key] = value
   348  	}
   349  	value.durationSamples[bucketUpperBound] = int(samples)
   350  	r.hg.Done()
   351  }
   352  
   353  func (r *testStatsReporter) Capabilities() Capabilities {
   354  	return capabilitiesReportingNoTagging
   355  }
   356  
   357  func (r *testStatsReporter) Flush() {
   358  	atomic.AddInt32(&r.flushes, 1)
   359  }
   360  
   361  func TestWriteTimerImmediately(t *testing.T) {
   362  	r := newTestStatsReporter()
   363  	s, closer := NewRootScope(ScopeOptions{Reporter: r, OmitCardinalityMetrics: true}, 0)
   364  	defer closer.Close()
   365  	r.tg.Add(1)
   366  	s.Timer("ticky").Record(time.Millisecond * 175)
   367  	r.tg.Wait()
   368  }
   369  
   370  func TestWriteTimerClosureImmediately(t *testing.T) {
   371  	r := newTestStatsReporter()
   372  	s, closer := NewRootScope(ScopeOptions{Reporter: r, OmitCardinalityMetrics: true}, 0)
   373  	defer closer.Close()
   374  	r.tg.Add(1)
   375  	tm := s.Timer("ticky")
   376  	tm.Start().Stop()
   377  	r.tg.Wait()
   378  }
   379  
   380  func TestWriteReportLoop(t *testing.T) {
   381  	r := newTestStatsReporter()
   382  	s, closer := NewRootScope(ScopeOptions{Reporter: r, OmitCardinalityMetrics: true}, 10)
   383  	defer closer.Close()
   384  
   385  	r.cg.Add(1)
   386  	s.Counter("bar").Inc(1)
   387  	r.gg.Add(1)
   388  	s.Gauge("zed").Update(1)
   389  	r.tg.Add(1)
   390  	s.Timer("ticky").Record(time.Millisecond * 175)
   391  	r.hg.Add(1)
   392  	s.Histogram("baz", MustMakeLinearValueBuckets(0, 10, 10)).
   393  		RecordValue(42.42)
   394  
   395  	r.WaitAll()
   396  }
   397  
   398  func TestWriteReportLoopDefaultInterval(t *testing.T) {
   399  	r := newTestStatsReporter()
   400  	s, closer := NewRootScopeWithDefaultInterval(
   401  		ScopeOptions{Reporter: r, OmitCardinalityMetrics: true},
   402  	)
   403  	defer closer.Close()
   404  
   405  	r.cg.Add(1)
   406  	s.Counter("bar").Inc(1)
   407  	r.gg.Add(1)
   408  	s.Gauge("zed").Update(1)
   409  	r.tg.Add(1)
   410  	s.Timer("ticky").Record(time.Millisecond * 175)
   411  	r.hg.Add(1)
   412  	s.Histogram("baz", MustMakeLinearValueBuckets(0, 10, 10)).
   413  		RecordValue(42.42)
   414  
   415  	r.WaitAll()
   416  }
   417  
   418  func TestCachedReportLoop(t *testing.T) {
   419  	r := newTestStatsReporter()
   420  	s, closer := NewRootScope(ScopeOptions{CachedReporter: r, OmitCardinalityMetrics: true}, 10)
   421  	defer closer.Close()
   422  
   423  	r.cg.Add(1)
   424  	s.Counter("bar").Inc(1)
   425  	r.gg.Add(1)
   426  	s.Gauge("zed").Update(1)
   427  	r.tg.Add(1)
   428  	s.Timer("ticky").Record(time.Millisecond * 175)
   429  	r.hg.Add(1)
   430  	s.Histogram("baz", MustMakeLinearValueBuckets(0, 10, 10)).
   431  		RecordValue(42.42)
   432  	r.WaitAll()
   433  }
   434  
   435  func testReportLoopFlushOnce(t *testing.T, cached bool) {
   436  	r := newTestStatsReporter()
   437  
   438  	scopeOpts := ScopeOptions{CachedReporter: r, OmitCardinalityMetrics: true}
   439  	if !cached {
   440  		scopeOpts = ScopeOptions{Reporter: r, OmitCardinalityMetrics: true}
   441  	}
   442  
   443  	s, closer := NewRootScope(scopeOpts, 10*time.Minute)
   444  
   445  	r.cg.Add(2)
   446  	s.Counter("foobar").Inc(1)
   447  	s.SubScope("baz").Counter("bar").Inc(1)
   448  	r.gg.Add(2)
   449  	s.Gauge("zed").Update(1)
   450  	s.SubScope("baz").Gauge("zed").Update(1)
   451  	r.tg.Add(2)
   452  	s.Timer("ticky").Record(time.Millisecond * 175)
   453  	s.SubScope("woof").Timer("sod").Record(time.Millisecond * 175)
   454  	r.hg.Add(2)
   455  	s.SubScope("woofers").Histogram("boo", MustMakeLinearValueBuckets(0, 10, 10)).
   456  		RecordValue(42.42)
   457  	s.Histogram("baz", MustMakeLinearValueBuckets(0, 10, 10)).
   458  		RecordValue(42.42)
   459  
   460  	closer.Close()
   461  	r.WaitAll()
   462  
   463  	v := atomic.LoadInt32(&r.flushes)
   464  	assert.Equal(t, int32(1), v)
   465  }
   466  
   467  func TestCachedReporterFlushOnce(t *testing.T) {
   468  	testReportLoopFlushOnce(t, true)
   469  }
   470  
   471  func TestReporterFlushOnce(t *testing.T) {
   472  	testReportLoopFlushOnce(t, false)
   473  }
   474  
   475  func TestWriteOnce(t *testing.T) {
   476  	r := newTestStatsReporter()
   477  
   478  	root, closer := NewRootScope(ScopeOptions{Reporter: r, OmitCardinalityMetrics: true}, 0)
   479  	defer closer.Close()
   480  
   481  	s := root.(*scope)
   482  
   483  	r.cg.Add(1)
   484  	s.Counter("bar").Inc(1)
   485  	r.gg.Add(1)
   486  	s.Gauge("zed").Update(1)
   487  	r.tg.Add(1)
   488  	s.Timer("ticky").Record(time.Millisecond * 175)
   489  	r.hg.Add(1)
   490  	s.Histogram("baz", MustMakeLinearValueBuckets(0, 10, 10)).
   491  		RecordValue(42.42)
   492  	r.hg.Add(1)
   493  	s.Histogram("bat", MustMakeLinearValueBuckets(1, 1, 3)).RecordValue(2.1)
   494  	r.hg.Add(1)
   495  	s.SubScope("test").Histogram("bat", MustMakeLinearValueBuckets(1, 1, 3)).RecordValue(1.1)
   496  	r.hg.Add(1)
   497  	s.SubScope("test").Histogram("bat", MustMakeLinearValueBuckets(1, 1, 3)).RecordValue(2.1)
   498  
   499  	buckets := MustMakeLinearValueBuckets(100, 10, 3)
   500  	r.hg.Add(1)
   501  	s.SubScope("test").Histogram("qux", buckets).RecordValue(135.0)
   502  	r.hg.Add(1)
   503  	s.SubScope("test").Histogram("quux", buckets).RecordValue(101.0)
   504  	r.hg.Add(1)
   505  	s.SubScope("test2").Histogram("quux", buckets).RecordValue(101.0)
   506  
   507  	s.reportLoopRun()
   508  
   509  	r.WaitAll()
   510  
   511  	var (
   512  		counters   = r.getCounters()
   513  		gauges     = r.getGauges()
   514  		timers     = r.getTimers()
   515  		histograms = r.getHistograms()
   516  	)
   517  
   518  	assert.EqualValues(t, 1, counters["bar"].val)
   519  	assert.EqualValues(t, 1, gauges["zed"].val)
   520  	assert.EqualValues(t, time.Millisecond*175, timers["ticky"].val)
   521  	assert.EqualValues(t, 1, histograms["baz"].valueSamples[50.0])
   522  	assert.EqualValues(t, 1, histograms["bat"].valueSamples[3.0])
   523  	assert.EqualValues(t, 1, histograms["test.bat"].valueSamples[2.0])
   524  	assert.EqualValues(t, 1, histograms["test.bat"].valueSamples[3.0])
   525  	assert.EqualValues(t, 1, histograms["test.qux"].valueSamples[math.MaxFloat64])
   526  	assert.EqualValues(t, 1, histograms["test.quux"].valueSamples[110.0])
   527  	assert.EqualValues(t, 1, histograms["test2.quux"].valueSamples[110.0])
   528  
   529  	r = newTestStatsReporter()
   530  	s.reportLoopRun()
   531  
   532  	counters = r.getCounters()
   533  	gauges = r.getGauges()
   534  	timers = r.getTimers()
   535  	histograms = r.getHistograms()
   536  
   537  	assert.Nil(t, counters["bar"])
   538  	assert.Nil(t, gauges["zed"])
   539  	assert.Nil(t, timers["ticky"])
   540  	assert.Nil(t, histograms["baz"])
   541  	assert.Nil(t, histograms["bat"])
   542  	assert.Nil(t, histograms["test.qux"])
   543  }
   544  
   545  func TestHistogramSharedBucketMetrics(t *testing.T) {
   546  	var (
   547  		r     = newTestStatsReporter()
   548  		scope = newRootScope(ScopeOptions{
   549  			Prefix:                 "",
   550  			Tags:                   nil,
   551  			CachedReporter:         r,
   552  			OmitCardinalityMetrics: true,
   553  		}, 0)
   554  		builder = func(s Scope) func(map[string]string) {
   555  			buckets := MustMakeLinearValueBuckets(10, 10, 3)
   556  			return func(tags map[string]string) {
   557  				s.Tagged(tags).Histogram("hist", buckets).RecordValue(19.0)
   558  			}
   559  		}
   560  	)
   561  
   562  	var (
   563  		wg     = &sync.WaitGroup{}
   564  		record = builder(scope)
   565  	)
   566  
   567  	r.hg.Add(4)
   568  	for i := 0; i < 10000; i++ {
   569  		i := i
   570  		wg.Add(1)
   571  		go func() {
   572  			defer wg.Done()
   573  
   574  			val := strconv.Itoa(i % 4)
   575  			record(
   576  				map[string]string{
   577  					"key": val,
   578  				},
   579  			)
   580  
   581  			time.Sleep(time.Duration(rand.Float64() * float64(time.Second)))
   582  		}()
   583  	}
   584  
   585  	wg.Wait()
   586  	scope.reportRegistry()
   587  	r.WaitAll()
   588  
   589  	unseen := map[string]struct{}{
   590  		"0": {},
   591  		"1": {},
   592  		"2": {},
   593  		"3": {},
   594  	}
   595  
   596  	require.Equal(t, len(unseen), len(r.histograms))
   597  
   598  	for name, value := range r.histograms {
   599  		if !strings.HasPrefix(name, "hist+") {
   600  			continue
   601  		}
   602  
   603  		count, ok := value.valueSamples[20.0]
   604  		require.True(t, ok)
   605  		require.Equal(t, 2500, count)
   606  
   607  		delete(unseen, value.tags["key"])
   608  	}
   609  
   610  	require.Equal(t, 0, len(unseen), fmt.Sprintf("%v", unseen))
   611  }
   612  
   613  func TestConcurrentUpdates(t *testing.T) {
   614  	var (
   615  		r                = newTestStatsReporter()
   616  		wg               = &sync.WaitGroup{}
   617  		workerCount      = 20
   618  		scopeCount       = 4
   619  		countersPerScope = 4
   620  		counterIncrs     = 5000
   621  		rs               = newRootScope(
   622  			ScopeOptions{
   623  				Prefix:                 "",
   624  				Tags:                   nil,
   625  				CachedReporter:         r,
   626  				OmitCardinalityMetrics: true,
   627  			}, 0,
   628  		)
   629  		scopes   = []Scope{rs}
   630  		counters []Counter
   631  	)
   632  
   633  	// Instantiate Subscopes.
   634  	for i := 1; i < scopeCount; i++ {
   635  		scopes = append(scopes, rs.SubScope(fmt.Sprintf("subscope_%d", i)))
   636  	}
   637  
   638  	// Instantiate Counters.
   639  	for sNum, s := range scopes {
   640  		for cNum := 0; cNum < countersPerScope; cNum++ {
   641  			counters = append(counters, s.Counter(fmt.Sprintf("scope_%d_counter_%d", sNum, cNum)))
   642  		}
   643  	}
   644  
   645  	// Instantiate workers.
   646  	r.cg.Add(scopeCount * countersPerScope)
   647  	for worker := 0; worker < workerCount; worker++ {
   648  		wg.Add(1)
   649  		go func() {
   650  			defer wg.Done()
   651  			// Counter should have counterIncrs * workerCount.
   652  			for i := 0; i < counterIncrs*len(counters); i++ {
   653  				counters[i%len(counters)].Inc(1)
   654  			}
   655  		}()
   656  	}
   657  
   658  	wg.Wait()
   659  	rs.reportRegistry()
   660  	r.WaitAll()
   661  
   662  	wantVal := int64(workerCount * counterIncrs)
   663  	for _, gotCounter := range r.getCounters() {
   664  		assert.Equal(t, gotCounter.val, wantVal)
   665  	}
   666  }
   667  
   668  func TestCounterSanitized(t *testing.T) {
   669  	r := newTestStatsReporter()
   670  
   671  	root, closer := NewRootScope(ScopeOptions{
   672  		Reporter:               r,
   673  		SanitizeOptions:        &alphanumericSanitizerOpts,
   674  		OmitCardinalityMetrics: true,
   675  	}, 0)
   676  	defer closer.Close()
   677  
   678  	s := root.(*scope)
   679  
   680  	r.cg.Add(1)
   681  	s.Counter("how?").Inc(1)
   682  	r.gg.Add(1)
   683  	s.Gauge("does!").Update(1)
   684  	r.tg.Add(1)
   685  	s.Timer("this!").Record(time.Millisecond * 175)
   686  	r.hg.Add(1)
   687  	s.Histogram("work1!?", MustMakeLinearValueBuckets(0, 10, 10)).
   688  		RecordValue(42.42)
   689  
   690  	s.report(r)
   691  	r.WaitAll()
   692  
   693  	var (
   694  		counters   = r.getCounters()
   695  		gauges     = r.getGauges()
   696  		timers     = r.getTimers()
   697  		histograms = r.getHistograms()
   698  	)
   699  
   700  	assert.Nil(t, counters["how?"])
   701  	assert.EqualValues(t, 1, counters["how_"].val)
   702  	assert.Nil(t, gauges["does!"])
   703  	assert.EqualValues(t, 1, gauges["does_"].val)
   704  	assert.Nil(t, timers["this!"])
   705  	assert.EqualValues(t, time.Millisecond*175, timers["this_"].val)
   706  	assert.Nil(t, histograms["work1!?"])
   707  	assert.EqualValues(t, 1, histograms["work1__"].valueSamples[50.0])
   708  
   709  	r = newTestStatsReporter()
   710  	s.report(r)
   711  
   712  	counters = r.getCounters()
   713  	gauges = r.getGauges()
   714  	timers = r.getTimers()
   715  	histograms = r.getHistograms()
   716  
   717  	assert.Nil(t, counters["how?"])
   718  	assert.Nil(t, counters["how_"])
   719  	assert.Nil(t, gauges["does!"])
   720  	assert.Nil(t, gauges["does_"])
   721  	assert.Nil(t, timers["this!"])
   722  	assert.Nil(t, timers["this_"])
   723  	assert.Nil(t, histograms["work1!?"])
   724  	assert.Nil(t, histograms["work1__"])
   725  }
   726  
   727  func TestCachedReporter(t *testing.T) {
   728  	r := newTestStatsReporter()
   729  
   730  	root, closer := NewRootScope(ScopeOptions{CachedReporter: r, OmitCardinalityMetrics: true}, 0)
   731  	defer closer.Close()
   732  
   733  	s := root.(*scope)
   734  
   735  	r.cg.Add(1)
   736  	s.Counter("bar").Inc(1)
   737  	r.gg.Add(1)
   738  	s.Gauge("zed").Update(1)
   739  	r.tg.Add(1)
   740  	s.Timer("ticky").Record(time.Millisecond * 175)
   741  	r.hg.Add(2)
   742  	s.Histogram("baz", MustMakeLinearValueBuckets(0, 10, 10)).
   743  		RecordValue(42.42)
   744  	s.Histogram("qux", MustMakeLinearDurationBuckets(0, 10*time.Millisecond, 10)).
   745  		RecordDuration(42 * time.Millisecond)
   746  
   747  	s.cachedReport()
   748  	r.WaitAll()
   749  
   750  	var (
   751  		counters   = r.getCounters()
   752  		gauges     = r.getGauges()
   753  		timers     = r.getTimers()
   754  		histograms = r.getHistograms()
   755  	)
   756  
   757  	assert.EqualValues(t, 1, counters["bar"].val)
   758  	assert.EqualValues(t, 1, gauges["zed"].val)
   759  	assert.EqualValues(t, time.Millisecond*175, timers["ticky"].val)
   760  	assert.EqualValues(t, 1, histograms["baz"].valueSamples[50.0])
   761  	assert.EqualValues(t, 1, histograms["qux"].durationSamples[50*time.Millisecond])
   762  }
   763  
   764  func TestRootScopeWithoutPrefix(t *testing.T) {
   765  	r := newTestStatsReporter()
   766  
   767  	root, closer := NewRootScope(ScopeOptions{Reporter: r, OmitCardinalityMetrics: true}, 0)
   768  	defer closer.Close()
   769  
   770  	s := root.(*scope)
   771  	r.cg.Add(1)
   772  	s.Counter("bar").Inc(1)
   773  	s.Counter("bar").Inc(20)
   774  	r.gg.Add(1)
   775  	s.Gauge("zed").Update(1)
   776  	r.tg.Add(1)
   777  	s.Timer("blork").Record(time.Millisecond * 175)
   778  	r.hg.Add(1)
   779  	s.Histogram("baz", MustMakeLinearValueBuckets(0, 10, 10)).
   780  		RecordValue(42.42)
   781  
   782  	s.report(r)
   783  	r.WaitAll()
   784  
   785  	var (
   786  		counters   = r.getCounters()
   787  		gauges     = r.getGauges()
   788  		timers     = r.getTimers()
   789  		histograms = r.getHistograms()
   790  	)
   791  
   792  	assert.EqualValues(t, 21, counters["bar"].val)
   793  	assert.EqualValues(t, 1, gauges["zed"].val)
   794  	assert.EqualValues(t, time.Millisecond*175, timers["blork"].val)
   795  	assert.EqualValues(t, 1, histograms["baz"].valueSamples[50.0])
   796  }
   797  
   798  func TestRootScopeWithPrefix(t *testing.T) {
   799  	r := newTestStatsReporter()
   800  
   801  	root, closer := NewRootScope(
   802  		ScopeOptions{Prefix: "foo", Reporter: r, OmitCardinalityMetrics: true}, 0,
   803  	)
   804  	defer closer.Close()
   805  
   806  	s := root.(*scope)
   807  	r.cg.Add(1)
   808  	s.Counter("bar").Inc(1)
   809  	s.Counter("bar").Inc(20)
   810  	r.gg.Add(1)
   811  	s.Gauge("zed").Update(1)
   812  	r.tg.Add(1)
   813  	s.Timer("blork").Record(time.Millisecond * 175)
   814  	r.hg.Add(1)
   815  	s.Histogram("baz", MustMakeLinearValueBuckets(0, 10, 10)).
   816  		RecordValue(42.42)
   817  
   818  	s.report(r)
   819  	r.WaitAll()
   820  
   821  	var (
   822  		counters   = r.getCounters()
   823  		gauges     = r.getGauges()
   824  		timers     = r.getTimers()
   825  		histograms = r.getHistograms()
   826  	)
   827  
   828  	assert.EqualValues(t, 21, counters["foo.bar"].val)
   829  	assert.EqualValues(t, 1, gauges["foo.zed"].val)
   830  	assert.EqualValues(t, time.Millisecond*175, timers["foo.blork"].val)
   831  	assert.EqualValues(t, 1, histograms["foo.baz"].valueSamples[50.0])
   832  }
   833  
   834  func TestRootScopeWithDifferentSeparator(t *testing.T) {
   835  	r := newTestStatsReporter()
   836  
   837  	root, closer := NewRootScope(
   838  		ScopeOptions{
   839  			Prefix: "foo", Separator: "_", Reporter: r, OmitCardinalityMetrics: true,
   840  		}, 0,
   841  	)
   842  	defer closer.Close()
   843  
   844  	s := root.(*scope)
   845  	r.cg.Add(1)
   846  	s.Counter("bar").Inc(1)
   847  	s.Counter("bar").Inc(20)
   848  	r.gg.Add(1)
   849  	s.Gauge("zed").Update(1)
   850  	r.tg.Add(1)
   851  	s.Timer("blork").Record(time.Millisecond * 175)
   852  	r.hg.Add(1)
   853  	s.Histogram("baz", MustMakeLinearValueBuckets(0, 10, 10)).
   854  		RecordValue(42.42)
   855  
   856  	s.report(r)
   857  	r.WaitAll()
   858  
   859  	var (
   860  		counters   = r.getCounters()
   861  		gauges     = r.getGauges()
   862  		timers     = r.getTimers()
   863  		histograms = r.getHistograms()
   864  	)
   865  
   866  	assert.EqualValues(t, 21, counters["foo_bar"].val)
   867  	assert.EqualValues(t, 1, gauges["foo_zed"].val)
   868  	assert.EqualValues(t, time.Millisecond*175, timers["foo_blork"].val)
   869  	assert.EqualValues(t, 1, histograms["foo_baz"].valueSamples[50.0])
   870  }
   871  
   872  func TestSubScope(t *testing.T) {
   873  	r := newTestStatsReporter()
   874  
   875  	root, closer := NewRootScope(
   876  		ScopeOptions{Prefix: "foo", Reporter: r, OmitCardinalityMetrics: true}, 0,
   877  	)
   878  	defer closer.Close()
   879  
   880  	tags := map[string]string{"foo": "bar"}
   881  	s := root.Tagged(tags).SubScope("mork").(*scope)
   882  	r.cg.Add(1)
   883  	s.Counter("bar").Inc(1)
   884  	s.Counter("bar").Inc(20)
   885  	r.gg.Add(1)
   886  	s.Gauge("zed").Update(1)
   887  	r.tg.Add(1)
   888  	s.Timer("blork").Record(time.Millisecond * 175)
   889  	r.hg.Add(1)
   890  	s.Histogram("baz", MustMakeLinearValueBuckets(0, 10, 10)).
   891  		RecordValue(42.42)
   892  
   893  	s.report(r)
   894  	r.WaitAll()
   895  
   896  	var (
   897  		counters   = r.getCounters()
   898  		gauges     = r.getGauges()
   899  		timers     = r.getTimers()
   900  		histograms = r.getHistograms()
   901  	)
   902  
   903  	// Assert prefixed correctly
   904  	assert.EqualValues(t, 21, counters["foo.mork.bar"].val)
   905  	assert.EqualValues(t, 1, gauges["foo.mork.zed"].val)
   906  	assert.EqualValues(t, time.Millisecond*175, timers["foo.mork.blork"].val)
   907  	assert.EqualValues(t, 1, histograms["foo.mork.baz"].valueSamples[50.0])
   908  
   909  	// Assert tags inherited
   910  	assert.Equal(t, tags, counters["foo.mork.bar"].tags)
   911  	assert.Equal(t, tags, gauges["foo.mork.zed"].tags)
   912  	assert.Equal(t, tags, timers["foo.mork.blork"].tags)
   913  	assert.Equal(t, tags, histograms["foo.mork.baz"].tags)
   914  }
   915  
   916  func TestSubScopeClose(t *testing.T) {
   917  	r := newTestStatsReporter()
   918  
   919  	rs, closer := NewRootScope(ScopeOptions{Prefix: "foo", Reporter: r, OmitCardinalityMetrics: true}, 0)
   920  	// defer closer.Close()
   921  	_ = closer
   922  
   923  	var (
   924  		root        = rs.(*scope)
   925  		s           = root.SubScope("mork").(*scope)
   926  		rootCounter = root.Counter("foo")
   927  		subCounter  = s.Counter("foo")
   928  	)
   929  
   930  	// Emit a metric from both scopes.
   931  	r.cg.Add(1)
   932  	rootCounter.Inc(1)
   933  	r.cg.Add(1)
   934  	subCounter.Inc(1)
   935  
   936  	// Verify that we got both metrics.
   937  	root.reportRegistry()
   938  	r.WaitAll()
   939  	counters := r.getCounters()
   940  	require.EqualValues(t, 1, counters["foo.foo"].val)
   941  	require.EqualValues(t, 1, counters["foo.mork.foo"].val)
   942  
   943  	// Close the subscope. We expect both metrics to still be reported, because
   944  	// we won't have reported the registry before we update the metrics.
   945  	require.NoError(t, s.Close())
   946  
   947  	// Create a subscope from the now-closed scope; it should nop.
   948  	ns := s.SubScope("foobar")
   949  	require.Equal(t, NoopScope, ns)
   950  
   951  	// Emit a metric from all scopes.
   952  	r.cg.Add(1)
   953  	rootCounter.Inc(2)
   954  	r.cg.Add(1)
   955  	subCounter.Inc(2)
   956  
   957  	// Verify that we still got both metrics.
   958  	root.reportLoopRun()
   959  	r.WaitAll()
   960  	counters = r.getCounters()
   961  	require.EqualValues(t, 2, counters["foo.foo"].val)
   962  	require.EqualValues(t, 2, counters["foo.mork.foo"].val)
   963  
   964  	// Emit a metric for both scopes. The root counter should succeed, and the
   965  	// subscope counter should not update what's in the reporter.
   966  	r.cg.Add(1)
   967  	rootCounter.Inc(3)
   968  	subCounter.Inc(3)
   969  	root.reportLoopRun()
   970  	r.WaitAll()
   971  	time.Sleep(time.Second) // since we can't wg.Add the non-reported counter
   972  
   973  	// We only expect the root scope counter; the subscope counter will be the
   974  	// value previously held by the reporter, because it has not been udpated.
   975  	counters = r.getCounters()
   976  	require.EqualValues(t, 3, counters["foo.foo"].val)
   977  	require.EqualValues(t, 2, counters["foo.mork.foo"].val)
   978  
   979  	// Ensure that we can double-close harmlessly.
   980  	require.NoError(t, s.Close())
   981  
   982  	// Create one more scope so that we can ensure it's defunct once the root is
   983  	// closed.
   984  	ns = root.SubScope("newscope")
   985  
   986  	// Close the root scope. We should not be able to emit any more metrics,
   987  	// because the root scope reports the registry prior to closing.
   988  	require.NoError(t, closer.Close())
   989  
   990  	ns.Counter("newcounter").Inc(1)
   991  	rootCounter.Inc(4)
   992  	root.registry.Report(r)
   993  	time.Sleep(time.Second) // since we can't wg.Add the non-reported counter
   994  
   995  	// We do not expect any updates.
   996  	counters = r.getCounters()
   997  	require.EqualValues(t, 3, counters["foo.foo"].val)
   998  	require.EqualValues(t, 2, counters["foo.mork.foo"].val)
   999  	_, found := counters["newscope.newcounter"]
  1000  	require.False(t, found)
  1001  
  1002  	// Ensure that we can double-close harmlessly.
  1003  	require.NoError(t, closer.Close())
  1004  }
  1005  
  1006  func TestTaggedSubScope(t *testing.T) {
  1007  	r := newTestStatsReporter()
  1008  
  1009  	ts := map[string]string{"env": "test"}
  1010  	root, closer := NewRootScope(
  1011  		ScopeOptions{
  1012  			Prefix: "foo", Tags: ts, Reporter: r, OmitCardinalityMetrics: true,
  1013  		}, 0,
  1014  	)
  1015  	defer closer.Close()
  1016  
  1017  	s := root.(*scope)
  1018  
  1019  	tscope := root.Tagged(map[string]string{"service": "test"}).(*scope)
  1020  	scope := root
  1021  
  1022  	r.cg.Add(1)
  1023  	scope.Counter("beep").Inc(1)
  1024  	r.cg.Add(1)
  1025  	tscope.Counter("boop").Inc(1)
  1026  	r.hg.Add(1)
  1027  	scope.Histogram("baz", MustMakeLinearValueBuckets(0, 10, 10)).
  1028  		RecordValue(42.42)
  1029  	r.hg.Add(1)
  1030  	tscope.Histogram("bar", MustMakeLinearValueBuckets(0, 10, 10)).
  1031  		RecordValue(42.42)
  1032  
  1033  	s.report(r)
  1034  	tscope.report(r)
  1035  	r.cg.Wait()
  1036  
  1037  	var (
  1038  		counters   = r.getCounters()
  1039  		histograms = r.getHistograms()
  1040  	)
  1041  
  1042  	assert.EqualValues(t, 1, counters["foo.beep"].val)
  1043  	assert.EqualValues(t, ts, counters["foo.beep"].tags)
  1044  
  1045  	assert.EqualValues(t, 1, counters["foo.boop"].val)
  1046  	assert.EqualValues(
  1047  		t, map[string]string{
  1048  			"env":     "test",
  1049  			"service": "test",
  1050  		}, counters["foo.boop"].tags,
  1051  	)
  1052  
  1053  	assert.EqualValues(t, 1, histograms["foo.baz"].valueSamples[50.0])
  1054  	assert.EqualValues(t, ts, histograms["foo.baz"].tags)
  1055  
  1056  	assert.EqualValues(t, 1, histograms["foo.bar"].valueSamples[50.0])
  1057  	assert.EqualValues(
  1058  		t, map[string]string{
  1059  			"env":     "test",
  1060  			"service": "test",
  1061  		}, histograms["foo.bar"].tags,
  1062  	)
  1063  }
  1064  
  1065  func TestTaggedSanitizedSubScope(t *testing.T) {
  1066  	r := newTestStatsReporter()
  1067  
  1068  	ts := map[string]string{"env": "test:env"}
  1069  	root, closer := NewRootScope(ScopeOptions{
  1070  		Prefix:                 "foo",
  1071  		Tags:                   ts,
  1072  		Reporter:               r,
  1073  		SanitizeOptions:        &alphanumericSanitizerOpts,
  1074  		OmitCardinalityMetrics: true,
  1075  	}, 0)
  1076  	defer closer.Close()
  1077  	s := root.(*scope)
  1078  
  1079  	tscope := root.Tagged(map[string]string{"service": "test.service"}).(*scope)
  1080  
  1081  	r.cg.Add(1)
  1082  	tscope.Counter("beep").Inc(1)
  1083  
  1084  	s.report(r)
  1085  	tscope.report(r)
  1086  	r.cg.Wait()
  1087  
  1088  	counters := r.getCounters()
  1089  	assert.EqualValues(t, 1, counters["foo_beep"].val)
  1090  	assert.EqualValues(
  1091  		t, map[string]string{
  1092  			"env":     "test_env",
  1093  			"service": "test_service",
  1094  		}, counters["foo_beep"].tags,
  1095  	)
  1096  }
  1097  
  1098  func TestTaggedExistingReturnsSameScope(t *testing.T) {
  1099  	r := newTestStatsReporter()
  1100  
  1101  	for _, initialTags := range []map[string]string{
  1102  		nil,
  1103  		{"env": "test"},
  1104  	} {
  1105  		root, closer := NewRootScope(
  1106  			ScopeOptions{
  1107  				Prefix: "foo", Tags: initialTags, Reporter: r, OmitCardinalityMetrics: true,
  1108  			}, 0,
  1109  		)
  1110  		defer closer.Close()
  1111  
  1112  		rootScope := root.(*scope)
  1113  		fooScope := root.Tagged(map[string]string{"foo": "bar"}).(*scope)
  1114  
  1115  		assert.NotEqual(t, rootScope, fooScope)
  1116  		assert.Equal(t, fooScope, fooScope.Tagged(nil))
  1117  
  1118  		fooBarScope := fooScope.Tagged(map[string]string{"bar": "baz"}).(*scope)
  1119  
  1120  		assert.NotEqual(t, fooScope, fooBarScope)
  1121  		assert.Equal(t, fooBarScope, fooScope.Tagged(map[string]string{"bar": "baz"}).(*scope))
  1122  	}
  1123  }
  1124  
  1125  func TestSnapshot(t *testing.T) {
  1126  	commonTags := map[string]string{"env": "test"}
  1127  	s := NewTestScope("foo", map[string]string{"env": "test"})
  1128  	child := s.Tagged(map[string]string{"service": "test"})
  1129  	s.Counter("beep").Inc(1)
  1130  	s.Gauge("bzzt").Update(2)
  1131  	s.Timer("brrr").Record(1 * time.Second)
  1132  	s.Timer("brrr").Record(2 * time.Second)
  1133  	s.Histogram("fizz", ValueBuckets{0, 2, 4}).RecordValue(1)
  1134  	s.Histogram("fizz", ValueBuckets{0, 2, 4}).RecordValue(5)
  1135  	s.Histogram("buzz", DurationBuckets{time.Second * 2, time.Second * 4}).RecordDuration(time.Second)
  1136  	child.Counter("boop").Inc(1)
  1137  
  1138  	// Should be able to call Snapshot any number of times and get same result.
  1139  	for i := 0; i < 3; i++ {
  1140  		t.Run(
  1141  			fmt.Sprintf("attempt %d", i), func(t *testing.T) {
  1142  				snap := s.Snapshot()
  1143  				counters, gauges, timers, histograms :=
  1144  					snap.Counters(), snap.Gauges(), snap.Timers(), snap.Histograms()
  1145  
  1146  				assert.EqualValues(t, 1, counters["foo.beep+env=test"].Value())
  1147  				assert.EqualValues(t, commonTags, counters["foo.beep+env=test"].Tags())
  1148  
  1149  				assert.EqualValues(t, 2, gauges["foo.bzzt+env=test"].Value())
  1150  				assert.EqualValues(t, commonTags, gauges["foo.bzzt+env=test"].Tags())
  1151  
  1152  				assert.EqualValues(
  1153  					t, []time.Duration{
  1154  						1 * time.Second,
  1155  						2 * time.Second,
  1156  					}, timers["foo.brrr+env=test"].Values(),
  1157  				)
  1158  				assert.EqualValues(t, commonTags, timers["foo.brrr+env=test"].Tags())
  1159  
  1160  				assert.EqualValues(
  1161  					t, map[float64]int64{
  1162  						0:               0,
  1163  						2:               1,
  1164  						4:               0,
  1165  						math.MaxFloat64: 1,
  1166  					}, histograms["foo.fizz+env=test"].Values(),
  1167  				)
  1168  				assert.EqualValues(t, map[time.Duration]int64(nil), histograms["foo.fizz+env=test"].Durations())
  1169  				assert.EqualValues(t, commonTags, histograms["foo.fizz+env=test"].Tags())
  1170  
  1171  				assert.EqualValues(t, map[float64]int64(nil), histograms["foo.buzz+env=test"].Values())
  1172  				assert.EqualValues(
  1173  					t, map[time.Duration]int64{
  1174  						time.Second * 2: 1,
  1175  						time.Second * 4: 0,
  1176  						math.MaxInt64:   0,
  1177  					}, histograms["foo.buzz+env=test"].Durations(),
  1178  				)
  1179  				assert.EqualValues(t, commonTags, histograms["foo.buzz+env=test"].Tags())
  1180  
  1181  				assert.EqualValues(t, 1, counters["foo.boop+env=test,service=test"].Value())
  1182  				assert.EqualValues(
  1183  					t, map[string]string{
  1184  						"env":     "test",
  1185  						"service": "test",
  1186  					}, counters["foo.boop+env=test,service=test"].Tags(),
  1187  				)
  1188  			},
  1189  		)
  1190  	}
  1191  }
  1192  
  1193  func TestSnapshotConcurrent(t *testing.T) {
  1194  	var (
  1195  		scope = NewTestScope("", nil)
  1196  		quit  = make(chan struct{})
  1197  		done  = make(chan struct{})
  1198  	)
  1199  
  1200  	go func() {
  1201  		defer close(done)
  1202  		for {
  1203  			select {
  1204  			case <-quit:
  1205  				return
  1206  			default:
  1207  				hello := scope.Tagged(map[string]string{"a": "b"}).Counter("hello")
  1208  				hello.Inc(1)
  1209  			}
  1210  		}
  1211  	}()
  1212  	var val CounterSnapshot
  1213  	for {
  1214  		val = scope.Snapshot().Counters()["hello+a=b"]
  1215  		if val != nil {
  1216  			quit <- struct{}{}
  1217  			break
  1218  		}
  1219  	}
  1220  	require.NotNil(t, val)
  1221  
  1222  	<-done
  1223  }
  1224  
  1225  func TestCapabilities(t *testing.T) {
  1226  	r := newTestStatsReporter()
  1227  	s, closer := NewRootScope(ScopeOptions{Reporter: r, OmitCardinalityMetrics: true}, 0)
  1228  	defer closer.Close()
  1229  	assert.True(t, s.Capabilities().Reporting())
  1230  	assert.False(t, s.Capabilities().Tagging())
  1231  }
  1232  
  1233  func TestCapabilitiesNoReporter(t *testing.T) {
  1234  	s, closer := NewRootScope(ScopeOptions{}, 0)
  1235  	defer closer.Close()
  1236  	assert.False(t, s.Capabilities().Reporting())
  1237  	assert.False(t, s.Capabilities().Tagging())
  1238  }
  1239  
  1240  func TestNilTagMerge(t *testing.T) {
  1241  	assert.Nil(t, nil, mergeRightTags(nil, nil))
  1242  }
  1243  
  1244  func TestScopeDefaultBuckets(t *testing.T) {
  1245  	r := newTestStatsReporter()
  1246  
  1247  	root, closer := NewRootScope(ScopeOptions{
  1248  		DefaultBuckets: DurationBuckets{
  1249  			0 * time.Millisecond,
  1250  			30 * time.Millisecond,
  1251  			60 * time.Millisecond,
  1252  			90 * time.Millisecond,
  1253  			120 * time.Millisecond,
  1254  		},
  1255  		Reporter:               r,
  1256  		OmitCardinalityMetrics: true,
  1257  	}, 0)
  1258  	defer closer.Close()
  1259  
  1260  	s := root.(*scope)
  1261  	r.hg.Add(2)
  1262  	s.Histogram("baz", DefaultBuckets).RecordDuration(42 * time.Millisecond)
  1263  	s.Histogram("baz", DefaultBuckets).RecordDuration(84 * time.Millisecond)
  1264  	s.Histogram("baz", DefaultBuckets).RecordDuration(84 * time.Millisecond)
  1265  
  1266  	s.report(r)
  1267  	r.WaitAll()
  1268  
  1269  	histograms := r.getHistograms()
  1270  	assert.EqualValues(t, 1, histograms["baz"].durationSamples[60*time.Millisecond])
  1271  	assert.EqualValues(t, 2, histograms["baz"].durationSamples[90*time.Millisecond])
  1272  }
  1273  
  1274  type testMets struct {
  1275  	c Counter
  1276  }
  1277  
  1278  func newTestMets(scope Scope) testMets {
  1279  	return testMets{
  1280  		c: scope.Counter("honk"),
  1281  	}
  1282  }
  1283  
  1284  func TestReturnByValue(t *testing.T) {
  1285  	r := newTestStatsReporter()
  1286  
  1287  	root, closer := NewRootScope(ScopeOptions{Reporter: r, OmitCardinalityMetrics: true}, 0)
  1288  	defer closer.Close()
  1289  
  1290  	s := root.(*scope)
  1291  	mets := newTestMets(s)
  1292  
  1293  	r.cg.Add(1)
  1294  	mets.c.Inc(3)
  1295  	s.report(r)
  1296  	r.cg.Wait()
  1297  
  1298  	counters := r.getCounters()
  1299  	assert.EqualValues(t, 3, counters["honk"].val)
  1300  }
  1301  
  1302  func TestScopeAvoidReportLoopRunOnClose(t *testing.T) {
  1303  	r := newTestStatsReporter()
  1304  	root, closer := NewRootScope(ScopeOptions{Reporter: r, OmitCardinalityMetrics: true}, 0)
  1305  
  1306  	s := root.(*scope)
  1307  	s.reportLoopRun()
  1308  
  1309  	assert.Equal(t, int32(1), atomic.LoadInt32(&r.flushes))
  1310  
  1311  	assert.NoError(t, closer.Close())
  1312  
  1313  	s.reportLoopRun()
  1314  	assert.Equal(t, int32(2), atomic.LoadInt32(&r.flushes))
  1315  }
  1316  
  1317  func TestScopeFlushOnClose(t *testing.T) {
  1318  	r := newTestStatsReporter()
  1319  	root, closer := NewRootScope(ScopeOptions{Reporter: r, OmitCardinalityMetrics: true}, time.Hour)
  1320  
  1321  	r.cg.Add(1)
  1322  	root.Counter("foo").Inc(1)
  1323  
  1324  	counters := r.getCounters()
  1325  	assert.Nil(t, counters["foo"])
  1326  	assert.NoError(t, closer.Close())
  1327  
  1328  	counters = r.getCounters()
  1329  	assert.EqualValues(t, 1, counters["foo"].val)
  1330  	assert.NoError(t, closer.Close())
  1331  }