1 /* 2 * Copyright (C) 2023 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 android.adservices.adselection.AdSelectionConfig; 20 import android.adservices.adselection.AdSelectionOutcome; 21 import android.adservices.clients.adselection.AdSelectionClient; 22 import android.adservices.clients.customaudience.AdvertisingCustomAudienceClient; 23 import android.adservices.common.AdData; 24 import android.adservices.common.AdSelectionSignals; 25 import android.adservices.common.AdTechIdentifier; 26 import android.adservices.customaudience.CustomAudience; 27 import android.adservices.customaudience.TrustedBiddingData; 28 import android.adservices.test.scenario.adservices.utils.SelectAdsFlagRule; 29 import android.adservices.test.scenario.adservices.utils.StaticAdTechServerUtils; 30 import android.content.Context; 31 import android.net.Uri; 32 import android.platform.test.rule.CleanPackageRule; 33 import android.platform.test.rule.KillAppsRule; 34 import android.platform.test.scenario.annotation.Scenario; 35 import android.util.Log; 36 37 import androidx.test.core.app.ApplicationProvider; 38 39 import com.android.adservices.common.AdServicesFlagsSetterRule; 40 import com.android.adservices.common.AdservicesTestHelper; 41 import com.android.compatibility.common.util.ShellUtils; 42 import com.android.modules.utils.build.SdkLevel; 43 44 import com.google.common.base.Stopwatch; 45 import com.google.common.base.Ticker; 46 import com.google.common.collect.ImmutableList; 47 import com.google.common.collect.ImmutableMap; 48 import com.google.common.io.CharStreams; 49 50 import org.json.JSONArray; 51 import org.json.JSONObject; 52 import org.junit.Assert; 53 import org.junit.BeforeClass; 54 import org.junit.Rule; 55 import org.junit.rules.RuleChain; 56 import org.junit.runner.RunWith; 57 import org.junit.runners.JUnit4; 58 59 import java.io.IOException; 60 import java.io.InputStream; 61 import java.io.InputStreamReader; 62 import java.nio.charset.StandardCharsets; 63 import java.time.Instant; 64 import java.time.temporal.ChronoUnit; 65 import java.util.ArrayList; 66 import java.util.List; 67 import java.util.concurrent.Executor; 68 import java.util.concurrent.Executors; 69 import java.util.concurrent.TimeUnit; 70 71 @Scenario 72 @RunWith(JUnit4.class) 73 public class AbstractSelectAdsLatencyTest { 74 protected static final String TAG = "SelectAds"; 75 76 private static final Executor CALLBACK_EXECUTOR = Executors.newCachedThreadPool(); 77 78 private static final Context CONTEXT = ApplicationProvider.getApplicationContext(); 79 protected static final int API_RESPONSE_TIMEOUT_SECONDS = 100; 80 protected static final AdSelectionClient AD_SELECTION_CLIENT = 81 new AdSelectionClient.Builder() 82 .setContext(CONTEXT) 83 .setExecutor(CALLBACK_EXECUTOR) 84 .build(); 85 protected static final AdvertisingCustomAudienceClient CUSTOM_AUDIENCE_CLIENT = 86 new AdvertisingCustomAudienceClient.Builder() 87 .setContext(CONTEXT) 88 .setExecutor(CALLBACK_EXECUTOR) 89 .build(); 90 protected final Ticker mTicker = 91 new Ticker() { 92 public long read() { 93 return android.os.SystemClock.elapsedRealtimeNanos(); 94 } 95 }; 96 protected static List<CustomAudience> sCustomAudiences; 97 98 // Per-test method rules, run in the given order. 99 @Rule 100 public RuleChain rules = 101 RuleChain.outerRule( 102 // CleanPackageRule should not execute after each test method because 103 // there's a chance it interferes with ShowmapSnapshotListener snapshot 104 // at the end of the test, impacting collection of memory metrics for 105 // AdServices process. 106 new CleanPackageRule( 107 AdservicesTestHelper.getAdServicesPackageName(CONTEXT), 108 /* clearOnStarting = */ true, 109 /* clearOnFinished = */ false)) 110 .around( 111 new KillAppsRule( 112 AdservicesTestHelper.getAdServicesPackageName(CONTEXT))) 113 .around(new SelectAdsFlagRule()); 114 115 @Rule 116 public final AdServicesFlagsSetterRule flags = 117 AdServicesFlagsSetterRule.forAllApisEnabledTests().setCompatModeFlags(); 118 119 @BeforeClass setupBeforeClass()120 public static void setupBeforeClass() { 121 StaticAdTechServerUtils.warmupServers(); 122 sCustomAudiences = new ArrayList<>(); 123 } 124 runSelectAds( String customAudienceJson, String adSelectionConfig, String testClassName, String testName)125 protected void runSelectAds( 126 String customAudienceJson, 127 String adSelectionConfig, 128 String testClassName, 129 String testName) 130 throws Exception { 131 // TODO(b/266194876): Clean up CA db entries before starting a test run. Cleaning up CAs 132 // would ensure we run select ads only the CAs added by the test. 133 sCustomAudiences.addAll(readCustomAudiences(customAudienceJson)); 134 joinCustomAudiences(sCustomAudiences); 135 AdSelectionConfig config = readAdSelectionConfig(adSelectionConfig); 136 137 Stopwatch timer = Stopwatch.createStarted(mTicker); 138 AdSelectionOutcome outcome = 139 AD_SELECTION_CLIENT 140 .selectAds(config) 141 .get(API_RESPONSE_TIMEOUT_SECONDS, TimeUnit.SECONDS); 142 timer.stop(); 143 144 Log.i(TAG, generateLogLabel(testClassName, testName, timer.elapsed(TimeUnit.MILLISECONDS))); 145 Assert.assertFalse(outcome.getRenderUri().toString().isEmpty()); 146 } 147 disableJsCache()148 protected void disableJsCache() throws Exception { 149 ShellUtils.runShellCommand( 150 "device_config put adservices fledge_http_cache_enable_js_caching false"); 151 // TODO(b/266194876): Clean up cache db entries when cache is disabled. Cleaning up cache 152 // would ensure we are not occuping memory unnecessarily. 153 } 154 enableJsCache()155 protected void enableJsCache() throws Exception { 156 ShellUtils.runShellCommand( 157 "device_config put adservices fledge_http_cache_enable_js_caching true"); 158 } 159 warmupSingleBuyerProcess()160 protected void warmupSingleBuyerProcess() throws Exception { 161 joinCustomAudiences(readCustomAudiences("CustomAudiencesOneBuyerOneCAOneAd.json")); 162 AD_SELECTION_CLIENT 163 .selectAds(readAdSelectionConfig("AdSelectionConfigOneBuyerOneCAOneAd.json")) 164 .get(API_RESPONSE_TIMEOUT_SECONDS, TimeUnit.SECONDS); 165 } 166 warmupFiveBuyersProcess()167 protected void warmupFiveBuyersProcess() throws Exception { 168 joinCustomAudiences(readCustomAudiences("CustomAudiencesFiveBuyersOneCAFiveAds.json")); 169 AD_SELECTION_CLIENT 170 .selectAds(readAdSelectionConfig("AdSelectionConfigFiveBuyersOneCAFiveAds.json")) 171 .get(API_RESPONSE_TIMEOUT_SECONDS, TimeUnit.SECONDS); 172 } 173 readCustomAudiences(String fileName)174 private ImmutableList<CustomAudience> readCustomAudiences(String fileName) throws Exception { 175 ImmutableList.Builder<CustomAudience> customAudienceBuilder = ImmutableList.builder(); 176 InputStream is = ApplicationProvider.getApplicationContext().getAssets().open(fileName); 177 JSONArray customAudiencesJson = new JSONArray(readString(is)); 178 is.close(); 179 180 for (int i = 0; i < customAudiencesJson.length(); i++) { 181 JSONObject caJson = customAudiencesJson.getJSONObject(i); 182 JSONObject trustedBiddingDataJson = caJson.getJSONObject("trustedBiddingData"); 183 JSONArray trustedBiddingKeysJson = 184 trustedBiddingDataJson.getJSONArray("trustedBiddingKeys"); 185 JSONArray adsJson = caJson.getJSONArray("ads"); 186 187 ImmutableList.Builder<String> biddingKeys = ImmutableList.builder(); 188 for (int index = 0; index < trustedBiddingKeysJson.length(); index++) { 189 biddingKeys.add(trustedBiddingKeysJson.getString(index)); 190 } 191 192 ImmutableList.Builder<AdData> adDatas = ImmutableList.builder(); 193 for (int index = 0; index < adsJson.length(); index++) { 194 JSONObject adJson = adsJson.getJSONObject(index); 195 adDatas.add( 196 new AdData.Builder() 197 .setRenderUri(Uri.parse(adJson.getString("render_uri"))) 198 .setMetadata(adJson.getString("metadata")) 199 .build()); 200 } 201 202 customAudienceBuilder.add( 203 new CustomAudience.Builder() 204 .setBuyer(AdTechIdentifier.fromString(caJson.getString("buyer"))) 205 .setName(caJson.getString("name")) 206 .setActivationTime(Instant.now()) 207 .setExpirationTime(Instant.now().plus(90000, ChronoUnit.SECONDS)) 208 .setDailyUpdateUri(Uri.parse(caJson.getString("dailyUpdateUri"))) 209 .setUserBiddingSignals( 210 AdSelectionSignals.fromString( 211 caJson.getString("userBiddingSignals"))) 212 .setTrustedBiddingData( 213 new TrustedBiddingData.Builder() 214 .setTrustedBiddingKeys(biddingKeys.build()) 215 .setTrustedBiddingUri( 216 Uri.parse( 217 trustedBiddingDataJson.getString( 218 "trustedBiddingUri"))) 219 .build()) 220 .setBiddingLogicUri(Uri.parse(caJson.getString("biddingLogicUri"))) 221 .setAds(adDatas.build()) 222 .build()); 223 } 224 return customAudienceBuilder.build(); 225 } 226 readAdSelectionConfig(String fileName)227 protected AdSelectionConfig readAdSelectionConfig(String fileName) throws Exception { 228 InputStream is = ApplicationProvider.getApplicationContext().getAssets().open(fileName); 229 JSONObject adSelectionConfigJson = new JSONObject(readString(is)); 230 JSONArray buyersJson = adSelectionConfigJson.getJSONArray("custom_audience_buyers"); 231 JSONObject perBuyerSignalsJson = adSelectionConfigJson.getJSONObject("per_buyer_signals"); 232 is.close(); 233 234 ImmutableList.Builder<AdTechIdentifier> buyersBuilder = ImmutableList.builder(); 235 ImmutableMap.Builder<AdTechIdentifier, AdSelectionSignals> perBuyerSignals = 236 ImmutableMap.builder(); 237 for (int i = 0; i < buyersJson.length(); i++) { 238 AdTechIdentifier buyer = AdTechIdentifier.fromString(buyersJson.getString(i)); 239 buyersBuilder.add(buyer); 240 perBuyerSignals.put( 241 buyer, 242 AdSelectionSignals.fromString(perBuyerSignalsJson.getString(buyer.toString()))); 243 } 244 245 return new AdSelectionConfig.Builder() 246 .setSeller(AdTechIdentifier.fromString(adSelectionConfigJson.getString("seller"))) 247 .setDecisionLogicUri( 248 Uri.parse(adSelectionConfigJson.getString("decision_logic_uri"))) 249 .setAdSelectionSignals( 250 AdSelectionSignals.fromString( 251 adSelectionConfigJson.getString("auction_signals"))) 252 .setSellerSignals( 253 AdSelectionSignals.fromString( 254 adSelectionConfigJson.getString("seller_signals"))) 255 .setTrustedScoringSignalsUri( 256 Uri.parse(adSelectionConfigJson.getString("trusted_scoring_signal_uri"))) 257 .setPerBuyerSignals(perBuyerSignals.build()) 258 .setCustomAudienceBuyers(buyersBuilder.build()) 259 .build(); 260 } 261 joinCustomAudiences(List<CustomAudience> customAudiences)262 private void joinCustomAudiences(List<CustomAudience> customAudiences) throws Exception { 263 for (CustomAudience ca : customAudiences) { 264 CUSTOM_AUDIENCE_CLIENT 265 .joinCustomAudience(ca) 266 .get(API_RESPONSE_TIMEOUT_SECONDS, TimeUnit.SECONDS); 267 } 268 } 269 generateLogLabel(String classSimpleName, String testName, long elapsedMs)270 private String generateLogLabel(String classSimpleName, String testName, long elapsedMs) { 271 return "(" 272 + "SELECT_ADS_LATENCY_" 273 + classSimpleName 274 + "#" 275 + testName 276 + ": " 277 + elapsedMs 278 + " ms)"; 279 } 280 readString(InputStream inputStream)281 private String readString(InputStream inputStream) throws IOException { 282 // readAllBytes() was added in API level 33. As a result, when this test executes on S-, we 283 // will need a workaround to process the InputStream. 284 return SdkLevel.isAtLeastT() 285 ? new String(inputStream.readAllBytes()) 286 : CharStreams.toString(new InputStreamReader(inputStream, StandardCharsets.UTF_8)); 287 } 288 } 289