package io.dropwizard.metrics;
import org.junit.Test;
import io.dropwizard.metrics.ExponentiallyDecayingReservoir;
import io.dropwizard.metrics.Snapshot;
import java.util.concurrent.TimeUnit;
import static org.assertj.core.api.Assertions.assertThat;
public class ExponentiallyDecayingReservoirTest {
@Test
public void aReservoirOf100OutOf1000Elements() throws Exception {
final ExponentiallyDecayingReservoir reservoir = new ExponentiallyDecayingReservoir(100, 0.99);
for (int i = 0; i < 1000; i++) {
reservoir.update(i);
}
assertThat(reservoir.size())
.isEqualTo(100);
final Snapshot snapshot = reservoir.getSnapshot();
assertThat(snapshot.size())
.isEqualTo(100);
assertAllValuesBetween(reservoir, 0, 1000);
}
@Test
public void aReservoirOf100OutOf10Elements() throws Exception {
final ExponentiallyDecayingReservoir reservoir = new ExponentiallyDecayingReservoir(100, 0.99);
for (int i = 0; i < 10; i++) {
reservoir.update(i);
}
final Snapshot snapshot = reservoir.getSnapshot();
assertThat(snapshot.size())
.isEqualTo(10);
assertThat(snapshot.size())
.isEqualTo(10);
assertAllValuesBetween(reservoir, 0, 10);
}
@Test
public void aHeavilyBiasedReservoirOf100OutOf1000Elements() throws Exception {
final ExponentiallyDecayingReservoir reservoir = new ExponentiallyDecayingReservoir(1000, 0.01);
for (int i = 0; i < 100; i++) {
reservoir.update(i);
}
assertThat(reservoir.size())
.isEqualTo(100);
final Snapshot snapshot = reservoir.getSnapshot();
assertThat(snapshot.size())
.isEqualTo(100);
assertAllValuesBetween(reservoir, 0, 100);
}
@Test
public void longPeriodsOfInactivityShouldNotCorruptSamplingState() {
final ManualClock clock = new ManualClock();
final ExponentiallyDecayingReservoir reservoir = new ExponentiallyDecayingReservoir(10,
0.015,
clock);
// add 1000 values at a rate of 10 values/second
for (int i = 0; i < 1000; i++) {
reservoir.update(1000 + i);
clock.addMillis(100);
}
assertThat(reservoir.getSnapshot().size())
.isEqualTo(10);
assertAllValuesBetween(reservoir, 1000, 2000);
// wait for 15 hours and add another value.
// this should trigger a rescale. Note that the number of samples will be reduced to 2
// because of the very small scaling factor that will make all existing priorities equal to
// zero after rescale.
clock.addHours(15);
reservoir.update(2000);
assertThat(reservoir.getSnapshot().size())
.isEqualTo(2);
assertAllValuesBetween(reservoir, 1000, 3000);
// add 1000 values at a rate of 10 values/second
for (int i = 0; i < 1000; i++) {
reservoir.update(3000 + i);
clock.addMillis(100);
}
assertThat(reservoir.getSnapshot().size())
.isEqualTo(10);
assertAllValuesBetween(reservoir, 3000, 4000);
}
@Test
public void spotLift() {
final ManualClock clock = new ManualClock();
final ExponentiallyDecayingReservoir reservoir = new ExponentiallyDecayingReservoir(1000,
0.015,
clock);
final int valuesRatePerMinute = 10;
final int valuesIntervalMillis = (int) (TimeUnit.MINUTES.toMillis(1) / valuesRatePerMinute);
// mode 1: steady regime for 120 minutes
for (int i = 0; i < 120*valuesRatePerMinute; i++) {
reservoir.update(177);
clock.addMillis(valuesIntervalMillis);
}
// switching to mode 2: 10 minutes more with the same rate, but larger value
for (int i = 0; i < 10*valuesRatePerMinute; i++) {
reservoir.update(9999);
clock.addMillis(valuesIntervalMillis);
}
// expect that quantiles should be more about mode 2 after 10 minutes
assertThat(reservoir.getSnapshot().getMedian())
.isEqualTo(9999);
}
@Test
public void spotFall() {
final ManualClock clock = new ManualClock();
final ExponentiallyDecayingReservoir reservoir = new ExponentiallyDecayingReservoir(1000,
0.015,
clock);
final int valuesRatePerMinute = 10;
final int valuesIntervalMillis = (int) (TimeUnit.MINUTES.toMillis(1) / valuesRatePerMinute);
// mode 1: steady regime for 120 minutes
for (int i = 0; i < 120*valuesRatePerMinute; i++) {
reservoir.update(9998);
clock.addMillis(valuesIntervalMillis);
}
// switching to mode 2: 10 minutes more with the same rate, but smaller value
for (int i = 0; i < 10*valuesRatePerMinute; i++) {
reservoir.update(178);
clock.addMillis(valuesIntervalMillis);
}
// expect that quantiles should be more about mode 2 after 10 minutes
assertThat(reservoir.getSnapshot().get95thPercentile())
.isEqualTo(178);
}
@Test
public void quantiliesShouldBeBasedOnWeights() {
final ManualClock clock = new ManualClock();
final ExponentiallyDecayingReservoir reservoir = new ExponentiallyDecayingReservoir(1000,
0.015,
clock);
for (int i = 0; i < 40; i++) {
reservoir.update(177);
}
clock.addSeconds(120);
for (int i = 0; i < 10; i++) {
reservoir.update(9999);
}
assertThat(reservoir.getSnapshot().size())
.isEqualTo(50);
// the first added 40 items (177) have weights 1
// the next added 10 items (9999) have weights ~6
// so, it's 40 vs 60 distribution, not 40 vs 10
assertThat(reservoir.getSnapshot().getMedian())
.isEqualTo(9999);
assertThat(reservoir.getSnapshot().get75thPercentile())
.isEqualTo(9999);
}
private static void assertAllValuesBetween(ExponentiallyDecayingReservoir reservoir,
double min,
double max) {
for (double i : reservoir.getSnapshot().getValues()) {
assertThat(i)
.isLessThan(max)
.isGreaterThanOrEqualTo(min);
}
}
}