diff --git a/build.gradle b/build.gradle index 400299556..bc2ff08da 100644 --- a/build.gradle +++ b/build.gradle @@ -52,9 +52,6 @@ dependencies { compile 'joda-time:joda-time:2.9.9' compileOnly 'com.google.appengine:appengine-api-1.0-sdk:1.9.54' compile 'org.slf4j:slf4j-api:1.7.25' - compile 'com.google.guava:guava:23.0-android' // Note, v23.0-android is the last JDK7 supporting release of Guava - // but still don't use google-maps-services-java on android - testCompile 'junit:junit:4.12' testCompile 'org.mockito:mockito-core:1.10.19' testCompile 'com.squareup.okhttp3:mockwebserver:3.8.1' diff --git a/src/main/java/com/google/maps/internal/RateLimitExecutorService.java b/src/main/java/com/google/maps/internal/RateLimitExecutorService.java index 995c8212d..f2eb1f232 100644 --- a/src/main/java/com/google/maps/internal/RateLimitExecutorService.java +++ b/src/main/java/com/google/maps/internal/RateLimitExecutorService.java @@ -15,7 +15,7 @@ package com.google.maps.internal; -import com.google.common.util.concurrent.RateLimiter; +import com.google.maps.internal.ratelimiter.RateLimiter; import java.util.Collection; import java.util.List; import java.util.concurrent.BlockingQueue; diff --git a/src/main/java/com/google/maps/internal/ratelimiter/LongMath.java b/src/main/java/com/google/maps/internal/ratelimiter/LongMath.java new file mode 100644 index 000000000..0e21377f8 --- /dev/null +++ b/src/main/java/com/google/maps/internal/ratelimiter/LongMath.java @@ -0,0 +1,54 @@ +/* + * Copyright (C) 2012 The Guava Authors + * + * Licensed 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. + */ +/* + * Copyright 2017 Google Inc. All rights reserved. + * + * + * Licensed 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 com.google.maps.internal.ratelimiter; + +/** + * A class for arithmetic on values of type {@code long}. + * + *
This is a minimal port of Google Guava's com.google.common.math.LongMath, just sufficient to + * implement the ratelimiter classes. + */ +public final class LongMath { + private LongMath() {} + + /** + * Returns the sum of {@code a} and {@code b} unless it would overflow or underflow in which case + * {@code Long.MAX_VALUE} or {@code Long.MIN_VALUE} is returned, respectively. + */ + public static long saturatedAdd(long a, long b) { + long naiveSum = a + b; + if ((a ^ b) < 0 | (a ^ naiveSum) >= 0) { + // If a and b have different signs or a has the same sign as the result then there was no + // overflow, return. + return naiveSum; + } + // we did over/under flow, if the sign is negative we should return MAX otherwise MIN + return Long.MAX_VALUE + ((naiveSum >>> (Long.SIZE - 1)) ^ 1); + } +} diff --git a/src/main/java/com/google/maps/internal/ratelimiter/Platform.java b/src/main/java/com/google/maps/internal/ratelimiter/Platform.java new file mode 100644 index 000000000..45ccd3487 --- /dev/null +++ b/src/main/java/com/google/maps/internal/ratelimiter/Platform.java @@ -0,0 +1,52 @@ +/* + * Copyright (C) 2009 The Guava Authors + * + * Licensed 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. + */ +/* + * Copyright 2017 Google Inc. All rights reserved. + * + * + * Licensed 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 com.google.maps.internal.ratelimiter; + +import java.util.Locale; + +/** + * Methods factored out so that they can be emulated differently in GWT. + * + *
This is a minimal port of Google Guava's com.google.common.base.Platform, sufficient to + * implement the ratelimiter classes. + * + * @author Jesse Wilson + */ +final class Platform { + private Platform() {} + + /** Calls {@link System#nanoTime()}. */ + static long systemNanoTime() { + return System.nanoTime(); + } + + static String formatCompact4Digits(double value) { + return String.format(Locale.ROOT, "%.4g", value); + } +} diff --git a/src/main/java/com/google/maps/internal/ratelimiter/Preconditions.java b/src/main/java/com/google/maps/internal/ratelimiter/Preconditions.java new file mode 100644 index 000000000..24151222d --- /dev/null +++ b/src/main/java/com/google/maps/internal/ratelimiter/Preconditions.java @@ -0,0 +1,150 @@ +/* + * Copyright (C) 2012 The Guava Authors + * + * Licensed 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. + */ +/* + * Copyright 2017 Google Inc. All rights reserved. + * + * + * Licensed 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 com.google.maps.internal.ratelimiter; + +/** + * Static convenience methods that help a method or constructor check whether it was invoked + * correctly (that is, whether its preconditions were met). + * + *
This is a minimal port of Google Guava's com.google.common.base.Preconditions necessary to
+ * implement the ratelimiter classes.
+ */
+public final class Preconditions {
+ private Preconditions() {}
+
+ /**
+ * Ensures the truth of an expression involving one or more parameters to the calling method.
+ *
+ * @param expression a boolean expression
+ * @param errorMessageTemplate a template for the exception message should the check fail. The
+ * message is formed by replacing each {@code %s} placeholder in the template with an
+ * argument. These are matched by position - the first {@code %s} gets {@code
+ * errorMessageArgs[0]}, etc. Unmatched arguments will be appended to the formatted message in
+ * square braces. Unmatched placeholders will be left as-is.
+ * @param errorMessageArgs the arguments to be substituted into the message template. Arguments
+ * are converted to strings using {@link String#valueOf(Object)}.
+ * @throws IllegalArgumentException if {@code expression} is false
+ */
+ public static void checkArgument(
+ boolean expression, String errorMessageTemplate, Object... errorMessageArgs) {
+ if (!expression) {
+ throw new IllegalArgumentException(format(errorMessageTemplate, errorMessageArgs));
+ }
+ }
+
+ /**
+ * Ensures that an object reference passed as a parameter to the calling method is not null.
+ *
+ * @param reference an object reference
+ * @return the non-null reference that was validated
+ * @throws NullPointerException if {@code reference} is null
+ */
+ public static Rate limiters are often used to restrict the rate at which some physical or logical resource
+ * is accessed. This is in contrast to {@link java.util.concurrent.Semaphore} which restricts the
+ * number of concurrent accesses instead of the rate (note though that concurrency and rate are
+ * closely related, e.g. see Little's
+ * Law).
+ *
+ * A {@code RateLimiter} is defined primarily by the rate at which permits are issued. Absent
+ * additional configuration, permits will be distributed at a fixed rate, defined in terms of
+ * permits per second. Permits will be distributed smoothly, with the delay between individual
+ * permits being adjusted to ensure that the configured rate is maintained.
+ *
+ * It is possible to configure a {@code RateLimiter} to have a warmup period during which time
+ * the permits issued each second steadily increases until it hits the stable rate.
+ *
+ * As an example, imagine that we have a list of tasks to execute, but we don't want to submit
+ * more than 2 per second:
+ *
+ * As another example, imagine that we produce a stream of data, and we want to cap it at 5kb per
+ * second. This could be accomplished by requiring a permit per byte, and specifying a rate of 5000
+ * permits per second:
+ *
+ * It is important to note that the number of permits requested never affects the
+ * throttling of the request itself (an invocation to {@code acquire(1)} and an invocation to {@code
+ * acquire(1000)} will result in exactly the same throttling, if any), but it affects the throttling
+ * of the next request. I.e., if an expensive task arrives at an idle RateLimiter, it will be
+ * granted immediately, but it is the next request that will experience extra throttling,
+ * thus paying for the cost of the expensive task.
+ *
+ * Note: {@code RateLimiter} does not provide fairness guarantees.
+ *
+ * @author Dimitris Andreou
+ * @since 13.0
+ */
+// TODO(user): switch to nano precision. A natural unit of cost is "bytes", and a micro precision
+// would mean a maximum rate of "1MB/s", which might be small in some cases.
+public abstract class RateLimiter {
+ /**
+ * Creates a {@code RateLimiter} with the specified stable throughput, given as "permits per
+ * second" (commonly referred to as QPS, queries per second).
+ *
+ * The returned {@code RateLimiter} ensures that on average no more than {@code
+ * permitsPerSecond} are issued during any given second, with sustained requests being smoothly
+ * spread over each second. When the incoming request rate exceeds {@code permitsPerSecond} the
+ * rate limiter will release one permit every {@code (1.0 / permitsPerSecond)} seconds. When the
+ * rate limiter is unused, bursts of up to {@code permitsPerSecond} permits will be allowed, with
+ * subsequent requests being smoothly limited at the stable rate of {@code permitsPerSecond}.
+ *
+ * @param permitsPerSecond the rate of the returned {@code RateLimiter}, measured in how many
+ * permits become available per second
+ * @throws IllegalArgumentException if {@code permitsPerSecond} is negative or zero
+ */
+ // TODO(user): "This is equivalent to
+ // {@code createWithCapacity(permitsPerSecond, 1, TimeUnit.SECONDS)}".
+ public static RateLimiter create(double permitsPerSecond) {
+ /*
+ * The default RateLimiter configuration can save the unused permits of up to one second. This
+ * is to avoid unnecessary stalls in situations like this: A RateLimiter of 1qps, and 4 threads,
+ * all calling acquire() at these moments:
+ *
+ * T0 at 0 seconds
+ * T1 at 1.05 seconds
+ * T2 at 2 seconds
+ * T3 at 3 seconds
+ *
+ * Due to the slight delay of T1, T2 would have to sleep till 2.05 seconds, and T3 would also
+ * have to sleep till 3.05 seconds.
+ */
+ return create(permitsPerSecond, SleepingStopwatch.createFromSystemTimer());
+ }
+
+ static RateLimiter create(double permitsPerSecond, SleepingStopwatch stopwatch) {
+ RateLimiter rateLimiter = new SmoothBursty(stopwatch, 1.0 /* maxBurstSeconds */);
+ rateLimiter.setRate(permitsPerSecond);
+ return rateLimiter;
+ }
+
+ /**
+ * Creates a {@code RateLimiter} with the specified stable throughput, given as "permits per
+ * second" (commonly referred to as QPS, queries per second), and a warmup period,
+ * during which the {@code RateLimiter} smoothly ramps up its rate, until it reaches its maximum
+ * rate at the end of the period (as long as there are enough requests to saturate it). Similarly,
+ * if the {@code RateLimiter} is left unused for a duration of {@code warmupPeriod}, it
+ * will gradually return to its "cold" state, i.e. it will go through the same warming up process
+ * as when it was first created.
+ *
+ * The returned {@code RateLimiter} is intended for cases where the resource that actually
+ * fulfills the requests (e.g., a remote server) needs "warmup" time, rather than being
+ * immediately accessed at the stable (maximum) rate.
+ *
+ * The returned {@code RateLimiter} starts in a "cold" state (i.e. the warmup period will
+ * follow), and if it is left unused for long enough, it will return to that state.
+ *
+ * @param permitsPerSecond the rate of the returned {@code RateLimiter}, measured in how many
+ * permits become available per second
+ * @param warmupPeriod the duration of the period where the {@code RateLimiter} ramps up its rate,
+ * before reaching its stable (maximum) rate
+ * @param unit the time unit of the warmupPeriod argument
+ * @throws IllegalArgumentException if {@code permitsPerSecond} is negative or zero or {@code
+ * warmupPeriod} is negative
+ */
+ public static RateLimiter create(double permitsPerSecond, long warmupPeriod, TimeUnit unit) {
+ checkArgument(warmupPeriod >= 0, "warmupPeriod must not be negative: %s", warmupPeriod);
+ return create(
+ permitsPerSecond, warmupPeriod, unit, 3.0, SleepingStopwatch.createFromSystemTimer());
+ }
+
+ static RateLimiter create(
+ double permitsPerSecond,
+ long warmupPeriod,
+ TimeUnit unit,
+ double coldFactor,
+ SleepingStopwatch stopwatch) {
+ RateLimiter rateLimiter = new SmoothWarmingUp(stopwatch, warmupPeriod, unit, coldFactor);
+ rateLimiter.setRate(permitsPerSecond);
+ return rateLimiter;
+ }
+
+ /**
+ * The underlying timer; used both to measure elapsed time and sleep as necessary. A separate
+ * object to facilitate testing.
+ */
+ private final SleepingStopwatch stopwatch;
+
+ // Can't be initialized in the constructor because mocks don't call the constructor.
+ private volatile Object mutexDoNotUseDirectly;
+
+ private Object mutex() {
+ Object mutex = mutexDoNotUseDirectly;
+ if (mutex == null) {
+ synchronized (this) {
+ mutex = mutexDoNotUseDirectly;
+ if (mutex == null) {
+ mutexDoNotUseDirectly = mutex = new Object();
+ }
+ }
+ }
+ return mutex;
+ }
+
+ RateLimiter(SleepingStopwatch stopwatch) {
+ this.stopwatch = checkNotNull(stopwatch);
+ }
+
+ /**
+ * Updates the stable rate of this {@code RateLimiter}, that is, the {@code permitsPerSecond}
+ * argument provided in the factory method that constructed the {@code RateLimiter}. Currently
+ * throttled threads will not be awakened as a result of this invocation, thus they do not
+ * observe the new rate; only subsequent requests will.
+ *
+ * Note though that, since each request repays (by waiting, if necessary) the cost of the
+ * previous request, this means that the very next request after an invocation to {@code
+ * setRate} will not be affected by the new rate; it will pay the cost of the previous request,
+ * which is in terms of the previous rate.
+ *
+ * The behavior of the {@code RateLimiter} is not modified in any other way, e.g. if the {@code
+ * RateLimiter} was configured with a warmup period of 20 seconds, it still has a warmup period of
+ * 20 seconds after this method invocation.
+ *
+ * @param permitsPerSecond the new stable rate of this {@code RateLimiter}
+ * @throws IllegalArgumentException if {@code permitsPerSecond} is negative or zero
+ */
+ public final void setRate(double permitsPerSecond) {
+ checkArgument(
+ permitsPerSecond > 0.0 && !Double.isNaN(permitsPerSecond), "rate must be positive");
+ synchronized (mutex()) {
+ doSetRate(permitsPerSecond, stopwatch.readMicros());
+ }
+ }
+
+ abstract void doSetRate(double permitsPerSecond, long nowMicros);
+
+ /**
+ * Returns the stable rate (as {@code permits per seconds}) with which this {@code RateLimiter} is
+ * configured with. The initial value of this is the same as the {@code permitsPerSecond} argument
+ * passed in the factory method that produced this {@code RateLimiter}, and it is only updated
+ * after invocations to {@linkplain #setRate}.
+ */
+ public final double getRate() {
+ synchronized (mutex()) {
+ return doGetRate();
+ }
+ }
+
+ abstract double doGetRate();
+
+ /**
+ * Acquires a single permit from this {@code RateLimiter}, blocking until the request can be
+ * granted. Tells the amount of time slept, if any.
+ *
+ * This method is equivalent to {@code acquire(1)}.
+ *
+ * @return time spent sleeping to enforce rate, in seconds; 0.0 if not rate-limited
+ * @since 16.0 (present in 13.0 with {@code void} return type})
+ */
+ public double acquire() {
+ return acquire(1);
+ }
+
+ /**
+ * Acquires the given number of permits from this {@code RateLimiter}, blocking until the request
+ * can be granted. Tells the amount of time slept, if any.
+ *
+ * @param permits the number of permits to acquire
+ * @return time spent sleeping to enforce rate, in seconds; 0.0 if not rate-limited
+ * @throws IllegalArgumentException if the requested number of permits is negative or zero
+ * @since 16.0 (present in 13.0 with {@code void} return type})
+ */
+ public double acquire(int permits) {
+ long microsToWait = reserve(permits);
+ stopwatch.sleepMicrosUninterruptibly(microsToWait);
+ return 1.0 * microsToWait / SECONDS.toMicros(1L);
+ }
+
+ /**
+ * Reserves the given number of permits from this {@code RateLimiter} for future use, returning
+ * the number of microseconds until the reservation can be consumed.
+ *
+ * @return time in microseconds to wait until the resource can be acquired, never negative
+ */
+ final long reserve(int permits) {
+ checkPermits(permits);
+ synchronized (mutex()) {
+ return reserveAndGetWaitLength(permits, stopwatch.readMicros());
+ }
+ }
+
+ /**
+ * Acquires a permit from this {@code RateLimiter} if it can be obtained without exceeding the
+ * specified {@code timeout}, or returns {@code false} immediately (without waiting) if the permit
+ * would not have been granted before the timeout expired.
+ *
+ * This method is equivalent to {@code tryAcquire(1, timeout, unit)}.
+ *
+ * @param timeout the maximum time to wait for the permit. Negative values are treated as zero.
+ * @param unit the time unit of the timeout argument
+ * @return {@code true} if the permit was acquired, {@code false} otherwise
+ * @throws IllegalArgumentException if the requested number of permits is negative or zero
+ */
+ public boolean tryAcquire(long timeout, TimeUnit unit) {
+ return tryAcquire(1, timeout, unit);
+ }
+
+ /**
+ * Acquires permits from this {@link RateLimiter} if it can be acquired immediately without delay.
+ *
+ * This method is equivalent to {@code tryAcquire(permits, 0, anyUnit)}.
+ *
+ * @param permits the number of permits to acquire
+ * @return {@code true} if the permits were acquired, {@code false} otherwise
+ * @throws IllegalArgumentException if the requested number of permits is negative or zero
+ * @since 14.0
+ */
+ public boolean tryAcquire(int permits) {
+ return tryAcquire(permits, 0, MICROSECONDS);
+ }
+
+ /**
+ * Acquires a permit from this {@link RateLimiter} if it can be acquired immediately without
+ * delay.
+ *
+ * This method is equivalent to {@code tryAcquire(1)}.
+ *
+ * @return {@code true} if the permit was acquired, {@code false} otherwise
+ * @since 14.0
+ */
+ public boolean tryAcquire() {
+ return tryAcquire(1, 0, MICROSECONDS);
+ }
+
+ /**
+ * Acquires the given number of permits from this {@code RateLimiter} if it can be obtained
+ * without exceeding the specified {@code timeout}, or returns {@code false} immediately (without
+ * waiting) if the permits would not have been granted before the timeout expired.
+ *
+ * @param permits the number of permits to acquire
+ * @param timeout the maximum time to wait for the permits. Negative values are treated as zero.
+ * @param unit the time unit of the timeout argument
+ * @return {@code true} if the permits were acquired, {@code false} otherwise
+ * @throws IllegalArgumentException if the requested number of permits is negative or zero
+ */
+ public boolean tryAcquire(int permits, long timeout, TimeUnit unit) {
+ long timeoutMicros = max(unit.toMicros(timeout), 0);
+ checkPermits(permits);
+ long microsToWait;
+ synchronized (mutex()) {
+ long nowMicros = stopwatch.readMicros();
+ if (!canAcquire(nowMicros, timeoutMicros)) {
+ return false;
+ } else {
+ microsToWait = reserveAndGetWaitLength(permits, nowMicros);
+ }
+ }
+ stopwatch.sleepMicrosUninterruptibly(microsToWait);
+ return true;
+ }
+
+ private boolean canAcquire(long nowMicros, long timeoutMicros) {
+ return queryEarliestAvailable(nowMicros) - timeoutMicros <= nowMicros;
+ }
+
+ /**
+ * Reserves next ticket and returns the wait time that the caller must wait for.
+ *
+ * @return the required wait time, never negative
+ */
+ final long reserveAndGetWaitLength(int permits, long nowMicros) {
+ long momentAvailable = reserveEarliestAvailable(permits, nowMicros);
+ return max(momentAvailable - nowMicros, 0);
+ }
+
+ /**
+ * Returns the earliest time that permits are available (with one caveat).
+ *
+ * @return the time that permits are available, or, if permits are available immediately, an
+ * arbitrary past or present time
+ */
+ abstract long queryEarliestAvailable(long nowMicros);
+
+ /**
+ * Reserves the requested number of permits and returns the time that those permits can be used
+ * (with one caveat).
+ *
+ * @return the time that the permits may be used, or, if the permits may be used immediately, an
+ * arbitrary past or present time
+ */
+ abstract long reserveEarliestAvailable(int permits, long nowMicros);
+
+ @Override
+ public String toString() {
+ return String.format(Locale.ROOT, "RateLimiter[stableRate=%3.1fqps]", getRate());
+ }
+
+ abstract static class SleepingStopwatch {
+ /** Constructor for use by subclasses. */
+ protected SleepingStopwatch() {}
+
+ /*
+ * We always hold the mutex when calling this. TODO(cpovirk): Is that important? Perhaps we need
+ * to guarantee that each call to reserveEarliestAvailable, etc. sees a value >= the previous?
+ * Also, is it OK that we don't hold the mutex when sleeping?
+ */
+ protected abstract long readMicros();
+
+ protected abstract void sleepMicrosUninterruptibly(long micros);
+
+ public static final SleepingStopwatch createFromSystemTimer() {
+ return new SleepingStopwatch() {
+ final Stopwatch stopwatch = Stopwatch.createStarted();
+
+ @Override
+ protected long readMicros() {
+ return stopwatch.elapsed(MICROSECONDS);
+ }
+
+ @Override
+ protected void sleepMicrosUninterruptibly(long micros) {
+ if (micros > 0) {
+ sleepUninterruptibly(micros, MICROSECONDS);
+ }
+ }
+ };
+ }
+ }
+
+ private static void checkPermits(int permits) {
+ checkArgument(permits > 0, "Requested permits (%s) must be positive", permits);
+ }
+
+ /** Invokes {@code unit.}{@link TimeUnit#sleep(long) sleep(sleepFor)} uninterruptibly. */
+ private static void sleepUninterruptibly(long sleepFor, TimeUnit unit) {
+ boolean interrupted = false;
+ try {
+ long remainingNanos = unit.toNanos(sleepFor);
+ long end = System.nanoTime() + remainingNanos;
+ while (true) {
+ try {
+ // TimeUnit.sleep() treats negative timeouts just like zero.
+ NANOSECONDS.sleep(remainingNanos);
+ return;
+ } catch (InterruptedException e) {
+ interrupted = true;
+ remainingNanos = end - System.nanoTime();
+ }
+ }
+ } finally {
+ if (interrupted) {
+ Thread.currentThread().interrupt();
+ }
+ }
+ }
+}
diff --git a/src/main/java/com/google/maps/internal/ratelimiter/SmoothRateLimiter.java b/src/main/java/com/google/maps/internal/ratelimiter/SmoothRateLimiter.java
new file mode 100644
index 000000000..e909c2f88
--- /dev/null
+++ b/src/main/java/com/google/maps/internal/ratelimiter/SmoothRateLimiter.java
@@ -0,0 +1,404 @@
+/*
+ * Copyright (C) 2012 The Guava Authors
+ *
+ * Licensed 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.
+ */
+/*
+ * Copyright 2017 Google Inc. All rights reserved.
+ *
+ *
+ * Licensed 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 com.google.maps.internal.ratelimiter;
+
+import static java.lang.Math.min;
+import static java.util.concurrent.TimeUnit.SECONDS;
+
+import java.util.concurrent.TimeUnit;
+
+abstract class SmoothRateLimiter extends RateLimiter {
+ /*
+ * How is the RateLimiter designed, and why?
+ *
+ * The primary feature of a RateLimiter is its "stable rate", the maximum rate that is should
+ * allow at normal conditions. This is enforced by "throttling" incoming requests as needed, i.e.
+ * compute, for an incoming request, the appropriate throttle time, and make the calling thread
+ * wait as much.
+ *
+ * The simplest way to maintain a rate of QPS is to keep the timestamp of the last granted
+ * request, and ensure that (1/QPS) seconds have elapsed since then. For example, for a rate of
+ * QPS=5 (5 tokens per second), if we ensure that a request isn't granted earlier than 200ms after
+ * the last one, then we achieve the intended rate. If a request comes and the last request was
+ * granted only 100ms ago, then we wait for another 100ms. At this rate, serving 15 fresh permits
+ * (i.e. for an acquire(15) request) naturally takes 3 seconds.
+ *
+ * It is important to realize that such a RateLimiter has a very superficial memory of the past:
+ * it only remembers the last request. What if the RateLimiter was unused for a long period of
+ * time, then a request arrived and was immediately granted? This RateLimiter would immediately
+ * forget about that past underutilization. This may result in either underutilization or
+ * overflow, depending on the real world consequences of not using the expected rate.
+ *
+ * Past underutilization could mean that excess resources are available. Then, the RateLimiter
+ * should speed up for a while, to take advantage of these resources. This is important when the
+ * rate is applied to networking (limiting bandwidth), where past underutilization typically
+ * translates to "almost empty buffers", which can be filled immediately.
+ *
+ * On the other hand, past underutilization could mean that "the server responsible for handling
+ * the request has become less ready for future requests", i.e. its caches become stale, and
+ * requests become more likely to trigger expensive operations (a more extreme case of this
+ * example is when a server has just booted, and it is mostly busy with getting itself up to
+ * speed).
+ *
+ * To deal with such scenarios, we add an extra dimension, that of "past underutilization",
+ * modeled by "storedPermits" variable. This variable is zero when there is no underutilization,
+ * and it can grow up to maxStoredPermits, for sufficiently large underutilization. So, the
+ * requested permits, by an invocation acquire(permits), are served from:
+ *
+ * - stored permits (if available)
+ *
+ * - fresh permits (for any remaining permits)
+ *
+ * How this works is best explained with an example:
+ *
+ * For a RateLimiter that produces 1 token per second, every second that goes by with the
+ * RateLimiter being unused, we increase storedPermits by 1. Say we leave the RateLimiter unused
+ * for 10 seconds (i.e., we expected a request at time X, but we are at time X + 10 seconds before
+ * a request actually arrives; this is also related to the point made in the last paragraph), thus
+ * storedPermits becomes 10.0 (assuming maxStoredPermits >= 10.0). At that point, a request of
+ * acquire(3) arrives. We serve this request out of storedPermits, and reduce that to 7.0 (how
+ * this is translated to throttling time is discussed later). Immediately after, assume that an
+ * acquire(10) request arriving. We serve the request partly from storedPermits, using all the
+ * remaining 7.0 permits, and the remaining 3.0, we serve them by fresh permits produced by the
+ * rate limiter.
+ *
+ * We already know how much time it takes to serve 3 fresh permits: if the rate is
+ * "1 token per second", then this will take 3 seconds. But what does it mean to serve 7 stored
+ * permits? As explained above, there is no unique answer. If we are primarily interested to deal
+ * with underutilization, then we want stored permits to be given out /faster/ than fresh ones,
+ * because underutilization = free resources for the taking. If we are primarily interested to
+ * deal with overflow, then stored permits could be given out /slower/ than fresh ones. Thus, we
+ * require a (different in each case) function that translates storedPermits to throtting time.
+ *
+ * This role is played by storedPermitsToWaitTime(double storedPermits, double permitsToTake). The
+ * underlying model is a continuous function mapping storedPermits (from 0.0 to maxStoredPermits)
+ * onto the 1/rate (i.e. intervals) that is effective at the given storedPermits. "storedPermits"
+ * essentially measure unused time; we spend unused time buying/storing permits. Rate is
+ * "permits / time", thus "1 / rate = time / permits". Thus, "1/rate" (time / permits) times
+ * "permits" gives time, i.e., integrals on this function (which is what storedPermitsToWaitTime()
+ * computes) correspond to minimum intervals between subsequent requests, for the specified number
+ * of requested permits.
+ *
+ * Here is an example of storedPermitsToWaitTime: If storedPermits == 10.0, and we want 3 permits,
+ * we take them from storedPermits, reducing them to 7.0, and compute the throttling for these as
+ * a call to storedPermitsToWaitTime(storedPermits = 10.0, permitsToTake = 3.0), which will
+ * evaluate the integral of the function from 7.0 to 10.0.
+ *
+ * Using integrals guarantees that the effect of a single acquire(3) is equivalent to {
+ * acquire(1); acquire(1); acquire(1); }, or { acquire(2); acquire(1); }, etc, since the integral
+ * of the function in [7.0, 10.0] is equivalent to the sum of the integrals of [7.0, 8.0], [8.0,
+ * 9.0], [9.0, 10.0] (and so on), no matter what the function is. This guarantees that we handle
+ * correctly requests of varying weight (permits), /no matter/ what the actual function is - so we
+ * can tweak the latter freely. (The only requirement, obviously, is that we can compute its
+ * integrals).
+ *
+ * Note well that if, for this function, we chose a horizontal line, at height of exactly (1/QPS),
+ * then the effect of the function is non-existent: we serve storedPermits at exactly the same
+ * cost as fresh ones (1/QPS is the cost for each). We use this trick later.
+ *
+ * If we pick a function that goes /below/ that horizontal line, it means that we reduce the area
+ * of the function, thus time. Thus, the RateLimiter becomes /faster/ after a period of
+ * underutilization. If, on the other hand, we pick a function that goes /above/ that horizontal
+ * line, then it means that the area (time) is increased, thus storedPermits are more costly than
+ * fresh permits, thus the RateLimiter becomes /slower/ after a period of underutilization.
+ *
+ * Last, but not least: consider a RateLimiter with rate of 1 permit per second, currently
+ * completely unused, and an expensive acquire(100) request comes. It would be nonsensical to just
+ * wait for 100 seconds, and /then/ start the actual task. Why wait without doing anything? A much
+ * better approach is to /allow/ the request right away (as if it was an acquire(1) request
+ * instead), and postpone /subsequent/ requests as needed. In this version, we allow starting the
+ * task immediately, and postpone by 100 seconds future requests, thus we allow for work to get
+ * done in the meantime instead of waiting idly.
+ *
+ * This has important consequences: it means that the RateLimiter doesn't remember the time of the
+ * _last_ request, but it remembers the (expected) time of the _next_ request. This also enables
+ * us to tell immediately (see tryAcquire(timeout)) whether a particular timeout is enough to get
+ * us to the point of the next scheduling time, since we always maintain that. And what we mean by
+ * "an unused RateLimiter" is also defined by that notion: when we observe that the
+ * "expected arrival time of the next request" is actually in the past, then the difference (now -
+ * past) is the amount of time that the RateLimiter was formally unused, and it is that amount of
+ * time which we translate to storedPermits. (We increase storedPermits with the amount of permits
+ * that would have been produced in that idle time). So, if rate == 1 permit per second, and
+ * arrivals come exactly one second after the previous, then storedPermits is _never_ increased --
+ * we would only increase it for arrivals _later_ than the expected one second.
+ */
+
+ /**
+ * This implements the following function where coldInterval = coldFactor * stableInterval.
+ *
+ * In summary, the time it takes to move to the left (spend K permits), is equal to the area of
+ * the function of width == K.
+ *
+ * Assuming we have saturated demand, the time to go from maxPermits to thresholdPermits is
+ * equal to warmupPeriod. And the time to go from thresholdPermits to 0 is warmupPeriod/2. (The
+ * reason that this is warmupPeriod/2 is to maintain the behavior of the original implementation
+ * where coldFactor was hard coded as 3.)
+ *
+ * It remains to calculate thresholdsPermits and maxPermits.
+ *
+ * This always holds: {@code 0 <= permitsToTake <= storedPermits}
+ */
+ abstract long storedPermitsToWaitTime(double storedPermits, double permitsToTake);
+
+ /**
+ * Returns the number of microseconds during cool down that we have to wait to get a new permit.
+ */
+ abstract double coolDownIntervalMicros();
+
+ /** Updates {@code storedPermits} and {@code nextFreeTicketMicros} based on the current time. */
+ void resync(long nowMicros) {
+ // if nextFreeTicket is in the past, resync to now
+ if (nowMicros > nextFreeTicketMicros) {
+ double newPermits = (nowMicros - nextFreeTicketMicros) / coolDownIntervalMicros();
+ storedPermits = min(maxPermits, storedPermits + newPermits);
+ nextFreeTicketMicros = nowMicros;
+ }
+ }
+}
diff --git a/src/main/java/com/google/maps/internal/ratelimiter/Stopwatch.java b/src/main/java/com/google/maps/internal/ratelimiter/Stopwatch.java
new file mode 100644
index 000000000..ebf780e97
--- /dev/null
+++ b/src/main/java/com/google/maps/internal/ratelimiter/Stopwatch.java
@@ -0,0 +1,248 @@
+/*
+ * Copyright (C) 2008 The Guava Authors
+ *
+ * Licensed 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.
+ */
+/*
+ * Copyright 2017 Google Inc. All rights reserved.
+ *
+ *
+ * Licensed 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 com.google.maps.internal.ratelimiter;
+
+import static com.google.maps.internal.ratelimiter.Preconditions.checkNotNull;
+import static com.google.maps.internal.ratelimiter.Preconditions.checkState;
+import static java.util.concurrent.TimeUnit.DAYS;
+import static java.util.concurrent.TimeUnit.HOURS;
+import static java.util.concurrent.TimeUnit.MICROSECONDS;
+import static java.util.concurrent.TimeUnit.MILLISECONDS;
+import static java.util.concurrent.TimeUnit.MINUTES;
+import static java.util.concurrent.TimeUnit.NANOSECONDS;
+import static java.util.concurrent.TimeUnit.SECONDS;
+
+import java.util.concurrent.TimeUnit;
+
+/**
+ * An object that measures elapsed time in nanoseconds. It is useful to measure elapsed time using
+ * this class instead of direct calls to {@link System#nanoTime} for a few reasons:
+ *
+ * Basic usage:
+ *
+ * Stopwatch methods are not idempotent; it is an error to start or stop a stopwatch that is
+ * already in the desired state.
+ *
+ * When testing code that uses this class, use {@link #createUnstarted(Ticker)} or {@link
+ * #createStarted(Ticker)} to supply a fake or mock ticker. This allows you to simulate any valid
+ * behavior of the stopwatch.
+ *
+ * Note: This class is not thread-safe.
+ *
+ * Warning for Android users: a stopwatch with default behavior may not continue to keep
+ * time while the device is asleep. Instead, create one like this:
+ *
+ * Note that the overhead of measurement can be more than a microsecond, so it is generally not
+ * useful to specify {@link TimeUnit#NANOSECONDS} precision here.
+ */
+ public long elapsed(TimeUnit desiredUnit) {
+ return desiredUnit.convert(elapsedNanos(), NANOSECONDS);
+ }
+
+ /** Returns a string representation of the current elapsed time. */
+ @Override
+ public String toString() {
+ long nanos = elapsedNanos();
+
+ TimeUnit unit = chooseUnit(nanos);
+ double value = (double) nanos / NANOSECONDS.convert(1, unit);
+
+ // Too bad this functionality is not exposed as a regular method call
+ return Platform.formatCompact4Digits(value) + " " + abbreviate(unit);
+ }
+
+ private static TimeUnit chooseUnit(long nanos) {
+ if (DAYS.convert(nanos, NANOSECONDS) > 0) {
+ return DAYS;
+ }
+ if (HOURS.convert(nanos, NANOSECONDS) > 0) {
+ return HOURS;
+ }
+ if (MINUTES.convert(nanos, NANOSECONDS) > 0) {
+ return MINUTES;
+ }
+ if (SECONDS.convert(nanos, NANOSECONDS) > 0) {
+ return SECONDS;
+ }
+ if (MILLISECONDS.convert(nanos, NANOSECONDS) > 0) {
+ return MILLISECONDS;
+ }
+ if (MICROSECONDS.convert(nanos, NANOSECONDS) > 0) {
+ return MICROSECONDS;
+ }
+ return NANOSECONDS;
+ }
+
+ private static String abbreviate(TimeUnit unit) {
+ switch (unit) {
+ case NANOSECONDS:
+ return "ns";
+ case MICROSECONDS:
+ return "\u03bcs"; // μs
+ case MILLISECONDS:
+ return "ms";
+ case SECONDS:
+ return "s";
+ case MINUTES:
+ return "min";
+ case HOURS:
+ return "h";
+ case DAYS:
+ return "d";
+ default:
+ throw new AssertionError();
+ }
+ }
+}
diff --git a/src/main/java/com/google/maps/internal/ratelimiter/Ticker.java b/src/main/java/com/google/maps/internal/ratelimiter/Ticker.java
new file mode 100644
index 000000000..bb3576259
--- /dev/null
+++ b/src/main/java/com/google/maps/internal/ratelimiter/Ticker.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2011 The Guava Authors
+ *
+ * Licensed 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.
+ */
+/*
+ * Copyright 2017 Google Inc. All rights reserved.
+ *
+ *
+ * Licensed 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 com.google.maps.internal.ratelimiter;
+
+/**
+ * A time source; returns a time value representing the number of nanoseconds elapsed since some
+ * fixed but arbitrary point in time. Note that most users should use {@link Stopwatch} instead of
+ * interacting with this class directly.
+ *
+ * Warning: this interface can only be used to measure elapsed time, not wall time.
+ *
+ * @author Kevin Bourrillion
+ */
+public abstract class Ticker {
+ /** Constructor for use by subclasses. */
+ protected Ticker() {}
+
+ /** Returns the number of nanoseconds elapsed since this ticker's fixed point of reference. */
+ public abstract long read();
+
+ /**
+ * A ticker that reads the current time using {@link System#nanoTime}.
+ */
+ public static Ticker systemTicker() {
+ return SYSTEM_TICKER;
+ }
+
+ private static final Ticker SYSTEM_TICKER =
+ new Ticker() {
+ @Override
+ public long read() {
+ return Platform.systemNanoTime();
+ }
+ };
+}
{@code
+ * final RateLimiter rateLimiter = RateLimiter.create(2.0); // rate is "2 permits per second"
+ * void submitTasks(List
+ *
+ * {@code
+ * final RateLimiter rateLimiter = RateLimiter.create(5000.0); // rate = 5000 permits per second
+ * void submitPacket(byte[] packet) {
+ * rateLimiter.acquire(packet.length);
+ * networkService.send(packet);
+ * }
+ * }
+ *
+ *
+ * ^ throttling
+ * |
+ * cold + /
+ * interval | /.
+ * | / .
+ * | / . ← "warmup period" is the area of the trapezoid between
+ * | / . thresholdPermits and maxPermits
+ * | / .
+ * | / .
+ * | / .
+ * stable +----------/ WARM .
+ * interval | . UP .
+ * | . PERIOD.
+ * | . .
+ * 0 +----------+-------+--------------→ storedPermits
+ * 0 thresholdPermits maxPermits
+ *
+ *
+ * Before going into the details of this particular function, let's keep in mind the basics:
+ *
+ *
+ *
+ *
+ *
+ *
+ */
+ static final class SmoothWarmingUp extends SmoothRateLimiter {
+ private final long warmupPeriodMicros;
+ /**
+ * The slope of the line from the stable interval (when permits == 0), to the cold interval
+ * (when permits == maxPermits)
+ */
+ private double slope;
+
+ private double thresholdPermits;
+ private double coldFactor;
+
+ SmoothWarmingUp(
+ SleepingStopwatch stopwatch, long warmupPeriod, TimeUnit timeUnit, double coldFactor) {
+ super(stopwatch);
+ this.warmupPeriodMicros = timeUnit.toMicros(warmupPeriod);
+ this.coldFactor = coldFactor;
+ }
+
+ @Override
+ void doSetRate(double permitsPerSecond, double stableIntervalMicros) {
+ double oldMaxPermits = maxPermits;
+ double coldIntervalMicros = stableIntervalMicros * coldFactor;
+ thresholdPermits = 0.5 * warmupPeriodMicros / stableIntervalMicros;
+ maxPermits =
+ thresholdPermits + 2.0 * warmupPeriodMicros / (stableIntervalMicros + coldIntervalMicros);
+ slope = (coldIntervalMicros - stableIntervalMicros) / (maxPermits - thresholdPermits);
+ if (oldMaxPermits == Double.POSITIVE_INFINITY) {
+ // if we don't special-case this, we would get storedPermits == NaN, below
+ storedPermits = 0.0;
+ } else {
+ storedPermits =
+ (oldMaxPermits == 0.0)
+ ? maxPermits // initial state is cold
+ : storedPermits * maxPermits / oldMaxPermits;
+ }
+ }
+
+ @Override
+ long storedPermitsToWaitTime(double storedPermits, double permitsToTake) {
+ double availablePermitsAboveThreshold = storedPermits - thresholdPermits;
+ long micros = 0;
+ // measuring the integral on the right part of the function (the climbing line)
+ if (availablePermitsAboveThreshold > 0.0) {
+ double permitsAboveThresholdToTake = min(availablePermitsAboveThreshold, permitsToTake);
+ // TODO(cpovirk): Figure out a good name for this variable.
+ double length =
+ permitsToTime(availablePermitsAboveThreshold)
+ + permitsToTime(availablePermitsAboveThreshold - permitsAboveThresholdToTake);
+ micros = (long) (permitsAboveThresholdToTake * length / 2.0);
+ permitsToTake -= permitsAboveThresholdToTake;
+ }
+ // measuring the integral on the left part of the function (the horizontal line)
+ micros += (long) (stableIntervalMicros * permitsToTake);
+ return micros;
+ }
+
+ private double permitsToTime(double permits) {
+ return stableIntervalMicros + permits * slope;
+ }
+
+ @Override
+ double coolDownIntervalMicros() {
+ return warmupPeriodMicros / maxPermits;
+ }
+ }
+
+ /**
+ * This implements a "bursty" RateLimiter, where storedPermits are translated to zero throttling.
+ * The maximum number of permits that can be saved (when the RateLimiter is unused) is defined in
+ * terms of time, in this sense: if a RateLimiter is 2qps, and this time is specified as 10
+ * seconds, we can save up to 2 * 10 = 20 permits.
+ */
+ static final class SmoothBursty extends SmoothRateLimiter {
+ /** The work (permits) of how many seconds can be saved up if this RateLimiter is unused? */
+ final double maxBurstSeconds;
+
+ SmoothBursty(SleepingStopwatch stopwatch, double maxBurstSeconds) {
+ super(stopwatch);
+ this.maxBurstSeconds = maxBurstSeconds;
+ }
+
+ @Override
+ void doSetRate(double permitsPerSecond, double stableIntervalMicros) {
+ double oldMaxPermits = this.maxPermits;
+ maxPermits = maxBurstSeconds * permitsPerSecond;
+ if (oldMaxPermits == Double.POSITIVE_INFINITY) {
+ // if we don't special-case this, we would get storedPermits == NaN, below
+ storedPermits = maxPermits;
+ } else {
+ storedPermits =
+ (oldMaxPermits == 0.0)
+ ? 0.0 // initial state
+ : storedPermits * maxPermits / oldMaxPermits;
+ }
+ }
+
+ @Override
+ long storedPermitsToWaitTime(double storedPermits, double permitsToTake) {
+ return 0L;
+ }
+
+ @Override
+ double coolDownIntervalMicros() {
+ return stableIntervalMicros;
+ }
+ }
+
+ /** The currently stored permits. */
+ double storedPermits;
+
+ /** The maximum number of stored permits. */
+ double maxPermits;
+
+ /**
+ * The interval between two unit requests, at our stable rate. E.g., a stable rate of 5 permits
+ * per second has a stable interval of 200ms.
+ */
+ double stableIntervalMicros;
+
+ /**
+ * The time when the next request (no matter its size) will be granted. After granting a request,
+ * this is pushed further in the future. Large requests push this further than small requests.
+ */
+ private long nextFreeTicketMicros = 0L; // could be either in the past or future
+
+ private SmoothRateLimiter(SleepingStopwatch stopwatch) {
+ super(stopwatch);
+ }
+
+ @Override
+ final void doSetRate(double permitsPerSecond, long nowMicros) {
+ resync(nowMicros);
+ double stableIntervalMicros = SECONDS.toMicros(1L) / permitsPerSecond;
+ this.stableIntervalMicros = stableIntervalMicros;
+ doSetRate(permitsPerSecond, stableIntervalMicros);
+ }
+
+ abstract void doSetRate(double permitsPerSecond, double stableIntervalMicros);
+
+ @Override
+ final double doGetRate() {
+ return SECONDS.toMicros(1L) / stableIntervalMicros;
+ }
+
+ @Override
+ final long queryEarliestAvailable(long nowMicros) {
+ return nextFreeTicketMicros;
+ }
+
+ @Override
+ final long reserveEarliestAvailable(int requiredPermits, long nowMicros) {
+ resync(nowMicros);
+ long returnValue = nextFreeTicketMicros;
+ double storedPermitsToSpend = min(requiredPermits, this.storedPermits);
+ double freshPermits = requiredPermits - storedPermitsToSpend;
+ long waitMicros =
+ storedPermitsToWaitTime(this.storedPermits, storedPermitsToSpend)
+ + (long) (freshPermits * stableIntervalMicros);
+
+ this.nextFreeTicketMicros = LongMath.saturatedAdd(nextFreeTicketMicros, waitMicros);
+ this.storedPermits -= storedPermitsToSpend;
+ return returnValue;
+ }
+
+ /**
+ * Translates a specified portion of our currently stored permits which we want to spend/acquire,
+ * into a throttling time. Conceptually, this evaluates the integral of the underlying function we
+ * use, for the range of [(storedPermits - permitsToTake), storedPermits].
+ *
+ *
+ * thresholdPermits = 0.5 * warmupPeriod / stableInterval
+ *
+ *
+ *
+ * maxPermits = thresholdPermits + 2 * warmupPeriod / (stableInterval + coldInterval)
+ *
+ *
+ *
+ *
+ *
+ * {@code
+ * Stopwatch stopwatch = Stopwatch.createStarted();
+ * doSomething();
+ * stopwatch.stop(); // optional
+ *
+ * long millis = stopwatch.elapsed(MILLISECONDS);
+ *
+ * log.info("time: " + stopwatch); // formatted string like "12.3 ms"
+ * }
+ *
+ * {@code
+ * Stopwatch.createStarted(
+ * new Ticker() {
+ * public long read() {
+ * return android.os.SystemClock.elapsedRealtimeNanos();
+ * }
+ * });
+ * }
+ *
+ * @author Kevin Bourrillion
+ */
+public final class Stopwatch {
+ private final Ticker ticker;
+ private boolean isRunning;
+ private long elapsedNanos;
+ private long startTick;
+
+ /**
+ * Creates (but does not start) a new stopwatch using {@link System#nanoTime} as its time source.
+ */
+ public static Stopwatch createUnstarted() {
+ return new Stopwatch();
+ }
+
+ /**
+ * Creates (but does not start) a new stopwatch, using the specified time source.
+ */
+ public static Stopwatch createUnstarted(Ticker ticker) {
+ return new Stopwatch(ticker);
+ }
+
+ /**
+ * Creates (and starts) a new stopwatch using {@link System#nanoTime} as its time source.
+ */
+ public static Stopwatch createStarted() {
+ return new Stopwatch().start();
+ }
+
+ /**
+ * Creates (and starts) a new stopwatch, using the specified time source.
+ */
+ public static Stopwatch createStarted(Ticker ticker) {
+ return new Stopwatch(ticker).start();
+ }
+
+ Stopwatch() {
+ this.ticker = Ticker.systemTicker();
+ }
+
+ Stopwatch(Ticker ticker) {
+ this.ticker = checkNotNull(ticker, "ticker");
+ }
+
+ /**
+ * Returns {@code true} if {@link #start()} has been called on this stopwatch, and {@link #stop()}
+ * has not been called since the last call to {@code start()}.
+ */
+ public boolean isRunning() {
+ return isRunning;
+ }
+
+ /**
+ * Starts the stopwatch.
+ *
+ * @return this {@code Stopwatch} instance
+ * @throws IllegalStateException if the stopwatch is already running.
+ */
+ public Stopwatch start() {
+ checkState(!isRunning, "This stopwatch is already running.");
+ isRunning = true;
+ startTick = ticker.read();
+ return this;
+ }
+
+ /**
+ * Stops the stopwatch. Future reads will return the fixed duration that had elapsed up to this
+ * point.
+ *
+ * @return this {@code Stopwatch} instance
+ * @throws IllegalStateException if the stopwatch is already stopped.
+ */
+ public Stopwatch stop() {
+ long tick = ticker.read();
+ checkState(isRunning, "This stopwatch is already stopped.");
+ isRunning = false;
+ elapsedNanos += tick - startTick;
+ return this;
+ }
+
+ /**
+ * Sets the elapsed time for this stopwatch to zero, and places it in a stopped state.
+ *
+ * @return this {@code Stopwatch} instance
+ */
+ public Stopwatch reset() {
+ elapsedNanos = 0;
+ isRunning = false;
+ return this;
+ }
+
+ private long elapsedNanos() {
+ return isRunning ? ticker.read() - startTick + elapsedNanos : elapsedNanos;
+ }
+
+ /**
+ * Returns the current elapsed time shown on this stopwatch, expressed in the desired time unit,
+ * with any fraction rounded down.
+ *
+ *