/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.activemq.artemis.tests.timing.util;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import org.apache.activemq.artemis.tests.unit.UnitTestLogger;
import org.apache.activemq.artemis.tests.util.ActiveMQTestBase;
import org.apache.activemq.artemis.utils.TokenBucketLimiterImpl;
import org.junit.Assert;
import org.junit.Test;
public class TokenBucketLimiterImplTest extends ActiveMQTestBase {
private static final UnitTestLogger log = UnitTestLogger.LOGGER;
@Test
public void testRateWithSpin1() throws Exception {
testRate(1, true);
}
@Test
public void testRateWithSpin10() throws Exception {
testRate(10, true);
}
@Test
public void testRateWithSpin100() throws Exception {
testRate(100, true);
}
@Test
public void testRateWithSpin1000() throws Exception {
testRate(1000, true);
}
@Test
public void testRateWithSpin10000() throws Exception {
testRate(10000, true);
}
@Test
public void testRateWithSpin100000() throws Exception {
testRate(100000, true);
}
@Test
public void testRateWithoutSpin1() throws Exception {
testRate(1, false);
}
@Test
public void testRateWithoutSpin10() throws Exception {
testRate(10, false);
}
@Test
public void testRateWithoutSpin100() throws Exception {
testRate(100, false);
}
@Test
public void testRateWithoutSpin1000() throws Exception {
testRate(1000, false);
}
@Test
public void testRateWithoutSpin10000() throws Exception {
testRate(10000, false);
}
@Test
public void testRateWithoutSpin100000() throws Exception {
testRate(100000, false);
}
private void testRate(final int rate, final boolean spin) throws Exception {
final double error = 0.05; // Allow for 5% error
TokenBucketLimiterImpl tbl = new TokenBucketLimiterImpl(rate, spin);
long start = System.currentTimeMillis();
long count = 0;
final long measureTime = 5000;
// Do some initial testing .. to ramp up the calculations
while (System.currentTimeMillis() - start < measureTime) {
tbl.limit();
}
// wait some time
Thread.sleep(2000);
// start measuring again
start = System.currentTimeMillis();
while (System.currentTimeMillis() - start < measureTime) {
tbl.limit();
// when using a low rate (1 for instance), the very last could come after or very close to 5 seconds
// what could give us a false negative
count++;
}
long end = System.currentTimeMillis();
if (rate == 1) {
// in 5 seconds you may get exactly 6 buckets..
// Count... 1, 2, 3, 4, 5, 6
// Time.... 0, 1, 2, 3, 4, 5
// any variation on 1 could make the test to fail, for that reason we make it this way
Assert.assertTrue(count == 5 || count == 6);
} else {
double actualRate = (double) (1000 * count) / measureTime;
Assert.assertTrue("actual rate = " + actualRate + " expected=" + rate, actualRate > rate * (1 - error));
Assert.assertTrue("actual rate = " + actualRate + " expected=" + rate, actualRate < rate * (1 + error));
}
}
@Test
public void testVerifyMaxRate1() throws Exception {
testVerifyRate(1, 1, 5000);
}
@Test
public void testVerifyMaxRate5() throws Exception {
testVerifyRate(5, 1, 5000);
}
@Test
public void testVerifyMaxRate50() throws Exception {
testVerifyRate(50, 1, 5000);
}
@Test
public void testVerifyMaxRate50_per_5seconds() throws Exception {
testVerifyRate(50, 5, 20000);
}
public void testVerifyRate(final int rate, final int window, final int timeRunning) throws Exception {
final double error = 1.05; // Allow for 5% error
final AtomicBoolean running = new AtomicBoolean(true);
final AtomicBoolean rateError = new AtomicBoolean(false);
final AtomicInteger iterations = new AtomicInteger(0);
TokenBucketLimiterImpl tbl = new TokenBucketLimiterImpl(rate, false, TimeUnit.SECONDS, window);
Thread t = new Thread() {
@Override
public void run() {
int lastRun = 0;
long lastTime = System.currentTimeMillis();
while (running.get()) {
int tmpValue = iterations.get();
if (lastRun != 0) {
int consumed = tmpValue - lastRun;
double calculatedRate = consumed * window * 1000 / ((System.currentTimeMillis() - lastTime));
if (calculatedRate > rate * error) {
System.out.println("got more than " + rate + " tokens / second");
rateError.set(true);
} else if (calculatedRate > rate) {
System.out.println("got more than " + rate + " tokens / second but still on the error marging" +
"make sure it's ok though, if you see to many of this message it's an issue");
}
System.out.println("Rate = " + calculatedRate + " consumed = " + consumed);
}
lastTime = System.currentTimeMillis();
lastRun = tmpValue;
try {
Thread.sleep(window * 1000);
} catch (Exception e) {
e.printStackTrace();
}
}
}
};
t.start();
long timeout;
timeout = System.currentTimeMillis() + 3000;
while (timeout > System.currentTimeMillis()) {
tbl.limit();
iterations.incrementAndGet();
}
Thread.sleep(3000);
timeout = System.currentTimeMillis() + timeRunning;
while (timeout > System.currentTimeMillis()) {
tbl.limit();
iterations.incrementAndGet();
}
running.set(false);
t.join();
}
}