/**
* 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.hadoop.hbase.quotas;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import java.util.concurrent.TimeUnit;
import org.apache.hadoop.hbase.util.EnvironmentEdgeManager;
import org.apache.hadoop.hbase.testclassification.RegionServerTests;
import org.apache.hadoop.hbase.testclassification.SmallTests;
import org.apache.hadoop.hbase.util.ManualEnvironmentEdge;
import org.junit.Test;
import org.junit.experimental.categories.Category;
/**
* Verify the behaviour of the Rate Limiter.
*/
@Category({RegionServerTests.class, SmallTests.class})
public class TestRateLimiter {
@Test
public void testWaitIntervalTimeUnitSeconds() {
testWaitInterval(TimeUnit.SECONDS, 10, 100);
}
@Test
public void testWaitIntervalTimeUnitMinutes() {
testWaitInterval(TimeUnit.MINUTES, 10, 6000);
}
@Test
public void testWaitIntervalTimeUnitHours() {
testWaitInterval(TimeUnit.HOURS, 10, 360000);
}
@Test
public void testWaitIntervalTimeUnitDays() {
testWaitInterval(TimeUnit.DAYS, 10, 8640000);
}
private void testWaitInterval(final TimeUnit timeUnit, final long limit,
final long expectedWaitInterval) {
RateLimiter limiter = new AverageIntervalRateLimiter();
limiter.set(limit, timeUnit);
long nowTs = 0;
// consume all the available resources, one request at the time.
// the wait interval should be 0
for (int i = 0; i < (limit - 1); ++i) {
assertTrue(limiter.canExecute());
limiter.consume();
long waitInterval = limiter.waitInterval();
assertEquals(0, waitInterval);
}
for (int i = 0; i < (limit * 4); ++i) {
// There is one resource available, so we should be able to
// consume it without waiting.
limiter.setNextRefillTime(limiter.getNextRefillTime() - nowTs);
assertTrue(limiter.canExecute());
assertEquals(0, limiter.waitInterval());
limiter.consume();
// No more resources are available, we should wait for at least an interval.
long waitInterval = limiter.waitInterval();
assertEquals(expectedWaitInterval, waitInterval);
// set the nowTs to be the exact time when resources should be available again.
nowTs = waitInterval;
// artificially go into the past to prove that when too early we should fail.
long temp = nowTs + 500;
limiter.setNextRefillTime(limiter.getNextRefillTime() + temp);
assertFalse(limiter.canExecute());
//Roll back the nextRefillTime set to continue further testing
limiter.setNextRefillTime(limiter.getNextRefillTime() - temp);
}
}
@Test
public void testOverconsumptionAverageIntervalRefillStrategy() {
RateLimiter limiter = new AverageIntervalRateLimiter();
limiter.set(10, TimeUnit.SECONDS);
// 10 resources are available, but we need to consume 20 resources
// Verify that we have to wait at least 1.1sec to have 1 resource available
assertTrue(limiter.canExecute());
limiter.consume(20);
// To consume 1 resource wait for 100ms
assertEquals(100, limiter.waitInterval(1));
// To consume 10 resource wait for 1000ms
assertEquals(1000, limiter.waitInterval(10));
limiter.setNextRefillTime(limiter.getNextRefillTime() - 900);
// Verify that after 1sec the 1 resource is available
assertTrue(limiter.canExecute(1));
limiter.setNextRefillTime(limiter.getNextRefillTime() - 100);
// Verify that after 1sec the 10 resource is available
assertTrue(limiter.canExecute());
assertEquals(0, limiter.waitInterval());
}
@Test
public void testOverconsumptionFixedIntervalRefillStrategy() throws InterruptedException {
RateLimiter limiter = new FixedIntervalRateLimiter();
limiter.set(10, TimeUnit.SECONDS);
// 10 resources are available, but we need to consume 20 resources
// Verify that we have to wait at least 1.1sec to have 1 resource available
assertTrue(limiter.canExecute());
limiter.consume(20);
// To consume 1 resource also wait for 1000ms
assertEquals(1000, limiter.waitInterval(1));
// To consume 10 resource wait for 100ms
assertEquals(1000, limiter.waitInterval(10));
limiter.setNextRefillTime(limiter.getNextRefillTime() - 900);
// Verify that after 1sec also no resource should be available
assertFalse(limiter.canExecute(1));
limiter.setNextRefillTime(limiter.getNextRefillTime() - 100);
// Verify that after 1sec the 10 resource is available
assertTrue(limiter.canExecute());
assertEquals(0, limiter.waitInterval());
}
@Test
public void testFixedIntervalResourceAvailability() throws Exception {
RateLimiter limiter = new FixedIntervalRateLimiter();
limiter.set(10, TimeUnit.SECONDS);
assertTrue(limiter.canExecute(10));
limiter.consume(3);
assertEquals(7, limiter.getAvailable());
assertFalse(limiter.canExecute(10));
limiter.setNextRefillTime(limiter.getNextRefillTime() - 1000);
assertTrue(limiter.canExecute(10));
assertEquals(10, limiter.getAvailable());
}
@Test
public void testLimiterBySmallerRate() throws InterruptedException {
// set limiter is 10 resources per seconds
RateLimiter limiter = new FixedIntervalRateLimiter();
limiter.set(10, TimeUnit.SECONDS);
int count = 0; // control the test count
while ((count++) < 10) {
// test will get 3 resources per 0.5 sec. so it will get 6 resources per sec.
limiter.setNextRefillTime(limiter.getNextRefillTime() - 500);
for (int i = 0; i < 3; i++) {
// 6 resources/sec < limit, so limiter.canExecute(nowTs, lastTs) should be true
assertEquals(true, limiter.canExecute());
limiter.consume();
}
}
}
@Test
public void testCanExecuteOfAverageIntervalRateLimiter() throws InterruptedException {
RateLimiter limiter = new AverageIntervalRateLimiter();
// when set limit is 100 per sec, this AverageIntervalRateLimiter will support at max 200 per sec
limiter.set(100, TimeUnit.SECONDS);
limiter.setNextRefillTime(EnvironmentEdgeManager.currentTime());
assertEquals(50, testCanExecuteByRate(limiter, 50));
// refill the avail to limit
limiter.set(100, TimeUnit.SECONDS);
limiter.setNextRefillTime(EnvironmentEdgeManager.currentTime());
assertEquals(100, testCanExecuteByRate(limiter, 100));
// refill the avail to limit
limiter.set(100, TimeUnit.SECONDS);
limiter.setNextRefillTime(EnvironmentEdgeManager.currentTime());
assertEquals(200, testCanExecuteByRate(limiter, 200));
// refill the avail to limit
limiter.set(100, TimeUnit.SECONDS);
limiter.setNextRefillTime(EnvironmentEdgeManager.currentTime());
assertEquals(200, testCanExecuteByRate(limiter, 500));
}
@Test
public void testCanExecuteOfFixedIntervalRateLimiter() throws InterruptedException {
RateLimiter limiter = new FixedIntervalRateLimiter();
// when set limit is 100 per sec, this FixedIntervalRateLimiter will support at max 100 per sec
limiter.set(100, TimeUnit.SECONDS);
limiter.setNextRefillTime(EnvironmentEdgeManager.currentTime());
assertEquals(50, testCanExecuteByRate(limiter, 50));
// refill the avail to limit
limiter.set(100, TimeUnit.SECONDS);
limiter.setNextRefillTime(EnvironmentEdgeManager.currentTime());
assertEquals(100, testCanExecuteByRate(limiter, 100));
// refill the avail to limit
limiter.set(100, TimeUnit.SECONDS);
limiter.setNextRefillTime(EnvironmentEdgeManager.currentTime());
assertEquals(100, testCanExecuteByRate(limiter, 200));
}
public int testCanExecuteByRate(RateLimiter limiter, int rate) {
int request = 0;
int count = 0;
while ((request++) < rate) {
limiter.setNextRefillTime(limiter.getNextRefillTime() - limiter.getTimeUnitInMillis() / rate);
if (limiter.canExecute()) {
count++;
limiter.consume();
}
}
return count;
}
@Test
public void testRefillOfAverageIntervalRateLimiter() throws InterruptedException {
RateLimiter limiter = new AverageIntervalRateLimiter();
limiter.set(60, TimeUnit.SECONDS);
assertEquals(60, limiter.getAvailable());
// first refill, will return the number same with limit
assertEquals(60, limiter.refill(limiter.getLimit()));
limiter.consume(30);
// after 0.2 sec, refill should return 12
limiter.setNextRefillTime(limiter.getNextRefillTime() - 200);
assertEquals(12, limiter.refill(limiter.getLimit()));
// after 0.5 sec, refill should return 30
limiter.setNextRefillTime(limiter.getNextRefillTime() - 500);
assertEquals(30, limiter.refill(limiter.getLimit()));
// after 1 sec, refill should return 60
limiter.setNextRefillTime(limiter.getNextRefillTime() - 1000);
assertEquals(60, limiter.refill(limiter.getLimit()));
// after more than 1 sec, refill should return at max 60
limiter.setNextRefillTime(limiter.getNextRefillTime() - 3000);
assertEquals(60, limiter.refill(limiter.getLimit()));
limiter.setNextRefillTime(limiter.getNextRefillTime() - 5000);
assertEquals(60, limiter.refill(limiter.getLimit()));
}
@Test
public void testRefillOfFixedIntervalRateLimiter() throws InterruptedException {
RateLimiter limiter = new FixedIntervalRateLimiter();
limiter.set(60, TimeUnit.SECONDS);
assertEquals(60, limiter.getAvailable());
// first refill, will return the number same with limit
assertEquals(60, limiter.refill(limiter.getLimit()));
limiter.consume(30);
// after 0.2 sec, refill should return 0
limiter.setNextRefillTime(limiter.getNextRefillTime() - 200);
assertEquals(0, limiter.refill(limiter.getLimit()));
// after 0.5 sec, refill should return 0
limiter.setNextRefillTime(limiter.getNextRefillTime() - 500);
assertEquals(0, limiter.refill(limiter.getLimit()));
// after 1 sec, refill should return 60
limiter.setNextRefillTime(limiter.getNextRefillTime() - 1000);
assertEquals(60, limiter.refill(limiter.getLimit()));
// after more than 1 sec, refill should return at max 60
limiter.setNextRefillTime(limiter.getNextRefillTime() - 3000);
assertEquals(60, limiter.refill(limiter.getLimit()));
limiter.setNextRefillTime(limiter.getNextRefillTime() - 5000);
assertEquals(60, limiter.refill(limiter.getLimit()));
}
@Test
public void testUnconfiguredLimiters() throws InterruptedException {
ManualEnvironmentEdge testEdge = new ManualEnvironmentEdge();
EnvironmentEdgeManager.injectEdge(testEdge);
long limit = Long.MAX_VALUE;
// For unconfigured limiters, it is supposed to use as much as possible
RateLimiter avgLimiter = new AverageIntervalRateLimiter();
RateLimiter fixLimiter = new FixedIntervalRateLimiter();
assertEquals(limit, avgLimiter.getAvailable());
assertEquals(limit, fixLimiter.getAvailable());
assertTrue(avgLimiter.canExecute(limit));
avgLimiter.consume(limit);
assertTrue(fixLimiter.canExecute(limit));
fixLimiter.consume(limit);
// Make sure that available is Long.MAX_VALUE
assertTrue(limit == avgLimiter.getAvailable());
assertTrue(limit == fixLimiter.getAvailable());
// after 100 millseconds, it should be able to execute limit as well
testEdge.incValue(100);
assertTrue(avgLimiter.canExecute(limit));
avgLimiter.consume(limit);
assertTrue(fixLimiter.canExecute(limit));
fixLimiter.consume(limit);
// Make sure that available is Long.MAX_VALUE
assertTrue(limit == avgLimiter.getAvailable());
assertTrue(limit == fixLimiter.getAvailable());
EnvironmentEdgeManager.reset();
}
@Test
public void testExtremeLimiters() throws InterruptedException {
ManualEnvironmentEdge testEdge = new ManualEnvironmentEdge();
EnvironmentEdgeManager.injectEdge(testEdge);
long limit = Long.MAX_VALUE - 1;
RateLimiter avgLimiter = new AverageIntervalRateLimiter();
avgLimiter.set(limit, TimeUnit.SECONDS);
RateLimiter fixLimiter = new FixedIntervalRateLimiter();
fixLimiter.set(limit, TimeUnit.SECONDS);
assertEquals(limit, avgLimiter.getAvailable());
assertEquals(limit, fixLimiter.getAvailable());
assertTrue(avgLimiter.canExecute(limit / 2));
avgLimiter.consume(limit / 2);
assertTrue(fixLimiter.canExecute(limit / 2));
fixLimiter.consume(limit / 2);
// Make sure that available is whatever left
assertTrue((limit - (limit / 2)) == avgLimiter.getAvailable());
assertTrue((limit - (limit / 2)) == fixLimiter.getAvailable());
// after 100 millseconds, both should not be able to execute the limit
testEdge.incValue(100);
assertFalse(avgLimiter.canExecute(limit));
assertFalse(fixLimiter.canExecute(limit));
// after 500 millseconds, average interval limiter should be able to execute the limit
testEdge.incValue(500);
assertTrue(avgLimiter.canExecute(limit));
assertFalse(fixLimiter.canExecute(limit));
// Make sure that available is correct
assertTrue(limit == avgLimiter.getAvailable());
assertTrue((limit - (limit / 2)) == fixLimiter.getAvailable());
// after 500 millseconds, both should be able to execute
testEdge.incValue(500);
assertTrue(avgLimiter.canExecute(limit));
assertTrue(fixLimiter.canExecute(limit));
// Make sure that available is Long.MAX_VALUE
assertTrue(limit == avgLimiter.getAvailable());
assertTrue(limit == fixLimiter.getAvailable());
EnvironmentEdgeManager.reset();
}
/*
* This test case is tricky. Basically, it simulates the following events:
* Thread-1 Thread-2
* t0: canExecute(100) and consume(100)
* t1: canExecute(100), avail may be increased by 80
* t2: consume(-80) as actual size is 20
* It will check if consume(-80) can handle overflow correctly.
*/
@Test
public void testLimiterCompensationOverflow() throws InterruptedException {
long limit = Long.MAX_VALUE - 1;
long guessNumber = 100;
// For unconfigured limiters, it is supposed to use as much as possible
RateLimiter avgLimiter = new AverageIntervalRateLimiter();
avgLimiter.set(limit, TimeUnit.SECONDS);
assertEquals(limit, avgLimiter.getAvailable());
// The initial guess is that 100 bytes.
assertTrue(avgLimiter.canExecute(guessNumber));
avgLimiter.consume(guessNumber);
// Make sure that available is whatever left
assertTrue((limit - guessNumber) == avgLimiter.getAvailable());
// Manually set avil to simulate that another thread call canExecute().
// It is simulated by consume().
avgLimiter.consume(-80);
assertTrue((limit - guessNumber + 80) == avgLimiter.getAvailable());
// Now thread1 compensates 80
avgLimiter.consume(-80);
assertTrue(limit == avgLimiter.getAvailable());
}
}