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