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