go.uber.org/cadence@v1.2.9/internal/common/metrics/scope_test.go (about)

     1  // Copyright (c) 2017 Uber Technologies, Inc.
     2  //
     3  // Permission is hereby granted, free of charge, to any person obtaining a copy
     4  // of this software and associated documentation files (the "Software"), to deal
     5  // in the Software without restriction, including without limitation the rights
     6  // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
     7  // copies of the Software, and to permit persons to whom the Software is
     8  // furnished to do so, subject to the following conditions:
     9  //
    10  // The above copyright notice and this permission notice shall be included in
    11  // all copies or substantial portions of the Software.
    12  //
    13  // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    14  // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    15  // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
    16  // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
    17  // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
    18  // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
    19  // THE SOFTWARE.
    20  
    21  package metrics
    22  
    23  import (
    24  	"io"
    25  	"sync"
    26  	"testing"
    27  	"time"
    28  
    29  	"github.com/stretchr/testify/require"
    30  	"github.com/uber-go/tally"
    31  )
    32  
    33  func Test_Counter(t *testing.T) {
    34  	t.Parallel()
    35  	replayed, executed := withScope(t, func(scope tally.Scope) {
    36  		scope.Counter("test-name").Inc(3)
    37  	})
    38  	require.Equal(t, 0, len(replayed.Counts()))
    39  	require.Equal(t, 1, len(executed.Counts()))
    40  	require.Equal(t, int64(3), executed.Counts()[0].Value())
    41  }
    42  
    43  func Test_Gauge(t *testing.T) {
    44  	t.Parallel()
    45  	replayed, executed := withScope(t, func(scope tally.Scope) {
    46  		scope.Gauge("test-name").Update(3)
    47  	})
    48  	require.Equal(t, 0, len(replayed.Gauges()))
    49  	require.Equal(t, 1, len(executed.Gauges()))
    50  	require.Equal(t, float64(3), executed.Gauges()[0].Value())
    51  }
    52  
    53  func Test_Timer(t *testing.T) {
    54  	t.Parallel()
    55  	replayed, executed := withScope(t, func(scope tally.Scope) {
    56  		scope.Timer("test-name").Record(time.Second)
    57  		scope.Timer("test-stopwatch").Start().Stop()
    58  	})
    59  	require.Equal(t, 0, len(replayed.Timers()))
    60  	require.Equal(t, 2, len(executed.Timers()))
    61  	require.Equal(t, time.Second, executed.Timers()[0].Value())
    62  }
    63  
    64  func Test_Histogram(t *testing.T) {
    65  	t.Parallel()
    66  	t.Run("values", func(t *testing.T) {
    67  		t.Parallel()
    68  		replayed, executed := withScope(t, func(scope tally.Scope) {
    69  			valueBuckets := tally.MustMakeLinearValueBuckets(0, 10, 10)
    70  			scope.Histogram("test-hist-1", valueBuckets).RecordValue(5)
    71  			scope.Histogram("test-hist-2", valueBuckets).RecordValue(15)
    72  		})
    73  		require.Equal(t, 0, len(replayed.HistogramValueSamples()))
    74  		require.Equal(t, 2, len(executed.HistogramValueSamples()))
    75  	})
    76  	t.Run("durations", func(t *testing.T) {
    77  		t.Parallel()
    78  		replayed, executed := withScope(t, func(scope tally.Scope) {
    79  			durationBuckets := tally.MustMakeLinearDurationBuckets(0, time.Hour, 10)
    80  			scope.Histogram("test-hist-1", durationBuckets).RecordDuration(time.Minute)
    81  			scope.Histogram("test-hist-2", durationBuckets).RecordDuration(time.Minute * 61)
    82  			scope.Histogram("test-hist-3", durationBuckets).Start().Stop()
    83  		})
    84  		require.Equal(t, 0, len(replayed.HistogramDurationSamples()))
    85  		require.Equal(t, 3, len(executed.HistogramDurationSamples()))
    86  	})
    87  }
    88  
    89  func Test_ScopeCoverage(t *testing.T) {
    90  	isReplay := false
    91  	scope, closer, reporter := NewMetricsScope(&isReplay)
    92  	caps := scope.Capabilities()
    93  	require.Equal(t, true, caps.Reporting())
    94  	require.Equal(t, true, caps.Tagging())
    95  	subScope := scope.SubScope("test")
    96  	taggedScope := subScope.Tagged(make(map[string]string))
    97  	taggedScope.Counter("test-counter").Inc(1)
    98  	closer.Close()
    99  	require.Equal(t, 1, len(reporter.Counts()))
   100  }
   101  
   102  func Test_TaggedScope(t *testing.T) {
   103  	taggedScope, closer, reporter := NewTaggedMetricsScope()
   104  	scope := taggedScope.GetTaggedScope("tag1", "val1")
   105  	scope.Counter("test-name").Inc(3)
   106  	closer.Close()
   107  	require.Equal(t, 1, len(reporter.Counts()))
   108  	require.Equal(t, int64(3), reporter.Counts()[0].Value())
   109  
   110  	m := &sync.Map{}
   111  	taggedScope, closer, reporter = NewTaggedMetricsScope()
   112  	taggedScope.Map = m
   113  	scope = taggedScope.GetTaggedScope("tag2", "val1")
   114  	scope.Counter("test-name").Inc(2)
   115  	taggedScope, closer2, reporter2 := NewTaggedMetricsScope()
   116  	taggedScope.Map = m
   117  	scope = taggedScope.GetTaggedScope("tag2", "val1")
   118  	scope.Counter("test-name").Inc(1)
   119  	closer2.Close()
   120  	require.Equal(t, 0, len(reporter2.Counts()))
   121  	closer.Close()
   122  	require.Equal(t, 1, len(reporter.Counts()))
   123  	require.Equal(t, int64(3), reporter.Counts()[0].Value())
   124  }
   125  
   126  func Test_TaggedScope_WithMultiTags(t *testing.T) {
   127  	taggedScope, closer, reporter := newTaggedMetricsScope()
   128  	scope := taggedScope.GetTaggedScope("tag1", "val1", "tag2", "val2")
   129  	scope.Counter("test-name").Inc(3)
   130  	closer.Close()
   131  	require.Equal(t, 1, len(reporter.counts))
   132  	require.Equal(t, int64(3), reporter.counts[0].value)
   133  
   134  	m := &sync.Map{}
   135  	taggedScope, closer, reporter = newTaggedMetricsScope()
   136  	taggedScope.Map = m
   137  	scope = taggedScope.GetTaggedScope("tag2", "val1", "tag3", "val3")
   138  	scope.Counter("test-name").Inc(2)
   139  	taggedScope, closer2, reporter2 := newTaggedMetricsScope()
   140  	taggedScope.Map = m
   141  	scope = taggedScope.GetTaggedScope("tag2", "val1", "tag3", "val3")
   142  	scope.Counter("test-name").Inc(1)
   143  	closer2.Close()
   144  	require.Equal(t, 0, len(reporter2.counts))
   145  	closer.Close()
   146  	require.Equal(t, 1, len(reporter.counts))
   147  	require.Equal(t, int64(3), reporter.counts[0].value)
   148  
   149  	require.Panics(t, func() { taggedScope.GetTaggedScope("tag") })
   150  }
   151  
   152  func newMetricsScope(isReplay *bool) (tally.Scope, io.Closer, *capturingStatsReporter) {
   153  	reporter := &capturingStatsReporter{}
   154  	opts := tally.ScopeOptions{Reporter: reporter}
   155  	scope, closer := tally.NewRootScope(opts, time.Second)
   156  	return WrapScope(isReplay, scope, &realClock{}), closer, reporter
   157  }
   158  
   159  func newTaggedMetricsScope() (*TaggedScope, io.Closer, *capturingStatsReporter) {
   160  	isReplay := false
   161  	scope, closer, reporter := newMetricsScope(&isReplay)
   162  	return &TaggedScope{Scope: scope}, closer, reporter
   163  }
   164  
   165  // withScope runs your callback twice, once for "during replay" and once for "after replay" / "executing".
   166  // stats are captured, and the results are returned for your validation.
   167  func withScope(t *testing.T, cb func(scope tally.Scope)) (replayed *CapturingStatsReporter, executed *CapturingStatsReporter) {
   168  	replaying, executing := true, false
   169  
   170  	replayingScope, replayingCloser, replayed := NewMetricsScope(&replaying)
   171  	executingScope, executingCloser, executed := NewMetricsScope(&executing)
   172  
   173  	defer func() {
   174  		require.NoError(t, replayingCloser.Close())
   175  		require.NoError(t, executingCloser.Close())
   176  	}()
   177  
   178  	cb(replayingScope)
   179  	cb(executingScope)
   180  
   181  	return replayed, executed
   182  }
   183  
   184  // capturingStatsReporter is a reporter used by tests to capture the metric so we can verify our tests.
   185  type capturingStatsReporter struct {
   186  	counts                   []capturedCount
   187  	gauges                   []capturedGauge
   188  	timers                   []capturedTimer
   189  	histogramValueSamples    []capturedHistogramValueSamples
   190  	histogramDurationSamples []capturedHistogramDurationSamples
   191  	capabilities             int
   192  	flush                    int
   193  }
   194  
   195  type capturedCount struct {
   196  	name  string
   197  	tags  map[string]string
   198  	value int64
   199  }
   200  
   201  type capturedGauge struct {
   202  	name  string
   203  	tags  map[string]string
   204  	value float64
   205  }
   206  
   207  type capturedTimer struct {
   208  	name  string
   209  	tags  map[string]string
   210  	value time.Duration
   211  }
   212  
   213  type capturedHistogramValueSamples struct {
   214  	name             string
   215  	tags             map[string]string
   216  	bucketLowerBound float64
   217  	bucketUpperBound float64
   218  	samples          int64
   219  }
   220  
   221  type capturedHistogramDurationSamples struct {
   222  	name             string
   223  	tags             map[string]string
   224  	bucketLowerBound time.Duration
   225  	bucketUpperBound time.Duration
   226  	samples          int64
   227  }
   228  
   229  func (r *capturingStatsReporter) ReportCounter(
   230  	name string,
   231  	tags map[string]string,
   232  	value int64,
   233  ) {
   234  	r.counts = append(r.counts, capturedCount{name, tags, value})
   235  }
   236  
   237  func (r *capturingStatsReporter) ReportGauge(
   238  	name string,
   239  	tags map[string]string,
   240  	value float64,
   241  ) {
   242  	r.gauges = append(r.gauges, capturedGauge{name, tags, value})
   243  }
   244  
   245  func (r *capturingStatsReporter) ReportTimer(
   246  	name string,
   247  	tags map[string]string,
   248  	value time.Duration,
   249  ) {
   250  	r.timers = append(r.timers, capturedTimer{name, tags, value})
   251  }
   252  
   253  func (r *capturingStatsReporter) ReportHistogramValueSamples(
   254  	name string,
   255  	tags map[string]string,
   256  	buckets tally.Buckets,
   257  	bucketLowerBound,
   258  	bucketUpperBound float64,
   259  	samples int64,
   260  ) {
   261  	elem := capturedHistogramValueSamples{name, tags,
   262  		bucketLowerBound, bucketUpperBound, samples}
   263  	r.histogramValueSamples = append(r.histogramValueSamples, elem)
   264  }
   265  
   266  func (r *capturingStatsReporter) ReportHistogramDurationSamples(
   267  	name string,
   268  	tags map[string]string,
   269  	buckets tally.Buckets,
   270  	bucketLowerBound,
   271  	bucketUpperBound time.Duration,
   272  	samples int64,
   273  ) {
   274  	elem := capturedHistogramDurationSamples{name, tags,
   275  		bucketLowerBound, bucketUpperBound, samples}
   276  	r.histogramDurationSamples = append(r.histogramDurationSamples, elem)
   277  }
   278  
   279  func (r *capturingStatsReporter) Capabilities() tally.Capabilities {
   280  	r.capabilities++
   281  	return r
   282  }
   283  
   284  func (r *capturingStatsReporter) Reporting() bool {
   285  	return true
   286  }
   287  
   288  func (r *capturingStatsReporter) Tagging() bool {
   289  	return true
   290  }
   291  
   292  func (r *capturingStatsReporter) Flush() {
   293  	r.flush++
   294  }