Skip to content

Commit

Permalink
Fully introduce snapshots for all metric types.
Browse files Browse the repository at this point in the history
  • Loading branch information
rcrowley committed Jan 7, 2014
1 parent 6bd8d76 commit 10aaf9c
Show file tree
Hide file tree
Showing 12 changed files with 554 additions and 130 deletions.
38 changes: 35 additions & 3 deletions counter.go
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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
}
Expand All @@ -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 changes: 10 additions & 0 deletions counter_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
35 changes: 32 additions & 3 deletions ewma.go
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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)
Expand Down
29 changes: 26 additions & 3 deletions gauge.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Expand Down Expand Up @@ -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.
Expand All @@ -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)
}
Expand Down
10 changes: 10 additions & 0 deletions gauge_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
84 changes: 76 additions & 8 deletions histogram.go
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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 }
Expand Down Expand Up @@ -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
Expand Down
64 changes: 39 additions & 25 deletions histogram_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down Expand Up @@ -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])
}
}
Loading

0 comments on commit 10aaf9c

Please sign in to comment.