diff --git a/slidingwindow.go b/slidingwindow.go index 741b7f8..c78a13d 100644 --- a/slidingwindow.go +++ b/slidingwindow.go @@ -93,6 +93,16 @@ func (sw *SlidingWindow) Average(window time.Duration) float64 { return float64(total) / float64(sampleCount) } +// RangeAverage returns the unweighted mean of the window +// from start to end +func (sw *SlidingWindow) RangeAverage(start, end time.Duration) float64 { + total, sampleCount := sw.RangeTotal(start, end) + if sampleCount == 0 { + return 0 + } + return float64(total) / float64(sampleCount) +} + // Reset the samples in this sliding time window. func (sw *SlidingWindow) Reset() { sw.Lock() @@ -139,3 +149,30 @@ func (sw *SlidingWindow) Total(window time.Duration) (int64, int) { return total, sampleCount } + +// RangeTotal returns the sum of all values from start to end, as well as +// the number of samples. +func (sw *SlidingWindow) RangeTotal(start, end time.Duration) (int64, int) { + if start > end { + start = end + } + window := end - start + sampleCount := int(window / sw.granularity) + if sampleCount > sw.size { + sampleCount = sw.size + } + sw.RLock() + defer sw.RUnlock() + + var total int64 + endPos := sw.pos - sw.size + int( end / sw.granularity) + for i := 1; i <= sampleCount; i++ { + pos := endPos - i + if pos < 0 { + pos += len(sw.samples) + } + + total += sw.samples[pos] + } + return total, sampleCount +} diff --git a/slidingwindow_test.go b/slidingwindow_test.go index 61abccd..17450ca 100644 --- a/slidingwindow_test.go +++ b/slidingwindow_test.go @@ -75,6 +75,29 @@ func TestAverage(t *testing.T) { } } +func TestRangeAverage(t *testing.T) { + sw := &SlidingWindow{ + window: 10 * time.Second, + granularity: time.Second, + samples: []int64{1, 2, 5, 0, 0, 0, 0, 0, 4, 0}, + pos: 2, + size: 10, + } + + if v := sw.RangeAverage(0, 0); v != 0 { + t.Errorf("expected the average with a window of 0 seconds be 0, not %f", v) + } + if v := sw.RangeAverage(9 * time.Second, 10 * time.Second); v != 2 { + t.Errorf("expected the average from second 9 to 10 to be 2, not %f", v) + } + if v := sw.RangeAverage(8 * time.Second, 10 * time.Second); v != 1.5 { + t.Errorf("expected the average from second 8 to 10 to be 1.5, not %f", v) + } + if v := sw.RangeAverage(4 * time.Second, 9 * time.Second); v != 1 { + t.Errorf("expected the average from second 4 to 9 to be 1, not %f", v) + } +} + func TestReset(t *testing.T) { sw := MustNew(2*time.Second, time.Second) defer sw.Stop() @@ -131,3 +154,44 @@ func TestTotal(t *testing.T) { t.Errorf("expected the total over the last 10 seconds to be 12, not %d", v) } } + +func TestRangeTotal(t *testing.T) { + sw := &SlidingWindow{ + window: 10 * time.Second, + granularity: time.Second, + samples: []int64{1, 2, 5, 0, 0, 0, 0, 0, 4, 8}, + pos: 10, + size: 10, + } + + if v, _ := sw.RangeTotal(time.Second, 2 * time.Second); v != 2 { + t.Errorf("expected the total from second 1 to 2 to be 2, not %d", v) + } + if v, _ := sw.RangeTotal(time.Second, 3 * time.Second); v != 7 { + t.Errorf("expected the total from second 1 to 3 to be 7, not %d", v) + } + if v, _ := sw.RangeTotal(time.Second * 3, time.Second * 8); v != 0 { + t.Errorf("expected the total from second 3 to 8 to be 0, not %d", v) + } + if v, _ := sw.RangeTotal(0, time.Second * 2); v != 3 { + t.Errorf("expected the total from second 0 to 2 to be 3, not %d", v) + } + if v, _ := sw.RangeTotal(9 * time.Second, 10 * time.Second); v != 8 { + t.Errorf("expected the total from second 9 to 10 to be 8, not %d", v) + } + + sw = &SlidingWindow{ + window: 10 * time.Second, + granularity: time.Second, + samples: []int64{1, 2, 5, 0, 0, 0, 0, 0, 4, 8}, + pos: 2, + size: 10, + } + + if v, _ := sw.RangeTotal(9 * time.Second, 10 * time.Second); v != 2 { + t.Errorf("expected the total from second 9 to 10 to be 2, not %d", v) + } + if v, _ := sw.RangeTotal(7 * time.Second, 9 * time.Second); v != 9 { + t.Errorf("expected the total from second 7 to 9 to be 9, not %d", v) + } +}