diff --git a/scope.go b/scope.go index 1716bf4f..ed648d29 100644 --- a/scope.go +++ b/scope.go @@ -459,7 +459,18 @@ func (s *scope) Snapshot() Snapshot { } } ss.tm.RUnlock() - // TODO: histograms in snapshot + 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() } s.registry.RUnlock() @@ -505,6 +516,9 @@ type Snapshot interface { // 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 @@ -543,6 +557,21 @@ type TimerSnapshot interface { 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 { @@ -574,16 +603,18 @@ func copyStringMap(stringMap map[string]string) map[string]string { } type snapshot struct { - counters map[string]CounterSnapshot - gauges map[string]GaugeSnapshot - timers map[string]TimerSnapshot + 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), + counters: make(map[string]CounterSnapshot), + gauges: make(map[string]GaugeSnapshot), + timers: make(map[string]TimerSnapshot), + histograms: make(map[string]HistogramSnapshot), } } @@ -599,6 +630,10 @@ 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 @@ -652,3 +687,26 @@ func (s *timerSnapshot) Tags() map[string]string { 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 +} diff --git a/scope_test.go b/scope_test.go index 6dba12e6..242737e8 100644 --- a/scope_test.go +++ b/scope_test.go @@ -21,6 +21,7 @@ package tally import ( + "math" "sync" "sync/atomic" "testing" @@ -560,11 +561,14 @@ func TestSnapshot(t *testing.T) { s.Gauge("bzzt").Update(2) s.Timer("brrr").Record(1 * time.Second) s.Timer("brrr").Record(2 * time.Second) + s.Histogram("fizz", ValueBuckets{0, 2, 4}).RecordValue(1) + s.Histogram("fizz", ValueBuckets{0, 2, 4}).RecordValue(5) + s.Histogram("buzz", DurationBuckets{time.Second * 2, time.Second * 4}).RecordDuration(time.Second) child.Counter("boop").Inc(1) snap := s.Snapshot() - counters, gauges, timers := - snap.Counters(), snap.Gauges(), snap.Timers() + counters, gauges, timers, histograms := + snap.Counters(), snap.Gauges(), snap.Timers(), snap.Histograms() assert.EqualValues(t, 1, counters["foo.beep+env=test"].Value()) assert.EqualValues(t, commonTags, counters["foo.beep+env=test"].Tags()) @@ -578,6 +582,24 @@ func TestSnapshot(t *testing.T) { }, timers["foo.brrr+env=test"].Values()) assert.EqualValues(t, commonTags, timers["foo.brrr+env=test"].Tags()) + assert.EqualValues(t, map[float64]int64{ + 0: 0, + 2: 1, + 4: 0, + math.MaxFloat64: 1, + }, histograms["foo.fizz+env=test"].Values()) + assert.EqualValues(t, map[time.Duration]int64(nil), histograms["foo.fizz+env=test"].Durations()) + assert.EqualValues(t, commonTags, histograms["foo.fizz+env=test"].Tags()) + + assert.EqualValues(t, map[float64]int64(nil), histograms["foo.buzz+env=test"].Values()) + assert.EqualValues(t, map[time.Duration]int64{ + 0: 0, + time.Second * 2: 1, + time.Second * 4: 0, + math.MaxInt64: 0, + }, histograms["foo.buzz+env=test"].Durations()) + assert.EqualValues(t, commonTags, histograms["foo.buzz+env=test"].Tags()) + assert.EqualValues(t, 1, counters["foo.boop+env=test,service=test"].Value()) assert.EqualValues(t, map[string]string{ "env": "test", diff --git a/stats.go b/stats.go index a32b3af5..d30dec0f 100644 --- a/stats.go +++ b/stats.go @@ -375,6 +375,32 @@ func (h *histogram) RecordStopwatch(stopwatchStart time.Time) { h.RecordDuration(d) } +func (h *histogram) snapshotValues() map[float64]int64 { + if h.htype == durationHistogramType { + return nil + } + + vals := make(map[float64]int64, len(h.buckets)) + for i := range h.buckets { + vals[h.buckets[i].valueUpperBound] = h.buckets[i].samples.value() + } + + return vals +} + +func (h *histogram) snapshotDurations() map[time.Duration]int64 { + if h.htype == valueHistogramType { + return nil + } + + durations := make(map[time.Duration]int64, len(h.buckets)) + for i := range h.buckets { + durations[h.buckets[i].durationUpperBound] = h.buckets[i].samples.value() + } + + return durations +} + type histogramBucket struct { h *histogram samples *counter