package com.twitter.common.stats; import org.junit.Test; import org.pantsbuild.junit.annotations.TestParallel; import com.twitter.common.quantity.Amount; import com.twitter.common.quantity.Time; import com.twitter.common.stats.WindowedStatistics; import com.twitter.common.util.testing.FakeClock; import static org.junit.Assert.assertEquals; @TestParallel public class WindowedStatsTest { private Amount<Long, Time> window = Amount.of(1L, Time.MINUTES); private int slices = 3; private long sliceDuration = window.as(Time.NANOSECONDS) / slices; @Test public void testEmptyStats() { FakeClock clock = new FakeClock(); WindowedStatistics ws = new WindowedStatistics(window, slices, clock); assertEmpty(ws); } @Test public void testStatsCorrectness() { FakeClock clock = new FakeClock(); Statistics reference = new Statistics(); WindowedStatistics ws = new WindowedStatistics(window, slices, clock); for (int i=0; i<1000; i++) { reference.accumulate(i); ws.accumulate(i); } clock.advance(Amount.of(1 + sliceDuration, Time.NANOSECONDS)); ws.refresh(); assertEquals(reference.max(), ws.max()); assertEquals(reference.min(), ws.min()); assertEquals(reference.populationSize(), ws.populationSize()); assertEquals(reference.sum(), ws.sum()); assertEquals(reference.range(), ws.range()); assertEquals(reference.mean(), ws.mean(), 0.01); assertEquals(reference.variance(), ws.variance(), 0.01); assertEquals(reference.standardDeviation(), ws.standardDeviation(), 0.01); for (int i=0; i<1000; i++) { long x = i + 500; reference.accumulate(x); ws.accumulate(x); } clock.advance(Amount.of(sliceDuration, Time.NANOSECONDS)); ws.refresh(); assertEquals(reference.max(), ws.max()); assertEquals(reference.min(), ws.min()); assertEquals(reference.populationSize(), ws.populationSize()); assertEquals(reference.sum(), ws.sum()); assertEquals(reference.range(), ws.range()); assertEquals(reference.mean(), ws.mean(), 0.01); assertEquals(reference.variance(), ws.variance(), 0.01); assertEquals(reference.standardDeviation(), ws.standardDeviation(), 0.01); for (int i=0; i<1000; i++) { long x = i * i; reference.accumulate(x); ws.accumulate(x); } clock.advance(Amount.of(sliceDuration, Time.NANOSECONDS)); ws.refresh(); assertEquals(reference.max(), ws.max()); assertEquals(reference.min(), ws.min()); assertEquals(reference.populationSize(), ws.populationSize()); assertEquals(reference.sum(), ws.sum()); assertEquals(reference.range(), ws.range()); assertEquals(reference.mean(), ws.mean(), 0.01); assertEquals(reference.variance(), ws.variance(), 0.01); assertEquals(reference.standardDeviation(), ws.standardDeviation(), 0.01); } @Test public void testWindowStats() { FakeClock clock = new FakeClock(); WindowedStatistics ws = new WindowedStatistics(window, slices, clock); ws.accumulate(1L); assertEmpty(ws); clock.advance(Amount.of(1 + sliceDuration, Time.NANOSECONDS)); ws.refresh(); assertEquals(1L, ws.max()); assertEquals(1L, ws.min()); assertEquals(1L, ws.populationSize()); assertEquals(1L, ws.sum()); assertEquals(1.0, ws.mean(), 0.01); assertEquals(0.0, ws.standardDeviation(), 0.01); clock.advance(Amount.of(slices * sliceDuration, Time.NANOSECONDS)); ws.refresh(); assertEmpty(ws); } @Test public void testCleaningOfExpiredWindows() { FakeClock clock = new FakeClock(); WindowedStatistics ws = new WindowedStatistics(window, slices, clock); long n = 1000L; for (int i=0; i<n; i++) { ws.accumulate(i); } assertEmpty(ws); clock.advance(Amount.of(1 + sliceDuration, Time.NANOSECONDS)); ws.refresh(); assertEquals(n, ws.populationSize()); // this window is not empty clock.advance(Amount.of(100 * sliceDuration, Time.NANOSECONDS)); ws.refresh(); assertEmpty(ws); // this window has been cleaned } @Test public void testAddNewValueToFullWS() { FakeClock clock = new FakeClock(); WindowedStatistics ws = new WindowedStatistics(window, slices, clock); // AAAAA // BBBBB // CCCCC // DDDDD // | | | | //---------------------------------> t // t=0 t=1 t=2 t=3 // t=0 fill {D} long n = 1000L; for (int i=0; i<n; i++) { ws.accumulate(i); } // read {A,B,C}, which should be empty assertEmpty(ws); clock.advance(Amount.of(1 + sliceDuration, Time.NANOSECONDS)); ws.refresh(); // t=1, read {B,C,D} which shouldn't be empty assertEquals(n - 1L, ws.max()); assertEquals(0L, ws.min()); assertEquals(n, ws.populationSize()); assertEquals(n * (n - 1) / 2, ws.sum()); assertEquals((n - 1) / 2.0, ws.mean(), 0.01); clock.advance(Amount.of(1 + sliceDuration, Time.NANOSECONDS)); ws.refresh(); // t=2, read {C,D,A} which shouldn't be empty as well assertEquals(n - 1L, ws.max()); assertEquals(0L, ws.min()); assertEquals(n, ws.populationSize()); assertEquals(n * (n - 1) / 2, ws.sum()); assertEquals((n - 1) / 2.0, ws.mean(), 0.01); clock.advance(Amount.of(1 + sliceDuration, Time.NANOSECONDS)); ws.refresh(); // t=3, read {D,A,B} which shouldn't be empty as well assertEquals(n - 1L, ws.max()); assertEquals(0L, ws.min()); assertEquals(n, ws.populationSize()); assertEquals(n * (n - 1) / 2, ws.sum()); assertEquals((n - 1) / 2.0, ws.mean(), 0.01); clock.advance(Amount.of(1 + sliceDuration, Time.NANOSECONDS)); ws.refresh(); // t=4, read {A,B,C} which must be empty (cleaned by the Windowed class) assertEmpty(ws); } private void assertEmpty(WindowedStatistics ws) { assertEquals(Long.MIN_VALUE, ws.max()); assertEquals(Long.MAX_VALUE, ws.min()); assertEquals(0L, ws.populationSize()); assertEquals(0L, ws.sum()); assertEquals(0.0, ws.mean(), 0.01); assertEquals(0.0, ws.standardDeviation(), 0.01); } }