Skip to content

Commit a70ec5e

Browse files
committed
Fix fireperf RateLimiter
1 parent f23b397 commit a70ec5e

File tree

5 files changed

+378
-109
lines changed

5 files changed

+378
-109
lines changed

firebase-perf/src/main/java/com/google/firebase/perf/transport/RateLimiter.java

Lines changed: 30 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616

1717
import static com.google.firebase.perf.metrics.resource.ResourceType.NETWORK;
1818
import static com.google.firebase.perf.metrics.resource.ResourceType.TRACE;
19+
import static java.util.concurrent.TimeUnit.SECONDS;
1920

2021
import android.content.Context;
2122
import androidx.annotation.NonNull;
@@ -25,6 +26,7 @@
2526
import com.google.firebase.perf.metrics.resource.ResourceType;
2627
import com.google.firebase.perf.util.Clock;
2728
import com.google.firebase.perf.util.Constants;
29+
import com.google.firebase.perf.util.Rate;
2830
import com.google.firebase.perf.util.Timer;
2931
import com.google.firebase.perf.util.Utils;
3032
import com.google.firebase.perf.v1.NetworkRequestMetric;
@@ -34,7 +36,6 @@
3436
import com.google.firebase.perf.v1.TraceMetric;
3537
import java.util.List;
3638
import java.util.Random;
37-
import java.util.concurrent.TimeUnit;
3839

3940
/**
4041
* Implement the Token Bucket rate limiting algorithm. The token bucket initially holds "capacity"
@@ -60,10 +61,10 @@ final class RateLimiter {
6061
* Construct a token bucket rate limiter.
6162
*
6263
* @param appContext the application's context.
63-
* @param rate number of token generated per minute.
64+
* @param rate the Rate object representing the number of tokens generated per specified time unit
6465
* @param capacity token bucket capacity
6566
*/
66-
public RateLimiter(@NonNull Context appContext, final double rate, final long capacity) {
67+
public RateLimiter(@NonNull Context appContext, final Rate rate, final long capacity) {
6768
this(rate, capacity, new Clock(), getSamplingBucketId(), ConfigResolver.getInstance());
6869
this.isLogcatEnabled = Utils.isDebugLoggingEnabled(appContext);
6970
}
@@ -75,7 +76,7 @@ static float getSamplingBucketId() {
7576
}
7677

7778
RateLimiter(
78-
final double rate,
79+
final Rate rate,
7980
final long capacity,
8081
final Clock clock,
8182
float samplingBucketId,
@@ -211,29 +212,29 @@ boolean getIsDeviceAllowedToSendNetworkEvents() {
211212
static class RateLimiterImpl {
212213

213214
private static final AndroidLogger logger = AndroidLogger.getInstance();
214-
private static final long MICROS_IN_A_SECOND = TimeUnit.SECONDS.toMicros(1);
215+
private static final long MICROS_IN_A_SECOND = SECONDS.toMicros(1);
215216

216217
private final Clock clock;
217218
private final boolean isLogcatEnabled;
218219

219220
// Last time a token is consumed.
220-
private Timer lastTimeTokenConsumed;
221+
private Timer lastTimeTokenReplenished;
221222

222-
// Number of new tokens generated per second.
223-
private double rate;
223+
// The Rate object representing the number of tokens generated per specified time unit
224+
private Rate rate;
224225
// Token bucket capacity, also the initial number of tokens in the bucket.
225226
private long capacity;
226227
// Number of tokens in the bucket.
227228
private long tokenCount;
228229

229-
private double foregroundRate;
230-
private double backgroundRate;
230+
private Rate foregroundRate;
231+
private Rate backgroundRate;
231232

232233
private long foregroundCapacity;
233234
private long backgroundCapacity;
234235

235236
RateLimiterImpl(
236-
final double rate,
237+
final Rate rate,
237238
final long capacity,
238239
final Clock clock,
239240
ConfigResolver configResolver,
@@ -243,7 +244,7 @@ static class RateLimiterImpl {
243244
this.capacity = capacity;
244245
this.rate = rate;
245246
tokenCount = capacity;
246-
lastTimeTokenConsumed = this.clock.getTime();
247+
lastTimeTokenReplenished = this.clock.getTime();
247248
setRateByReadingRemoteConfigValues(configResolver, type, isLogcatEnabled);
248249
this.isLogcatEnabled = isLogcatEnabled;
249250
}
@@ -260,11 +261,20 @@ synchronized boolean check(@NonNull PerfMetric metric) {
260261
Timer now = clock.getTime();
261262
long newTokens =
262263
Math.max(
263-
0, (long) (lastTimeTokenConsumed.getDurationMicros(now) * rate / MICROS_IN_A_SECOND));
264+
0,
265+
(long)
266+
(lastTimeTokenReplenished.getDurationMicros(now)
267+
* rate.getTokenPerSeconds()
268+
/ MICROS_IN_A_SECOND));
264269
tokenCount = Math.min(tokenCount + newTokens, capacity);
270+
if (newTokens > 0) {
271+
lastTimeTokenReplenished =
272+
new Timer(
273+
lastTimeTokenReplenished.getMicros()
274+
+ (long) (newTokens * MICROS_IN_A_SECOND / rate.getTokenPerSeconds()));
275+
}
265276
if (tokenCount > 0) {
266277
tokenCount--;
267-
lastTimeTokenConsumed = now;
268278
return true;
269279
}
270280
if (isLogcatEnabled) {
@@ -297,7 +307,7 @@ private void setRateByReadingRemoteConfigValues(
297307
long fLimitTime = getFlimitSec(configResolver, type);
298308
long fLimitEvents = getFlimitEvents(configResolver, type);
299309

300-
foregroundRate = ((double) fLimitEvents) / fLimitTime;
310+
foregroundRate = new Rate(fLimitEvents, fLimitTime, SECONDS);
301311
foregroundCapacity = fLimitEvents;
302312
if (isLogcatEnabled) {
303313
logger.debug(
@@ -309,7 +319,7 @@ private void setRateByReadingRemoteConfigValues(
309319
long bLimitTime = getBlimitSec(configResolver, type);
310320
long bLimitEvents = getBlimitEvents(configResolver, type);
311321

312-
backgroundRate = ((double) bLimitEvents) / bLimitTime;
322+
backgroundRate = new Rate(bLimitEvents, bLimitTime, SECONDS);
313323
backgroundCapacity = bLimitEvents;
314324
if (isLogcatEnabled) {
315325
logger.debug(
@@ -350,7 +360,7 @@ private static long getBlimitEvents(
350360
}
351361

352362
@VisibleForTesting
353-
double getForegroundRate() {
363+
Rate getForegroundRate() {
354364
return foregroundRate;
355365
}
356366

@@ -360,7 +370,7 @@ long getForegroundCapacity() {
360370
}
361371

362372
@VisibleForTesting
363-
double getBackgroundRate() {
373+
Rate getBackgroundRate() {
364374
return backgroundRate;
365375
}
366376

@@ -370,12 +380,12 @@ long getBackgroundCapacity() {
370380
}
371381

372382
@VisibleForTesting
373-
double getRate() {
383+
Rate getRate() {
374384
return rate;
375385
}
376386

377387
@VisibleForTesting
378-
void setRate(double newRate) {
388+
void setRate(Rate newRate) {
379389
rate = newRate;
380390
}
381391
}

firebase-perf/src/main/java/com/google/firebase/perf/transport/TransportManager.java

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
package com.google.firebase.perf.transport;
1616

1717
import static java.util.concurrent.TimeUnit.MILLISECONDS;
18+
import static java.util.concurrent.TimeUnit.MINUTES;
1819

1920
import android.content.Context;
2021
import android.content.pm.PackageInfo;
@@ -39,6 +40,7 @@
3940
import com.google.firebase.perf.session.SessionManager;
4041
import com.google.firebase.perf.util.Constants;
4142
import com.google.firebase.perf.util.Constants.CounterNames;
43+
import com.google.firebase.perf.util.Rate;
4244
import com.google.firebase.perf.v1.AndroidApplicationInfo;
4345
import com.google.firebase.perf.v1.ApplicationInfo;
4446
import com.google.firebase.perf.v1.ApplicationProcessState;
@@ -202,7 +204,9 @@ public void initialize(
202204
private void syncInit() {
203205
appContext = firebaseApp.getApplicationContext();
204206
configResolver = ConfigResolver.getInstance();
205-
rateLimiter = new RateLimiter(appContext, Constants.RATE_PER_MINUTE, Constants.BURST_CAPACITY);
207+
rateLimiter =
208+
new RateLimiter(
209+
appContext, new Rate(Constants.RATE_PER_MINUTE, 1, MINUTES), Constants.BURST_CAPACITY);
206210
appStateMonitor = AppStateMonitor.getInstance();
207211
flgTransport =
208212
new FlgTransport(flgTransportFactoryProvider, configResolver.getAndCacheLogSourceName());
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
// Copyright 2021 Google LLC
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package com.google.firebase.perf.util;
16+
17+
import java.util.concurrent.TimeUnit;
18+
19+
/** A Rate object representing the number of tokens per total specified time unit. */
20+
public class Rate {
21+
22+
private static final long NANO_IN_SECOND = 1000 * 1000 * 1000;
23+
private static final long MICRO_IN_SECOND = 1000 * 1000;
24+
private static final long MILLI_IN_SECOND = 1000;
25+
26+
private long numTokensPerTotalTimeUnit;
27+
private long numTimeUnits;
28+
private TimeUnit timeUnit;
29+
30+
/**
31+
* Constructs a Rate object.
32+
*
33+
* <p>For example, a rate of 3 tokens per minute can be represented by new Rate(3, 1, MINUTES) or
34+
* new Rate(3, 60, SECONDS).
35+
*
36+
* @param numTokensPerTotalTimeUnit The number of tokens to be issued within the specified time
37+
* @param numTimeUnits The number of specified time unit
38+
* @param timeUnit The specified time unit
39+
*/
40+
public Rate(long numTokensPerTotalTimeUnit, long numTimeUnits, TimeUnit timeUnit) {
41+
assert numTimeUnits > 0;
42+
this.numTokensPerTotalTimeUnit = numTokensPerTotalTimeUnit;
43+
this.numTimeUnits = numTimeUnits;
44+
this.timeUnit = timeUnit;
45+
}
46+
47+
/**
48+
* Converts the rate to tokens per second.
49+
*
50+
* @return rate in tokens per second
51+
*/
52+
public double getTokenPerSeconds() {
53+
switch (timeUnit) {
54+
case NANOSECONDS:
55+
return ((double) numTokensPerTotalTimeUnit / numTimeUnits) * NANO_IN_SECOND;
56+
case MICROSECONDS:
57+
return ((double) numTokensPerTotalTimeUnit / numTimeUnits) * MICRO_IN_SECOND;
58+
case MILLISECONDS:
59+
return ((double) numTokensPerTotalTimeUnit / numTimeUnits) * MILLI_IN_SECOND;
60+
default:
61+
return (double) numTokensPerTotalTimeUnit / timeUnit.toSeconds(numTimeUnits);
62+
}
63+
}
64+
}

0 commit comments

Comments
 (0)