• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2022 The Android Open Source Project
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 
17 package com.android.adservices.service.common;
18 
19 import android.util.Log;
20 import android.util.Pair;
21 
22 import com.android.adservices.service.Flags;
23 import com.android.adservices.service.FlagsFactory;
24 import com.android.internal.annotations.VisibleForTesting;
25 
26 import com.google.common.util.concurrent.RateLimiter;
27 
28 import java.io.PrintWriter;
29 import java.util.HashMap;
30 import java.util.Locale;
31 import java.util.Map;
32 import java.util.Objects;
33 import java.util.concurrent.ConcurrentHashMap;
34 import java.util.function.Function;
35 
36 /** Class to throttle PPAPI requests. */
37 public final class Throttler {
38 
39     private static final String TAG = Throttler.class.getSimpleName();
40 
41     // Enum for each PP API or entry point that will be throttled.
42     public enum ApiKey {
43         UNKNOWN,
44 
45         // Key to throttle AdId API, based on app package name.
46         ADID_API_APP_PACKAGE_NAME,
47 
48         // Key to throttle AppSetId API, based on app package name.
49         APPSETID_API_APP_PACKAGE_NAME,
50 
51         // Key to throttle Join Custom Audience API
52         FLEDGE_API_JOIN_CUSTOM_AUDIENCE,
53 
54         // Key to throttle Fetch Custom Audience API
55         FLEDGE_API_FETCH_CUSTOM_AUDIENCE,
56 
57         // Key to throttle Leave Custom Audience API
58         FLEDGE_API_LEAVE_CUSTOM_AUDIENCE,
59 
60         // Key to throttle Report impressions API
61         FLEDGE_API_REPORT_IMPRESSIONS,
62 
63         // Key to throttle Report impressions API
64         FLEDGE_API_REPORT_INTERACTION,
65 
66         // Key to throttle Select Ads API
67         FLEDGE_API_SELECT_ADS,
68 
69         // Key to throttle Get Ad Selection Data API
70         FLEDGE_API_GET_AD_SELECTION_DATA,
71         // Key to throttle Persist Ad Selection Result API
72         FLEDGE_API_PERSIST_AD_SELECTION_RESULT,
73 
74         // Key to throttle Schedule Custom Audience Update API
75         FLEDGE_API_SCHEDULE_CUSTOM_AUDIENCE_UPDATE,
76 
77         // Key to throttle Set App Install Advertisers API
78         FLEDGE_API_SET_APP_INSTALL_ADVERTISERS,
79 
80         // Key to throttle FLEDGE updateAdCounterHistogram API
81         FLEDGE_API_UPDATE_AD_COUNTER_HISTOGRAM,
82 
83         // Key to throttle Measurement Deletion Registration API
84         MEASUREMENT_API_DELETION_REGISTRATION,
85 
86         // Key to throttle Measurement Register Source API
87         MEASUREMENT_API_REGISTER_SOURCE,
88 
89         // Key to throttle Measurement Register Trigger API
90         MEASUREMENT_API_REGISTER_TRIGGER,
91 
92         // Key to throttle Measurement Register Web Source API
93         MEASUREMENT_API_REGISTER_WEB_SOURCE,
94 
95         // Key to throttle Measurement Register Web Trigger API
96         MEASUREMENT_API_REGISTER_WEB_TRIGGER,
97 
98         // Key to throttle Measurement Register Sources API
99         MEASUREMENT_API_REGISTER_SOURCES,
100         // Key to throttle updateSignals API
101         PROTECTED_SIGNAL_API_UPDATE_SIGNALS,
102 
103         // Key to throttle Topics API based on the App Package Name.
104         TOPICS_API_APP_PACKAGE_NAME,
105 
106         // Key to throttle Topics API based on the Sdk Name.
107         TOPICS_API_SDK_NAME,
108 
109         // Key to throttle select ads with outcomes api
110         FLEDGE_API_SELECT_ADS_WITH_OUTCOMES,
111     }
112 
113     private static final double DEFAULT_RATE_LIMIT = 1d;
114 
115     // A Map from a Pair<ApiKey, Requester> to its RateLimiter.
116     // The Requester could be a SdkName or an AppPackageName depending on the rate limiting needs.
117     // Example Pair<TOPICS_API, "SomeSdkName">, Pair<TOPICS_API, "SomePackageName">.
118     private final ConcurrentHashMap<Pair<ApiKey, String>, RateLimiter> mSdkRateLimitMap =
119             new ConcurrentHashMap<>();
120 
121     // Used as a configuration to determine the rate limit per API
122     // Example:
123     // - TOPICS_API_SDK_NAME, 1 request per second
124     // - MEASUREMENT_API_REGISTER_SOURCE, 5 requests per second
125     private final Map<ApiKey, Double> mRateLimitPerApiMap = new HashMap<>();
126 
127     // Lazy initialization holder class idiom for static fields as described in Effective Java Item
128     // 83 - this is needed because otherwise the singleton would be initialized in unit tests, even
129     // when they (correctly) call newInstance() instead of getInstance().
130     private static final class FieldHolder {
131         private static final Throttler sSingleton;
132 
133         static {
134             Flags flags = FlagsFactory.getFlags();
Log.v(TAG, "Initializing singleton with " + flags)135             Log.v(TAG, "Initializing singleton with " + flags);
136             sSingleton = new Throttler(flags);
137         }
138     }
139 
140     /** Returns the singleton instance of the Throttler. */
getInstance()141     public static Throttler getInstance() {
142         return FieldHolder.sSingleton;
143     }
144 
145     /** Factory method - should only be used for tests. */
146     @VisibleForTesting
newInstance(Flags flags)147     public static Throttler newInstance(Flags flags) {
148         return new Throttler(flags);
149     }
150 
Throttler(Flags flags)151     private Throttler(Flags flags) {
152         Objects.requireNonNull(flags, "flags cannot be null");
153         setRateLimitPerApiMap(flags);
154     }
155 
156     /**
157      * Acquires a permit for an API and a Requester if it can be acquired immediately without delay.
158      * Example: {@code tryAcquire(TOPICS_API, "SomeSdkName") }
159      *
160      * @return {@code true} if the permit was acquired, {@code false} otherwise
161      */
tryAcquire(ApiKey apiKey, String requester)162     public boolean tryAcquire(ApiKey apiKey, String requester) {
163         // Negative Permits Per Second turns off rate limiting.
164         double permitsPerSecond = mRateLimitPerApiMap.getOrDefault(apiKey, DEFAULT_RATE_LIMIT);
165         if (permitsPerSecond <= 0) {
166             return true;
167         }
168 
169         RateLimiter rateLimiter =
170                 mSdkRateLimitMap.computeIfAbsent(
171                         Pair.create(apiKey, requester), ignored -> create(permitsPerSecond));
172 
173         return rateLimiter.tryAcquire();
174     }
175 
176     /** Configures permits per second per {@link ApiKey} */
setRateLimitPerApiMap(Flags flags)177     private void setRateLimitPerApiMap(Flags flags) {
178         // Set default values first
179         double defaultPermitsPerSecond = flags.getSdkRequestPermitsPerSecond();
180         for (var key : ApiKey.values()) {
181             mRateLimitPerApiMap.put(key, defaultPermitsPerSecond);
182         }
183 
184         // Then override some using flags:
185         double adIdPermitsPerSecond = flags.getAdIdRequestPermitsPerSecond();
186         double appSetIdPermitsPerSecond = flags.getAppSetIdRequestPermitsPerSecond();
187         double registerSource = flags.getMeasurementRegisterSourceRequestPermitsPerSecond();
188         double registerWebSource = flags.getMeasurementRegisterWebSourceRequestPermitsPerSecond();
189         double registerSources = flags.getMeasurementRegisterSourcesRequestPermitsPerSecond();
190         double registerTrigger = flags.getMeasurementRegisterTriggerRequestPermitsPerSecond();
191         double registerWebTrigger = flags.getMeasurementRegisterWebTriggerRequestPermitsPerSecond();
192         double topicsApiAppRequestPermitsPerSecond = flags.getTopicsApiAppRequestPermitsPerSecond();
193         double topicsApiSdkRequestPermitsPerSecond = flags.getTopicsApiSdkRequestPermitsPerSecond();
194         double fledgeJoinCustomAudienceRequestPermitsPerSecond =
195                 flags.getFledgeJoinCustomAudienceRequestPermitsPerSecond();
196         double fledgeFetchAndJoinCustomAudienceRequestPermitsPerSecond =
197                 flags.getFledgeFetchAndJoinCustomAudienceRequestPermitsPerSecond();
198         double fledgeScheduleCustomAudienceUpdateRequestPermitsPerSecond =
199                 flags.getFledgeScheduleCustomAudienceUpdateRequestPermitsPerSecond();
200         double fledgeLeaveCustomAudienceRequestPermitsPerSecond =
201                 flags.getFledgeLeaveCustomAudienceRequestPermitsPerSecond();
202         double fledgeUpdateSignalsRequestPermitsPerSecond =
203                 flags.getFledgeUpdateSignalsRequestPermitsPerSecond();
204         double fledgeSelectAdsRequestPermitsPerSecond =
205                 flags.getFledgeSelectAdsRequestPermitsPerSecond();
206         double fledgeSelectAdsWithOutcomesRequestPermitsPerSecond =
207                 flags.getFledgeSelectAdsWithOutcomesRequestPermitsPerSecond();
208         double fledgeGetAdSelectionDataRequestPermitsPerSecond =
209                 flags.getFledgeGetAdSelectionDataRequestPermitsPerSecond();
210         double fledgeReportImpressionRequestPermitsPerSecond =
211                 flags.getFledgeReportImpressionRequestPermitsPerSecond();
212         double fledgeReportInteractionRequestPermitsPerSecond =
213                 flags.getFledgeReportInteractionRequestPermitsPerSecond();
214         double fledgePersistAdSelectionResultRequestPermitsPerSecond =
215                 flags.getFledgePersistAdSelectionResultRequestPermitsPerSecond();
216         double fledgeSetAppInstallAdvertisersRequestPermitsPerSecond =
217                 flags.getFledgeSetAppInstallAdvertisersRequestPermitsPerSecond();
218         double fledgeUpdateAdCounterHistogramRequestPermitsPerSecond =
219                 flags.getFledgeUpdateAdCounterHistogramRequestPermitsPerSecond();
220 
221         mRateLimitPerApiMap.put(ApiKey.ADID_API_APP_PACKAGE_NAME, adIdPermitsPerSecond);
222         mRateLimitPerApiMap.put(ApiKey.APPSETID_API_APP_PACKAGE_NAME, appSetIdPermitsPerSecond);
223 
224         mRateLimitPerApiMap.put(ApiKey.MEASUREMENT_API_REGISTER_SOURCE, registerSource);
225         mRateLimitPerApiMap.put(ApiKey.MEASUREMENT_API_REGISTER_TRIGGER, registerTrigger);
226         mRateLimitPerApiMap.put(ApiKey.MEASUREMENT_API_REGISTER_WEB_SOURCE, registerWebSource);
227         mRateLimitPerApiMap.put(ApiKey.MEASUREMENT_API_REGISTER_WEB_TRIGGER, registerWebTrigger);
228         mRateLimitPerApiMap.put(ApiKey.MEASUREMENT_API_REGISTER_SOURCES, registerSources);
229 
230         mRateLimitPerApiMap.put(
231                 ApiKey.TOPICS_API_APP_PACKAGE_NAME, topicsApiAppRequestPermitsPerSecond);
232         mRateLimitPerApiMap.put(ApiKey.TOPICS_API_SDK_NAME, topicsApiSdkRequestPermitsPerSecond);
233         mRateLimitPerApiMap.put(
234                 ApiKey.FLEDGE_API_JOIN_CUSTOM_AUDIENCE,
235                 fledgeJoinCustomAudienceRequestPermitsPerSecond);
236         mRateLimitPerApiMap.put(
237                 ApiKey.FLEDGE_API_FETCH_CUSTOM_AUDIENCE,
238                 fledgeFetchAndJoinCustomAudienceRequestPermitsPerSecond);
239         mRateLimitPerApiMap.put(
240                 ApiKey.FLEDGE_API_SCHEDULE_CUSTOM_AUDIENCE_UPDATE,
241                 fledgeScheduleCustomAudienceUpdateRequestPermitsPerSecond);
242         mRateLimitPerApiMap.put(
243                 ApiKey.FLEDGE_API_LEAVE_CUSTOM_AUDIENCE,
244                 fledgeLeaveCustomAudienceRequestPermitsPerSecond);
245         mRateLimitPerApiMap.put(
246                 ApiKey.PROTECTED_SIGNAL_API_UPDATE_SIGNALS,
247                 fledgeUpdateSignalsRequestPermitsPerSecond);
248         mRateLimitPerApiMap.put(
249                 ApiKey.FLEDGE_API_SELECT_ADS, fledgeSelectAdsRequestPermitsPerSecond);
250         mRateLimitPerApiMap.put(
251                 ApiKey.FLEDGE_API_SELECT_ADS_WITH_OUTCOMES,
252                 fledgeSelectAdsWithOutcomesRequestPermitsPerSecond);
253         mRateLimitPerApiMap.put(
254                 ApiKey.FLEDGE_API_GET_AD_SELECTION_DATA,
255                 fledgeGetAdSelectionDataRequestPermitsPerSecond);
256         mRateLimitPerApiMap.put(
257                 ApiKey.FLEDGE_API_REPORT_IMPRESSIONS,
258                 fledgeReportImpressionRequestPermitsPerSecond);
259         mRateLimitPerApiMap.put(
260                 ApiKey.FLEDGE_API_REPORT_INTERACTION,
261                 fledgeReportInteractionRequestPermitsPerSecond);
262         mRateLimitPerApiMap.put(
263                 ApiKey.FLEDGE_API_PERSIST_AD_SELECTION_RESULT,
264                 fledgePersistAdSelectionResultRequestPermitsPerSecond);
265         mRateLimitPerApiMap.put(
266                 ApiKey.FLEDGE_API_SET_APP_INSTALL_ADVERTISERS,
267                 fledgeSetAppInstallAdvertisersRequestPermitsPerSecond);
268         mRateLimitPerApiMap.put(
269                 ApiKey.FLEDGE_API_UPDATE_AD_COUNTER_HISTOGRAM,
270                 fledgeUpdateAdCounterHistogramRequestPermitsPerSecond);
271     }
272 
273     /**
274      * Creates a Burst RateLimiter. This is a workaround since Guava does not support RateLimiter
275      * with initial Burst.
276      *
277      * <p>The RateLimiter is created with {@link Double#POSITIVE_INFINITY} to open all permit slots
278      * immediately. It is immediately overridden to the expected rate based on the permitsPerSecond
279      * parameter. Then {@link RateLimiter#tryAcquire()} is called to use the first acquisition so
280      * the expected bursting rate could kick in on the following calls. This flow enables initial
281      * bursting, multiple simultaneous permits would be acquired as soon as RateLimiter is created.
282      * Otherwise, if only {@link RateLimiter#create(double)} is called, after the 1st call
283      * subsequent request would have to be spread out evenly over 1 second.
284      */
create(double permitsPerSecond)285     private RateLimiter create(double permitsPerSecond) {
286         RateLimiter rateLimiter = RateLimiter.create(Double.POSITIVE_INFINITY);
287         rateLimiter.setRate(permitsPerSecond);
288         boolean unused = rateLimiter.tryAcquire();
289         return rateLimiter;
290     }
291 
292     /** Dump it! */
dump(PrintWriter writer)293     public void dump(PrintWriter writer) {
294         writer.println("Throttler");
295 
296         writer.println("  Rate limit per API");
297         dumpsSortedMap(
298                 writer,
299                 mRateLimitPerApiMap,
300                 entry ->
301                         String.format(
302                                 Locale.ENGLISH,
303                                 "%s: %3.2f",
304                                 entry.getKey(),
305                                 (Double) entry.getValue()));
306 
307         writer.printf("  SDK rate limit per API:");
308         if (mSdkRateLimitMap.isEmpty()) {
309             writer.println(" N/A");
310             return;
311         }
312         writer.println();
313         dumpsSortedMap(
314                 writer,
315                 mSdkRateLimitMap,
316                 entry -> {
317                     Pair<?, ?> pair = (Pair<?, ?>) entry.getKey();
318                     return String.format(
319                             Locale.ENGLISH, "%s.%s: %s", pair.first, pair.second, entry.getValue());
320                 });
321     }
322 
dumpsSortedMap( PrintWriter writer, Map<?, ?> map, Function<Map.Entry<?, ?>, String> entryStringanizer)323     private void dumpsSortedMap(
324             PrintWriter writer,
325             Map<?, ?> map,
326             Function<Map.Entry<?, ?>, String> entryStringanizer) {
327         map.entrySet().stream()
328                 .map(entryStringanizer)
329                 .sorted()
330                 .forEachOrdered(line -> writer.printf("    %s\n", line));
331     }
332 }
333