github.com/uber-go/tally/v4@v4.1.17/scope.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  	"io"
    25  	"sync"
    26  	"time"
    27  
    28  	"go.uber.org/atomic"
    29  )
    30  
    31  const (
    32  	_defaultInitialSliceSize  = 16
    33  	_defaultReportingInterval = 2 * time.Second
    34  )
    35  
    36  var (
    37  	// NoopScope is a scope that does nothing
    38  	NoopScope, _ = NewRootScope(ScopeOptions{Reporter: NullStatsReporter}, 0)
    39  	// DefaultSeparator is the default separator used to join nested scopes
    40  	DefaultSeparator = "."
    41  
    42  	globalNow = time.Now
    43  
    44  	defaultScopeBuckets = DurationBuckets{
    45  		0 * time.Millisecond,
    46  		10 * time.Millisecond,
    47  		25 * time.Millisecond,
    48  		50 * time.Millisecond,
    49  		75 * time.Millisecond,
    50  		100 * time.Millisecond,
    51  		200 * time.Millisecond,
    52  		300 * time.Millisecond,
    53  		400 * time.Millisecond,
    54  		500 * time.Millisecond,
    55  		600 * time.Millisecond,
    56  		800 * time.Millisecond,
    57  		1 * time.Second,
    58  		2 * time.Second,
    59  		5 * time.Second,
    60  	}
    61  )
    62  
    63  type scope struct {
    64  	separator      string
    65  	prefix         string
    66  	tags           map[string]string
    67  	reporter       StatsReporter
    68  	cachedReporter CachedStatsReporter
    69  	baseReporter   BaseStatsReporter
    70  	defaultBuckets Buckets
    71  	sanitizer      Sanitizer
    72  
    73  	registry *scopeRegistry
    74  
    75  	cm sync.RWMutex
    76  	gm sync.RWMutex
    77  	tm sync.RWMutex
    78  	hm sync.RWMutex
    79  
    80  	counters        map[string]*counter
    81  	countersSlice   []*counter
    82  	gauges          map[string]*gauge
    83  	gaugesSlice     []*gauge
    84  	histograms      map[string]*histogram
    85  	histogramsSlice []*histogram
    86  	timers          map[string]*timer
    87  	// nb: deliberately skipping timersSlice as we report timers immediately,
    88  	// no buffering is involved.
    89  
    90  	bucketCache *bucketCache
    91  	closed      atomic.Bool
    92  	done        chan struct{}
    93  	wg          sync.WaitGroup
    94  	root        bool
    95  	testScope   bool
    96  }
    97  
    98  // ScopeOptions is a set of options to construct a scope.
    99  type ScopeOptions struct {
   100  	Tags                   map[string]string
   101  	Prefix                 string
   102  	Reporter               StatsReporter
   103  	CachedReporter         CachedStatsReporter
   104  	Separator              string
   105  	DefaultBuckets         Buckets
   106  	SanitizeOptions        *SanitizeOptions
   107  	OmitCardinalityMetrics bool
   108  	CardinalityMetricsTags map[string]string
   109  
   110  	testScope          bool
   111  	registryShardCount uint
   112  }
   113  
   114  // NewRootScope creates a new root Scope with a set of options and
   115  // a reporting interval.
   116  // Must provide either a StatsReporter or a CachedStatsReporter.
   117  func NewRootScope(opts ScopeOptions, interval time.Duration) (Scope, io.Closer) {
   118  	s := newRootScope(opts, interval)
   119  	return s, s
   120  }
   121  
   122  // NewRootScopeWithDefaultInterval invokes NewRootScope with the default
   123  // reporting interval of 2s.
   124  func NewRootScopeWithDefaultInterval(opts ScopeOptions) (Scope, io.Closer) {
   125  	return NewRootScope(opts, _defaultReportingInterval)
   126  }
   127  
   128  // NewTestScope creates a new Scope without a stats reporter with the
   129  // given prefix and adds the ability to take snapshots of metrics emitted
   130  // to it.
   131  func NewTestScope(
   132  	prefix string,
   133  	tags map[string]string,
   134  ) TestScope {
   135  	return newRootScope(ScopeOptions{
   136  		Prefix:    prefix,
   137  		Tags:      tags,
   138  		testScope: true,
   139  	}, 0)
   140  }
   141  
   142  func newRootScope(opts ScopeOptions, interval time.Duration) *scope {
   143  	sanitizer := NewNoOpSanitizer()
   144  	if o := opts.SanitizeOptions; o != nil {
   145  		sanitizer = NewSanitizer(*o)
   146  	}
   147  
   148  	if opts.Tags == nil {
   149  		opts.Tags = make(map[string]string)
   150  	}
   151  	if opts.Separator == "" {
   152  		opts.Separator = DefaultSeparator
   153  	}
   154  
   155  	var baseReporter BaseStatsReporter
   156  	if opts.Reporter != nil {
   157  		baseReporter = opts.Reporter
   158  	} else if opts.CachedReporter != nil {
   159  		baseReporter = opts.CachedReporter
   160  	}
   161  
   162  	if opts.DefaultBuckets == nil || opts.DefaultBuckets.Len() < 1 {
   163  		opts.DefaultBuckets = defaultScopeBuckets
   164  	}
   165  
   166  	s := &scope{
   167  		baseReporter:    baseReporter,
   168  		bucketCache:     newBucketCache(),
   169  		cachedReporter:  opts.CachedReporter,
   170  		counters:        make(map[string]*counter),
   171  		countersSlice:   make([]*counter, 0, _defaultInitialSliceSize),
   172  		defaultBuckets:  opts.DefaultBuckets,
   173  		done:            make(chan struct{}),
   174  		gauges:          make(map[string]*gauge),
   175  		gaugesSlice:     make([]*gauge, 0, _defaultInitialSliceSize),
   176  		histograms:      make(map[string]*histogram),
   177  		histogramsSlice: make([]*histogram, 0, _defaultInitialSliceSize),
   178  		prefix:          sanitizer.Name(opts.Prefix),
   179  		reporter:        opts.Reporter,
   180  		sanitizer:       sanitizer,
   181  		separator:       sanitizer.Name(opts.Separator),
   182  		timers:          make(map[string]*timer),
   183  		root:            true,
   184  		testScope:       opts.testScope,
   185  	}
   186  
   187  	// NB(r): Take a copy of the tags on creation
   188  	// so that it cannot be modified after set.
   189  	s.tags = s.copyAndSanitizeMap(opts.Tags)
   190  
   191  	// Register the root scope
   192  	s.registry = newScopeRegistryWithShardCount(s, opts.registryShardCount, opts.OmitCardinalityMetrics, opts.CardinalityMetricsTags)
   193  
   194  	if interval > 0 {
   195  		s.wg.Add(1)
   196  		go func() {
   197  			defer s.wg.Done()
   198  			s.reportLoop(interval)
   199  		}()
   200  	}
   201  
   202  	return s
   203  }
   204  
   205  // report dumps all aggregated stats into the reporter. Should be called automatically by the root scope periodically.
   206  func (s *scope) report(r StatsReporter) {
   207  	s.cm.RLock()
   208  	for name, counter := range s.counters {
   209  		counter.report(s.fullyQualifiedName(name), s.tags, r)
   210  	}
   211  	s.cm.RUnlock()
   212  
   213  	s.gm.RLock()
   214  	for name, gauge := range s.gauges {
   215  		gauge.report(s.fullyQualifiedName(name), s.tags, r)
   216  	}
   217  	s.gm.RUnlock()
   218  
   219  	// we do nothing for timers here because timers report directly to ths StatsReporter without buffering
   220  
   221  	s.hm.RLock()
   222  	for name, histogram := range s.histograms {
   223  		histogram.report(s.fullyQualifiedName(name), s.tags, r)
   224  	}
   225  	s.hm.RUnlock()
   226  }
   227  
   228  func (s *scope) cachedReport() {
   229  	s.cm.RLock()
   230  	for _, counter := range s.countersSlice {
   231  		counter.cachedReport()
   232  	}
   233  	s.cm.RUnlock()
   234  
   235  	s.gm.RLock()
   236  	for _, gauge := range s.gaugesSlice {
   237  		gauge.cachedReport()
   238  	}
   239  	s.gm.RUnlock()
   240  
   241  	// we do nothing for timers here because timers report directly to ths StatsReporter without buffering
   242  
   243  	s.hm.RLock()
   244  	for _, histogram := range s.histogramsSlice {
   245  		histogram.cachedReport()
   246  	}
   247  	s.hm.RUnlock()
   248  }
   249  
   250  // reportLoop is used by the root scope for periodic reporting
   251  func (s *scope) reportLoop(interval time.Duration) {
   252  	ticker := time.NewTicker(interval)
   253  	defer ticker.Stop()
   254  
   255  	for {
   256  		select {
   257  		case <-ticker.C:
   258  			s.reportLoopRun()
   259  		case <-s.done:
   260  			return
   261  		}
   262  	}
   263  }
   264  
   265  func (s *scope) reportLoopRun() {
   266  	if s.closed.Load() {
   267  		return
   268  	}
   269  
   270  	s.reportRegistry()
   271  }
   272  
   273  func (s *scope) reportRegistry() {
   274  	if s.reporter != nil {
   275  		s.registry.Report(s.reporter)
   276  		s.reporter.Flush()
   277  	} else if s.cachedReporter != nil {
   278  		s.registry.CachedReport()
   279  		s.cachedReporter.Flush()
   280  	}
   281  }
   282  
   283  func (s *scope) Counter(name string) Counter {
   284  	name = s.sanitizer.Name(name)
   285  	if c, ok := s.counter(name); ok {
   286  		return c
   287  	}
   288  
   289  	s.cm.Lock()
   290  	defer s.cm.Unlock()
   291  
   292  	if c, ok := s.counters[name]; ok {
   293  		return c
   294  	}
   295  
   296  	var cachedCounter CachedCount
   297  	if s.cachedReporter != nil {
   298  		cachedCounter = s.cachedReporter.AllocateCounter(
   299  			s.fullyQualifiedName(name),
   300  			s.tags,
   301  		)
   302  	}
   303  
   304  	c := newCounter(cachedCounter)
   305  	s.counters[name] = c
   306  	s.countersSlice = append(s.countersSlice, c)
   307  
   308  	return c
   309  }
   310  
   311  func (s *scope) counter(sanitizedName string) (Counter, bool) {
   312  	s.cm.RLock()
   313  	defer s.cm.RUnlock()
   314  
   315  	c, ok := s.counters[sanitizedName]
   316  	return c, ok
   317  }
   318  
   319  func (s *scope) Gauge(name string) Gauge {
   320  	name = s.sanitizer.Name(name)
   321  	if g, ok := s.gauge(name); ok {
   322  		return g
   323  	}
   324  
   325  	s.gm.Lock()
   326  	defer s.gm.Unlock()
   327  
   328  	if g, ok := s.gauges[name]; ok {
   329  		return g
   330  	}
   331  
   332  	var cachedGauge CachedGauge
   333  	if s.cachedReporter != nil {
   334  		cachedGauge = s.cachedReporter.AllocateGauge(
   335  			s.fullyQualifiedName(name), s.tags,
   336  		)
   337  	}
   338  
   339  	g := newGauge(cachedGauge)
   340  	s.gauges[name] = g
   341  	s.gaugesSlice = append(s.gaugesSlice, g)
   342  
   343  	return g
   344  }
   345  
   346  func (s *scope) gauge(name string) (Gauge, bool) {
   347  	s.gm.RLock()
   348  	defer s.gm.RUnlock()
   349  
   350  	g, ok := s.gauges[name]
   351  	return g, ok
   352  }
   353  
   354  func (s *scope) Timer(name string) Timer {
   355  	name = s.sanitizer.Name(name)
   356  	if t, ok := s.timer(name); ok {
   357  		return t
   358  	}
   359  
   360  	s.tm.Lock()
   361  	defer s.tm.Unlock()
   362  
   363  	if t, ok := s.timers[name]; ok {
   364  		return t
   365  	}
   366  
   367  	var cachedTimer CachedTimer
   368  	if s.cachedReporter != nil {
   369  		cachedTimer = s.cachedReporter.AllocateTimer(
   370  			s.fullyQualifiedName(name), s.tags,
   371  		)
   372  	}
   373  
   374  	t := newTimer(
   375  		s.fullyQualifiedName(name), s.tags, s.reporter, cachedTimer,
   376  	)
   377  	s.timers[name] = t
   378  
   379  	return t
   380  }
   381  
   382  func (s *scope) timer(sanitizedName string) (Timer, bool) {
   383  	s.tm.RLock()
   384  	defer s.tm.RUnlock()
   385  
   386  	t, ok := s.timers[sanitizedName]
   387  	return t, ok
   388  }
   389  
   390  func (s *scope) Histogram(name string, b Buckets) Histogram {
   391  	name = s.sanitizer.Name(name)
   392  	if h, ok := s.histogram(name); ok {
   393  		return h
   394  	}
   395  
   396  	if b == nil {
   397  		b = s.defaultBuckets
   398  	}
   399  
   400  	htype := valueHistogramType
   401  	if _, ok := b.(DurationBuckets); ok {
   402  		htype = durationHistogramType
   403  	}
   404  
   405  	s.hm.Lock()
   406  	defer s.hm.Unlock()
   407  
   408  	if h, ok := s.histograms[name]; ok {
   409  		return h
   410  	}
   411  
   412  	var cachedHistogram CachedHistogram
   413  	if s.cachedReporter != nil {
   414  		cachedHistogram = s.cachedReporter.AllocateHistogram(
   415  			s.fullyQualifiedName(name), s.tags, b,
   416  		)
   417  	}
   418  
   419  	h := newHistogram(
   420  		htype,
   421  		s.fullyQualifiedName(name),
   422  		s.tags,
   423  		s.reporter,
   424  		s.bucketCache.Get(htype, b),
   425  		cachedHistogram,
   426  	)
   427  	s.histograms[name] = h
   428  	s.histogramsSlice = append(s.histogramsSlice, h)
   429  
   430  	return h
   431  }
   432  
   433  func (s *scope) histogram(sanitizedName string) (Histogram, bool) {
   434  	s.hm.RLock()
   435  	defer s.hm.RUnlock()
   436  
   437  	h, ok := s.histograms[sanitizedName]
   438  	return h, ok
   439  }
   440  
   441  func (s *scope) Tagged(tags map[string]string) Scope {
   442  	return s.subscope(s.prefix, tags)
   443  }
   444  
   445  func (s *scope) SubScope(prefix string) Scope {
   446  	prefix = s.sanitizer.Name(prefix)
   447  	return s.subscope(s.fullyQualifiedName(prefix), nil)
   448  }
   449  
   450  func (s *scope) subscope(prefix string, tags map[string]string) Scope {
   451  	return s.registry.Subscope(s, prefix, tags)
   452  }
   453  
   454  func (s *scope) Capabilities() Capabilities {
   455  	if s.baseReporter == nil {
   456  		return capabilitiesNone
   457  	}
   458  	return s.baseReporter.Capabilities()
   459  }
   460  
   461  func (s *scope) Snapshot() Snapshot {
   462  	snap := newSnapshot()
   463  
   464  	s.registry.ForEachScope(func(ss *scope) {
   465  		// NB(r): tags are immutable, no lock required to read.
   466  		tags := make(map[string]string, len(s.tags))
   467  		for k, v := range ss.tags {
   468  			tags[k] = v
   469  		}
   470  
   471  		ss.cm.RLock()
   472  		for key, c := range ss.counters {
   473  			name := ss.fullyQualifiedName(key)
   474  			id := KeyForPrefixedStringMap(name, tags)
   475  			snap.counters[id] = &counterSnapshot{
   476  				name:  name,
   477  				tags:  tags,
   478  				value: c.snapshot(),
   479  			}
   480  		}
   481  		ss.cm.RUnlock()
   482  		ss.gm.RLock()
   483  		for key, g := range ss.gauges {
   484  			name := ss.fullyQualifiedName(key)
   485  			id := KeyForPrefixedStringMap(name, tags)
   486  			snap.gauges[id] = &gaugeSnapshot{
   487  				name:  name,
   488  				tags:  tags,
   489  				value: g.snapshot(),
   490  			}
   491  		}
   492  		ss.gm.RUnlock()
   493  		ss.tm.RLock()
   494  		for key, t := range ss.timers {
   495  			name := ss.fullyQualifiedName(key)
   496  			id := KeyForPrefixedStringMap(name, tags)
   497  			snap.timers[id] = &timerSnapshot{
   498  				name:   name,
   499  				tags:   tags,
   500  				values: t.snapshot(),
   501  			}
   502  		}
   503  		ss.tm.RUnlock()
   504  		ss.hm.RLock()
   505  		for key, h := range ss.histograms {
   506  			name := ss.fullyQualifiedName(key)
   507  			id := KeyForPrefixedStringMap(name, tags)
   508  			snap.histograms[id] = &histogramSnapshot{
   509  				name:      name,
   510  				tags:      tags,
   511  				values:    h.snapshotValues(),
   512  				durations: h.snapshotDurations(),
   513  			}
   514  		}
   515  		ss.hm.RUnlock()
   516  	})
   517  
   518  	return snap
   519  }
   520  
   521  func (s *scope) Close() error {
   522  	// n.b. Once this flag is set, the next scope report will remove it from
   523  	//      the registry and clear its metrics.
   524  	if !s.closed.CAS(false, true) {
   525  		return nil
   526  	}
   527  
   528  	close(s.done)
   529  
   530  	if s.root {
   531  		s.reportRegistry()
   532  		if closer, ok := s.baseReporter.(io.Closer); ok {
   533  			return closer.Close()
   534  		}
   535  	}
   536  
   537  	return nil
   538  }
   539  
   540  func (s *scope) clearMetrics() {
   541  	s.cm.Lock()
   542  	s.gm.Lock()
   543  	s.tm.Lock()
   544  	s.hm.Lock()
   545  	defer s.cm.Unlock()
   546  	defer s.gm.Unlock()
   547  	defer s.tm.Unlock()
   548  	defer s.hm.Unlock()
   549  
   550  	for k := range s.counters {
   551  		delete(s.counters, k)
   552  	}
   553  	s.countersSlice = nil
   554  
   555  	for k := range s.gauges {
   556  		delete(s.gauges, k)
   557  	}
   558  	s.gaugesSlice = nil
   559  
   560  	for k := range s.timers {
   561  		delete(s.timers, k)
   562  	}
   563  
   564  	for k := range s.histograms {
   565  		delete(s.histograms, k)
   566  	}
   567  	s.histogramsSlice = nil
   568  }
   569  
   570  // NB(prateek): We assume concatenation of sanitized inputs is
   571  // sanitized. If that stops being true, then we need to sanitize the
   572  // output of this function.
   573  func (s *scope) fullyQualifiedName(name string) string {
   574  	if len(s.prefix) == 0 {
   575  		return name
   576  	}
   577  	// NB: we don't need to sanitize the output of this function as we
   578  	// sanitize all the the inputs (prefix, separator, name); and the
   579  	// output we're creating is a concatenation of the sanitized inputs.
   580  	// If we change the concatenation to involve other inputs or characters,
   581  	// we'll need to sanitize them too.
   582  	return s.prefix + s.separator + name
   583  }
   584  
   585  func (s *scope) copyAndSanitizeMap(tags map[string]string) map[string]string {
   586  	result := make(map[string]string, len(tags))
   587  	for k, v := range tags {
   588  		k = s.sanitizer.Key(k)
   589  		v = s.sanitizer.Value(v)
   590  		result[k] = v
   591  	}
   592  	return result
   593  }
   594  
   595  // TestScope is a metrics collector that has no reporting, ensuring that
   596  // all emitted values have a given prefix or set of tags
   597  type TestScope interface {
   598  	Scope
   599  
   600  	// Snapshot returns a copy of all values since the last report execution,
   601  	// this is an expensive operation and should only be use for testing purposes
   602  	Snapshot() Snapshot
   603  }
   604  
   605  // Snapshot is a snapshot of values since last report execution
   606  type Snapshot interface {
   607  	// Counters returns a snapshot of all counter summations since last report execution
   608  	Counters() map[string]CounterSnapshot
   609  
   610  	// Gauges returns a snapshot of gauge last values since last report execution
   611  	Gauges() map[string]GaugeSnapshot
   612  
   613  	// Timers returns a snapshot of timer values since last report execution
   614  	Timers() map[string]TimerSnapshot
   615  
   616  	// Histograms returns a snapshot of histogram samples since last report execution
   617  	Histograms() map[string]HistogramSnapshot
   618  }
   619  
   620  // CounterSnapshot is a snapshot of a counter
   621  type CounterSnapshot interface {
   622  	// Name returns the name
   623  	Name() string
   624  
   625  	// Tags returns the tags
   626  	Tags() map[string]string
   627  
   628  	// Value returns the value
   629  	Value() int64
   630  }
   631  
   632  // GaugeSnapshot is a snapshot of a gauge
   633  type GaugeSnapshot interface {
   634  	// Name returns the name
   635  	Name() string
   636  
   637  	// Tags returns the tags
   638  	Tags() map[string]string
   639  
   640  	// Value returns the value
   641  	Value() float64
   642  }
   643  
   644  // TimerSnapshot is a snapshot of a timer
   645  type TimerSnapshot interface {
   646  	// Name returns the name
   647  	Name() string
   648  
   649  	// Tags returns the tags
   650  	Tags() map[string]string
   651  
   652  	// Values returns the values
   653  	Values() []time.Duration
   654  }
   655  
   656  // HistogramSnapshot is a snapshot of a histogram
   657  type HistogramSnapshot interface {
   658  	// Name returns the name
   659  	Name() string
   660  
   661  	// Tags returns the tags
   662  	Tags() map[string]string
   663  
   664  	// Values returns the sample values by upper bound for a valueHistogram
   665  	Values() map[float64]int64
   666  
   667  	// Durations returns the sample values by upper bound for a durationHistogram
   668  	Durations() map[time.Duration]int64
   669  }
   670  
   671  // mergeRightTags merges 2 sets of tags with the tags from tagsRight overriding values from tagsLeft
   672  func mergeRightTags(tagsLeft, tagsRight map[string]string) map[string]string {
   673  	if tagsLeft == nil && tagsRight == nil {
   674  		return nil
   675  	}
   676  	if len(tagsRight) == 0 {
   677  		return tagsLeft
   678  	}
   679  	if len(tagsLeft) == 0 {
   680  		return tagsRight
   681  	}
   682  
   683  	result := make(map[string]string, len(tagsLeft)+len(tagsRight))
   684  	for k, v := range tagsLeft {
   685  		result[k] = v
   686  	}
   687  	for k, v := range tagsRight {
   688  		result[k] = v
   689  	}
   690  	return result
   691  }
   692  
   693  type snapshot struct {
   694  	counters   map[string]CounterSnapshot
   695  	gauges     map[string]GaugeSnapshot
   696  	timers     map[string]TimerSnapshot
   697  	histograms map[string]HistogramSnapshot
   698  }
   699  
   700  func newSnapshot() *snapshot {
   701  	return &snapshot{
   702  		counters:   make(map[string]CounterSnapshot),
   703  		gauges:     make(map[string]GaugeSnapshot),
   704  		timers:     make(map[string]TimerSnapshot),
   705  		histograms: make(map[string]HistogramSnapshot),
   706  	}
   707  }
   708  
   709  func (s *snapshot) Counters() map[string]CounterSnapshot {
   710  	return s.counters
   711  }
   712  
   713  func (s *snapshot) Gauges() map[string]GaugeSnapshot {
   714  	return s.gauges
   715  }
   716  
   717  func (s *snapshot) Timers() map[string]TimerSnapshot {
   718  	return s.timers
   719  }
   720  
   721  func (s *snapshot) Histograms() map[string]HistogramSnapshot {
   722  	return s.histograms
   723  }
   724  
   725  type counterSnapshot struct {
   726  	name  string
   727  	tags  map[string]string
   728  	value int64
   729  }
   730  
   731  func (s *counterSnapshot) Name() string {
   732  	return s.name
   733  }
   734  
   735  func (s *counterSnapshot) Tags() map[string]string {
   736  	return s.tags
   737  }
   738  
   739  func (s *counterSnapshot) Value() int64 {
   740  	return s.value
   741  }
   742  
   743  type gaugeSnapshot struct {
   744  	name  string
   745  	tags  map[string]string
   746  	value float64
   747  }
   748  
   749  func (s *gaugeSnapshot) Name() string {
   750  	return s.name
   751  }
   752  
   753  func (s *gaugeSnapshot) Tags() map[string]string {
   754  	return s.tags
   755  }
   756  
   757  func (s *gaugeSnapshot) Value() float64 {
   758  	return s.value
   759  }
   760  
   761  type timerSnapshot struct {
   762  	name   string
   763  	tags   map[string]string
   764  	values []time.Duration
   765  }
   766  
   767  func (s *timerSnapshot) Name() string {
   768  	return s.name
   769  }
   770  
   771  func (s *timerSnapshot) Tags() map[string]string {
   772  	return s.tags
   773  }
   774  
   775  func (s *timerSnapshot) Values() []time.Duration {
   776  	return s.values
   777  }
   778  
   779  type histogramSnapshot struct {
   780  	name      string
   781  	tags      map[string]string
   782  	values    map[float64]int64
   783  	durations map[time.Duration]int64
   784  }
   785  
   786  func (s *histogramSnapshot) Name() string {
   787  	return s.name
   788  }
   789  
   790  func (s *histogramSnapshot) Tags() map[string]string {
   791  	return s.tags
   792  }
   793  
   794  func (s *histogramSnapshot) Values() map[float64]int64 {
   795  	return s.values
   796  }
   797  
   798  func (s *histogramSnapshot) Durations() map[time.Duration]int64 {
   799  	return s.durations
   800  }