797 lines
18 KiB
Go
797 lines
18 KiB
Go
// Copyright (c) 2019 Uber Technologies, Inc.
|
|
//
|
|
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
// of this software and associated documentation files (the "Software"), to deal
|
|
// in the Software without restriction, including without limitation the rights
|
|
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
// copies of the Software, and to permit persons to whom the Software is
|
|
// furnished to do so, subject to the following conditions:
|
|
//
|
|
// The above copyright notice and this permission notice shall be included in
|
|
// all copies or substantial portions of the Software.
|
|
//
|
|
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
// THE SOFTWARE.
|
|
|
|
package tally
|
|
|
|
import (
|
|
"fmt"
|
|
"io"
|
|
"sync"
|
|
"time"
|
|
)
|
|
|
|
var (
|
|
// NoopScope is a scope that does nothing
|
|
NoopScope, _ = NewRootScope(ScopeOptions{Reporter: NullStatsReporter}, 0)
|
|
// DefaultSeparator is the default separator used to join nested scopes
|
|
DefaultSeparator = "."
|
|
|
|
globalNow = time.Now
|
|
|
|
defaultScopeBuckets = DurationBuckets{
|
|
0 * time.Millisecond,
|
|
10 * time.Millisecond,
|
|
25 * time.Millisecond,
|
|
50 * time.Millisecond,
|
|
75 * time.Millisecond,
|
|
100 * time.Millisecond,
|
|
200 * time.Millisecond,
|
|
300 * time.Millisecond,
|
|
400 * time.Millisecond,
|
|
500 * time.Millisecond,
|
|
600 * time.Millisecond,
|
|
800 * time.Millisecond,
|
|
1 * time.Second,
|
|
2 * time.Second,
|
|
5 * time.Second,
|
|
}
|
|
)
|
|
|
|
type scope struct {
|
|
separator string
|
|
prefix string
|
|
tags map[string]string
|
|
reporter StatsReporter
|
|
cachedReporter CachedStatsReporter
|
|
baseReporter BaseStatsReporter
|
|
defaultBuckets Buckets
|
|
sanitizer Sanitizer
|
|
|
|
registry *scopeRegistry
|
|
status scopeStatus
|
|
|
|
cm sync.RWMutex
|
|
gm sync.RWMutex
|
|
tm sync.RWMutex
|
|
hm sync.RWMutex
|
|
|
|
counters map[string]*counter
|
|
gauges map[string]*gauge
|
|
timers map[string]*timer
|
|
histograms map[string]*histogram
|
|
}
|
|
|
|
type scopeStatus struct {
|
|
sync.RWMutex
|
|
closed bool
|
|
quit chan struct{}
|
|
}
|
|
|
|
type scopeRegistry struct {
|
|
sync.RWMutex
|
|
subscopes map[string]*scope
|
|
}
|
|
|
|
var scopeRegistryKey = KeyForPrefixedStringMap
|
|
|
|
// ScopeOptions is a set of options to construct a scope.
|
|
type ScopeOptions struct {
|
|
Tags map[string]string
|
|
Prefix string
|
|
Reporter StatsReporter
|
|
CachedReporter CachedStatsReporter
|
|
Separator string
|
|
DefaultBuckets Buckets
|
|
SanitizeOptions *SanitizeOptions
|
|
}
|
|
|
|
// NewRootScope creates a new root Scope with a set of options and
|
|
// a reporting interval.
|
|
// Must provide either a StatsReporter or a CachedStatsReporter.
|
|
func NewRootScope(opts ScopeOptions, interval time.Duration) (Scope, io.Closer) {
|
|
s := newRootScope(opts, interval)
|
|
return s, s
|
|
}
|
|
|
|
// NewTestScope creates a new Scope without a stats reporter with the
|
|
// given prefix and adds the ability to take snapshots of metrics emitted
|
|
// to it.
|
|
func NewTestScope(
|
|
prefix string,
|
|
tags map[string]string,
|
|
) TestScope {
|
|
return newRootScope(ScopeOptions{Prefix: prefix, Tags: tags}, 0)
|
|
}
|
|
|
|
func newRootScope(opts ScopeOptions, interval time.Duration) *scope {
|
|
sanitizer := NewNoOpSanitizer()
|
|
if o := opts.SanitizeOptions; o != nil {
|
|
sanitizer = NewSanitizer(*o)
|
|
}
|
|
|
|
if opts.Tags == nil {
|
|
opts.Tags = make(map[string]string)
|
|
}
|
|
if opts.Separator == "" {
|
|
opts.Separator = DefaultSeparator
|
|
}
|
|
|
|
var baseReporter BaseStatsReporter
|
|
if opts.Reporter != nil {
|
|
baseReporter = opts.Reporter
|
|
} else if opts.CachedReporter != nil {
|
|
baseReporter = opts.CachedReporter
|
|
}
|
|
|
|
if opts.DefaultBuckets == nil || opts.DefaultBuckets.Len() < 1 {
|
|
opts.DefaultBuckets = defaultScopeBuckets
|
|
}
|
|
|
|
s := &scope{
|
|
separator: sanitizer.Name(opts.Separator),
|
|
prefix: sanitizer.Name(opts.Prefix),
|
|
reporter: opts.Reporter,
|
|
cachedReporter: opts.CachedReporter,
|
|
baseReporter: baseReporter,
|
|
defaultBuckets: opts.DefaultBuckets,
|
|
sanitizer: sanitizer,
|
|
|
|
registry: &scopeRegistry{
|
|
subscopes: make(map[string]*scope),
|
|
},
|
|
status: scopeStatus{
|
|
closed: false,
|
|
quit: make(chan struct{}, 1),
|
|
},
|
|
|
|
counters: make(map[string]*counter),
|
|
gauges: make(map[string]*gauge),
|
|
timers: make(map[string]*timer),
|
|
histograms: make(map[string]*histogram),
|
|
}
|
|
|
|
// NB(r): Take a copy of the tags on creation
|
|
// so that it cannot be modified after set.
|
|
s.tags = s.copyAndSanitizeMap(opts.Tags)
|
|
|
|
// Register the root scope
|
|
s.registry.subscopes[scopeRegistryKey(s.prefix, s.tags)] = s
|
|
|
|
if interval > 0 {
|
|
go s.reportLoop(interval)
|
|
}
|
|
|
|
return s
|
|
}
|
|
|
|
// report dumps all aggregated stats into the reporter. Should be called automatically by the root scope periodically.
|
|
func (s *scope) report(r StatsReporter) {
|
|
s.cm.RLock()
|
|
for name, counter := range s.counters {
|
|
counter.report(s.fullyQualifiedName(name), s.tags, r)
|
|
}
|
|
s.cm.RUnlock()
|
|
|
|
s.gm.RLock()
|
|
for name, gauge := range s.gauges {
|
|
gauge.report(s.fullyQualifiedName(name), s.tags, r)
|
|
}
|
|
s.gm.RUnlock()
|
|
|
|
// we do nothing for timers here because timers report directly to ths StatsReporter without buffering
|
|
|
|
s.hm.RLock()
|
|
for name, histogram := range s.histograms {
|
|
histogram.report(s.fullyQualifiedName(name), s.tags, r)
|
|
}
|
|
s.hm.RUnlock()
|
|
}
|
|
|
|
func (s *scope) cachedReport() {
|
|
s.cm.RLock()
|
|
for _, counter := range s.counters {
|
|
counter.cachedReport()
|
|
}
|
|
s.cm.RUnlock()
|
|
|
|
s.gm.RLock()
|
|
for _, gauge := range s.gauges {
|
|
gauge.cachedReport()
|
|
}
|
|
s.gm.RUnlock()
|
|
|
|
// we do nothing for timers here because timers report directly to ths StatsReporter without buffering
|
|
|
|
s.hm.RLock()
|
|
for _, histogram := range s.histograms {
|
|
histogram.cachedReport()
|
|
}
|
|
s.hm.RUnlock()
|
|
}
|
|
|
|
// reportLoop is used by the root scope for periodic reporting
|
|
func (s *scope) reportLoop(interval time.Duration) {
|
|
ticker := time.NewTicker(interval)
|
|
defer ticker.Stop()
|
|
|
|
for {
|
|
select {
|
|
case <-ticker.C:
|
|
s.reportLoopRun()
|
|
case <-s.status.quit:
|
|
return
|
|
}
|
|
}
|
|
}
|
|
|
|
func (s *scope) reportLoopRun() {
|
|
// Need to hold a status lock to ensure not to report
|
|
// and flush after a close
|
|
s.status.RLock()
|
|
defer s.status.RUnlock()
|
|
|
|
if s.status.closed {
|
|
return
|
|
}
|
|
|
|
s.reportRegistryWithLock()
|
|
}
|
|
|
|
// reports current registry with scope status lock held
|
|
func (s *scope) reportRegistryWithLock() {
|
|
s.registry.RLock()
|
|
defer s.registry.RUnlock()
|
|
|
|
if s.reporter != nil {
|
|
for _, ss := range s.registry.subscopes {
|
|
ss.report(s.reporter)
|
|
}
|
|
s.reporter.Flush()
|
|
} else if s.cachedReporter != nil {
|
|
for _, ss := range s.registry.subscopes {
|
|
ss.cachedReport()
|
|
}
|
|
s.cachedReporter.Flush()
|
|
}
|
|
}
|
|
|
|
func (s *scope) Counter(name string) Counter {
|
|
name = s.sanitizer.Name(name)
|
|
if c, ok := s.counter(name); ok {
|
|
return c
|
|
}
|
|
|
|
s.cm.Lock()
|
|
defer s.cm.Unlock()
|
|
|
|
if c, ok := s.counters[name]; ok {
|
|
return c
|
|
}
|
|
|
|
var cachedCounter CachedCount
|
|
if s.cachedReporter != nil {
|
|
cachedCounter = s.cachedReporter.AllocateCounter(
|
|
s.fullyQualifiedName(name),
|
|
s.tags,
|
|
)
|
|
}
|
|
|
|
c := newCounter(cachedCounter)
|
|
s.counters[name] = c
|
|
|
|
return c
|
|
}
|
|
|
|
func (s *scope) counter(sanitizedName string) (Counter, bool) {
|
|
s.cm.RLock()
|
|
defer s.cm.RUnlock()
|
|
|
|
c, ok := s.counters[sanitizedName]
|
|
return c, ok
|
|
}
|
|
|
|
func (s *scope) Gauge(name string) Gauge {
|
|
name = s.sanitizer.Name(name)
|
|
if g, ok := s.gauge(name); ok {
|
|
return g
|
|
}
|
|
|
|
s.gm.Lock()
|
|
defer s.gm.Unlock()
|
|
|
|
if g, ok := s.gauges[name]; ok {
|
|
return g
|
|
}
|
|
|
|
var cachedGauge CachedGauge
|
|
if s.cachedReporter != nil {
|
|
cachedGauge = s.cachedReporter.AllocateGauge(
|
|
s.fullyQualifiedName(name), s.tags,
|
|
)
|
|
}
|
|
|
|
g := newGauge(cachedGauge)
|
|
s.gauges[name] = g
|
|
|
|
return g
|
|
}
|
|
|
|
func (s *scope) gauge(name string) (Gauge, bool) {
|
|
s.gm.RLock()
|
|
defer s.gm.RUnlock()
|
|
|
|
g, ok := s.gauges[name]
|
|
return g, ok
|
|
}
|
|
|
|
func (s *scope) Timer(name string) Timer {
|
|
name = s.sanitizer.Name(name)
|
|
if t, ok := s.timer(name); ok {
|
|
return t
|
|
}
|
|
|
|
s.tm.Lock()
|
|
defer s.tm.Unlock()
|
|
|
|
if t, ok := s.timers[name]; ok {
|
|
return t
|
|
}
|
|
|
|
var cachedTimer CachedTimer
|
|
if s.cachedReporter != nil {
|
|
cachedTimer = s.cachedReporter.AllocateTimer(
|
|
s.fullyQualifiedName(name), s.tags,
|
|
)
|
|
}
|
|
|
|
t := newTimer(
|
|
s.fullyQualifiedName(name), s.tags, s.reporter, cachedTimer,
|
|
)
|
|
s.timers[name] = t
|
|
|
|
return t
|
|
}
|
|
|
|
func (s *scope) timer(sanitizedName string) (Timer, bool) {
|
|
s.tm.RLock()
|
|
defer s.tm.RUnlock()
|
|
|
|
t, ok := s.timers[sanitizedName]
|
|
return t, ok
|
|
}
|
|
|
|
func (s *scope) Histogram(name string, b Buckets) Histogram {
|
|
name = s.sanitizer.Name(name)
|
|
if h, ok := s.histogram(name); ok {
|
|
return h
|
|
}
|
|
|
|
if b == nil {
|
|
b = s.defaultBuckets
|
|
}
|
|
|
|
s.hm.Lock()
|
|
defer s.hm.Unlock()
|
|
|
|
if h, ok := s.histograms[name]; ok {
|
|
return h
|
|
}
|
|
|
|
var cachedHistogram CachedHistogram
|
|
if s.cachedReporter != nil {
|
|
cachedHistogram = s.cachedReporter.AllocateHistogram(
|
|
s.fullyQualifiedName(name), s.tags, b,
|
|
)
|
|
}
|
|
|
|
h := newHistogram(
|
|
s.fullyQualifiedName(name), s.tags, s.reporter, b, cachedHistogram,
|
|
)
|
|
s.histograms[name] = h
|
|
|
|
return h
|
|
}
|
|
|
|
func (s *scope) histogram(sanitizedName string) (Histogram, bool) {
|
|
s.hm.RLock()
|
|
defer s.hm.RUnlock()
|
|
|
|
h, ok := s.histograms[sanitizedName]
|
|
return h, ok
|
|
}
|
|
|
|
func (s *scope) Tagged(tags map[string]string) Scope {
|
|
tags = s.copyAndSanitizeMap(tags)
|
|
return s.subscope(s.prefix, tags)
|
|
}
|
|
|
|
func (s *scope) SubScope(prefix string) Scope {
|
|
prefix = s.sanitizer.Name(prefix)
|
|
return s.subscope(s.fullyQualifiedName(prefix), nil)
|
|
}
|
|
|
|
func (s *scope) cachedSubscope(key string) (*scope, bool) {
|
|
s.registry.RLock()
|
|
defer s.registry.RUnlock()
|
|
|
|
ss, ok := s.registry.subscopes[key]
|
|
return ss, ok
|
|
}
|
|
|
|
func (s *scope) subscope(prefix string, immutableTags map[string]string) Scope {
|
|
immutableTags = mergeRightTags(s.tags, immutableTags)
|
|
key := scopeRegistryKey(prefix, immutableTags)
|
|
|
|
if ss, ok := s.cachedSubscope(key); ok {
|
|
return ss
|
|
}
|
|
|
|
s.registry.Lock()
|
|
defer s.registry.Unlock()
|
|
|
|
if ss, ok := s.registry.subscopes[key]; ok {
|
|
return ss
|
|
}
|
|
|
|
subscope := &scope{
|
|
separator: s.separator,
|
|
prefix: prefix,
|
|
// NB(prateek): don't need to copy the tags here,
|
|
// we assume the map provided is immutable.
|
|
tags: immutableTags,
|
|
reporter: s.reporter,
|
|
cachedReporter: s.cachedReporter,
|
|
baseReporter: s.baseReporter,
|
|
defaultBuckets: s.defaultBuckets,
|
|
sanitizer: s.sanitizer,
|
|
registry: s.registry,
|
|
|
|
counters: make(map[string]*counter),
|
|
gauges: make(map[string]*gauge),
|
|
timers: make(map[string]*timer),
|
|
histograms: make(map[string]*histogram),
|
|
}
|
|
|
|
s.registry.subscopes[key] = subscope
|
|
return subscope
|
|
}
|
|
|
|
func (s *scope) Capabilities() Capabilities {
|
|
if s.baseReporter == nil {
|
|
return capabilitiesNone
|
|
}
|
|
return s.baseReporter.Capabilities()
|
|
}
|
|
|
|
func (s *scope) Snapshot() Snapshot {
|
|
snap := newSnapshot()
|
|
|
|
s.registry.RLock()
|
|
defer s.registry.RUnlock()
|
|
|
|
for _, ss := range s.registry.subscopes {
|
|
// NB(r): tags are immutable, no lock required to read.
|
|
tags := make(map[string]string, len(s.tags))
|
|
for k, v := range ss.tags {
|
|
tags[k] = v
|
|
}
|
|
|
|
ss.cm.RLock()
|
|
for key, c := range ss.counters {
|
|
name := ss.fullyQualifiedName(key)
|
|
id := KeyForPrefixedStringMap(name, tags)
|
|
snap.counters[id] = &counterSnapshot{
|
|
name: name,
|
|
tags: tags,
|
|
value: c.snapshot(),
|
|
}
|
|
}
|
|
ss.cm.RUnlock()
|
|
ss.gm.RLock()
|
|
for key, g := range ss.gauges {
|
|
name := ss.fullyQualifiedName(key)
|
|
id := KeyForPrefixedStringMap(name, tags)
|
|
snap.gauges[id] = &gaugeSnapshot{
|
|
name: name,
|
|
tags: tags,
|
|
value: g.snapshot(),
|
|
}
|
|
}
|
|
ss.gm.RUnlock()
|
|
ss.tm.RLock()
|
|
for key, t := range ss.timers {
|
|
name := ss.fullyQualifiedName(key)
|
|
id := KeyForPrefixedStringMap(name, tags)
|
|
snap.timers[id] = &timerSnapshot{
|
|
name: name,
|
|
tags: tags,
|
|
values: t.snapshot(),
|
|
}
|
|
}
|
|
ss.tm.RUnlock()
|
|
ss.hm.RLock()
|
|
for key, h := range ss.histograms {
|
|
name := ss.fullyQualifiedName(key)
|
|
id := KeyForPrefixedStringMap(name, tags)
|
|
snap.histograms[id] = &histogramSnapshot{
|
|
name: name,
|
|
tags: tags,
|
|
values: h.snapshotValues(),
|
|
durations: h.snapshotDurations(),
|
|
}
|
|
}
|
|
ss.hm.RUnlock()
|
|
}
|
|
|
|
return snap
|
|
}
|
|
|
|
func (s *scope) Close() error {
|
|
s.status.Lock()
|
|
defer s.status.Unlock()
|
|
|
|
// don't wait to close more than once (panic on double close of
|
|
// s.status.quit)
|
|
if s.status.closed {
|
|
return nil
|
|
}
|
|
|
|
s.status.closed = true
|
|
close(s.status.quit)
|
|
s.reportRegistryWithLock()
|
|
|
|
if closer, ok := s.baseReporter.(io.Closer); ok {
|
|
return closer.Close()
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// NB(prateek): We assume concatenation of sanitized inputs is
|
|
// sanitized. If that stops being true, then we need to sanitize the
|
|
// output of this function.
|
|
func (s *scope) fullyQualifiedName(name string) string {
|
|
if len(s.prefix) == 0 {
|
|
return name
|
|
}
|
|
// NB: we don't need to sanitize the output of this function as we
|
|
// sanitize all the the inputs (prefix, separator, name); and the
|
|
// output we're creating is a concatenation of the sanitized inputs.
|
|
// If we change the concatenation to involve other inputs or characters,
|
|
// we'll need to sanitize them too.
|
|
return fmt.Sprintf("%s%s%s", s.prefix, s.separator, name)
|
|
}
|
|
|
|
func (s *scope) copyAndSanitizeMap(tags map[string]string) map[string]string {
|
|
result := make(map[string]string, len(tags))
|
|
for k, v := range tags {
|
|
k = s.sanitizer.Key(k)
|
|
v = s.sanitizer.Value(v)
|
|
result[k] = v
|
|
}
|
|
return result
|
|
}
|
|
|
|
// TestScope is a metrics collector that has no reporting, ensuring that
|
|
// all emitted values have a given prefix or set of tags
|
|
type TestScope interface {
|
|
Scope
|
|
|
|
// Snapshot returns a copy of all values since the last report execution,
|
|
// this is an expensive operation and should only be use for testing purposes
|
|
Snapshot() Snapshot
|
|
}
|
|
|
|
// Snapshot is a snapshot of values since last report execution
|
|
type Snapshot interface {
|
|
// Counters returns a snapshot of all counter summations since last report execution
|
|
Counters() map[string]CounterSnapshot
|
|
|
|
// Gauges returns a snapshot of gauge last values since last report execution
|
|
Gauges() map[string]GaugeSnapshot
|
|
|
|
// Timers returns a snapshot of timer values since last report execution
|
|
Timers() map[string]TimerSnapshot
|
|
|
|
// Histograms returns a snapshot of histogram samples since last report execution
|
|
Histograms() map[string]HistogramSnapshot
|
|
}
|
|
|
|
// CounterSnapshot is a snapshot of a counter
|
|
type CounterSnapshot interface {
|
|
// Name returns the name
|
|
Name() string
|
|
|
|
// Tags returns the tags
|
|
Tags() map[string]string
|
|
|
|
// Value returns the value
|
|
Value() int64
|
|
}
|
|
|
|
// GaugeSnapshot is a snapshot of a gauge
|
|
type GaugeSnapshot interface {
|
|
// Name returns the name
|
|
Name() string
|
|
|
|
// Tags returns the tags
|
|
Tags() map[string]string
|
|
|
|
// Value returns the value
|
|
Value() float64
|
|
}
|
|
|
|
// TimerSnapshot is a snapshot of a timer
|
|
type TimerSnapshot interface {
|
|
// Name returns the name
|
|
Name() string
|
|
|
|
// Tags returns the tags
|
|
Tags() map[string]string
|
|
|
|
// Values returns the values
|
|
Values() []time.Duration
|
|
}
|
|
|
|
// HistogramSnapshot is a snapshot of a histogram
|
|
type HistogramSnapshot interface {
|
|
// Name returns the name
|
|
Name() string
|
|
|
|
// Tags returns the tags
|
|
Tags() map[string]string
|
|
|
|
// Values returns the sample values by upper bound for a valueHistogram
|
|
Values() map[float64]int64
|
|
|
|
// Durations returns the sample values by upper bound for a durationHistogram
|
|
Durations() map[time.Duration]int64
|
|
}
|
|
|
|
// mergeRightTags merges 2 sets of tags with the tags from tagsRight overriding values from tagsLeft
|
|
func mergeRightTags(tagsLeft, tagsRight map[string]string) map[string]string {
|
|
if tagsLeft == nil && tagsRight == nil {
|
|
return nil
|
|
}
|
|
if len(tagsRight) == 0 {
|
|
return tagsLeft
|
|
}
|
|
if len(tagsLeft) == 0 {
|
|
return tagsRight
|
|
}
|
|
|
|
result := make(map[string]string, len(tagsLeft)+len(tagsRight))
|
|
for k, v := range tagsLeft {
|
|
result[k] = v
|
|
}
|
|
for k, v := range tagsRight {
|
|
result[k] = v
|
|
}
|
|
return result
|
|
}
|
|
|
|
type snapshot struct {
|
|
counters map[string]CounterSnapshot
|
|
gauges map[string]GaugeSnapshot
|
|
timers map[string]TimerSnapshot
|
|
histograms map[string]HistogramSnapshot
|
|
}
|
|
|
|
func newSnapshot() *snapshot {
|
|
return &snapshot{
|
|
counters: make(map[string]CounterSnapshot),
|
|
gauges: make(map[string]GaugeSnapshot),
|
|
timers: make(map[string]TimerSnapshot),
|
|
histograms: make(map[string]HistogramSnapshot),
|
|
}
|
|
}
|
|
|
|
func (s *snapshot) Counters() map[string]CounterSnapshot {
|
|
return s.counters
|
|
}
|
|
|
|
func (s *snapshot) Gauges() map[string]GaugeSnapshot {
|
|
return s.gauges
|
|
}
|
|
|
|
func (s *snapshot) Timers() map[string]TimerSnapshot {
|
|
return s.timers
|
|
}
|
|
|
|
func (s *snapshot) Histograms() map[string]HistogramSnapshot {
|
|
return s.histograms
|
|
}
|
|
|
|
type counterSnapshot struct {
|
|
name string
|
|
tags map[string]string
|
|
value int64
|
|
}
|
|
|
|
func (s *counterSnapshot) Name() string {
|
|
return s.name
|
|
}
|
|
|
|
func (s *counterSnapshot) Tags() map[string]string {
|
|
return s.tags
|
|
}
|
|
|
|
func (s *counterSnapshot) Value() int64 {
|
|
return s.value
|
|
}
|
|
|
|
type gaugeSnapshot struct {
|
|
name string
|
|
tags map[string]string
|
|
value float64
|
|
}
|
|
|
|
func (s *gaugeSnapshot) Name() string {
|
|
return s.name
|
|
}
|
|
|
|
func (s *gaugeSnapshot) Tags() map[string]string {
|
|
return s.tags
|
|
}
|
|
|
|
func (s *gaugeSnapshot) Value() float64 {
|
|
return s.value
|
|
}
|
|
|
|
type timerSnapshot struct {
|
|
name string
|
|
tags map[string]string
|
|
values []time.Duration
|
|
}
|
|
|
|
func (s *timerSnapshot) Name() string {
|
|
return s.name
|
|
}
|
|
|
|
func (s *timerSnapshot) Tags() map[string]string {
|
|
return s.tags
|
|
}
|
|
|
|
func (s *timerSnapshot) Values() []time.Duration {
|
|
return s.values
|
|
}
|
|
|
|
type histogramSnapshot struct {
|
|
name string
|
|
tags map[string]string
|
|
values map[float64]int64
|
|
durations map[time.Duration]int64
|
|
}
|
|
|
|
func (s *histogramSnapshot) Name() string {
|
|
return s.name
|
|
}
|
|
|
|
func (s *histogramSnapshot) Tags() map[string]string {
|
|
return s.tags
|
|
}
|
|
|
|
func (s *histogramSnapshot) Values() map[float64]int64 {
|
|
return s.values
|
|
}
|
|
|
|
func (s *histogramSnapshot) Durations() map[time.Duration]int64 {
|
|
return s.durations
|
|
}
|