package uk.co.acuminous.julez.scenario.limiter; import static java.util.concurrent.TimeUnit.MILLISECONDS; import java.util.concurrent.DelayQueue; import java.util.concurrent.Delayed; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; import uk.co.acuminous.julez.scenario.Scenario; import uk.co.acuminous.julez.scenario.ScenarioSource; import uk.co.acuminous.julez.util.JulezSugar; public class ThroughputLimiter implements ScenarioSource { private DelayQueue<Delayed> limiter = new DelayQueue<Delayed>(); private ScenarioSource scenarios; private int numScenarios; private long interval; private long frequency; private long startTime; private AtomicInteger counter = new AtomicInteger(); public ThroughputLimiter() { this.limiter.add(new DelayToken(System.currentTimeMillis())); } public ThroughputLimiter(ScenarioSource scenarios, int numScenarios, long interval, TimeUnit units) { this.scenarios = scenarios; this.numScenarios = numScenarios; this.interval = MILLISECONDS.convert(interval, units); this.limiter.add(new DelayToken(System.currentTimeMillis())); } public ThroughputLimiter applyLimitOf(int numScenarios, JulezSugar units) { this.numScenarios = numScenarios; return this; } public ThroughputLimiter perSecond() { this.interval = 1000; return this; } public ThroughputLimiter perMinute() { this.interval = 1000 * 60; return this; } public ThroughputLimiter perHour() { this.interval = 1000 * 60 * 60; return this; } public ThroughputLimiter to(ScenarioSource scenarios) { this.scenarios = scenarios; return this; } @Override public Scenario next() { Scenario scenario = null; try { limitThroughput(); scenario = scenarios.next(); } catch (InterruptedException e) { // Meh } return scenario; } private void limitThroughput() throws InterruptedException { init(); limiter.take(); counter.incrementAndGet(); long blockUntil = startTime + (counter.get() * frequency); limiter.add(new DelayToken(blockUntil)); } private void init() { if (startTime == 0) { frequency = interval / numScenarios; startTime = System.currentTimeMillis(); } } private class DelayToken implements Delayed { private long blockUntil; DelayToken(long blockUntil) { this.blockUntil = blockUntil; } @Override public int compareTo(Delayed other) { DelayToken otherToken = (DelayToken) other; return new Long(this.blockUntil).compareTo(otherToken.blockUntil); } @Override public long getDelay(TimeUnit unit) { return unit.convert(blockUntil - System.currentTimeMillis(), MILLISECONDS); } } }