package com.netflix.astyanax.contrib.valve;
import java.util.concurrent.Callable;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.atomic.AtomicReference;
import org.junit.Assert;
import org.junit.Test;
public class RollingTimeWindowValveTest {
public void testSingleThreadSingleBucket() throws Exception {
final RollingTimeWindowValve valve = new RollingTimeWindowValve(10000, 1);
Long counter = new Long(0L);
for (int i=0; i<100000; i++) {
boolean success = valve.decrementAndCheckQuota();
if (success) {
counter++;
}
}
Assert.assertTrue("Success: " + counter, counter == 10000);
}
public void testSingleThreadMultipleBucketsSingleSecond() throws Exception {
final RollingTimeWindowValve valve = new RollingTimeWindowValve(10000, 10);
runMultiThreadTest(valve, 1, 750, 8000);
}
public void testSingleThreadMultipleBucketsMultipleSeconds() throws Exception {
final RollingTimeWindowValve valve = new RollingTimeWindowValve(10000, 10);
runMultiThreadTest(valve, 1, 1750, 18000);
}
public void testMultipleThreadsSingleBucketSingleSecond() throws Exception {
final RollingTimeWindowValve valve = new RollingTimeWindowValve(10000, 1);
runMultiThreadTest(valve, 8, 800, 10000);
}
public void testMultipleThreadsSingleBucketMultipleSeconds() throws Exception {
final RollingTimeWindowValve valve = new RollingTimeWindowValve(10000, 1);
runMultiThreadTest(valve, 8, 1700, 20000);
}
public void testMultipleThreadsMultipleBucketsMultipleSeconds() throws Exception {
final RollingTimeWindowValve valve = new RollingTimeWindowValve(10000, 10);
runMultiThreadTest(valve, 8, 1750, 18000);
}
private void runMultiThreadTest(final RollingTimeWindowValve valve, int numThreads, int sleepMillis, long expectedSuccesses) throws Exception {
final AtomicLong successCount = new AtomicLong(0L);
final MultiThreadTestControl testControl = new MultiThreadTestControl(numThreads);
testControl.runTest(new Callable<Void>() {
@Override
public Void call() throws Exception {
boolean success = valve.decrementAndCheckQuota();
if (success) {
successCount.incrementAndGet();
}
return null;
}
});
Thread.sleep(sleepMillis);
testControl.stopTest();
long delta = Math.abs(expectedSuccesses-successCount.get());
int percentageDiff = (int) (delta*100/expectedSuccesses);
Assert.assertTrue("Success: " + successCount.get() + ", expected: " + expectedSuccesses + ", percentageDiff: " + percentageDiff, percentageDiff < 10);
//System.out.println("Success: " + successCount.get() + ", expected: " + expectedSuccesses + ", percentageDiff: " + percentageDiff);
}
public void testChangeInRate() throws Exception {
final RollingTimeWindowValve valve = new RollingTimeWindowValve(10000, 10);
final AtomicReference<PauseTest> pause = new AtomicReference<PauseTest>(new PauseTest(8));
final AtomicLong successCount = new AtomicLong(0L);
final MultiThreadTestControl testControl = new MultiThreadTestControl(8);
testControl.runTest(new Callable<Void>() {
@Override
public Void call() throws Exception {
if (pause.get().shouldPause()) {
pause.get().waitOnResume();
}
boolean success = valve.decrementAndCheckQuota();
if (success) {
successCount.incrementAndGet();
}
return null;
}
});
Thread.sleep(1450);
pause.get().pauseTest();
valve.setRatePerSecond(20000L);
pause.get().resumeTest();
Thread.sleep(970);
pause.set(new PauseTest(8));
pause.get().pauseTest();
valve.setRatePerSecond(5000L);
pause.get().resumeTest();
Thread.sleep(1000);
testControl.stopTest();
long expectedSuccesses = 40000; // 10000 for the 1st 1000 ms. and then 20000 for the next 1000 ms and 5000 for the last 1000 ms
long delta = Math.abs(expectedSuccesses-successCount.get());
int percentageDiff = (int) (delta*100/expectedSuccesses);
//Assert.assertTrue("Success: " + successCount.get() + ", expected: " + expectedSuccesses + ", percentageDiff: " + percentageDiff, percentageDiff < 10);
//System.out.println("Success: " + successCount.get() + ", expected: " + expectedSuccesses + ", percentageDiff: " + percentageDiff);
}
private class PauseTest {
private final CountDownLatch waitLatch = new CountDownLatch(1);
private final AtomicBoolean pauseEnabled = new AtomicBoolean(false);
private PauseTest(int numWorkersToPause) {
}
private void pauseTest() {
pauseEnabled.set(true);
}
private void resumeTest() {
pauseEnabled.set(false);
waitLatch.countDown();
}
private boolean shouldPause() {
return pauseEnabled.get();
}
private void waitOnResume() {
try {
waitLatch.await();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
}