blob: f3427740579b8055f2e6905777e1be8156c782fa [file] [log] [blame]
/*
* Copyright (C) 2022 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package android.adservices.test.scenario.adservices.fledge;
import android.Manifest;
import android.adservices.adselection.AdSelectionConfig;
import android.adservices.adselection.AdSelectionOutcome;
import android.adservices.clients.adselection.AdSelectionClient;
import android.adservices.clients.customaudience.AdvertisingCustomAudienceClient;
import android.adservices.common.AdData;
import android.adservices.common.AdSelectionSignals;
import android.adservices.common.AdTechIdentifier;
import android.adservices.customaudience.CustomAudience;
import android.adservices.customaudience.TrustedBiddingData;
import android.adservices.test.scenario.adservices.utils.StaticAdTechServerUtils;
import android.content.Context;
import android.net.Uri;
import android.platform.test.scenario.annotation.Scenario;
import android.util.Log;
import androidx.test.core.app.ApplicationProvider;
import androidx.test.platform.app.InstrumentationRegistry;
import com.android.compatibility.common.util.ShellUtils;
import com.google.common.base.Stopwatch;
import com.google.common.base.Ticker;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import org.json.JSONArray;
import org.json.JSONObject;
import org.junit.After;
import org.junit.AfterClass;
import org.junit.Assert;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
import java.io.InputStream;
import java.time.Instant;
import java.time.temporal.ChronoUnit;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
@Scenario
@RunWith(JUnit4.class)
public class SelectAdsTestServerLatency {
private static final String TAG = "SelectAds";
private static final Executor CALLBACK_EXECUTOR = Executors.newCachedThreadPool();
private static final int API_RESPONSE_TIMEOUT_SECONDS = 100;
private static final int DELAY_TO_AVOID_THROTTLE = 1001;
// The number of ms to sleep after killing the adservices process so it has time to recover
public static final long SLEEP_MS_AFTER_KILL = 2000L;
// Command to kill the adservices process
public static final String KILL_ADSERVICES_CMD =
"su 0 killall -9 com.google.android.adservices.api";
// Command prevent activity manager from backing off on restarting the adservices process
public static final String DISABLE_ADSERVICES_BACKOFF_CMD =
"am service-restart-backoff disable com.google.android.adservices.api";
private static final Context CONTEXT = ApplicationProvider.getApplicationContext();
private static final AdSelectionClient AD_SELECTION_CLIENT =
new AdSelectionClient.Builder()
.setContext(CONTEXT)
.setExecutor(CALLBACK_EXECUTOR)
.build();
private static final AdvertisingCustomAudienceClient CUSTOM_AUDIENCE_CLIENT =
new AdvertisingCustomAudienceClient.Builder()
.setContext(CONTEXT)
.setExecutor(CALLBACK_EXECUTOR)
.build();
private final Ticker mTicker =
new Ticker() {
public long read() {
return android.os.SystemClock.elapsedRealtimeNanos();
}
};
private static List<CustomAudience> sCustomAudiences;
@BeforeClass
public static void setupBeforeClass() {
StaticAdTechServerUtils.warmupServers();
sCustomAudiences = new ArrayList<>();
// Disable backoff since we will be killing the process between tests
ShellUtils.runShellCommand(DISABLE_ADSERVICES_BACKOFF_CMD);
InstrumentationRegistry.getInstrumentation()
.getUiAutomation()
.adoptShellPermissionIdentity(Manifest.permission.WRITE_DEVICE_CONFIG);
ShellUtils.runShellCommand(
"device_config put adservices fledge_ad_selection_bidding_timeout_per_ca_ms "
+ "120000");
ShellUtils.runShellCommand(
"device_config put adservices fledge_ad_selection_scoring_timeout_ms 120000");
ShellUtils.runShellCommand(
"device_config put adservices fledge_ad_selection_overall_timeout_ms 120000");
ShellUtils.runShellCommand(
"device_config put adservices fledge_ad_selection_bidding_timeout_per_buyer_ms "
+ "120000");
ShellUtils.runShellCommand("setprop debug.adservices.disable_fledge_enrollment_check true");
ShellUtils.runShellCommand("device_config put adservices global_kill_switch false");
ShellUtils.runShellCommand(
"device_config put adservices adservice_system_service_enabled true");
}
@AfterClass
public static void tearDownAfterClass() throws Exception {
for (CustomAudience ca : sCustomAudiences) {
Thread.sleep(DELAY_TO_AVOID_THROTTLE);
CUSTOM_AUDIENCE_CLIENT
.leaveCustomAudience(ca.getBuyer(), ca.getName())
.get(API_RESPONSE_TIMEOUT_SECONDS, TimeUnit.SECONDS);
}
}
@Before
public void setup() throws Exception {
ShellUtils.runShellCommand(KILL_ADSERVICES_CMD);
Thread.sleep(SLEEP_MS_AFTER_KILL);
}
@After
public void tearDown() throws Exception {
List<CustomAudience> removedCAs = new ArrayList<>();
try {
for (CustomAudience ca : sCustomAudiences) {
Thread.sleep(DELAY_TO_AVOID_THROTTLE);
CUSTOM_AUDIENCE_CLIENT
.leaveCustomAudience(ca.getBuyer(), ca.getName())
.get(API_RESPONSE_TIMEOUT_SECONDS, TimeUnit.SECONDS);
removedCAs.add(ca);
}
} finally {
sCustomAudiences.removeAll(removedCAs);
}
}
@Test
public void selectAds_oneBuyerLargeCAs() throws Exception {
// 1 Seller, 1 Buyer, 71 Custom Audiences
sCustomAudiences.addAll(readCustomAudiences("CustomAudiencesOneBuyerLargeCAs.json"));
joinCustomAudiences(sCustomAudiences);
AdSelectionConfig config = readAdSelectionConfig("AdSelectionConfigOneBuyerLargeCAs.json");
Stopwatch timer = Stopwatch.createStarted(mTicker);
AdSelectionOutcome outcome =
AD_SELECTION_CLIENT
.selectAds(config)
.get(API_RESPONSE_TIMEOUT_SECONDS, TimeUnit.SECONDS);
timer.stop();
Log.i(
TAG,
"("
+ generateLogLabel("selectAds_oneBuyerLargeCAs")
+ ": "
+ timer.elapsed(TimeUnit.MILLISECONDS)
+ " ms)");
Assert.assertFalse(outcome.getRenderUri().toString().isEmpty());
}
@Test
public void selectAds_fiveBuyersLargeCAs() throws Exception {
// 1 Seller, 5 Buyer, each buyer has 71 Custom Audiences
sCustomAudiences.addAll(readCustomAudiences("CustomAudiencesFiveBuyersLargeCAs.json"));
joinCustomAudiences(sCustomAudiences);
AdSelectionConfig config =
readAdSelectionConfig("AdSelectionConfigFiveBuyersLargeCAs.json");
Stopwatch timer = Stopwatch.createStarted(mTicker);
AdSelectionOutcome outcome =
AD_SELECTION_CLIENT
.selectAds(config)
.get(API_RESPONSE_TIMEOUT_SECONDS, TimeUnit.SECONDS);
timer.stop();
Log.i(
TAG,
"("
+ generateLogLabel("selectAds_fiveBuyersLargeCAs")
+ ": "
+ timer.elapsed(TimeUnit.MILLISECONDS)
+ " ms)");
Assert.assertFalse(outcome.getRenderUri().toString().isEmpty());
}
private ImmutableList<CustomAudience> readCustomAudiences(String fileName) throws Exception {
ImmutableList.Builder<CustomAudience> customAudienceBuilder = ImmutableList.builder();
InputStream is = ApplicationProvider.getApplicationContext().getAssets().open(fileName);
JSONArray customAudiencesJson = new JSONArray(new String(is.readAllBytes()));
is.close();
for (int i = 0; i < customAudiencesJson.length(); i++) {
JSONObject caJson = customAudiencesJson.getJSONObject(i);
JSONObject trustedBiddingDataJson = caJson.getJSONObject("trustedBiddingData");
JSONArray trustedBiddingKeysJson =
trustedBiddingDataJson.getJSONArray("trustedBiddingKeys");
JSONArray adsJson = caJson.getJSONArray("ads");
ImmutableList.Builder<String> biddingKeys = ImmutableList.builder();
for (int index = 0; index < trustedBiddingKeysJson.length(); index++) {
biddingKeys.add(trustedBiddingKeysJson.getString(index));
}
ImmutableList.Builder<AdData> adDatas = ImmutableList.builder();
for (int index = 0; index < adsJson.length(); index++) {
JSONObject adJson = adsJson.getJSONObject(index);
adDatas.add(
new AdData.Builder()
.setRenderUri(Uri.parse(adJson.getString("render_uri")))
.setMetadata(adJson.getString("metadata"))
.build());
}
customAudienceBuilder.add(
new CustomAudience.Builder()
.setBuyer(AdTechIdentifier.fromString(caJson.getString("buyer")))
.setName(caJson.getString("name"))
.setActivationTime(Instant.now())
.setExpirationTime(Instant.now().plus(90000, ChronoUnit.SECONDS))
.setDailyUpdateUri(Uri.parse(caJson.getString("dailyUpdateUri")))
.setUserBiddingSignals(
AdSelectionSignals.fromString(
caJson.getString("userBiddingSignals")))
.setTrustedBiddingData(
new TrustedBiddingData.Builder()
.setTrustedBiddingKeys(biddingKeys.build())
.setTrustedBiddingUri(
Uri.parse(
trustedBiddingDataJson.getString(
"trustedBiddingUri")))
.build())
.setBiddingLogicUri(Uri.parse(caJson.getString("biddingLogicUri")))
.setAds(adDatas.build())
.build());
}
return customAudienceBuilder.build();
}
private AdSelectionConfig readAdSelectionConfig(String fileName) throws Exception {
InputStream is = ApplicationProvider.getApplicationContext().getAssets().open(fileName);
JSONObject adSelectionConfigJson = new JSONObject(new String(is.readAllBytes()));
JSONArray buyersJson = adSelectionConfigJson.getJSONArray("custom_audience_buyers");
JSONObject perBuyerSignalsJson = adSelectionConfigJson.getJSONObject("per_buyer_signals");
is.close();
ImmutableList.Builder<AdTechIdentifier> buyersBuilder = ImmutableList.builder();
ImmutableMap.Builder<AdTechIdentifier, AdSelectionSignals> perBuyerSignals =
ImmutableMap.builder();
for (int i = 0; i < buyersJson.length(); i++) {
AdTechIdentifier buyer = AdTechIdentifier.fromString(buyersJson.getString(i));
buyersBuilder.add(buyer);
perBuyerSignals.put(
buyer,
AdSelectionSignals.fromString(perBuyerSignalsJson.getString(buyer.toString())));
}
return new AdSelectionConfig.Builder()
.setSeller(AdTechIdentifier.fromString(adSelectionConfigJson.getString("seller")))
.setDecisionLogicUri(
Uri.parse(adSelectionConfigJson.getString("decision_logic_uri")))
.setAdSelectionSignals(
AdSelectionSignals.fromString(
adSelectionConfigJson.getString("auction_signals")))
.setSellerSignals(
AdSelectionSignals.fromString(
adSelectionConfigJson.getString("seller_signals")))
.setTrustedScoringSignalsUri(
Uri.parse(adSelectionConfigJson.getString("trusted_scoring_signal_uri")))
.setPerBuyerSignals(perBuyerSignals.build())
.setCustomAudienceBuyers(buyersBuilder.build())
.build();
}
private void joinCustomAudiences(List<CustomAudience> customAudiences) throws Exception {
for (CustomAudience ca : customAudiences) {
Thread.sleep(DELAY_TO_AVOID_THROTTLE);
CUSTOM_AUDIENCE_CLIENT
.joinCustomAudience(ca)
.get(API_RESPONSE_TIMEOUT_SECONDS, TimeUnit.SECONDS);
}
}
private String generateLogLabel(String testName) {
return "SELECT_ADS_LATENCY_" + getClass().getSimpleName() + "#" + testName;
}
}