1/* 2 * Copyright (C) 2012 The Guava Authors 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17package com.google.common.util.concurrent; 18 19import com.google.common.collect.Lists; 20import com.google.common.testing.NullPointerTester; 21import com.google.common.testing.NullPointerTester.Visibility; 22 23import junit.framework.TestCase; 24 25import java.util.Arrays; 26import java.util.List; 27import java.util.Random; 28import java.util.concurrent.TimeUnit; 29 30/** 31 * Tests for RateLimiter. 32 * 33 * @author Dimitris Andreou 34 */ 35public class RateLimiterTest extends TestCase { 36 private static final double EPSILON = 1e-8; 37 38 /** 39 * The ticker gathers events and presents them as strings. 40 * R0.6 means a delay of 0.6 seconds caused by the (R)ateLimiter 41 * U1.0 means the (U)ser caused the ticker to sleep for a second. 42 */ 43 private final FakeTicker ticker = new FakeTicker(); 44 45 public void testSimple() { 46 RateLimiter limiter = RateLimiter.create(ticker, 5.0); 47 limiter.acquire(); // R0.00, since it's the first request 48 limiter.acquire(); // R0.20 49 limiter.acquire(); // R0.20 50 assertEvents("R0.00", "R0.20", "R0.20"); 51 } 52 53 public void testImmediateTryAcquire() { 54 RateLimiter r = RateLimiter.create(1); 55 assertTrue("Unable to acquire initial permit", r.tryAcquire()); 56 assertFalse("Capable of acquiring secondary permit", r.tryAcquire()); 57 } 58 59 public void testSimpleRateUpdate() { 60 RateLimiter limiter = RateLimiter.create(5.0, 5, TimeUnit.SECONDS); 61 assertEquals(5.0, limiter.getRate()); 62 limiter.setRate(10.0); 63 assertEquals(10.0, limiter.getRate()); 64 65 try { 66 limiter.setRate(0.0); 67 fail(); 68 } catch (IllegalArgumentException ok) {} 69 try { 70 limiter.setRate(-10.0); 71 fail(); 72 } catch (IllegalArgumentException ok) {} 73 } 74 75 public void testSimpleWithWait() { 76 RateLimiter limiter = RateLimiter.create(ticker, 5.0); 77 limiter.acquire(); // R0.00 78 ticker.sleepMillis(200); // U0.20, we are ready for the next request... 79 limiter.acquire(); // R0.00, ...which is granted immediately 80 limiter.acquire(); // R0.20 81 assertEvents("R0.00", "U0.20", "R0.00", "R0.20"); 82 } 83 84 public void testSimpleAcquireReturnValues() { 85 RateLimiter limiter = RateLimiter.create(ticker, 5.0); 86 assertEquals(0.0, limiter.acquire(), EPSILON); // R0.00 87 ticker.sleepMillis(200); // U0.20, we are ready for the next request... 88 assertEquals(0.0, limiter.acquire(), EPSILON); // R0.00, ...which is granted immediately 89 assertEquals(0.2, limiter.acquire(), EPSILON); // R0.20 90 assertEvents("R0.00", "U0.20", "R0.00", "R0.20"); 91 } 92 93 public void testOneSecondBurst() { 94 RateLimiter limiter = RateLimiter.create(ticker, 5.0); 95 ticker.sleepMillis(1000); // max capacity reached 96 ticker.sleepMillis(1000); // this makes no difference 97 limiter.acquire(1); // R0.00, since it's the first request 98 99 limiter.acquire(1); // R0.00, from capacity 100 limiter.acquire(3); // R0.00, from capacity 101 limiter.acquire(1); // R0.00, concluding a burst of 5 permits 102 103 limiter.acquire(); // R0.20, capacity exhausted 104 assertEvents("U1.00", "U1.00", 105 "R0.00", "R0.00", "R0.00", "R0.00", // first request and burst 106 "R0.20"); 107 } 108 109 public void testWarmUp() { 110 RateLimiter limiter = RateLimiter.create(ticker, 2.0, 4000, TimeUnit.MILLISECONDS); 111 for (int i = 0; i < 8; i++) { 112 limiter.acquire(); // #1 113 } 114 ticker.sleepMillis(500); // #2: to repay for the last acquire 115 ticker.sleepMillis(4000); // #3: becomes cold again 116 for (int i = 0; i < 8; i++) { 117 limiter.acquire(); // // #4 118 } 119 ticker.sleepMillis(500); // #5: to repay for the last acquire 120 ticker.sleepMillis(2000); // #6: didn't get cold! It would take another 2 seconds to go cold 121 for (int i = 0; i < 8; i++) { 122 limiter.acquire(); // #7 123 } 124 assertEvents( 125 "R0.00, R1.38, R1.13, R0.88, R0.63, R0.50, R0.50, R0.50", // #1 126 "U0.50", // #2 127 "U4.00", // #3 128 "R0.00, R1.38, R1.13, R0.88, R0.63, R0.50, R0.50, R0.50", // #4 129 "U0.50", // #5 130 "U2.00", // #6 131 "R0.00, R0.50, R0.50, R0.50, R0.50, R0.50, R0.50, R0.50"); // #7 132 } 133 134 public void testWarmUpAndUpdate() { 135 RateLimiter limiter = RateLimiter.create(ticker, 2.0, 4000, TimeUnit.MILLISECONDS); 136 for (int i = 0; i < 8; i++) { 137 limiter.acquire(); // // #1 138 } 139 ticker.sleepMillis(4500); // #2: back to cold state (warmup period + repay last acquire) 140 for (int i = 0; i < 3; i++) { // only three steps, we're somewhere in the warmup period 141 limiter.acquire(); // #3 142 } 143 144 limiter.setRate(4.0); // double the rate! 145 limiter.acquire(); // #4, we repay the debt of the last acquire (imposed by the old rate) 146 for (int i = 0; i < 4; i++) { 147 limiter.acquire(); // #5 148 } 149 ticker.sleepMillis(4250); // #6, back to cold state (warmup period + repay last acquire) 150 for (int i = 0; i < 11; i++) { 151 limiter.acquire(); // #7, showing off the warmup starting from totally cold 152 } 153 154 // make sure the areas (times) remain the same, while permits are different 155 assertEvents( 156 "R0.00, R1.38, R1.13, R0.88, R0.63, R0.50, R0.50, R0.50", // #1 157 "U4.50", // #2 158 "R0.00, R1.38, R1.13", // #3, after that the rate changes 159 "R0.88", // #4, this is what the throttling would be with the old rate 160 "R0.34, R0.28, R0.25, R0.25", // #5 161 "U4.25", // #6 162 "R0.00, R0.72, R0.66, R0.59, R0.53, R0.47, R0.41", // #7 163 "R0.34, R0.28, R0.25, R0.25"); // #7 (cont.), note, this matches #5 164 } 165 166 public void testBursty() { 167 RateLimiter limiter = RateLimiter.createWithCapacity(ticker, 1.0, 10, TimeUnit.SECONDS); 168 ticker.sleepMillis(10000); // reach full capacity 169 limiter.acquire(11); // all these are served in a burst (10 + 1 by borrowing from the future) 170 limiter.acquire(1); // out of capacity, we have to wait 171 limiter.acquire(1); // and wait 172 ticker.sleepMillis(3000); // fill up 3 permits 173 limiter.acquire(5); // we had 3 ready, thus we borrow 2 permits 174 limiter.acquire(1); // this acquire() will also repay for the previous acquire() 175 assertEvents( 176 "U10.00", 177 "R0.00", // 10 permits grabbed 178 "R1.00", "R1.00", // 1 and 1 179 "U3.00", "R0.00", // 5 grabbed 180 "R3.00" // 1 grabbed 181 ); 182 } 183 184 public void testBurstyAndUpdate() { 185 RateLimiter rateLimiter = RateLimiter.create(ticker, 1.0); 186 rateLimiter.acquire(1); // no wait 187 rateLimiter.acquire(1); // R1.00, to repay previous 188 189 rateLimiter.setRate(2.0); // update the rate! 190 191 rateLimiter.acquire(1); // R1.00, to repay previous (the previous was under the old rate!) 192 rateLimiter.acquire(2); // R0.50, to repay previous (now the rate takes effect) 193 rateLimiter.acquire(4); // R1.00, to repay previous 194 rateLimiter.acquire(1); // R2.00, to repay previous 195 assertEvents("R0.00", "R1.00", "R1.00", "R0.50", "R1.00", "R2.00"); 196 } 197 198 public void testTimeWrapping() { 199 ticker.instant = Long.MAX_VALUE - TimeUnit.SECONDS.toNanos(1); // 1 second before max value 200 RateLimiter limiter = RateLimiter.create(ticker, 1.0); 201 for (int i = 0; i < 4; i++) { 202 limiter.acquire(); 203 } 204 // Without protection from overflow, the last wait value would have been huge, 205 // because "now" would have wrapped into a value near MIN_VALUE, and the limiter would think 206 // that the next request should be admitted far into the future 207 assertEvents("R0.00", "R1.00", "R1.00", "R1.00"); 208 } 209 210 public void testTryGate() { 211 RateLimiter limiter = RateLimiter.create(ticker, 5.0); 212 assertTrue(limiter.tryAcquire(0, TimeUnit.SECONDS)); 213 assertFalse(limiter.tryAcquire(0, TimeUnit.SECONDS)); 214 assertFalse(limiter.tryAcquire(0, TimeUnit.SECONDS)); 215 ticker.sleepMillis(100); 216 assertFalse(limiter.tryAcquire(0, TimeUnit.SECONDS)); 217 } 218 219 public void testSimpleWeights() { 220 RateLimiter rateLimiter = RateLimiter.create(ticker, 1.0); 221 rateLimiter.acquire(1); // no wait 222 rateLimiter.acquire(1); // R1.00, to repay previous 223 rateLimiter.acquire(2); // R1.00, to repay previous 224 rateLimiter.acquire(4); // R2.00, to repay previous 225 rateLimiter.acquire(8); // R4.00, to repay previous 226 rateLimiter.acquire(1); // R8.00, to repay previous 227 assertEvents("R0.00", "R1.00", "R1.00", "R2.00", "R4.00", "R8.00"); 228 } 229 230 public void testInfinity_Bursty() { 231 RateLimiter limiter = RateLimiter.create(ticker, Double.POSITIVE_INFINITY); 232 limiter.acquire(Integer.MAX_VALUE / 4); 233 limiter.acquire(Integer.MAX_VALUE / 2); 234 limiter.acquire(Integer.MAX_VALUE); 235 assertEvents("R0.00", "R0.00", "R0.00"); // no wait, infinite rate! 236 237 limiter.setRate(1.0); 238 limiter.acquire(); 239 limiter.acquire(); 240 limiter.acquire(); 241 assertEvents("R0.00", "R1.00", "R1.00"); // we repay the last request (but that had no cost) 242 // and then we go to 1 second per request mode 243 244 limiter.setRate(Double.POSITIVE_INFINITY); 245 limiter.acquire(); 246 limiter.acquire(); 247 limiter.acquire(); 248 assertEvents("R1.00", "R0.00", "R0.00"); // we repay the last request (1sec), then back to +oo 249 } 250 251 public void testInfinity_WarmUp() { 252 RateLimiter limiter = RateLimiter.create( 253 ticker, Double.POSITIVE_INFINITY, 10, TimeUnit.SECONDS); 254 limiter.acquire(Integer.MAX_VALUE / 4); 255 limiter.acquire(Integer.MAX_VALUE / 2); 256 limiter.acquire(Integer.MAX_VALUE); 257 assertEvents("R0.00", "R0.00", "R0.00"); 258 259 limiter.setRate(1.0); 260 limiter.acquire(); 261 limiter.acquire(); 262 limiter.acquire(); 263 assertEvents("R0.00", "R1.00", "R1.00"); 264 265 limiter.setRate(Double.POSITIVE_INFINITY); 266 limiter.acquire(); 267 limiter.acquire(); 268 limiter.acquire(); 269 assertEvents("R1.00", "R0.00", "R0.00"); 270 } 271 272 /** 273 * Make sure that bursts can never go above 1-second-worth-of-work for the current 274 * rate, even when we change the rate. 275 */ 276 public void testWeNeverGetABurstMoreThanOneSec() { 277 RateLimiter limiter = RateLimiter.create(ticker, 1.0); 278 int[] rates = { 1000, 1, 10, 1000000, 10, 1}; 279 for (int rate : rates) { 280 int oneSecWorthOfWork = rate; 281 ticker.sleepMillis(rate * 1000); 282 limiter.setRate(rate); 283 long burst = measureTotalTimeMillis(limiter, oneSecWorthOfWork, new Random()); 284 // we allow one second worth of work to go in a burst (i.e. take less than a second) 285 assertTrue(burst <= 1000); 286 long afterBurst = measureTotalTimeMillis(limiter, oneSecWorthOfWork, new Random()); 287 // but work beyond that must take at least one second 288 assertTrue(afterBurst >= 1000); 289 } 290 } 291 292 /** 293 * This neat test shows that no matter what weights we use in our requests, if we push X 294 * amount of permits in a cool state, where X = rate * timeToCoolDown, and we have 295 * specified a timeToWarmUp() period, it will cost as the prescribed amount of time. E.g., 296 * calling [acquire(5), acquire(1)] takes exactly the same time as 297 * [acquire(2), acquire(3), acquire(1)]. 298 */ 299 public void testTimeToWarmUpIsHonouredEvenWithWeights() { 300 Random random = new Random(); 301 int maxPermits = 10; 302 double[] qpsToTest = { 4.0, 2.0, 1.0, 0.5, 0.1 }; 303 for (int trial = 0; trial < 100; trial++) { 304 for (double qps : qpsToTest) { 305 // Since we know that: maxPermits = 0.5 * warmup / stableInterval; 306 // then if maxPermits == 10, we have: 307 // warmupSeconds = 20 / qps 308 long warmupMillis = (long) ((2 * maxPermits / qps) * 1000.0); 309 RateLimiter rateLimiter = RateLimiter.create( 310 ticker, qps, warmupMillis, TimeUnit.MILLISECONDS); 311 assertEquals(warmupMillis, measureTotalTimeMillis(rateLimiter, maxPermits, random)); 312 } 313 } 314 } 315 316 public void testNulls() { 317 NullPointerTester tester = new NullPointerTester() 318 .setDefault(RateLimiter.SleepingTicker.class, ticker); 319 tester.testStaticMethods(RateLimiter.class, Visibility.PACKAGE); 320 tester.testInstanceMethods(RateLimiter.create(ticker, 5.0), Visibility.PACKAGE); 321 } 322 323 private long measureTotalTimeMillis(RateLimiter rateLimiter, int permits, Random random) { 324 long startTime = ticker.instant; 325 while (permits > 0) { 326 int nextPermitsToAcquire = Math.max(1, random.nextInt(permits)); 327 permits -= nextPermitsToAcquire; 328 rateLimiter.acquire(nextPermitsToAcquire); 329 } 330 rateLimiter.acquire(1); // to repay for any pending debt 331 return TimeUnit.NANOSECONDS.toMillis(ticker.instant - startTime); 332 } 333 334 private void assertEvents(String... events) { 335 assertEquals(Arrays.toString(events), ticker.readEventsAndClear()); 336 } 337 338 private static class FakeTicker extends RateLimiter.SleepingTicker { 339 long instant = 0L; 340 final List<String> events = Lists.newArrayList(); 341 342 @Override 343 public long read() { 344 return instant; 345 } 346 347 void sleepMillis(int millis) { 348 sleepMicros("U", TimeUnit.MILLISECONDS.toMicros(millis)); 349 } 350 351 void sleepMicros(String caption, long micros) { 352 instant += TimeUnit.MICROSECONDS.toNanos(micros); 353 events.add(caption + String.format("%3.2f", (micros / 1000000.0))); 354 } 355 356 @Override 357 void sleepMicrosUninterruptibly(long micros) { 358 sleepMicros("R", micros); 359 } 360 361 String readEventsAndClear() { 362 try { 363 return events.toString(); 364 } finally { 365 events.clear(); 366 } 367 } 368 369 @Override 370 public String toString() { 371 return events.toString(); 372 } 373 } 374} 375