Browse Source

Fully introduce snapshots for all metric types.

Richard Crowley 12 năm trước cách đây
mục cha
commit
10aaf9c455
12 tập tin đã thay đổi với 554 bổ sung130 xóa
  1. 35 3
      counter.go
  2. 10 0
      counter_test.go
  3. 32 3
      ewma.go
  4. 26 3
      gauge.go
  5. 10 0
      gauge_test.go
  6. 76 8
      histogram.go
  7. 39 25
      histogram_test.go
  8. 61 26
      meter.go
  9. 9 1
      meter_test.go
  10. 85 2
      sample.go
  11. 69 53
      sample_test.go
  12. 102 6
      timer.go

+ 35 - 3
counter.go

@@ -38,7 +38,31 @@ func NewRegisteredCounter(name string, r Registry) Counter {
 	return c
 }
 
-// No-op Counter.
+// CounterSnapshot is a read-only copy of another Counter.
+type CounterSnapshot int64
+
+// Clear panics.
+func (CounterSnapshot) Clear() {
+	panic("Clear called on a CounterSnapshot")
+}
+
+// Count returns the count at the time the snapshot was taken.
+func (c CounterSnapshot) Count() int64 { return int64(c) }
+
+// Dec panics.
+func (CounterSnapshot) Dec(int64) {
+	panic("Dec called on a CounterSnapshot")
+}
+
+// Inc panics.
+func (CounterSnapshot) Inc(int64) {
+	panic("Inc called on a CounterSnapshot")
+}
+
+// Snapshot returns the snapshot.
+func (c CounterSnapshot) Snapshot() Counter { return c }
+
+// NilCounter is a no-op Counter.
 type NilCounter struct{}
 
 // Clear is a no-op.
@@ -53,8 +77,11 @@ func (NilCounter) Dec(i int64) {}
 // Inc is a no-op.
 func (NilCounter) Inc(i int64) {}
 
-// The standard implementation of a Counter uses the sync/atomic package
-// to manage a single int64 value.
+// Snapshot is a no-op.
+func (NilCounter) Snapshot() Counter { return NilCounter{} }
+
+// StandardCounter is the standard implementation of a Counter and uses the
+// sync/atomic package to manage a single int64 value.
 type StandardCounter struct {
 	count int64
 }
@@ -78,3 +105,8 @@ func (c *StandardCounter) Dec(i int64) {
 func (c *StandardCounter) Inc(i int64) {
 	atomic.AddInt64(&c.count, i)
 }
+
+// Snapshot returns a read-only copy of the counter.
+func (c *StandardCounter) Snapshot() Counter {
+	return CounterSnapshot(c.Count())
+}

+ 10 - 0
counter_test.go

@@ -51,6 +51,16 @@ func TestCounterInc2(t *testing.T) {
 	}
 }
 
+func TestCounterSnapshot(t *testing.T) {
+	c := NewCounter()
+	c.Inc(1)
+	snapshot := c.Snapshot()
+	c.Inc(1)
+	if count := snapshot.Count(); 1 != count {
+		t.Errorf("c.Count(): 1 != %v\n", count)
+	}
+}
+
 func TestCounterZero(t *testing.T) {
 	c := NewCounter()
 	if count := c.Count(); 0 != count {

+ 32 - 3
ewma.go

@@ -38,13 +38,36 @@ func NewEWMA15() EWMA {
 	return NewEWMA(1 - math.Exp(-5.0/60.0/15))
 }
 
-// No-op EWMA.
+// EWMASnapshot is a read-only copy of another EWMA.
+type EWMASnapshot float64
+
+// Rate returns the rate of events per second at the time the snapshot was
+// taken.
+func (a EWMASnapshot) Rate() float64 { return float64(a) }
+
+// Snapshot returns the snapshot.
+func (a EWMASnapshot) Snapshot() EWMA { return a }
+
+// Tick panics.
+func (EWMASnapshot) Tick() {
+	panic("Tick called on an EWMASnapshot")
+}
+
+// Update panics.
+func (EWMASnapshot) Update(int64) {
+	panic("Update called on an EWMASnapshot")
+}
+
+// NilEWMA is a no-op EWMA.
 type NilEWMA struct{}
 
 // Rate is a no-op.
 func (NilEWMA) Rate() float64 { return 0.0 }
 
-// No-op.
+// Snapshot is a no-op.
+func (NilEWMA) Snapshot() EWMA { return NilEWMA{} }
+
+// Tick is a no-op.
 func (NilEWMA) Tick() {}
 
 // Update is a no-op.
@@ -68,7 +91,13 @@ func (a *StandardEWMA) Rate() float64 {
 	return a.rate * float64(1e9)
 }
 
-// Tick the clock to update the moving average.
+// Snapshot returns a read-only copy of the EWMA.
+func (a *StandardEWMA) Snapshot() EWMA {
+	return EWMASnapshot(a.Rate())
+}
+
+// Tick ticks the clock to update the moving average.  It assumes it is called
+// every five seconds.
 func (a *StandardEWMA) Tick() {
 	count := atomic.LoadInt64(&a.uncounted)
 	atomic.AddInt64(&a.uncounted, -count)

+ 26 - 3
gauge.go

@@ -4,6 +4,7 @@ import "sync/atomic"
 
 // Gauges hold an int64 value that can be set arbitrarily.
 type Gauge interface {
+	Snapshot() Gauge
 	Update(int64)
 	Value() int64
 }
@@ -35,10 +36,27 @@ func NewRegisteredGauge(name string, r Registry) Gauge {
 	return c
 }
 
-// No-op Gauge.
+// GaugeSnapshot is a read-only copy of another Gauge.
+type GaugeSnapshot int64
+
+// Snapshot returns the snapshot.
+func (g GaugeSnapshot) Snapshot() Gauge { return g }
+
+// Update panics.
+func (GaugeSnapshot) Update(int64) {
+	panic("Update called on a GaugeSnapshot")
+}
+
+// Value returns the value at the time the snapshot was taken.
+func (g GaugeSnapshot) Value() int64 { return int64(g) }
+
+// NilGauge is a no-op Gauge.
 type NilGauge struct{}
 
-// No-op.
+// Snapshot is a no-op.
+func (NilGauge) Snapshot() Gauge { return NilGauge{} }
+
+// Update is a no-op.
 func (NilGauge) Update(v int64) {}
 
 // Value is a no-op.
@@ -50,7 +68,12 @@ type StandardGauge struct {
 	value int64
 }
 
-// Update the gauge's value.
+// Snapshot returns a read-only copy of the gauge.
+func (g *StandardGauge) Snapshot() Gauge {
+	return GaugeSnapshot(g.Value())
+}
+
+// Update updates the gauge's value.
 func (g *StandardGauge) Update(v int64) {
 	atomic.StoreInt64(&g.value, v)
 }

+ 10 - 0
gauge_test.go

@@ -18,6 +18,16 @@ func TestGauge(t *testing.T) {
 	}
 }
 
+func TestGaugeSnapshot(t *testing.T) {
+	g := NewGauge()
+	g.Update(int64(47))
+	snapshot := g.Snapshot()
+	g.Update(int64(0))
+	if v := snapshot.Value(); 47 != v {
+		t.Errorf("g.Value(): 47 != %v\n", v)
+	}
+}
+
 func TestGetOrRegisterGauge(t *testing.T) {
 	r := NewRegistry()
 	NewRegisteredGauge("foo", r).Update(47)

+ 76 - 8
histogram.go

@@ -55,7 +55,63 @@ func NewRegisteredHistogram(name string, r Registry, s Sample) Histogram {
 	return c
 }
 
-// No-op Histogram.
+// HistogramSnapshot is a read-only copy of another Histogram.
+type HistogramSnapshot struct {
+	count, max, min, sum int64
+	sample               *SampleSnapshot
+	variance             float64
+}
+
+// Clear panics.
+func (*HistogramSnapshot) Clear() {
+	panic("Clear called on a HistogramSnapshot")
+}
+
+// Count returns the count of inputs at the time the snapshot was taken.
+func (h *HistogramSnapshot) Count() int64 { return h.count }
+
+// Max returns the maximum value at the time the snapshot was taken.
+func (h *HistogramSnapshot) Max() int64 { return h.max }
+
+// Mean returns the mean value at the time the snapshot was taken.
+func (h *HistogramSnapshot) Mean() float64 {
+	return float64(h.sum) / float64(h.count)
+}
+
+// Min returns the minimum value at the time the snapshot was taken.
+func (h *HistogramSnapshot) Min() int64 { return h.min }
+
+// Percentile returns an arbitrary percentile of sampled values at the time the
+// snapshot was taken.
+func (h *HistogramSnapshot) Percentile(p float64) float64 {
+	return h.sample.Percentile(p)
+}
+
+// Percentiles returns a slice of arbitrary percentiles of sampled values at
+// the time the snapshot was taken.
+func (h *HistogramSnapshot) Percentiles(ps []float64) []float64 {
+	return h.sample.Percentiles(ps)
+}
+
+// Sample returns the underlying Sample.
+func (h *HistogramSnapshot) Sample() Sample { return h.sample }
+
+// Snapshot returns the snapshot.
+func (h *HistogramSnapshot) Snapshot() Histogram { return h }
+
+// StdDev returns the standard deviation of inputs at the time the snapshot was
+// taken.
+func (h *HistogramSnapshot) StdDev() float64 { return math.Sqrt(h.variance) }
+
+// Update panics.
+func (*HistogramSnapshot) Update(int64) {
+	panic("Update called on a HistogramSnapshot")
+}
+
+// Variance returns the variance of inputs at the time the snapshot was taken.
+func (h *HistogramSnapshot) Variance() float64 { return h.variance }
+
+// NilHistogram is a no-op Histogram.
 type NilHistogram struct{}
 
 // Clear is a no-op.
@@ -84,8 +140,8 @@ func (NilHistogram) Percentiles(ps []float64) []float64 {
 // Sample is a no-op.
 func (NilHistogram) Sample() Sample { return NilSample{} }
 
-// No-op.
-func (NilHistogram) StdDev() float64 { return 0.0 }
+// Snapshot is a no-op.
+func (NilHistogram) Snapshot() Histogram { return NilHistogram{} }
 
 // StdDev is a no-op.
 func (NilHistogram) StdDev() float64 { return 0.0 }
@@ -157,18 +213,30 @@ func (h *StandardHistogram) Min() int64 {
 
 // Percentile returns an arbitrary percentile of the values in the sample.
 func (h *StandardHistogram) Percentile(p float64) float64 {
-	return h.s.Percentile(p)
+	return h.sample.Percentile(p)
 }
 
 // Percentiles returns a slice of arbitrary percentiles of the values in the
 // sample.
 func (h *StandardHistogram) Percentiles(ps []float64) []float64 {
-	return h.s.Percentiles(ps)
+	return h.sample.Percentiles(ps)
 }
 
-// Sample returns a copy of the Sample underlying the Histogram.
-func (h *StandardHistogram) Sample() Sample {
-	return h.s.Dup()
+// Sample returns a read-only copy of the underlying Sample.
+func (h *StandardHistogram) Sample() Sample { return h.sample.Snapshot() }
+
+// Snapshot returns a read-only copy of the histogram.
+func (h *StandardHistogram) Snapshot() Histogram {
+	h.mutex.Lock()
+	defer h.mutex.Unlock()
+	return &HistogramSnapshot{
+		count:    h.count,
+		max:      h.max,
+		min:      h.min,
+		sample:   h.sample.Snapshot().(*SampleSnapshot),
+		sum:      h.sum,
+		variance: h.variance(),
+	}
 }
 
 // StdDev returns the standard deviation of all values seen since the histogram

+ 39 - 25
histogram_test.go

@@ -24,31 +24,7 @@ func TestHistogram10000(t *testing.T) {
 	for i := 1; i <= 10000; i++ {
 		h.Update(int64(i))
 	}
-	if count := h.Count(); 10000 != count {
-		t.Errorf("h.Count(): 10000 != %v\n", count)
-	}
-	if min := h.Min(); 1 != min {
-		t.Errorf("h.Min(): 1 != %v\n", min)
-	}
-	if max := h.Max(); 10000 != max {
-		t.Errorf("h.Max(): 10000 != %v\n", max)
-	}
-	if mean := h.Mean(); 5000.5 != mean {
-		t.Errorf("h.Mean(): 5000.5 != %v\n", mean)
-	}
-	if stdDev := h.StdDev(); 2886.8956799071675 != stdDev {
-		t.Errorf("h.StdDev(): 2886.8956799071675 != %v\n", stdDev)
-	}
-	ps := h.Percentiles([]float64{0.5, 0.75, 0.99})
-	if 5000.5 != ps[0] {
-		t.Errorf("median: 5000.5 != %v\n", ps[0])
-	}
-	if 7500.75 != ps[1] {
-		t.Errorf("75th percentile: 7500.75 != %v\n", ps[1])
-	}
-	if 9900.99 != ps[2] {
-		t.Errorf("99th percentile: 9900.99 != %v\n", ps[2])
-	}
+	testHistogram10000(t, h)
 }
 
 func TestHistogramEmpty(t *testing.T) {
@@ -79,3 +55,41 @@ func TestHistogramEmpty(t *testing.T) {
 		t.Errorf("99th percentile: 0.0 != %v\n", ps[2])
 	}
 }
+
+func TestHistogramSnapshot(t *testing.T) {
+	h := NewHistogram(NewUniformSample(100000))
+	for i := 1; i <= 10000; i++ {
+		h.Update(int64(i))
+	}
+	snapshot := h.Snapshot()
+	h.Update(0)
+	testHistogram10000(t, snapshot)
+}
+
+func testHistogram10000(t *testing.T, h Histogram) {
+	if count := h.Count(); 10000 != count {
+		t.Errorf("h.Count(): 10000 != %v\n", count)
+	}
+	if min := h.Min(); 1 != min {
+		t.Errorf("h.Min(): 1 != %v\n", min)
+	}
+	if max := h.Max(); 10000 != max {
+		t.Errorf("h.Max(): 10000 != %v\n", max)
+	}
+	if mean := h.Mean(); 5000.5 != mean {
+		t.Errorf("h.Mean(): 5000.5 != %v\n", mean)
+	}
+	if stdDev := h.StdDev(); 2886.8956799071675 != stdDev {
+		t.Errorf("h.StdDev(): 2886.8956799071675 != %v\n", stdDev)
+	}
+	ps := h.Percentiles([]float64{0.5, 0.75, 0.99})
+	if 5000.5 != ps[0] {
+		t.Errorf("median: 5000.5 != %v\n", ps[0])
+	}
+	if 7500.75 != ps[1] {
+		t.Errorf("75th percentile: 7500.75 != %v\n", ps[1])
+	}
+	if 9900.99 != ps[2] {
+		t.Errorf("99th percentile: 9900.99 != %v\n", ps[2])
+	}
+}

+ 61 - 26
meter.go

@@ -11,6 +11,7 @@ type Meter interface {
 	Rate5() float64
 	Rate15() float64
 	RateMean() float64
+	Snapshot() Meter
 }
 
 // GetOrRegisterMeter returns an existing Meter or constructs and registers a
@@ -47,7 +48,40 @@ func NewRegisteredMeter(name string, r Registry) Meter {
 	return c
 }
 
-// No-op Meter.
+// MeterSnapshot is a read-only copy of another Meter.
+type MeterSnapshot struct {
+	count                          int64
+	rate1, rate5, rate15, rateMean float64
+}
+
+// Count returns the count of events at the time the snapshot was taken.
+func (m *MeterSnapshot) Count() int64 { return m.count }
+
+// Mark panics.
+func (*MeterSnapshot) Mark(n int64) {
+	panic("Mark called on a MeterSnapshot")
+}
+
+// Rate1 returns the one-minute moving average rate of events per second at the
+// time the snapshot was taken.
+func (m *MeterSnapshot) Rate1() float64 { return m.rate1 }
+
+// Rate5 returns the five-minute moving average rate of events per second at
+// the time the snapshot was taken.
+func (m *MeterSnapshot) Rate5() float64 { return m.rate5 }
+
+// Rate15 returns the fifteen-minute moving average rate of events per second
+// at the time the snapshot was taken.
+func (m *MeterSnapshot) Rate15() float64 { return m.rate15 }
+
+// RateMean returns the meter's mean rate of events per second at the time the
+// snapshot was taken.
+func (m *MeterSnapshot) RateMean() float64 { return m.rateMean }
+
+// Snapshot returns the snapshot.
+func (m *MeterSnapshot) Snapshot() Meter { return m }
+
+// NilMeter is a no-op Meter.
 type NilMeter struct{}
 
 // Count is a no-op.
@@ -68,12 +102,14 @@ func (NilMeter) Rate15() float64 { return 0.0 }
 // RateMean is a no-op.
 func (NilMeter) RateMean() float64 { return 0.0 }
 
-// The standard implementation of a Meter uses a goroutine to synchronize
-// its calculations and another goroutine (via time.Ticker) to produce
-// clock ticks.
+// Snapshot is a no-op.
+func (NilMeter) Snapshot() Meter { return NilMeter{} }
+
+// StandardMeter is the standard implementation of a Meter and uses a
+// goroutine to synchronize its calculations and a time.Ticker to pass time.
 type StandardMeter struct {
 	in     chan int64
-	out    chan meterV
+	out    chan *MeterSnapshot
 	ticker *time.Ticker
 }
 
@@ -107,11 +143,17 @@ func (m *StandardMeter) RateMean() float64 {
 	return (<-m.out).rateMean
 }
 
-// Receive inputs and send outputs.  Count each input and update the various
-// moving averages and the mean rate of events.  Send a copy of the meterV
-// as output.
+// Snapshot returns a read-only copy of the meter.
+func (m *StandardMeter) Snapshot() Meter {
+	snapshot := *<-m.out
+	return &snapshot
+}
+
+// arbiter receives inputs and sends outputs.  It counts each input and updates
+// the various moving averages and the mean rate of events.  It sends a copy of
+// the meterV as output.
 func (m *StandardMeter) arbiter() {
-	var mv meterV
+	snapshot := &MeterSnapshot{}
 	a1 := NewEWMA1()
 	a5 := NewEWMA5()
 	a15 := NewEWMA15()
@@ -119,30 +161,23 @@ func (m *StandardMeter) arbiter() {
 	for {
 		select {
 		case n := <-m.in:
-			mv.count += n
+			snapshot.count += n
 			a1.Update(n)
 			a5.Update(n)
 			a15.Update(n)
-			mv.rate1 = a1.Rate()
-			mv.rate5 = a5.Rate()
-			mv.rate15 = a15.Rate()
-			mv.rateMean = float64(1e9*mv.count) / float64(time.Since(t))
-		case m.out <- mv:
+			snapshot.rate1 = a1.Rate()
+			snapshot.rate5 = a5.Rate()
+			snapshot.rate15 = a15.Rate()
+			snapshot.rateMean = float64(1e9*snapshot.count) / float64(time.Since(t))
+		case m.out <- snapshot:
 		case <-m.ticker.C:
 			a1.Tick()
 			a5.Tick()
 			a15.Tick()
-			mv.rate1 = a1.Rate()
-			mv.rate5 = a5.Rate()
-			mv.rate15 = a15.Rate()
-			mv.rateMean = float64(1e9*mv.count) / float64(time.Since(t))
+			snapshot.rate1 = a1.Rate()
+			snapshot.rate5 = a5.Rate()
+			snapshot.rate15 = a15.Rate()
+			snapshot.rateMean = float64(1e9*snapshot.count) / float64(time.Since(t))
 		}
 	}
 }
-
-// A meterV contains all the values that would need to be passed back
-// from the synchronizing goroutine.
-type meterV struct {
-	count                          int64
-	rate1, rate5, rate15, rateMean float64
-}

+ 9 - 1
meter_test.go

@@ -24,7 +24,7 @@ func TestGetOrRegisterMeter(t *testing.T) {
 func TestMeterDecay(t *testing.T) {
 	m := &StandardMeter{
 		make(chan int64),
-		make(chan meterV),
+		make(chan *MeterSnapshot),
 		time.NewTicker(1),
 	}
 	go m.arbiter()
@@ -44,6 +44,14 @@ func TestMeterNonzero(t *testing.T) {
 	}
 }
 
+func TestMeterSnapshot(t *testing.T) {
+	m := NewMeter()
+	m.Mark(1)
+	if snapshot := m.Snapshot(); m.RateMean() != snapshot.RateMean() {
+		t.Fatal(snapshot)
+	}
+}
+
 func TestMeterZero(t *testing.T) {
 	m := NewMeter()
 	if count := m.Count(); 0 != count {

+ 85 - 2
sample.go

@@ -23,6 +23,7 @@ type Sample interface {
 	Percentile(float64) float64
 	Percentiles([]float64) []float64
 	Size() int
+	Snapshot() Sample
 	StdDev() float64
 	Sum() int64
 	Update(int64)
@@ -111,7 +112,21 @@ func (s *ExpDecaySample) Size() int {
 	return len(s.values)
 }
 
-// StdDev returns the standard deviation of the sample.
+// Snapshot returns a read-only copy of the sample.
+func (s *ExpDecaySample) Snapshot() Sample {
+	s.mutex.Lock()
+	defer s.mutex.Unlock()
+	values := make([]int64, len(s.values))
+	for i, v := range s.values {
+		values[i] = v.v
+	}
+	return &SampleSnapshot{
+		count:  s.count,
+		values: values,
+	}
+}
+
+// StdDev returns the standard deviation of the values in the sample.
 func (s *ExpDecaySample) StdDev() float64 {
 	return SampleStdDev(s.Values())
 }
@@ -197,7 +212,10 @@ func (NilSample) Percentiles(ps []float64) []float64 {
 // Size is a no-op.
 func (NilSample) Size() int { return 0 }
 
-// No-op.
+// Sample is a no-op.
+func (NilSample) Snapshot() Sample { return NilSample{} }
+
+// StdDev is a no-op.
 func (NilSample) StdDev() float64 { return 0.0 }
 
 // Sum is a no-op.
@@ -276,6 +294,71 @@ func SamplePercentiles(values int64Slice, ps []float64) []float64 {
 	return scores
 }
 
+// SampleSnapshot is a read-only copy of another Sample.
+type SampleSnapshot struct {
+	count  int64
+	values []int64
+}
+
+// Clear panics.
+func (*SampleSnapshot) Clear() {
+	panic("Clear called on a SampleSnapshot")
+}
+
+// Count returns the count of inputs at the time the snapshot was taken.
+func (s *SampleSnapshot) Count() int64 { return s.count }
+
+// Max returns the maximal value at the time the snapshot was taken.
+func (s *SampleSnapshot) Max() int64 { return SampleMax(s.values) }
+
+// Mean returns the mean value at the time the snapshot was taken.
+func (s *SampleSnapshot) Mean() float64 { return SampleMean(s.values) }
+
+// Min returns the minimal value at the time the snapshot was taken.
+func (s *SampleSnapshot) Min() int64 { return SampleMin(s.values) }
+
+// Percentile returns an arbitrary percentile of values at the time the
+// snapshot was taken.
+func (s *SampleSnapshot) Percentile(p float64) float64 {
+	return SamplePercentile(s.values, p)
+}
+
+// Percentiles returns a slice of arbitrary percentiles of values at the time
+// the snapshot was taken.
+func (s *SampleSnapshot) Percentiles(ps []float64) []float64 {
+	return SamplePercentiles(s.values, ps)
+}
+
+// Size returns the size of the sample at the time the snapshot was taken.
+func (s *SampleSnapshot) Size() int {
+	return len(s.values)
+}
+
+// Snapshot returns the snapshot.
+func (s *SampleSnapshot) Snapshot() Sample { return s }
+
+// StdDev returns the standard deviation of values at the time the snapshot was
+// taken.
+func (s *SampleSnapshot) StdDev() float64 { return SampleStdDev(s.values) }
+
+// Sum returns the sum of values at the time the snapshot was taken.
+func (s *SampleSnapshot) Sum() int64 { return SampleSum(s.values) }
+
+// Update panics.
+func (*SampleSnapshot) Update(int64) {
+	panic("Update called on a SampleSnapshot")
+}
+
+// Values returns a copy of the values in the sample.
+func (s *SampleSnapshot) Values() []int64 {
+	values := make([]int64, len(s.values))
+	copy(values, s.values)
+	return values
+}
+
+// Variance returns the variance of values at the time the snapshot was taken.
+func (s *SampleSnapshot) Variance() float64 { return SampleVariance(s.values) }
+
 // SampleStdDev returns the standard deviation of the slice of int64.
 func SampleStdDev(values []int64) float64 {
 	return math.Sqrt(SampleVariance(values))

+ 69 - 53
sample_test.go

@@ -169,38 +169,26 @@ func TestExpDecaySampleNanosecondRegression(t *testing.T) {
 	}
 }
 
-func TestExpDecaySampleStatistics(t *testing.T) {
+func TestExpDecaySampleSnapshot(t *testing.T) {
 	now := time.Now()
 	rand.Seed(1)
 	s := NewExpDecaySample(100, 0.99)
 	for i := 1; i <= 10000; i++ {
 		s.(*ExpDecaySample).update(now.Add(time.Duration(i)), int64(i))
 	}
-	if count := s.Count(); 10000 != count {
-		t.Errorf("s.Count(): 10000 != %v\n", count)
-	}
-	if min := s.Min(); 107 != min {
-		t.Errorf("s.Min(): 107 != %v\n", min)
-	}
-	if max := s.Max(); 10000 != max {
-		t.Errorf("s.Max(): 10000 != %v\n", max)
-	}
-	if mean := s.Mean(); 4965.98 != mean {
-		t.Errorf("s.Mean(): 4965.98 != %v\n", mean)
-	}
-	if stdDev := s.StdDev(); 2959.825156930727 != stdDev {
-		t.Errorf("s.StdDev(): 2959.825156930727 != %v\n", stdDev)
-	}
-	ps := s.Percentiles([]float64{0.5, 0.75, 0.99})
-	if 4615 != ps[0] {
-		t.Errorf("median: 4615 != %v\n", ps[0])
-	}
-	if 7672 != ps[1] {
-		t.Errorf("75th percentile: 7672 != %v\n", ps[1])
-	}
-	if 9998.99 != ps[2] {
-		t.Errorf("99th percentile: 9998.99 != %v\n", ps[2])
+	snapshot := s.Snapshot()
+	s.Update(1)
+	testExpDecaySampleStatistics(t, snapshot)
+}
+
+func TestExpDecaySampleStatistics(t *testing.T) {
+	now := time.Now()
+	rand.Seed(1)
+	s := NewExpDecaySample(100, 0.99)
+	for i := 1; i <= 10000; i++ {
+		s.(*ExpDecaySample).update(now.Add(time.Duration(i)), int64(i))
 	}
+	testExpDecaySampleStatistics(t, s)
 }
 
 func TestUniformSample(t *testing.T) {
@@ -225,44 +213,86 @@ func TestUniformSample(t *testing.T) {
 	}
 }
 
-func TestUniformSampleDup(t *testing.T) {
-	s1 := NewUniformSample(100)
-	s1.Update(1)
-	s2 := s1.Dup()
-	s1.Update(1)
-	if 1 != s2.Size() {
-		t.Fatal(s2)
-	}
-}
-
 func TestUniformSampleIncludesTail(t *testing.T) {
 	rand.Seed(1)
 	s := NewUniformSample(100)
 	max := 100
-
 	for i := 0; i < max; i++ {
 		s.Update(int64(i))
 	}
-
 	v := s.Values()
 	sum := 0
 	exp := (max - 1) * max / 2
-
 	for i := 0; i < len(v); i++ {
 		sum += int(v[i])
 	}
-
 	if exp != sum {
 		t.Errorf("sum: %v != %v\n", exp, sum)
 	}
 }
 
+func TestUniformSampleSnapshot(t *testing.T) {
+	s := NewUniformSample(100)
+	for i := 1; i <= 10000; i++ {
+		s.Update(int64(i))
+	}
+	snapshot := s.Snapshot()
+	s.Update(1)
+	testUniformSampleStatistics(t, snapshot)
+}
+
 func TestUniformSampleStatistics(t *testing.T) {
 	rand.Seed(1)
 	s := NewUniformSample(100)
 	for i := 1; i <= 10000; i++ {
 		s.Update(int64(i))
 	}
+	testUniformSampleStatistics(t, s)
+}
+
+func benchmarkSample(b *testing.B, s Sample) {
+	var memStats runtime.MemStats
+	runtime.ReadMemStats(&memStats)
+	pauseTotalNs := memStats.PauseTotalNs
+	b.ResetTimer()
+	for i := 0; i < b.N; i++ {
+		s.Update(1)
+	}
+	b.StopTimer()
+	runtime.GC()
+	runtime.ReadMemStats(&memStats)
+	b.Logf("GC cost: %d ns/op", int(memStats.PauseTotalNs-pauseTotalNs)/b.N)
+}
+
+func testExpDecaySampleStatistics(t *testing.T, s Sample) {
+	if count := s.Count(); 10000 != count {
+		t.Errorf("s.Count(): 10000 != %v\n", count)
+	}
+	if min := s.Min(); 107 != min {
+		t.Errorf("s.Min(): 107 != %v\n", min)
+	}
+	if max := s.Max(); 10000 != max {
+		t.Errorf("s.Max(): 10000 != %v\n", max)
+	}
+	if mean := s.Mean(); 4965.98 != mean {
+		t.Errorf("s.Mean(): 4965.98 != %v\n", mean)
+	}
+	if stdDev := s.StdDev(); 2959.825156930727 != stdDev {
+		t.Errorf("s.StdDev(): 2959.825156930727 != %v\n", stdDev)
+	}
+	ps := s.Percentiles([]float64{0.5, 0.75, 0.99})
+	if 4615 != ps[0] {
+		t.Errorf("median: 4615 != %v\n", ps[0])
+	}
+	if 7672 != ps[1] {
+		t.Errorf("75th percentile: 7672 != %v\n", ps[1])
+	}
+	if 9998.99 != ps[2] {
+		t.Errorf("99th percentile: 9998.99 != %v\n", ps[2])
+	}
+}
+
+func testUniformSampleStatistics(t *testing.T, s Sample) {
 	if count := s.Count(); 10000 != count {
 		t.Errorf("s.Count(): 10000 != %v\n", count)
 	}
@@ -289,17 +319,3 @@ func TestUniformSampleStatistics(t *testing.T) {
 		t.Errorf("99th percentile: 9999.99 != %v\n", ps[2])
 	}
 }
-
-func benchmarkSample(b *testing.B, s Sample) {
-	var memStats runtime.MemStats
-	runtime.ReadMemStats(&memStats)
-	pauseTotalNs := memStats.PauseTotalNs
-	b.ResetTimer()
-	for i := 0; i < b.N; i++ {
-		s.Update(1)
-	}
-	b.StopTimer()
-	runtime.GC()
-	runtime.ReadMemStats(&memStats)
-	b.Logf("GC cost: %d ns/op", int(memStats.PauseTotalNs-pauseTotalNs)/b.N)
-}

+ 102 - 6
timer.go

@@ -17,6 +17,7 @@ type Timer interface {
 	Rate5() float64
 	Rate15() float64
 	RateMean() float64
+	Snapshot() Timer
 	StdDev() float64
 	Time(func())
 	Update(time.Duration)
@@ -104,7 +105,10 @@ func (NilTimer) Rate15() float64 { return 0.0 }
 // RateMean is a no-op.
 func (NilTimer) RateMean() float64 { return 0.0 }
 
-// No-op.
+// Snapshot is a no-op.
+func (NilTimer) Snapshot() Timer { return NilTimer{} }
+
+// StdDev is a no-op.
 func (NilTimer) StdDev() float64 { return 0.0 }
 
 // Time is a no-op.
@@ -175,7 +179,17 @@ func (t *StandardTimer) Rate15() float64 {
 
 // RateMean returns the meter's mean rate of events per second.
 func (t *StandardTimer) RateMean() float64 {
-	return t.m.RateMean()
+	return t.meter.RateMean()
+}
+
+// Snapshot returns a read-only copy of the timer.
+func (t *StandardTimer) Snapshot() Timer {
+	t.mutex.Lock()
+	defer t.mutex.Unlock()
+	return &TimerSnapshot{
+		histogram: t.histogram.Snapshot().(*HistogramSnapshot),
+		meter:     t.meter.Snapshot().(*MeterSnapshot),
+	}
 }
 
 // StdDev returns the standard deviation of the values in the sample.
@@ -192,12 +206,94 @@ func (t *StandardTimer) Time(f func()) {
 
 // Record the duration of an event.
 func (t *StandardTimer) Update(d time.Duration) {
-	t.h.Update(int64(d))
-	t.m.Mark(1)
+	t.mutex.Lock()
+	defer t.mutex.Unlock()
+	t.histogram.Update(int64(d))
+	t.meter.Mark(1)
 }
 
 // Record the duration of an event that started at a time and ends now.
 func (t *StandardTimer) UpdateSince(ts time.Time) {
-	t.h.Update(int64(time.Since(ts)))
-	t.m.Mark(1)
+	t.mutex.Lock()
+	defer t.mutex.Unlock()
+	t.histogram.Update(int64(time.Since(ts)))
+	t.meter.Mark(1)
+}
+
+// Variance returns the variance of the values in the sample.
+func (t *StandardTimer) Variance() float64 {
+	return t.histogram.Variance()
+}
+
+// TimerSnapshot is a read-only copy of another Timer.
+type TimerSnapshot struct {
+	histogram *HistogramSnapshot
+	meter     *MeterSnapshot
 }
+
+// Count returns the number of events recorded at the time the snapshot was
+// taken.
+func (t *TimerSnapshot) Count() int64 { return t.histogram.Count() }
+
+// Max returns the maximum value at the time the snapshot was taken.
+func (t *TimerSnapshot) Max() int64 { return t.histogram.Max() }
+
+// Mean returns the mean value at the time the snapshot was taken.
+func (t *TimerSnapshot) Mean() float64 { return t.histogram.Mean() }
+
+// Min returns the minimum value at the time the snapshot was taken.
+func (t *TimerSnapshot) Min() int64 { return t.histogram.Min() }
+
+// Percentile returns an arbitrary percentile of sampled values at the time the
+// snapshot was taken.
+func (t *TimerSnapshot) Percentile(p float64) float64 {
+	return t.histogram.Percentile(p)
+}
+
+// Percentiles returns a slice of arbitrary percentiles of sampled values at
+// the time the snapshot was taken.
+func (t *TimerSnapshot) Percentiles(ps []float64) []float64 {
+	return t.histogram.Percentiles(ps)
+}
+
+// Rate1 returns the one-minute moving average rate of events per second at the
+// time the snapshot was taken.
+func (t *TimerSnapshot) Rate1() float64 { return t.meter.Rate1() }
+
+// Rate5 returns the five-minute moving average rate of events per second at
+// the time the snapshot was taken.
+func (t *TimerSnapshot) Rate5() float64 { return t.meter.Rate5() }
+
+// Rate15 returns the fifteen-minute moving average rate of events per second
+// at the time the snapshot was taken.
+func (t *TimerSnapshot) Rate15() float64 { return t.meter.Rate15() }
+
+// RateMean returns the meter's mean rate of events per second at the time the
+// snapshot was taken.
+func (t *TimerSnapshot) RateMean() float64 { return t.meter.RateMean() }
+
+// Snapshot returns the snapshot.
+func (t *TimerSnapshot) Snapshot() Timer { return t }
+
+// StdDev returns the standard deviation of the values at the time the snapshot
+// was taken.
+func (t *TimerSnapshot) StdDev() float64 { return t.histogram.StdDev() }
+
+// Time panics.
+func (*TimerSnapshot) Time(func()) {
+	panic("Time called on a TimerSnapshot")
+}
+
+// Update panics.
+func (*TimerSnapshot) Update(time.Duration) {
+	panic("Update called on a TimerSnapshot")
+}
+
+// UpdateSince panics.
+func (*TimerSnapshot) UpdateSince(time.Time) {
+	panic("UpdateSince called on a TimerSnapshot")
+}
+
+// Variance returns the variance of the values at the time the snapshot was
+// taken.
+func (t *TimerSnapshot) Variance() float64 { return t.histogram.Variance() }