diff --git a/metrics/histogram.go b/metrics/histogram.go index db294a9d3e..5f7e299624 100644 --- a/metrics/histogram.go +++ b/metrics/histogram.go @@ -7,7 +7,7 @@ type HistogramSnapshot interface { Min() int64 Percentile(float64) float64 Percentiles([]float64) []float64 - Sample() Sample + Sample() SampleSnapshot StdDev() float64 Sum() int64 Variance() float64 @@ -59,7 +59,7 @@ func NewRegisteredHistogram(name string, r Registry, s Sample) Histogram { // histogramSnapshot is a read-only copy of another Histogram. type histogramSnapshot struct { - sample *SampleSnapshot + sample *sampleSnapshot } // Count returns the number of samples recorded at the time the snapshot was @@ -91,7 +91,7 @@ func (h *histogramSnapshot) Percentiles(ps []float64) []float64 { } // Sample returns the Sample underlying the histogram. -func (h *histogramSnapshot) Sample() Sample { return h.sample } +func (h *histogramSnapshot) Sample() SampleSnapshot { return h.sample } // Snapshot returns the snapshot. func (h *histogramSnapshot) Snapshot() HistogramSnapshot { return h } @@ -133,7 +133,7 @@ func (NilHistogram) Percentiles(ps []float64) []float64 { } // Sample is a no-op. -func (NilHistogram) Sample() Sample { return NilSample{} } +func (NilHistogram) Sample() SampleSnapshot { return NilSample{} } // Snapshot is a no-op. func (NilHistogram) Snapshot() HistogramSnapshot { return NilHistogram{} } @@ -161,7 +161,7 @@ func (h *StandardHistogram) Clear() { h.sample.Clear() } // Snapshot returns a read-only copy of the histogram. func (h *StandardHistogram) Snapshot() HistogramSnapshot { - return &histogramSnapshot{sample: h.sample.Snapshot().(*SampleSnapshot)} + return &histogramSnapshot{sample: h.sample.Snapshot().(*sampleSnapshot)} } // Update samples a new value. diff --git a/metrics/resetting_sample.go b/metrics/resetting_sample.go index 43c1129cd0..c38ffcd3ec 100644 --- a/metrics/resetting_sample.go +++ b/metrics/resetting_sample.go @@ -17,7 +17,7 @@ type resettingSample struct { } // Snapshot returns a read-only copy of the sample with the original reset. -func (rs *resettingSample) Snapshot() Sample { +func (rs *resettingSample) Snapshot() SampleSnapshot { s := rs.Sample.Snapshot() rs.Sample.Clear() return s diff --git a/metrics/runtimehistogram.go b/metrics/runtimehistogram.go index f93a7188d9..6abdb67d59 100644 --- a/metrics/runtimehistogram.go +++ b/metrics/runtimehistogram.go @@ -57,69 +57,15 @@ func (h *runtimeHistogram) Clear() { func (h *runtimeHistogram) Update(int64) { panic("runtimeHistogram does not support Update") } -func (h *runtimeHistogram) Sample() Sample { - return NilSample{} -} // Snapshot returns a non-changing cop of the histogram. func (h *runtimeHistogram) Snapshot() HistogramSnapshot { return h.load() } -// Count returns the sample count. -func (h *runtimeHistogram) Count() int64 { - return h.load().Count() -} - -// Mean returns an approximation of the mean. -func (h *runtimeHistogram) Mean() float64 { - return h.load().Mean() -} - -// StdDev approximates the standard deviation of the histogram. -func (h *runtimeHistogram) StdDev() float64 { - return h.load().StdDev() -} - -// Variance approximates the variance of the histogram. -func (h *runtimeHistogram) Variance() float64 { - return h.load().Variance() -} - -// Percentile computes the p'th percentile value. -func (h *runtimeHistogram) Percentile(p float64) float64 { - return h.load().Percentile(p) -} - -// Percentiles computes all requested percentile values. -func (h *runtimeHistogram) Percentiles(ps []float64) []float64 { - return h.load().Percentiles(ps) -} - -// Max returns the highest sample value. -func (h *runtimeHistogram) Max() int64 { - return h.load().Max() -} - -// Min returns the lowest sample value. -func (h *runtimeHistogram) Min() int64 { - return h.load().Min() -} - -// Sum returns the sum of all sample values. -func (h *runtimeHistogram) Sum() int64 { - return h.load().Sum() -} - type runtimeHistogramSnapshot metrics.Float64Histogram -func (h *runtimeHistogramSnapshot) Clear() { - panic("runtimeHistogram does not support Clear") -} -func (h *runtimeHistogramSnapshot) Update(int64) { - panic("runtimeHistogram does not support Update") -} -func (h *runtimeHistogramSnapshot) Sample() Sample { +func (h *runtimeHistogramSnapshot) Sample() SampleSnapshot { return NilSample{} } diff --git a/metrics/sample.go b/metrics/sample.go index 252a878f58..42d3b61956 100644 --- a/metrics/sample.go +++ b/metrics/sample.go @@ -11,10 +11,7 @@ import ( const rescaleThreshold = time.Hour -// Samples maintain a statistically-significant selection of values from -// a stream. -type Sample interface { - Clear() +type SampleSnapshot interface { Count() int64 Max() int64 Mean() float64 @@ -22,14 +19,20 @@ type Sample interface { Percentile(float64) float64 Percentiles([]float64) []float64 Size() int - Snapshot() Sample StdDev() float64 Sum() int64 - Update(int64) Values() []int64 Variance() float64 } +// Samples maintain a statistically-significant selection of values from +// a stream. +type Sample interface { + Snapshot() SampleSnapshot + Clear() + Update(int64) +} + // ExpDecaySample is an exponentially-decaying sample using a forward-decaying // priority reservoir. See Cormode et al's "Forward Decay: A Practical Time // Decay Model for Streaming Systems". @@ -77,72 +80,16 @@ func (s *ExpDecaySample) Clear() { s.values.Clear() } -// Count returns the number of samples recorded, which may exceed the -// reservoir size. -func (s *ExpDecaySample) Count() int64 { - s.mutex.Lock() - defer s.mutex.Unlock() - return s.count -} - -// Max returns the maximum value in the sample, which may not be the maximum -// value ever to be part of the sample. -func (s *ExpDecaySample) Max() int64 { - return SampleMax(s.Values()) -} - -// Mean returns the mean of the values in the sample. -func (s *ExpDecaySample) Mean() float64 { - return SampleMean(s.Values()) -} - -// Min returns the minimum value in the sample, which may not be the minimum -// value ever to be part of the sample. -func (s *ExpDecaySample) Min() int64 { - return SampleMin(s.Values()) -} - -// Percentile returns an arbitrary percentile of values in the sample. -func (s *ExpDecaySample) Percentile(p float64) float64 { - return SamplePercentile(s.Values(), p) -} - -// Percentiles returns a slice of arbitrary percentiles of values in the -// sample. -func (s *ExpDecaySample) Percentiles(ps []float64) []float64 { - return SamplePercentiles(s.Values(), ps) -} - -// Size returns the size of the sample, which is at most the reservoir size. -func (s *ExpDecaySample) Size() int { - s.mutex.Lock() - defer s.mutex.Unlock() - return s.values.Size() -} - // Snapshot returns a read-only copy of the sample. -func (s *ExpDecaySample) Snapshot() Sample { +func (s *ExpDecaySample) Snapshot() SampleSnapshot { s.mutex.Lock() - defer s.mutex.Unlock() vals := s.values.Values() values := make([]int64, len(vals)) for i, v := range vals { 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()) -} - -// Sum returns the sum of the values in the sample. -func (s *ExpDecaySample) Sum() int64 { - return SampleSum(s.Values()) + s.mutex.Unlock() + return newSampleSnapshot(s.count, values) } // Update samples a new value. @@ -150,23 +97,6 @@ func (s *ExpDecaySample) Update(v int64) { s.update(time.Now(), v) } -// Values returns a copy of the values in the sample. -func (s *ExpDecaySample) Values() []int64 { - s.mutex.Lock() - defer s.mutex.Unlock() - vals := s.values.Values() - values := make([]int64, len(vals)) - for i, v := range vals { - values[i] = v.v - } - return values -} - -// Variance returns the variance of the values in the sample. -func (s *ExpDecaySample) Variance() float64 { - return SampleVariance(s.Values()) -} - // update samples a new value at a particular timestamp. This is a method all // its own to facilitate testing. func (s *ExpDecaySample) update(t time.Time, v int64) { @@ -229,7 +159,7 @@ func (NilSample) Percentiles(ps []float64) []float64 { func (NilSample) Size() int { return 0 } // Sample is a no-op. -func (NilSample) Snapshot() Sample { return NilSample{} } +func (NilSample) Snapshot() SampleSnapshot { return NilSample{} } // StdDev is a no-op. func (NilSample) StdDev() float64 { return 0.0 } @@ -246,42 +176,6 @@ func (NilSample) Values() []int64 { return []int64{} } // Variance is a no-op. func (NilSample) Variance() float64 { return 0.0 } -// SampleMax returns the maximum value of the slice of int64. -func SampleMax(values []int64) int64 { - if len(values) == 0 { - return 0 - } - var max int64 = math.MinInt64 - for _, v := range values { - if max < v { - max = v - } - } - return max -} - -// SampleMean returns the mean value of the slice of int64. -func SampleMean(values []int64) float64 { - if len(values) == 0 { - return 0.0 - } - return float64(SampleSum(values)) / float64(len(values)) -} - -// SampleMin returns the minimum value of the slice of int64. -func SampleMin(values []int64) int64 { - if len(values) == 0 { - return 0 - } - var min int64 = math.MaxInt64 - for _, v := range values { - if min > v { - min = v - } - } - return min -} - // SamplePercentiles returns an arbitrary percentile of the slice of int64. func SamplePercentile(values []int64, p float64) float64 { return SamplePercentiles(values, []float64{p})[0] @@ -310,99 +204,108 @@ func SamplePercentiles(values []int64, ps []float64) []float64 { return scores } -// SampleSnapshot is a read-only copy of another Sample. -type SampleSnapshot struct { +// sampleSnapshot is a read-only copy of another Sample. +type sampleSnapshot struct { count int64 values []int64 + + max int64 + min int64 + mean float64 + sum int64 } -func NewSampleSnapshot(count int64, values []int64) *SampleSnapshot { - return &SampleSnapshot{ +// newSampleSnapshot creates a read-only sampleSnapShot, and calculates some +// numbers. +func newSampleSnapshot(count int64, values []int64) *sampleSnapshot { + s := &sampleSnapshot{ count: count, values: values, } -} - -// Clear panics. -func (*SampleSnapshot) Clear() { - panic("Clear called on a SampleSnapshot") + if len(values) == 0 { + return s + } + var ( + max int64 = math.MinInt64 + min int64 = math.MaxInt64 + sum int64 + ) + for _, v := range values { + sum += v + if v > max { + max = v + } + if v < min { + min = v + } + } + s.min = min + s.max = max + s.mean = float64(sum) / float64(len(values)) + s.sum = sum + return s } // Count returns the count of inputs at the time the snapshot was taken. -func (s *SampleSnapshot) Count() int64 { return s.count } +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) } +func (s *sampleSnapshot) Max() int64 { return s.max } // Mean returns the mean value at the time the snapshot was taken. -func (s *SampleSnapshot) Mean() float64 { return SampleMean(s.values) } +func (s *sampleSnapshot) Mean() float64 { return s.mean } // Min returns the minimal value at the time the snapshot was taken. -func (s *SampleSnapshot) Min() int64 { return SampleMin(s.values) } +func (s *sampleSnapshot) Min() int64 { return s.min } // Percentile returns an arbitrary percentile of values at the time the // snapshot was taken. -func (s *SampleSnapshot) Percentile(p float64) float64 { +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 { +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) } +func (s *sampleSnapshot) Size() int { return len(s.values) } // Snapshot returns the snapshot. -func (s *SampleSnapshot) Snapshot() Sample { return s } +func (s *sampleSnapshot) Snapshot() SampleSnapshot { 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) } +func (s *sampleSnapshot) StdDev() float64 { return SampleStdDev(s.mean, 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") -} +func (s *sampleSnapshot) Sum() int64 { return s.sum } // Values returns a copy of the values in the sample. -func (s *SampleSnapshot) Values() []int64 { +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) } +func (s *sampleSnapshot) Variance() float64 { return SampleVariance(s.mean, s.values) } // SampleStdDev returns the standard deviation of the slice of int64. -func SampleStdDev(values []int64) float64 { - return math.Sqrt(SampleVariance(values)) -} - -// SampleSum returns the sum of the slice of int64. -func SampleSum(values []int64) int64 { - var sum int64 - for _, v := range values { - sum += v - } - return sum +func SampleStdDev(mean float64, values []int64) float64 { + return math.Sqrt(SampleVariance(mean, values)) } // SampleVariance returns the variance of the slice of int64. -func SampleVariance(values []int64) float64 { +func SampleVariance(mean float64, values []int64) float64 { if len(values) == 0 { return 0.0 } - m := SampleMean(values) var sum float64 for _, v := range values { - d := float64(v) - m + d := float64(v) - mean sum += d * d } return sum / float64(len(values)) @@ -445,83 +348,13 @@ func (s *UniformSample) Clear() { s.values = make([]int64, 0, s.reservoirSize) } -// Count returns the number of samples recorded, which may exceed the -// reservoir size. -func (s *UniformSample) Count() int64 { - s.mutex.Lock() - defer s.mutex.Unlock() - return s.count -} - -// Max returns the maximum value in the sample, which may not be the maximum -// value ever to be part of the sample. -func (s *UniformSample) Max() int64 { - s.mutex.Lock() - defer s.mutex.Unlock() - return SampleMax(s.values) -} - -// Mean returns the mean of the values in the sample. -func (s *UniformSample) Mean() float64 { - s.mutex.Lock() - defer s.mutex.Unlock() - return SampleMean(s.values) -} - -// Min returns the minimum value in the sample, which may not be the minimum -// value ever to be part of the sample. -func (s *UniformSample) Min() int64 { - s.mutex.Lock() - defer s.mutex.Unlock() - return SampleMin(s.values) -} - -// Percentile returns an arbitrary percentile of values in the sample. -func (s *UniformSample) Percentile(p float64) float64 { - s.mutex.Lock() - defer s.mutex.Unlock() - return SamplePercentile(s.values, p) -} - -// Percentiles returns a slice of arbitrary percentiles of values in the -// sample. -func (s *UniformSample) Percentiles(ps []float64) []float64 { - s.mutex.Lock() - defer s.mutex.Unlock() - return SamplePercentiles(s.values, ps) -} - -// Size returns the size of the sample, which is at most the reservoir size. -func (s *UniformSample) Size() int { - s.mutex.Lock() - defer s.mutex.Unlock() - return len(s.values) -} - // Snapshot returns a read-only copy of the sample. -func (s *UniformSample) Snapshot() Sample { +func (s *UniformSample) Snapshot() SampleSnapshot { s.mutex.Lock() - defer s.mutex.Unlock() values := make([]int64, len(s.values)) copy(values, s.values) - return &SampleSnapshot{ - count: s.count, - values: values, - } -} - -// StdDev returns the standard deviation of the values in the sample. -func (s *UniformSample) StdDev() float64 { - s.mutex.Lock() - defer s.mutex.Unlock() - return SampleStdDev(s.values) -} - -// Sum returns the sum of the values in the sample. -func (s *UniformSample) Sum() int64 { - s.mutex.Lock() - defer s.mutex.Unlock() - return SampleSum(s.values) + s.mutex.Unlock() + return newSampleSnapshot(s.count, s.values) } // Update samples a new value. @@ -544,22 +377,6 @@ func (s *UniformSample) Update(v int64) { } } -// Values returns a copy of the values in the sample. -func (s *UniformSample) Values() []int64 { - s.mutex.Lock() - defer s.mutex.Unlock() - values := make([]int64, len(s.values)) - copy(values, s.values) - return values -} - -// Variance returns the variance of the values in the sample. -func (s *UniformSample) Variance() float64 { - s.mutex.Lock() - defer s.mutex.Unlock() - return SampleVariance(s.values) -} - // expDecaySample represents an individual sample in a heap. type expDecaySample struct { k float64 diff --git a/metrics/sample_test.go b/metrics/sample_test.go index 3ae128d56f..018358d729 100644 --- a/metrics/sample_test.go +++ b/metrics/sample_test.go @@ -14,22 +14,29 @@ import ( // computation for small samples and only slightly less for large samples. func BenchmarkCompute1000(b *testing.B) { s := make([]int64, 1000) + var sum int64 for i := 0; i < len(s); i++ { s[i] = int64(i) + sum += int64(i) } + mean := float64(sum) / float64(len(s)) b.ResetTimer() for i := 0; i < b.N; i++ { - SampleVariance(s) + SampleVariance(mean, s) } } func BenchmarkCompute1000000(b *testing.B) { s := make([]int64, 1000000) + var sum int64 for i := 0; i < len(s); i++ { s[i] = int64(i) + sum += int64(i) + } + mean := float64(sum) / float64(len(s)) b.ResetTimer() for i := 0; i < b.N; i++ { - SampleVariance(s) + SampleVariance(mean, s) } } func BenchmarkCopy1000(b *testing.B) {