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