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