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.rubidium.js;
18 
19 import static com.android.adservices.service.js.JSScriptArgument.arrayArg;
20 import static com.android.adservices.service.js.JSScriptArgument.jsonArg;
21 import static com.android.adservices.service.js.JSScriptArgument.numericArg;
22 import static com.android.adservices.service.js.JSScriptArgument.recordArg;
23 import static com.android.adservices.service.js.JSScriptArgument.stringArg;
24 import static com.android.adservices.service.js.JSScriptArgument.stringArrayArg;
25 
26 import static com.google.common.truth.Truth.assertThat;
27 
28 import static org.junit.Assume.assumeTrue;
29 
30 import android.adservices.adselection.AdSelectionConfig;
31 import android.adservices.adselection.AdWithBid;
32 import android.adservices.common.AdData;
33 import android.adservices.common.AdSelectionSignals;
34 import android.adservices.common.AdTechIdentifier;
35 import android.annotation.SuppressLint;
36 import android.content.Context;
37 import android.net.Uri;
38 import android.perftests.utils.BenchmarkState;
39 import android.perftests.utils.PerfStatusReporter;
40 import android.util.Log;
41 
42 import androidx.annotation.NonNull;
43 import androidx.test.core.app.ApplicationProvider;
44 import androidx.test.filters.MediumTest;
45 import androidx.test.runner.AndroidJUnit4;
46 
47 import com.android.adservices.data.adselection.CustomAudienceSignals;
48 import com.android.adservices.service.adselection.AdCounterKeyCopier;
49 import com.android.adservices.service.adselection.AdCounterKeyCopierNoOpImpl;
50 import com.android.adservices.service.adselection.AdDataArgumentUtil;
51 import com.android.adservices.service.adselection.AdSelectionConfigArgumentUtil;
52 import com.android.adservices.service.adselection.AdWithBidArgumentUtil;
53 import com.android.adservices.service.adselection.CustomAudienceBiddingSignalsArgumentUtil;
54 import com.android.adservices.service.adselection.CustomAudienceScoringSignalsArgumentUtil;
55 import com.android.adservices.service.js.IsolateSettings;
56 import com.android.adservices.service.js.JSScriptArgument;
57 import com.android.adservices.service.js.JSScriptArrayArgument;
58 import com.android.adservices.service.js.JSScriptEngine;
59 import com.android.adservices.service.js.JSScriptRecordArgument;
60 import com.android.adservices.service.profiling.JSScriptEngineLogConstants;
61 import com.android.adservices.service.profiling.Profiler;
62 
63 import com.google.common.collect.ImmutableList;
64 import com.google.common.collect.ImmutableMap;
65 import com.google.common.util.concurrent.ListenableFuture;
66 
67 import org.json.JSONArray;
68 import org.json.JSONObject;
69 import org.junit.After;
70 import org.junit.Before;
71 import org.junit.Rule;
72 import org.junit.Test;
73 import org.junit.runner.RunWith;
74 
75 import java.io.IOException;
76 import java.io.InputStream;
77 import java.nio.charset.StandardCharsets;
78 import java.time.Clock;
79 import java.time.Duration;
80 import java.time.Instant;
81 import java.time.ZoneOffset;
82 import java.util.ArrayList;
83 import java.util.Collections;
84 import java.util.List;
85 import java.util.Locale;
86 import java.util.Map;
87 import java.util.Objects;
88 import java.util.Random;
89 import java.util.concurrent.CountDownLatch;
90 import java.util.concurrent.ExecutorService;
91 import java.util.concurrent.Executors;
92 import java.util.concurrent.TimeUnit;
93 import java.util.stream.Collectors;
94 import java.util.stream.IntStream;
95 
96 /** To run the unit tests for this class, run "atest RubidiumPerfTests:JSScriptEnginePerfTests" */
97 @MediumTest
98 @RunWith(AndroidJUnit4.class)
99 public class JSScriptEnginePerfTests {
100     private static final String TAG = JSScriptEngine.TAG;
101     private static final Context sContext = ApplicationProvider.getApplicationContext();
102     private static final ExecutorService sExecutorService = Executors.newFixedThreadPool(10);
103 
104     private static final JSScriptEngine sJSScriptEngine =
105             JSScriptEngine.getInstanceForTesting(
106                     sContext, Profiler.createInstance(JSScriptEngine.TAG));
107     private static final Clock CLOCK = Clock.fixed(Instant.now(), ZoneOffset.UTC);
108     private static final Instant ACTIVATION_TIME = CLOCK.instant();
109     private static final Instant EXPIRATION_TIME = CLOCK.instant().plus(Duration.ofDays(1));
110     private static final AdSelectionSignals CONTEXTUAL_SIGNALS = AdSelectionSignals.EMPTY;
111     private static final AdCounterKeyCopier AD_COUNTER_KEY_COPIER_NO_OP =
112             new AdCounterKeyCopierNoOpImpl();
113 
114     private final AdDataArgumentUtil mAdDataArgumentUtil =
115             new AdDataArgumentUtil(AD_COUNTER_KEY_COPIER_NO_OP);
116     private final AdWithBidArgumentUtil mAdWithBidArgumentUtil =
117             new AdWithBidArgumentUtil(mAdDataArgumentUtil);
118 
119     @Rule
120     public PerfStatusReporter mPerfStatusReporter = new PerfStatusReporter();
121 
122     @Before
before()123     public void before() throws Exception {
124         // Warm up the sandbox env.
125         callJSEngine(
126                 "function test() { return \"hello world\";" + " }", ImmutableList.of(), "test");
127     }
128 
129     @After
after()130     public void after() {
131         sJSScriptEngine.shutdown();
132     }
133 
134     @Test
evaluate_helloWorld()135     public void evaluate_helloWorld() throws Exception {
136         BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
137         while (state.keepRunning()) {
138             String res =
139                     callJSEngine(
140                             "function test() { return \"hello world\";" + " }",
141                             ImmutableList.of(),
142                             "test");
143             assertThat(res).isEqualTo("\"hello world\"");
144         }
145     }
146 
147     @Test
evaluate_emptyGenerateBid()148     public void evaluate_emptyGenerateBid() throws Exception {
149         BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
150         state.pauseTiming();
151 
152         InputStream testJsInputStream = sContext.getAssets().open("empty_generate_bid.js");
153         String jsTestFile = new String(testJsInputStream.readAllBytes(), StandardCharsets.UTF_8);
154         JSScriptArgument adDataArgument =
155                 recordArg(
156                         "ad",
157                         stringArg("render_url", "http://google.com"),
158                         recordArg("metadata", numericArg("input", 10)));
159         callJSEngine(jsTestFile, ImmutableList.of(adDataArgument), "generateBid");
160 
161         state.resumeTiming();
162         while (state.keepRunning()) {
163             callJSEngine(jsTestFile, ImmutableList.of(adDataArgument), "generateBid");
164         }
165     }
166 
167     @Test
evaluate_turtledoveSampleGenerateBid()168     public void evaluate_turtledoveSampleGenerateBid() throws Exception {
169         BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
170         state.pauseTiming();
171 
172         InputStream testJsInputStream = sContext.getAssets().open("turtledove_generate_bid.js");
173         String jsTestFile = new String(testJsInputStream.readAllBytes(), StandardCharsets.UTF_8);
174         // Initialize the environment with one call.
175         callJSEngine(jsTestFile, ImmutableList.of(), "generateBid");
176 
177         state.resumeTiming();
178         while (state.keepRunning()) {
179             callJSEngine(jsTestFile, ImmutableList.of(), "generateBid");
180         }
181     }
182 
183     @Test
evaluate_turtledoveSampleGenerateBid_parametrized_10Ads()184     public void evaluate_turtledoveSampleGenerateBid_parametrized_10Ads() throws Exception {
185         runParametrizedTurtledoveScript(10);
186     }
187 
188     @Test
evaluate_turtledoveSampleGenerateBid_parametrized_25Ads()189     public void evaluate_turtledoveSampleGenerateBid_parametrized_25Ads() throws Exception {
190         runParametrizedTurtledoveScript(25);
191     }
192 
193     @Test
evaluate_turtledoveSampleGenerateBid_parametrized_50Ads()194     public void evaluate_turtledoveSampleGenerateBid_parametrized_50Ads() throws Exception {
195         runParametrizedTurtledoveScript(50);
196     }
197 
198     @Test
evaluate_turtledoveSampleGenerateBid_parametrized_75Ads()199     public void evaluate_turtledoveSampleGenerateBid_parametrized_75Ads() throws Exception {
200         runParametrizedTurtledoveScript(75);
201     }
202 
203     @Test
evaluate_rubidiumGenerateBid_parametrized_1Ad()204     public void evaluate_rubidiumGenerateBid_parametrized_1Ad() throws Exception {
205         runParameterizedRubidiumGenerateBid(1);
206     }
207 
208     @Test
evaluate_rubidiumGenerateBid_parametrized_10Ads()209     public void evaluate_rubidiumGenerateBid_parametrized_10Ads() throws Exception {
210         runParameterizedRubidiumGenerateBid(10);
211     }
212 
213     @Test
evaluate_rubidiumGenerateBid_parametrized_25Ads()214     public void evaluate_rubidiumGenerateBid_parametrized_25Ads() throws Exception {
215         runParameterizedRubidiumGenerateBid(25);
216     }
217 
218     @Test
evaluate_rubidiumGenerateBid_parametrized_50Ads()219     public void evaluate_rubidiumGenerateBid_parametrized_50Ads() throws Exception {
220         runParameterizedRubidiumGenerateBid(50);
221     }
222 
223     @Test
evaluate_rubidiumGenerateBid_parametrized_75Ads()224     public void evaluate_rubidiumGenerateBid_parametrized_75Ads() throws Exception {
225         runParameterizedRubidiumGenerateBid(75);
226     }
227 
228     @Test
evaluate_rubidiumScoreAd_parametrized_1Ad()229     public void evaluate_rubidiumScoreAd_parametrized_1Ad() throws Exception {
230         runParameterizedRubidiumScoreAd(1);
231     }
232 
233     @Test
evaluate_rubidiumScoreAd_parametrized_10Ads()234     public void evaluate_rubidiumScoreAd_parametrized_10Ads() throws Exception {
235         runParameterizedRubidiumScoreAd(10);
236     }
237 
238     @Test
evaluate_rubidiumScoreAd_parametrized_25Ads()239     public void evaluate_rubidiumScoreAd_parametrized_25Ads() throws Exception {
240         runParameterizedRubidiumScoreAd(25);
241     }
242 
243     @Test
evaluate_rubidiumScoreAd_parametrized_50Ads()244     public void evaluate_rubidiumScoreAd_parametrized_50Ads() throws Exception {
245         runParameterizedRubidiumScoreAd(50);
246     }
247 
248     @Test
evaluate_rubidiumScoreAd_parametrized_75Ads()249     public void evaluate_rubidiumScoreAd_parametrized_75Ads() throws Exception {
250         runParameterizedRubidiumScoreAd(75);
251     }
252 
253     @SuppressLint("DefaultLocale")
runParametrizedTurtledoveScript(int numAds)254     private void runParametrizedTurtledoveScript(int numAds) throws Exception {
255         BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
256         state.pauseTiming();
257         InputStream testJsInputStream =
258                 sContext.getAssets().open("turtledove_parametrized_generateBid.js");
259         String jsTestFile = new String(testJsInputStream.readAllBytes(), StandardCharsets.UTF_8);
260 
261         state.resumeTiming();
262         while (state.keepRunning()) {
263             int numInterestGroups = 1;
264             String res =
265                     callJSEngine(
266                             sJSScriptEngine,
267                             jsTestFile,
268                             ImmutableList.of(
269                                     buildSampleInterestGroupArg(numInterestGroups, numAds)),
270                             "generateBid");
271 
272             // I modified the Turtledove script to have the total execution time
273             // (across all IGs) as the last element in the response array.
274             JSONArray obj = new JSONArray(res);
275             long webviewExecTime = obj.getJSONObject(obj.length() - 1).getLong("generateBidTime");
276             String webviewExecTimeLog =
277                     String.format(
278                             "(%s: %d)",
279                             JSScriptEngineLogConstants.WEBVIEW_EXECUTION_TIME, webviewExecTime);
280             // The listener picks up logs from JSScriptEngine, so simulate logging from there.
281             Log.d(TAG, webviewExecTimeLog);
282         }
283     }
284 
buildSampleInterestGroupArg( int numCustomAudiences, int numAds)285     private JSScriptArrayArgument<JSScriptRecordArgument> buildSampleInterestGroupArg(
286             int numCustomAudiences, int numAds) {
287         JSScriptRecordArgument ad =
288                 recordArg(
289                         "foo",
290                         ImmutableList.of(
291                                 stringArg(
292                                         "renderUrl",
293                                         "https://googleads.g.doubleclick.net/ads/simple-ad"
294                                                 + ".html?adg_id=52836427830&cr_id=310927197297"
295                                                 + "&cv_id=4"),
296                                 stringArrayArg(
297                                         "metadata",
298                                         ImmutableList.of(
299                                                 "52836427830", "310927197297", "4", "608936333"))));
300 
301         JSScriptRecordArgument interestGroupArg =
302                 recordArg(
303                         "foo",
304                         stringArg("owner", "https://googleads.g.doubleclick.net/"),
305                         stringArg("name", "1j115753478"),
306                         stringArg("biddingLogicUrl", "https://googleads.g.doubleclick.net/td/bjs"),
307                         stringArg(
308                                 "dailyUpdateUrl", "https://googleads.g.doubleclick.net/td/update"),
309                         stringArg(
310                                 "trustedBiddingSignalsUrl",
311                                 "https://googleads.g.doubleclick.net/td/sjs"),
312                         stringArrayArg(
313                                 "trustedBiddingSignalsKeys", ImmutableList.of("1j115753478")),
314                         stringArrayArg("userBiddingSignals", ImmutableList.of()),
315                         new JSScriptArrayArgument("ads", Collections.nCopies(numAds, ad)));
316 
317         return arrayArg("foo", Collections.nCopies(numCustomAudiences, interestGroupArg));
318     }
319 
320     @Test
evaluate_turtledoveWasm()321     public void evaluate_turtledoveWasm() throws Exception {
322         assumeTrue(sJSScriptEngine.isWasmSupported().get(3, TimeUnit.SECONDS));
323 
324         BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
325         state.pauseTiming();
326 
327         String jsTestFile = readAsset("generate_bid_using_wasm.js");
328         byte[] wasmTestFile = readBinaryAsset("generate_bid.wasm");
329         JSScriptArgument[] inputBytes = new JSScriptArgument[200];
330         Random rand = new Random();
331         for (int i = 0; i < inputBytes.length; i++) {
332             byte value = (byte) (rand.nextInt(2 * Byte.MAX_VALUE) - Byte.MIN_VALUE);
333             inputBytes[i] = JSScriptArgument.numericArg("_", value);
334         }
335         JSScriptArgument adDataArgument =
336                 recordArg(
337                         "ad",
338                         stringArg("render_url", "http://google.com"),
339                         recordArg("metadata", JSScriptArgument.arrayArg("input", inputBytes)));
340 
341         state.resumeTiming();
342         while (state.keepRunning()) {
343             callJSEngine(jsTestFile, wasmTestFile, ImmutableList.of(adDataArgument), "generateBid");
344         }
345     }
346 
callJSEngine( @onNull String jsScript, @NonNull List<JSScriptArgument> args, @NonNull String functionName)347     private String callJSEngine(
348             @NonNull String jsScript,
349             @NonNull List<JSScriptArgument> args,
350             @NonNull String functionName)
351             throws Exception {
352         return callJSEngine(sJSScriptEngine, jsScript, args, functionName);
353     }
354 
callJSEngine( @onNull String jsScript, @NonNull byte[] wasmScript, @NonNull List<JSScriptArgument> args, @NonNull String functionName)355     private String callJSEngine(
356             @NonNull String jsScript,
357             @NonNull byte[] wasmScript,
358             @NonNull List<JSScriptArgument> args,
359             @NonNull String functionName)
360             throws Exception {
361         return callJSEngine(sJSScriptEngine, jsScript, wasmScript, args, functionName);
362     }
363 
callJSEngine( @onNull JSScriptEngine jsScriptEngine, @NonNull String jsScript, @NonNull List<JSScriptArgument> args, @NonNull String functionName)364     private static String callJSEngine(
365             @NonNull JSScriptEngine jsScriptEngine,
366             @NonNull String jsScript,
367             @NonNull List<JSScriptArgument> args,
368             @NonNull String functionName)
369             throws Exception {
370         CountDownLatch resultLatch = new CountDownLatch(1);
371         ListenableFuture<String> futureResult =
372                 callJSEngineAsync(jsScriptEngine, jsScript, args, functionName, resultLatch);
373         resultLatch.await();
374         return futureResult.get();
375     }
376 
callJSEngine( @onNull JSScriptEngine jsScriptEngine, @NonNull String jsScript, @NonNull byte[] wasmScript, @NonNull List<JSScriptArgument> args, @NonNull String functionName)377     private String callJSEngine(
378             @NonNull JSScriptEngine jsScriptEngine,
379             @NonNull String jsScript,
380             @NonNull byte[] wasmScript,
381             @NonNull List<JSScriptArgument> args,
382             @NonNull String functionName)
383             throws Exception {
384         CountDownLatch resultLatch = new CountDownLatch(1);
385         ListenableFuture<String> futureResult =
386                 callJSEngineAsync(
387                         jsScriptEngine, jsScript, wasmScript, args, functionName, resultLatch);
388         resultLatch.await();
389         return futureResult.get();
390     }
391 
callJSEngineAsync( @onNull String jsScript, @NonNull List<JSScriptArgument> args, @NonNull String functionName, @NonNull CountDownLatch resultLatch)392     private static ListenableFuture<String> callJSEngineAsync(
393             @NonNull String jsScript,
394             @NonNull List<JSScriptArgument> args,
395             @NonNull String functionName,
396             @NonNull CountDownLatch resultLatch) {
397         return callJSEngineAsync(sJSScriptEngine, jsScript, args, functionName, resultLatch);
398     }
399 
callJSEngineAsync( @onNull JSScriptEngine engine, @NonNull String jsScript, @NonNull List<JSScriptArgument> args, @NonNull String functionName, @NonNull CountDownLatch resultLatch)400     private static ListenableFuture<String> callJSEngineAsync(
401             @NonNull JSScriptEngine engine,
402             @NonNull String jsScript,
403             @NonNull List<JSScriptArgument> args,
404             @NonNull String functionName,
405             @NonNull CountDownLatch resultLatch) {
406         Objects.requireNonNull(engine);
407         Objects.requireNonNull(resultLatch);
408         ListenableFuture<String> result = engine.evaluate(
409                 jsScript,
410                 args,
411                 functionName,
412                 IsolateSettings.forMaxHeapSizeEnforcementDisabled());
413         result.addListener(resultLatch::countDown, sExecutorService);
414         return result;
415     }
416 
callJSEngineAsync( @onNull JSScriptEngine engine, @NonNull String jsScript, @NonNull byte[] wasmScript, @NonNull List<JSScriptArgument> args, @NonNull String functionName, @NonNull CountDownLatch resultLatch)417     private ListenableFuture<String> callJSEngineAsync(
418             @NonNull JSScriptEngine engine,
419             @NonNull String jsScript,
420             @NonNull byte[] wasmScript,
421             @NonNull List<JSScriptArgument> args,
422             @NonNull String functionName,
423             @NonNull CountDownLatch resultLatch) {
424         Objects.requireNonNull(engine);
425         Objects.requireNonNull(resultLatch);
426         ListenableFuture<String> result = engine.evaluate(
427                 jsScript,
428                 wasmScript,
429                 args,
430                 functionName,
431                 IsolateSettings.forMaxHeapSizeEnforcementDisabled());
432         result.addListener(resultLatch::countDown, sExecutorService);
433         return result;
434     }
435 
readBinaryAsset(@onNull String assetName)436     private byte[] readBinaryAsset(@NonNull String assetName) throws IOException {
437         return sContext.getAssets().open(assetName).readAllBytes();
438     }
439 
readAsset(@onNull String assetName)440     private String readAsset(@NonNull String assetName) throws IOException {
441         return new String(readBinaryAsset(assetName), StandardCharsets.UTF_8);
442     }
443 
runParameterizedRubidiumGenerateBid(int numOfAds)444     public void runParameterizedRubidiumGenerateBid(int numOfAds) throws Exception {
445         BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
446         state.pauseTiming();
447         List<AdData> adDataList = getSampleAdDataList(numOfAds, "https://ads.example/");
448         ImmutableList.Builder<JSScriptArgument> adDataListArgument = new ImmutableList.Builder<>();
449         for (AdData adData : adDataList) {
450             adDataListArgument.add(mAdDataArgumentUtil.asScriptArgument("ignored", adData));
451         }
452         AdSelectionSignals perBuyerSignals = generatePerBuyerSignals(numOfAds);
453         AdSelectionSignals auctionSignals = AdSelectionSignals.fromString("{\"auctionSignal1"
454                 + "\":\"auctionValue1\",\"auctionSignal2\":\"auctionValue2\"}");
455 
456         AdTechIdentifier buyer = AdTechIdentifier.fromString("https://example-dsp.com");
457         AdSelectionSignals trustedBiddingSignals = AdSelectionSignals.fromString("{\"key1"
458                 + "\":\"tbs1\",\"key2\":{}}");
459         CustomAudienceSignals customAudienceSignals = getSampleCustomAudienceSignals(buyer,
460                 "shoes-running");
461 
462         ImmutableList<JSScriptArgument> args = ImmutableList.<JSScriptArgument>builder()
463                 .add(arrayArg("ads", adDataListArgument.build()))
464                 .add(jsonArg("auctionSignals", auctionSignals))
465                 .add(jsonArg("perBuyerSignals", perBuyerSignals))
466                 .add(jsonArg("trustedBiddingSignals", trustedBiddingSignals))
467                 .add(jsonArg("contextualSignals", CONTEXTUAL_SIGNALS))
468                 .add(CustomAudienceBiddingSignalsArgumentUtil.asScriptArgument(
469                         "customAudienceBiddingSignal", customAudienceSignals))
470                 .build();
471         InputStream testJsInputStream = sContext.getAssets().open(
472                 "rubidium_bidding_logic_compiled.js");
473         String jsTestFile = new String(testJsInputStream.readAllBytes(), StandardCharsets.UTF_8);
474         //logging time taken to call JS
475         state.resumeTiming();
476         while (state.keepRunning()) {
477             String res = callJSEngine(jsTestFile, args, "generateBidIterative");
478             JSONObject jsonObject = new JSONObject(res);
479             long webviewExecTime = jsonObject.getLong("duration");
480             String webviewExecTimeLog =
481                     String.format(Locale.ENGLISH,
482                             "(%s: %d)",
483                             JSScriptEngineLogConstants.WEBVIEW_EXECUTION_TIME,
484                             webviewExecTime);
485             // The listener picks up logs from JSScriptEngine, so simulate logging from there.
486             Log.d(TAG, webviewExecTimeLog);
487         }
488     }
489 
runParameterizedRubidiumScoreAd(int numOfAds)490     public void runParameterizedRubidiumScoreAd(int numOfAds) throws Exception {
491         BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
492         state.pauseTiming();
493         String adRenderUrl = "https://rtb.example/creative";
494         List<AdWithBid> adWithBidList = getSampleAdDataWithBidList(numOfAds, adRenderUrl);
495         ImmutableList.Builder<JSScriptArgument> adWithBidArrayArgument =
496                 new ImmutableList.Builder<>();
497         for (AdWithBid adWithBid : adWithBidList) {
498             adWithBidArrayArgument.add(
499                     mAdWithBidArgumentUtil.asScriptArgument("adWithBid", adWithBid));
500         }
501         AdTechIdentifier seller = AdTechIdentifier.fromString("www.example-ssp.com");
502         AdSelectionSignals sellerSignals = AdSelectionSignals.fromString("{\"signals\":[]}");
503         String trustedScoringSignalJson = String.format(Locale.ENGLISH,
504                 "{\"renderUrl\":{\"%s\":[]}}", adRenderUrl);
505         AdSelectionSignals trustedScoringSignalsJson = AdSelectionSignals.fromString(
506                 trustedScoringSignalJson);
507 
508         AdTechIdentifier buyer1 = AdTechIdentifier.fromString("https://example-dsp.com");
509         AdSelectionSignals buyer1Signals = AdSelectionSignals.fromString("{\"https://example-dsp"
510                 + ".com:1\":\"value1\",\"https://example-dsp.com:2\":\"value2\"}");
511         Map<AdTechIdentifier, AdSelectionSignals> perBuyerSignals = ImmutableMap.of(buyer1,
512                 buyer1Signals);
513 
514         AdSelectionConfig adSelectionConfig = getSampleAdSelectionConfig(seller, sellerSignals,
515                 perBuyerSignals);
516         CustomAudienceSignals customAudienceSignals = getSampleCustomAudienceSignals(buyer1,
517                 "shoes-running");
518 
519         ImmutableList<JSScriptArgument> args = ImmutableList.<JSScriptArgument>builder()
520                 .add(arrayArg("adsWithBids", adWithBidArrayArgument.build()))
521                 .add(AdSelectionConfigArgumentUtil.asScriptArgument(adSelectionConfig,
522                         "adSelectionConfig"))
523                 .add(jsonArg("sellerSignals", sellerSignals))
524                 .add(jsonArg("trustedScoringSignals", trustedScoringSignalsJson))
525                 .add(jsonArg("contextualSignals", CONTEXTUAL_SIGNALS))
526                 .add(CustomAudienceScoringSignalsArgumentUtil.asScriptArgument(
527                         "customAudienceScoringSignal", customAudienceSignals))
528                 .build();
529         InputStream testJsInputStream = sContext.getAssets().open(
530                 "rubidium_scoring_logic_compiled.js");
531         String jsTestFile = new String(testJsInputStream.readAllBytes(), StandardCharsets.UTF_8);
532         //logging time taken to call JS
533         state.resumeTiming();
534         while (state.keepRunning()) {
535             String res = callJSEngine(jsTestFile, args, "scoreAdIterative");
536             JSONObject jsonObject = new JSONObject(res);
537             long webviewExecTime = jsonObject.getLong("duration");
538             String webviewExecTimeLog =
539                     String.format(Locale.ENGLISH,
540                             "(%s: %d)",
541                             JSScriptEngineLogConstants.WEBVIEW_EXECUTION_TIME,
542                             webviewExecTime);
543             // The listener picks up logs from JSScriptEngine, so simulate logging from there.
544             Log.d(TAG, webviewExecTimeLog);
545         }
546     }
547 
getSampleAdDataWithBidList(int size, String baseUri)548     private List<AdWithBid> getSampleAdDataWithBidList(int size, String baseUri) {
549         double initialBid = 1.23;
550         return IntStream.rangeClosed(1, size).mapToObj(iterator -> {
551             Uri renderUri = Uri.parse(String.format(Locale.ENGLISH, "%s%d", baseUri, iterator));
552             String metaDataJson = String.format(Locale.ENGLISH, "{\"metadata\":[\"%d\",\"123\"]}",
553                     iterator);
554             AdData adData = new AdData.Builder().setRenderUri(renderUri).setMetadata(
555                     metaDataJson).build();
556             return new AdWithBid(adData, initialBid + iterator);
557         }).collect(Collectors.toCollection(ArrayList::new));
558     }
559 
getSampleAdDataList(int size, String baseUri)560     private List<AdData> getSampleAdDataList(int size, String baseUri) {
561         return IntStream.rangeClosed(1, size).mapToObj(iterator -> {
562             Uri renderUri = Uri.parse(String.format(Locale.ENGLISH, "%s%d", baseUri, iterator));
563             String metaDataJson = String.format(Locale.ENGLISH, "{\"metadata\":[\"%d\",\"123\"]}",
564                     iterator);
565             return new AdData.Builder().setRenderUri(renderUri).setMetadata(
566                     metaDataJson).build();
567         }).collect(Collectors.toCollection(ArrayList::new));
568     }
569 
570     private CustomAudienceSignals getSampleCustomAudienceSignals(AdTechIdentifier buyer,
571             String name) {
572         String owner = "www.example-dsp.com";
573         AdSelectionSignals userBiddingSignals = AdSelectionSignals.fromString("{\"signals\":[]}");
574         return new CustomAudienceSignals.Builder()
575                 .setOwner(owner)
576                 .setBuyer(buyer)
577                 .setActivationTime(ACTIVATION_TIME)
578                 .setExpirationTime(EXPIRATION_TIME)
579                 .setUserBiddingSignals(userBiddingSignals)
580                 .setName(name)
581                 .build();
582     }
583 
584     private AdSelectionConfig getSampleAdSelectionConfig(AdTechIdentifier seller,
585             AdSelectionSignals sellerSignals,
586             Map<AdTechIdentifier, AdSelectionSignals> perBuyerSignals) {
587         Uri decisionLogicUri = Uri.parse("https://www.example-ssp.com/decide.js");
588         Uri trustedScoringSignalsUri = Uri.parse("https://www.example-ssp.com/signals");
589         List<AdTechIdentifier> buyers = ImmutableList.copyOf(
590                 new ArrayList<>(perBuyerSignals.keySet()));
591         return new AdSelectionConfig.Builder()
592                 .setSeller(seller)
593                 .setDecisionLogicUri(decisionLogicUri)
594                 .setCustomAudienceBuyers(buyers)
595                 .setAdSelectionSignals(AdSelectionSignals.EMPTY)
596                 .setSellerSignals(sellerSignals)
597                 .setPerBuyerSignals(perBuyerSignals)
598                 .setTrustedScoringSignalsUri(trustedScoringSignalsUri)
599                 .build();
600     }
601 
602     private AdSelectionSignals generatePerBuyerSignals(int size) {
603         String signalArrayFormat = "[\"%d\",\"123\",%d]";
604         String signalArray = IntStream.rangeClosed(1, size)
605                 .mapToObj(i -> String.format(Locale.ENGLISH, signalArrayFormat, i, i))
606                 .collect(Collectors.joining(", ", "[", "]"));
607         return AdSelectionSignals.fromString(
608                 String.format(Locale.ENGLISH, "{\"signals\":[null,%s,[null]]}",
609                         signalArray));
610     }
611 }
612