github.com/uber-go/tally/v4@v4.1.17/scope_registry_test.go (about)

     1  // Copyright (c) 2024 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  	"strconv"
    26  	"sync"
    27  	"testing"
    28  	"time"
    29  
    30  	"github.com/stretchr/testify/assert"
    31  )
    32  
    33  var (
    34  	numInternalMetrics = 4
    35  )
    36  
    37  func TestVerifyCachedTaggedScopesAlloc(t *testing.T) {
    38  	root, _ := NewRootScope(ScopeOptions{
    39  		Prefix:   "funkytown",
    40  		Reporter: NullStatsReporter,
    41  		Tags: map[string]string{
    42  			"style":     "funky",
    43  			"hair":      "wavy",
    44  			"jefferson": "starship",
    45  		},
    46  	}, 0)
    47  
    48  	allocs := testing.AllocsPerRun(1000, func() {
    49  		root.Tagged(map[string]string{
    50  			"foo": "bar",
    51  			"baz": "qux",
    52  			"qux": "quux",
    53  		})
    54  	})
    55  	expected := 3.0
    56  	assert.True(t, allocs <= expected, "the cached tagged scopes should allocate at most %.0f allocations, but did allocate %.0f", expected, allocs)
    57  }
    58  
    59  func TestVerifyOmitCardinalityMetricsTags(t *testing.T) {
    60  	r := newTestStatsReporter()
    61  	_, closer := NewRootScope(ScopeOptions{
    62  		Reporter:               r,
    63  		OmitCardinalityMetrics: false,
    64  		CardinalityMetricsTags: map[string]string{
    65  			"cardinality_tag_key": "cardinality_tag_value",
    66  		},
    67  	}, 0)
    68  	wantOmitCardinalityMetricsTags := map[string]string{
    69  		"cardinality_tag_key": "cardinality_tag_value",
    70  		"version":             Version,
    71  		"host":                "global",
    72  		"instance":            "global",
    73  	}
    74  
    75  	r.gg.Add(numInternalMetrics)
    76  	closer.Close()
    77  	r.WaitAll()
    78  
    79  	assert.NotNil(t, r.gauges[counterCardinalityName], "counter cardinality should not be nil")
    80  	assert.Equal(
    81  		t, wantOmitCardinalityMetricsTags, r.gauges[counterCardinalityName].tags, "expected tags %v, got tags %v",
    82  		wantOmitCardinalityMetricsTags, r.gauges[counterCardinalityName].tags,
    83  	)
    84  }
    85  
    86  func TestNewTestStatsReporterOneScope(t *testing.T) {
    87  	r := newTestStatsReporter()
    88  	root, closer := NewRootScope(ScopeOptions{Reporter: r, OmitCardinalityMetrics: false}, 0)
    89  	s := root.(*scope)
    90  
    91  	numFakeCounters := 3
    92  	numFakeGauges := 5
    93  	numFakeHistograms := 11
    94  	numScopes := 1
    95  
    96  	r.cg.Add(numFakeCounters)
    97  	for c := 1; c <= numFakeCounters; c++ {
    98  		s.Counter(fmt.Sprintf("counter-%d", c)).Inc(int64(c))
    99  	}
   100  
   101  	r.gg.Add(numFakeGauges + numInternalMetrics)
   102  	for g := 1; g <= numFakeGauges; g++ {
   103  		s.Gauge(fmt.Sprintf("gauge_%d", g)).Update(float64(g))
   104  	}
   105  
   106  	r.hg.Add(numFakeHistograms)
   107  	for h := 1; h <= numFakeHistograms; h++ {
   108  		s.Histogram(fmt.Sprintf("histogram_%d", h), MustMakeLinearValueBuckets(0, 1, 10)).RecordValue(float64(h))
   109  	}
   110  
   111  	closer.Close()
   112  	r.WaitAll()
   113  
   114  	assert.NotNil(t, r.gauges[counterCardinalityName], "counter cardinality should not be nil")
   115  	assert.Equal(
   116  		t, numFakeCounters, int(r.gauges[counterCardinalityName].val), "expected %d counters, got %d counters",
   117  		numFakeCounters, r.gauges[counterCardinalityName].val,
   118  	)
   119  
   120  	assert.NotNil(t, r.gauges[gaugeCardinalityName], "gauge cardinality should not be nil")
   121  	assert.Equal(
   122  		t, numFakeGauges, int(r.gauges[gaugeCardinalityName].val), "expected %d gauges, got %d gauges",
   123  		numFakeGauges, r.gauges[gaugeCardinalityName].val,
   124  	)
   125  
   126  	assert.NotNil(t, r.gauges[histogramCardinalityName], "histogram cardinality should not be nil")
   127  	assert.Equal(
   128  		t, numFakeHistograms, int(r.gauges[histogramCardinalityName].val),
   129  		"expected %d histograms, got %d histograms", numFakeHistograms, r.gauges[histogramCardinalityName].val,
   130  	)
   131  
   132  	assert.NotNil(t, r.gauges[scopeCardinalityName], "scope cardinality should not be nil")
   133  	assert.Equal(
   134  		t, numScopes, int(r.gauges[scopeCardinalityName].val), "expected %d scopes, got %d scopes",
   135  		numScopes, r.gauges[scopeCardinalityName].val,
   136  	)
   137  }
   138  
   139  func TestNewTestStatsReporterManyScopes(t *testing.T) {
   140  	r := newTestStatsReporter()
   141  	root, closer := NewRootScope(ScopeOptions{Reporter: r, OmitCardinalityMetrics: false}, 0)
   142  	wantCounters, wantGauges, wantHistograms, wantScopes := 3, 2, 1, 2
   143  
   144  	s := root.(*scope)
   145  	r.cg.Add(2)
   146  	s.Counter("counter-foo").Inc(1)
   147  	s.Counter("counter-bar").Inc(2)
   148  	r.gg.Add(1 + numInternalMetrics)
   149  	s.Gauge("gauge-foo").Update(3)
   150  	r.hg.Add(1)
   151  	s.Histogram("histogram-foo", MustMakeLinearValueBuckets(0, 1, 10)).RecordValue(4)
   152  
   153  	ss := root.SubScope("sub-scope").(*scope)
   154  	r.cg.Add(1)
   155  	ss.Counter("counter-baz").Inc(5)
   156  	r.gg.Add(1)
   157  	ss.Gauge("gauge-bar").Update(6)
   158  
   159  	closer.Close()
   160  	r.WaitAll()
   161  
   162  	assert.NotNil(t, r.gauges[counterCardinalityName], "counter cardinality should not be nil")
   163  	assert.Equal(
   164  		t, wantCounters, int(r.gauges[counterCardinalityName].val), "expected %d counters, got %d counters", wantCounters,
   165  		r.gauges[counterCardinalityName].val,
   166  	)
   167  
   168  	assert.NotNil(t, r.gauges[gaugeCardinalityName], "gauge cardinality should not be nil")
   169  	assert.Equal(
   170  		t, wantGauges, int(r.gauges[gaugeCardinalityName].val), "expected %d gauges, got %d gauges", wantGauges,
   171  		r.gauges[gaugeCardinalityName].val,
   172  	)
   173  
   174  	assert.NotNil(t, r.gauges[histogramCardinalityName], "histogram cardinality should not be nil")
   175  	assert.Equal(
   176  		t, wantHistograms, int(r.gauges[histogramCardinalityName].val), "expected %d histograms, got %d histograms",
   177  		wantHistograms, r.gauges[histogramCardinalityName].val,
   178  	)
   179  
   180  	assert.NotNil(t, r.gauges[scopeCardinalityName], "scope cardinality should not be nil")
   181  	assert.Equal(
   182  		t, wantScopes, int(r.gauges[scopeCardinalityName].val), "expected %d scopes, got %d scopes",
   183  		wantScopes, r.gauges[scopeCardinalityName].val,
   184  	)
   185  }
   186  
   187  func TestForEachScopeConcurrent(t *testing.T) {
   188  	var (
   189  		root = newRootScope(ScopeOptions{Prefix: "", Tags: nil}, 0)
   190  		quit = make(chan struct{})
   191  		done = make(chan struct{})
   192  	)
   193  
   194  	go func() {
   195  		defer close(done)
   196  		for {
   197  			select {
   198  			case <-quit:
   199  				return
   200  			default:
   201  				hello := root.Tagged(map[string]string{"a": "b"}).Counter("hello")
   202  				hello.Inc(1)
   203  			}
   204  		}
   205  	}()
   206  
   207  	var c Counter = nil
   208  	for {
   209  		// Keep poking at the subscopes until the counter is written.
   210  		root.registry.ForEachScope(
   211  			func(ss *scope) {
   212  				ss.cm.RLock()
   213  				if ss.counters["hello"] != nil {
   214  					c = ss.counters["hello"]
   215  				}
   216  				ss.cm.RUnlock()
   217  			},
   218  		)
   219  		if c != nil {
   220  			quit <- struct{}{}
   221  			break
   222  		}
   223  	}
   224  
   225  	<-done
   226  }
   227  
   228  func TestCachedReporterInternalMetricsAlloc(t *testing.T) {
   229  	tests := []struct {
   230  		name                   string
   231  		omitCardinalityMetrics bool
   232  		wantGauges             int
   233  	}{
   234  		{
   235  			name:                   "omit metrics",
   236  			omitCardinalityMetrics: true,
   237  			wantGauges:             1,
   238  		},
   239  		{
   240  			name:                   "include metrics",
   241  			omitCardinalityMetrics: false,
   242  			wantGauges:             1 + numInternalMetrics,
   243  		},
   244  	}
   245  
   246  	for _, tt := range tests {
   247  		r := newTestStatsReporter()
   248  		root, closer := NewRootScope(ScopeOptions{CachedReporter: r, OmitCardinalityMetrics: tt.omitCardinalityMetrics}, 0)
   249  		s := root.(*scope)
   250  
   251  		r.gg.Add(tt.wantGauges)
   252  		s.Gauge("gauge-foo").Update(3)
   253  
   254  		closer.Close()
   255  		r.WaitAll()
   256  
   257  		assert.Equal(
   258  			t, tt.wantGauges, len(r.gauges), "%n: expected %d gauges, got %d gauges", tt.name, tt.wantGauges,
   259  			len(r.gauges),
   260  		)
   261  	}
   262  }
   263  
   264  func TestCachedReporterInternalMetricsConcurrent(t *testing.T) {
   265  	tr := newTestStatsReporter()
   266  	root, closer := NewRootScope(ScopeOptions{
   267  		CachedReporter:         tr,
   268  		OmitCardinalityMetrics: false,
   269  	}, 0)
   270  	s := root.(*scope)
   271  
   272  	var wg sync.WaitGroup
   273  
   274  	done := make(chan struct{})
   275  	time.AfterFunc(time.Second, func() {
   276  		close(done)
   277  	})
   278  
   279  	wg.Add(1)
   280  	go func() {
   281  		defer wg.Done()
   282  		var i int
   283  		for {
   284  			select {
   285  			case <-done:
   286  				return
   287  			default:
   288  			}
   289  			suffix := strconv.Itoa(i)
   290  			tr.gg.Add(1)
   291  			tr.tg.Add(1)
   292  			tr.cg.Add(1)
   293  			s.Gauge("gauge-foo" + suffix).Update(42)
   294  			s.Timer("timer-foo" + suffix).Record(42)
   295  			s.Counter("counter-foo" + suffix).Inc(42)
   296  			i++
   297  			time.Sleep(time.Microsecond)
   298  		}
   299  	}()
   300  
   301  	wg.Add(1)
   302  	go func() {
   303  		defer wg.Done()
   304  
   305  		ticker := time.NewTicker(time.Millisecond)
   306  		defer ticker.Stop()
   307  
   308  		for {
   309  			select {
   310  			case <-done:
   311  				return
   312  			case <-ticker.C:
   313  				// kick off report loop manually, so we can keep track of how many internal metrics
   314  				// we emitted.
   315  				tr.gg.Add(numInternalMetrics)
   316  				s.reportLoopRun()
   317  			}
   318  		}
   319  	}()
   320  	wg.Wait()
   321  
   322  	// Close should also trigger internal metric report.
   323  	tr.gg.Add(numInternalMetrics)
   324  	closer.Close()
   325  }