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 android.adservices.test.scenario.adservices.fledge; 18 19 import static android.adservices.customaudience.TrustedBiddingDataFixture.getValidTrustedBiddingUriByBuyer; 20 21 import android.adservices.adselection.AdSelectionConfig; 22 import android.adservices.adselection.AdSelectionOutcome; 23 import android.adservices.adselection.ReportImpressionRequest; 24 import android.adservices.common.AdData; 25 import android.adservices.common.AdSelectionSignals; 26 import android.adservices.common.AdTechIdentifier; 27 import android.adservices.common.CommonFixture; 28 import android.adservices.customaudience.CustomAudience; 29 import android.adservices.customaudience.TrustedBiddingData; 30 import android.net.Uri; 31 import android.platform.test.scenario.annotation.Scenario; 32 import android.util.Log; 33 34 import org.junit.Assert; 35 import org.junit.Test; 36 import org.junit.runner.RunWith; 37 import org.junit.runners.JUnit4; 38 39 import java.time.Duration; 40 import java.time.Instant; 41 import java.util.ArrayList; 42 import java.util.Collections; 43 import java.util.List; 44 import java.util.concurrent.TimeUnit; 45 46 @Scenario 47 @RunWith(JUnit4.class) 48 public class LimitPerfTests extends AbstractSelectAdsLatencyTest { 49 private static final String URL_PREFIX = "https://"; 50 private static final String BUYER = "performance-fledge-static-5jyy5ulagq-uc.a.run.app"; 51 public static final String BUYER_BIDDING_LOGIC_URI_PATH = "/buyer/bidding/simple_logic"; 52 public static final String BUYER_BIDDING_SIGNALS_URI_PATH = "/rb/bts"; 53 54 @Test test_joinBigCustomAudience()55 public void test_joinBigCustomAudience() throws Exception { 56 joinAndLeaveNBigCas(1); 57 } 58 59 @Test test_join100BigCustomAudiences()60 public void test_join100BigCustomAudiences() throws Exception { 61 // Will take 3+ minutes to run 62 joinAndLeaveNBigCas(100); 63 } 64 65 @Test test_auctionBigCa()66 public void test_auctionBigCa() throws Exception { 67 auctionNBigCas(1); 68 } 69 70 @Test test_auction10BigCas()71 public void test_auction10BigCas() throws Exception { 72 auctionNBigCas(10); 73 } 74 75 @Test test_auction100BigCas()76 public void test_auction100BigCas() throws Exception { 77 // Will take 3+ minutes to run 78 auctionNBigCas(100); 79 } 80 81 @Test test_100Auctions100BigCas()82 public void test_100Auctions100BigCas() throws Exception { 83 // Will take 5+ minutes to run 84 nAuctionsMBigCas(100, 100); 85 } 86 87 auctionNBigCas(int n)88 private void auctionNBigCas(int n) throws Exception { 89 nAuctionsMBigCas(1, n); 90 } 91 nAuctionsMBigCas(int n, int m)92 private void nAuctionsMBigCas(int n, int m) throws Exception { 93 warmupSingleBuyerProcess(); 94 AdSelectionConfig adSelectionConfig = 95 readAdSelectionConfig("AdSelectionConfigOneBuyerOneCAOneAd.json"); 96 List<CustomAudience> caList = createNBigCas(m); 97 joinAll(caList); 98 99 for (int i = 0; i < n; i++) { 100 AdSelectionOutcome outcome = 101 AD_SELECTION_CLIENT 102 .selectAds(adSelectionConfig) 103 .get(API_RESPONSE_TIMEOUT_SECONDS, TimeUnit.SECONDS); 104 105 // Check that a valid URL won 106 Assert.assertEquals( 107 adSelectionConfig.getCustomAudienceBuyers().get(0).toString(), 108 outcome.getRenderUri().getHost()); 109 110 ReportImpressionRequest reportImpressionRequest = 111 new ReportImpressionRequest(outcome.getAdSelectionId(), adSelectionConfig); 112 // Performing reporting, and asserting that no exception is thrown 113 AD_SELECTION_CLIENT 114 .reportImpression(reportImpressionRequest) 115 .get(API_RESPONSE_TIMEOUT_SECONDS, TimeUnit.SECONDS); 116 117 if ((i + 1) % 10 == 0) { 118 Log.i(TAG, "Completed " + (i + 1) + " auctions"); 119 } 120 } 121 122 leaveAll(caList); 123 } 124 joinAndLeaveNBigCas(int n)125 private void joinAndLeaveNBigCas(int n) throws Exception { 126 List<CustomAudience> caList = createNBigCas(n); 127 joinAll(caList); 128 leaveAll(caList); 129 } 130 joinAll(List<CustomAudience> caList)131 private void joinAll(List<CustomAudience> caList) throws Exception { 132 for (CustomAudience ca : caList) { 133 CUSTOM_AUDIENCE_CLIENT 134 .joinCustomAudience(ca) 135 .get(API_RESPONSE_TIMEOUT_SECONDS, TimeUnit.SECONDS); 136 } 137 } 138 leaveAll(List<CustomAudience> caList)139 private void leaveAll(List<CustomAudience> caList) throws Exception { 140 for (CustomAudience ca : caList) { 141 CUSTOM_AUDIENCE_CLIENT 142 .leaveCustomAudience(ca.getBuyer(), ca.getName()) 143 .get(API_RESPONSE_TIMEOUT_SECONDS, TimeUnit.SECONDS); 144 } 145 } 146 createNBigCas(int n)147 private List<CustomAudience> createNBigCas(int n) { 148 List<CustomAudience> caList = new ArrayList<>(); 149 for (int i = 0; i < n; i++) { 150 caList.add(createBigCustomAudience("" + i, i + 1)); 151 } 152 return caList; 153 } 154 /** 155 * Creates as large of a CA as possible with a name prefixed by nameStart 156 * 157 * @param nameStart The start of the CA name, the name will be padded out the maximum length 158 * @param bid How much all the ads in the CA should bid 159 * @return The large CA. 160 */ createBigCustomAudience(String nameStart, int bid)161 private CustomAudience createBigCustomAudience(String nameStart, int bid) { 162 int minUrlSize = URL_PREFIX.length() + BUYER.length(); 163 Uri trustedBiddingUri = CommonFixture.getUri(BUYER, BUYER_BIDDING_SIGNALS_URI_PATH); 164 getValidTrustedBiddingUriByBuyer(AdTechIdentifier.fromString(BUYER)); 165 return new CustomAudience.Builder() 166 // CA names are limited to 200 bytes 167 .setName(nameStart + repeatCompatImpl("a", 200 - nameStart.length())) 168 .setActivationTime(Instant.now()) 169 .setExpirationTime(Instant.now().plus(Duration.ofDays(1))) 170 // Daily update and bidding URLS are limited to 400 bytes 171 .setDailyUpdateUri(nBitUriFromAdtech(BUYER, 400)) 172 .setBiddingLogicUri(CommonFixture.getUri(BUYER, BUYER_BIDDING_LOGIC_URI_PATH)) 173 // User bidding signals are limited to 10,000 bytes 174 .setUserBiddingSignals(nBitAdSelectionSignals(10000)) 175 /* TrustedBidding signals are limited to 10 KiB. We're adding as many keys objects 176 * as possible to increase object overhead. 177 */ 178 .setTrustedBiddingData( 179 new TrustedBiddingData.Builder() 180 .setTrustedBiddingUri(trustedBiddingUri) 181 .setTrustedBiddingKeys( 182 Collections.nCopies( 183 10000 - trustedBiddingUri.toString().length(), "a")) 184 .build()) 185 .setBuyer(AdTechIdentifier.fromString(BUYER)) 186 // Maximum size is 10,000 bytes for each ad and 100 ads. Making 100 ads of size 100. 187 .setAds( 188 Collections.nCopies( 189 100, 190 new AdData.Builder() 191 .setRenderUri(nBitUriFromAdtech(BUYER, minUrlSize)) 192 .setMetadata( 193 nBitJsonWithFields( 194 100 - minUrlSize, "\"bid\": " + bid)) 195 .build())) 196 .build(); 197 } 198 nBitAdSelectionSignals(int n)199 private AdSelectionSignals nBitAdSelectionSignals(int n) { 200 return AdSelectionSignals.fromString(nBitJson(n)); 201 } 202 nBitJson(int n)203 private String nBitJson(int n) { 204 if (n < 8) { 205 throw new IllegalArgumentException("n too small"); 206 } 207 return "{\"a\":\"" + repeatCompatImpl("a", n - 8) + "\"}"; 208 } 209 nBitJsonWithFields(int n, String fields)210 private String nBitJsonWithFields(int n, String fields) { 211 if (n < 8) { 212 throw new IllegalArgumentException("n too small"); 213 } 214 215 return "{" 216 + fields 217 + ",\"a\":\"" 218 + repeatCompatImpl("a", n - (9 + fields.length())) 219 + "\"}"; 220 } 221 nBitUriFromAdtech(String adtech, int n)222 private Uri nBitUriFromAdtech(String adtech, int n) { 223 String uriStart = URL_PREFIX + adtech; 224 if (n < uriStart.length()) { 225 throw new IllegalArgumentException("n too small "); 226 } else if (n == uriStart.length()) { 227 return Uri.parse(uriStart); 228 } else { 229 return Uri.parse(uriStart + "#" + repeatCompatImpl("a", n - 3 - uriStart.length())); 230 } 231 } 232 nBitUriFromUri(Uri uri, int n)233 private Uri nBitUriFromUri(Uri uri, int n) { 234 if (n < uri.toString().length()) { 235 throw new IllegalArgumentException("n too small "); 236 } else if (n == uri.toString().length()) { 237 return uri; 238 } else { 239 return Uri.parse(uri + "#" + repeatCompatImpl("a", n - 3 - uri.toString().length())); 240 } 241 } 242 243 /** 244 * Since we run the test on both Android S and T, this util method provides a 245 * backward-compatible way to concatenate a string N times without using Java 11 repeat String 246 * method. 247 */ repeatCompatImpl(String str, int numTimes)248 private static String repeatCompatImpl(String str, int numTimes) { 249 StringBuilder sb = new StringBuilder(); 250 251 for (int num = 0; num < numTimes; num++) { 252 sb.append(str); 253 } 254 255 return sb.toString(); 256 } 257 } 258