• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.avf.test;
18 
19 import static com.android.tradefed.device.TestDevice.MicrodroidBuilder;
20 import static com.android.tradefed.testtype.DeviceJUnit4ClassRunner.TestMetrics;
21 
22 import static com.google.common.truth.Truth.assertThat;
23 import static com.google.common.truth.Truth.assertWithMessage;
24 import static com.google.common.truth.TruthJUnit.assume;
25 
26 import static org.junit.Assert.assertNotNull;
27 import static org.junit.Assume.assumeFalse;
28 import static org.junit.Assume.assumeTrue;
29 
30 import android.platform.test.annotations.RootPermissionTest;
31 
32 import com.android.microdroid.test.common.MetricsProcessor;
33 import com.android.microdroid.test.host.CommandRunner;
34 import com.android.microdroid.test.host.KvmHypTracer;
35 import com.android.microdroid.test.host.MicrodroidHostTestCaseBase;
36 import com.android.tradefed.device.DeviceNotAvailableException;
37 import com.android.tradefed.device.ITestDevice;
38 import com.android.tradefed.device.TestDevice;
39 import com.android.tradefed.log.LogUtil.CLog;
40 import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
41 import com.android.tradefed.util.CommandResult;
42 import com.android.tradefed.util.SimpleStats;
43 
44 import org.junit.After;
45 import org.junit.Before;
46 import org.junit.Rule;
47 import org.junit.Test;
48 import org.junit.runner.RunWith;
49 
50 import java.util.ArrayList;
51 import java.util.Collections;
52 import java.util.List;
53 import java.util.Map;
54 import java.util.regex.Matcher;
55 import java.util.regex.Pattern;
56 
57 @RootPermissionTest
58 @RunWith(DeviceJUnit4ClassRunner.class)
59 public final class AVFHostTestCase extends MicrodroidHostTestCaseBase {
60 
61     private static final String COMPOSD_CMD_BIN = "/apex/com.android.compos/bin/composd_cmd";
62 
63     // Files that define the "test" instance of CompOS
64     private static final String COMPOS_TEST_ROOT = "/data/misc/apexdata/com.android.compos/test/";
65 
66     private static final String BOOTLOADER_TIME_PROP_NAME = "ro.boot.boottime";
67     private static final String BOOTLOADER_PREFIX = "bootloader-";
68     private static final String BOOTLOADER_TIME = "bootloader_time";
69     private static final String BOOTLOADER_PHASE_SW = "SW";
70 
71     /** Boot time test related variables */
72     private static final int REINSTALL_APEX_RETRY_INTERVAL_MS = 5 * 1000;
73 
74     private static final int REINSTALL_APEX_TIMEOUT_SEC = 15;
75     private static final int COMPILE_STAGED_APEX_RETRY_INTERVAL_MS = 10 * 1000;
76     private static final int COMPILE_STAGED_APEX_TIMEOUT_SEC = 540;
77     private static final int BOOT_COMPLETE_TIMEOUT_MS = 10 * 60 * 1000;
78     private static final int ROUND_COUNT = 5;
79     private static final int ROUND_IGNORE_STARTUP_TIME = 3;
80     private static final String APK_NAME = "MicrodroidTestApp.apk";
81     private static final String PACKAGE_NAME = "com.android.microdroid.test";
82 
83     private MetricsProcessor mMetricsProcessor;
84     @Rule public TestMetrics mMetrics = new TestMetrics();
85 
86     private boolean mNeedTearDown = false;
87 
88     @Before
setUp()89     public void setUp() throws Exception {
90         mNeedTearDown = false;
91 
92         assumeDeviceIsCapable(getDevice());
93         mNeedTearDown = true;
94 
95         getDevice().installPackage(findTestFile(APK_NAME), /* reinstall */ false);
96 
97         mMetricsProcessor = new MetricsProcessor(getMetricPrefix() + "hostside/");
98     }
99 
100     @After
tearDown()101     public void tearDown() throws Exception {
102         if (!mNeedTearDown) {
103             // If we skipped setUp, we don't need to undo it, and that avoids potential exceptions
104             // incompatible hardware. (Note that tests can change what assumeDeviceIsCapable()
105             // sees, so we can't rely on that - b/268688303.)
106             return;
107         }
108 
109         CommandRunner android = new CommandRunner(getDevice());
110 
111         // Clear up any CompOS instance files we created.
112         android.tryRun("rm", "-rf", COMPOS_TEST_ROOT);
113     }
114 
115     @Test
testBootWithCompOS()116     public void testBootWithCompOS() throws Exception {
117         composTestHelper(true, "microdroid");
118     }
119 
120     @Test
testBootWithCompOS_os_android15_66()121     public void testBootWithCompOS_os_android15_66() throws Exception {
122         composTestHelper(true, "android15_66");
123     }
124 
125     @Test
testBootWithCompOS_os_microdroid_16k()126     public void testBootWithCompOS_os_microdroid_16k() throws Exception {
127         composTestHelper(true, "microdroid_16k");
128     }
129 
130     @Test
testBootWithoutCompOS()131     public void testBootWithoutCompOS() throws Exception {
132         composTestHelper(false, null);
133     }
134 
135     @Test
testNoLongHypSections()136     public void testNoLongHypSections() throws Exception {
137         noLongHypSectionsHelper("microdroid");
138     }
139 
140     @Test
testNoLongHypSections_os_android15_66()141     public void testNoLongHypSections_os_android15_66() throws Exception {
142         noLongHypSectionsHelper("android15_66");
143     }
144 
145     @Test
testNoLongHypSections_os_microdroid_16k()146     public void testNoLongHypSections_os_microdroid_16k() throws Exception {
147         noLongHypSectionsHelper("microdroid_16k");
148     }
149 
150     @Test
testPsciMemProtect()151     public void testPsciMemProtect() throws Exception {
152         psciMemProtectHelper("microdroid");
153     }
154 
155     @Test
testPsciMemProtect_os_android15_66()156     public void testPsciMemProtect_os_android15_66() throws Exception {
157         psciMemProtectHelper("android15_66");
158     }
159 
160     @Test
testPsciMemProtect_os_microdroid_16k()161     public void testPsciMemProtect_os_microdroid_16k() throws Exception {
162         psciMemProtectHelper("microdroid_16k");
163     }
164 
165     @Test
testCameraAppStartupTime()166     public void testCameraAppStartupTime() throws Exception {
167         String[] launchIntentPackages = {
168             "com.android.camera2",
169             "com.google.android.GoogleCamera/com.android.camera.CameraLauncher"
170         };
171         String launchIntentPackage = findSupportedPackage(launchIntentPackages);
172         assume().withMessage("No supported camera package").that(launchIntentPackage).isNotNull();
173         appStartupHelper(launchIntentPackage);
174     }
175 
176     @Test
testSettingsAppStartupTime()177     public void testSettingsAppStartupTime() throws Exception {
178         String[] launchIntentPackages = {"com.android.settings"};
179         String launchIntentPackage = findSupportedPackage(launchIntentPackages);
180         assume().withMessage("No supported settings package").that(launchIntentPackage).isNotNull();
181         appStartupHelper(launchIntentPackage);
182     }
183 
noLongHypSectionsHelper(String osKey)184     private void noLongHypSectionsHelper(String osKey) throws Exception {
185         assumeKernelSupported(osKey);
186         assumeVmTypeSupported(osKey, true);
187         String os = SUPPORTED_OSES.get(osKey);
188 
189         String[] hypEvents = {"hyp_enter", "hyp_exit"};
190 
191         assumeTrue(
192                 "Skip without hypervisor tracing",
193                 KvmHypTracer.isSupported(getDevice(), hypEvents));
194 
195         KvmHypTracer tracer = new KvmHypTracer(getDevice(), hypEvents);
196         String result = tracer.run(COMPOSD_CMD_BIN + " test-compile --os " + os);
197         assertWithMessage("Failed to test compilation VM.")
198                 .that(result)
199                 .ignoringCase()
200                 .contains("all ok");
201 
202         SimpleStats stats = tracer.getDurationStats();
203         reportMetric(stats.getData(), "hyp_sections", "s");
204         CLog.i("Hypervisor traces parsed successfully.");
205     }
206 
psciMemProtectHelper(String osKey)207     public void psciMemProtectHelper(String osKey) throws Exception {
208         assumeKernelSupported(osKey);
209         assumeVmTypeSupported(osKey, true);
210         String os = SUPPORTED_OSES.get(osKey);
211 
212         String[] hypEvents = {"psci_mem_protect"};
213 
214         assumeTrue(
215                 "Skip without hypervisor tracing",
216                 KvmHypTracer.isSupported(getDevice(), hypEvents));
217         KvmHypTracer tracer = new KvmHypTracer(getDevice(), hypEvents);
218 
219         /* We need to wait for crosvm to die so all the VM pages are reclaimed */
220         String result =
221                 tracer.run(
222                         COMPOSD_CMD_BIN
223                                 + " test-compile --os "
224                                 + os
225                                 + " && killall -w crosvm || true");
226         assertWithMessage("Failed to test compilation VM.")
227                 .that(result)
228                 .ignoringCase()
229                 .contains("all ok");
230 
231         List<Integer> values = tracer.getPsciMemProtect();
232 
233         assertWithMessage("PSCI MEM_PROTECT events not recorded")
234                 .that(values.size())
235                 .isGreaterThan(2);
236 
237         assertWithMessage("PSCI MEM_PROTECT counter not starting from 0")
238                 .that(values.get(0))
239                 .isEqualTo(0);
240 
241         assertWithMessage("PSCI MEM_PROTECT counter not ending with 0")
242                 .that(values.get(values.size() - 1))
243                 .isEqualTo(0);
244 
245         assertWithMessage("PSCI MEM_PROTECT counter didn't increment")
246                 .that(Collections.max(values))
247                 .isGreaterThan(0);
248     }
249 
appStartupHelper(String launchIntentPackage)250     private void appStartupHelper(String launchIntentPackage) throws Exception {
251         assumeTrue(
252                 "Skip on non-protected VMs",
253                 ((TestDevice) getDevice()).supportsMicrodroid(/* protectedVm= */ true));
254 
255         StartupTimeMetricCollection mCollection =
256                 new StartupTimeMetricCollection(getPackageName(launchIntentPackage), ROUND_COUNT);
257         getAppStartupTime(launchIntentPackage, mCollection);
258 
259         reportMetric(
260                 mCollection.mAppBeforeVmRunTotalTime,
261                 "app_startup/" + mCollection.getPkgName() + "/total_time/before_vm",
262                 "ms");
263         reportMetric(
264                 mCollection.mAppBeforeVmRunWaitTime,
265                 "app_startup/" + mCollection.getPkgName() + "/wait_time/before_vm",
266                 "ms");
267         reportMetric(
268                 mCollection.mAppDuringVmRunTotalTime,
269                 "app_startup/" + mCollection.getPkgName() + "/total_time/during_vm",
270                 "ms");
271         reportMetric(
272                 mCollection.mAppDuringVmRunWaitTime,
273                 "app_startup/" + mCollection.getPkgName() + "/wait_time/during_vm",
274                 "ms");
275         reportMetric(
276                 mCollection.mAppAfterVmRunTotalTime,
277                 "app_startup/" + mCollection.getPkgName() + "/total_time/after_vm",
278                 "ms");
279         reportMetric(
280                 mCollection.mAppAfterVmRunWaitTime,
281                 "app_startup/" + mCollection.getPkgName() + "/wait_time/after_vm",
282                 "ms");
283     }
284 
getPackageName(String launchIntentPackage)285     private String getPackageName(String launchIntentPackage) {
286         String appPkg = launchIntentPackage;
287 
288         // Does the appPkgName contain the intent ?
289         if (launchIntentPackage != null && launchIntentPackage.contains("/")) {
290             appPkg = launchIntentPackage.split("/")[0];
291         }
292         return appPkg;
293     }
294 
findSupportedPackage(String[] pkgNameList)295     private String findSupportedPackage(String[] pkgNameList) throws Exception {
296         CommandRunner android = new CommandRunner(getDevice());
297 
298         for (String pkgName : pkgNameList) {
299             String appPkg = getPackageName(pkgName);
300             String hasPackage =
301                     android.run(
302                             "pm list package | grep -w " + appPkg + " 1> /dev/null" + "; echo $?");
303             assertNotNull(hasPackage);
304 
305             if (hasPackage.equals("0")) {
306                 return pkgName;
307             }
308         }
309         return null;
310     }
311 
getColdRunStartupTimes(CommandRunner android, String pkgName)312     private AmStartupTimeCmdParser getColdRunStartupTimes(CommandRunner android, String pkgName)
313             throws DeviceNotAvailableException, InterruptedException {
314         unlockScreen(android);
315         // Ensure we are killing the app to get the cold app startup time
316         android.run("am force-stop " + pkgName);
317         android.run("echo 3 > /proc/sys/vm/drop_caches");
318         String vmStartAppLog = android.run("am", "start -W -S " + pkgName);
319         assertNotNull(vmStartAppLog);
320         assumeFalse(vmStartAppLog.isEmpty());
321         return new AmStartupTimeCmdParser(vmStartAppLog);
322     }
323 
324     // Returns an array of two elements containing the delta between the initial app startup time
325     // and the time measured after running the VM.
getAppStartupTime(String pkgName, StartupTimeMetricCollection metricColector)326     private void getAppStartupTime(String pkgName, StartupTimeMetricCollection metricColector)
327             throws Exception {
328         TestDevice device = (TestDevice) getDevice();
329 
330         // 1. Reboot the device to run the test without stage2 fragmentation
331         getDevice().rebootUntilOnline();
332         waitForBootCompleted();
333 
334         // 2. Start the app and ignore first runs to warm up caches
335         CommandRunner android = new CommandRunner(getDevice());
336         for (int i = 0; i < ROUND_IGNORE_STARTUP_TIME; i++) {
337             getColdRunStartupTimes(android, pkgName);
338         }
339 
340         // 3. Run the app before the VM run and collect app startup time statistics
341         for (int i = 0; i < ROUND_COUNT; i++) {
342             AmStartupTimeCmdParser beforeVmStartApp = getColdRunStartupTimes(android, pkgName);
343             metricColector.addStartupTimeMetricBeforeVmRun(beforeVmStartApp);
344         }
345 
346         // Clear up any test dir
347         android.tryRun("rm", "-rf", MicrodroidHostTestCaseBase.TEST_ROOT);
348 
349         // Donate 80% of the available device memory to the VM
350         final String configPath = "assets/vm_config.json";
351         final int vm_mem_mb = getFreeMemoryInfoMb(android) * 80 / 100;
352         ITestDevice microdroidDevice =
353                 MicrodroidBuilder.fromDevicePath(getPathForPackage(PACKAGE_NAME), configPath)
354                         .debugLevel("full")
355                         .memoryMib(vm_mem_mb)
356                         .cpuTopology("match_host")
357                         .build(device);
358         try {
359             microdroidDevice.waitForBootComplete(30000);
360             microdroidDevice.enableAdbRoot();
361 
362             CommandRunner microdroid = new CommandRunner(microdroidDevice);
363 
364             microdroid.run("mkdir -p /mnt/ramdisk && chmod 777 /mnt/ramdisk");
365             microdroid.run("mount -t tmpfs -o size=32G tmpfs /mnt/ramdisk");
366 
367             // Allocate memory for the VM until it fails and make sure that we touch
368             // the allocated memory in the guest to be able to create stage2 fragmentation.
369             try {
370                 microdroid.tryRun(
371                         String.format(
372                                 "cd /mnt/ramdisk && truncate -s %dM sprayMemory"
373                                         + " && dd if=/dev/zero of=sprayMemory bs=1MB count=%d",
374                                 vm_mem_mb, vm_mem_mb));
375             } catch (Exception expected) {
376             }
377 
378             // Run the app during the VM run and collect cold startup time.
379             for (int i = 0; i < ROUND_COUNT; i++) {
380                 AmStartupTimeCmdParser duringVmStartApp = getColdRunStartupTimes(android, pkgName);
381                 metricColector.addStartupTimeMetricDuringVmRun(duringVmStartApp);
382             }
383         } finally {
384             device.shutdownMicrodroid(microdroidDevice);
385         }
386 
387         // Run the app after the VM run and collect cold startup time.
388         for (int i = 0; i < ROUND_COUNT; i++) {
389             AmStartupTimeCmdParser afterVmStartApp = getColdRunStartupTimes(android, pkgName);
390             metricColector.addStartupTimerMetricAfterVmRun(afterVmStartApp);
391         }
392     }
393 
394     static class AmStartupTimeCmdParser {
395         private int mTotalTime;
396         private int mWaitTime;
397 
AmStartupTimeCmdParser(String startAppLog)398         AmStartupTimeCmdParser(String startAppLog) {
399             String[] lines = startAppLog.split("[\r\n]+");
400             mTotalTime = mWaitTime = 0;
401 
402             for (String line : lines) {
403                 if (line.contains("TotalTime:")) {
404                     mTotalTime = Integer.parseInt(line.replaceAll("\\D+", ""));
405                 }
406                 if (line.contains("WaitTime:")) {
407                     mWaitTime = Integer.parseInt(line.replaceAll("\\D+", ""));
408                 }
409             }
410         }
411     }
412 
413     static class StartupTimeMetricCollection {
414         List<Double> mAppBeforeVmRunTotalTime;
415         List<Double> mAppBeforeVmRunWaitTime;
416 
417         List<Double> mAppDuringVmRunTotalTime;
418         List<Double> mAppDuringVmRunWaitTime;
419 
420         List<Double> mAppAfterVmRunTotalTime;
421         List<Double> mAppAfterVmRunWaitTime;
422 
423         private final String mPkgName;
424 
StartupTimeMetricCollection(String pkgName, int size)425         StartupTimeMetricCollection(String pkgName, int size) {
426             mAppBeforeVmRunTotalTime = new ArrayList<>(size);
427             mAppBeforeVmRunWaitTime = new ArrayList<>(size);
428 
429             mAppDuringVmRunTotalTime = new ArrayList<>(size);
430             mAppDuringVmRunWaitTime = new ArrayList<>(size);
431 
432             mAppAfterVmRunTotalTime = new ArrayList<>(size);
433             mAppAfterVmRunWaitTime = new ArrayList<>(size);
434             mPkgName = pkgName;
435         }
436 
addStartupTimeMetricBeforeVmRun(AmStartupTimeCmdParser m)437         public void addStartupTimeMetricBeforeVmRun(AmStartupTimeCmdParser m) {
438             mAppBeforeVmRunTotalTime.add((double) m.mTotalTime);
439             mAppBeforeVmRunWaitTime.add((double) m.mWaitTime);
440         }
441 
addStartupTimeMetricDuringVmRun(AmStartupTimeCmdParser m)442         public void addStartupTimeMetricDuringVmRun(AmStartupTimeCmdParser m) {
443             mAppDuringVmRunTotalTime.add((double) m.mTotalTime);
444             mAppDuringVmRunWaitTime.add((double) m.mWaitTime);
445         }
446 
addStartupTimerMetricAfterVmRun(AmStartupTimeCmdParser m)447         public void addStartupTimerMetricAfterVmRun(AmStartupTimeCmdParser m) {
448             mAppAfterVmRunTotalTime.add((double) m.mTotalTime);
449             mAppAfterVmRunWaitTime.add((double) m.mWaitTime);
450         }
451 
getPkgName()452         public String getPkgName() {
453             return this.mPkgName;
454         }
455     }
456 
getFreeMemoryInfoMb(CommandRunner android)457     private int getFreeMemoryInfoMb(CommandRunner android)
458             throws DeviceNotAvailableException, IllegalArgumentException {
459         int freeMemory = 0;
460         String content = android.runForResult("cat /proc/meminfo").getStdout().trim();
461         String[] lines = content.split("[\r\n]+");
462 
463         for (String line : lines) {
464             if (line.contains("MemFree:")) {
465                 freeMemory = Integer.parseInt(line.replaceAll("\\D+", "")) / 1024;
466                 return freeMemory;
467             }
468         }
469 
470         throw new IllegalArgumentException();
471     }
472 
unlockScreen(CommandRunner android)473     private void unlockScreen(CommandRunner android)
474             throws DeviceNotAvailableException, InterruptedException {
475         android.run("input keyevent", "KEYCODE_WAKEUP");
476         Thread.sleep(500);
477         final String ret =
478                 android.runForResult("dumpsys nfc | grep 'mScreenState='").getStdout().trim();
479         if (ret != null && ret.contains("ON_LOCKED")) {
480             android.run("input keyevent", "KEYCODE_MENU");
481         }
482     }
483 
updateBootloaderTimeInfo(Map<String, List<Double>> bootloaderTime)484     private void updateBootloaderTimeInfo(Map<String, List<Double>> bootloaderTime)
485             throws Exception {
486 
487         String bootLoaderVal = getDevice().getProperty(BOOTLOADER_TIME_PROP_NAME);
488         // Sample Output : 1BLL:89,1BLE:590,2BLL:0,2BLE:1344,SW:6734,KL:1193
489         if (bootLoaderVal != null) {
490             String[] bootLoaderPhases = bootLoaderVal.split(",");
491             double bootLoaderTotalTime = 0d;
492             for (String bootLoaderPhase : bootLoaderPhases) {
493                 String[] bootKeyVal = bootLoaderPhase.split(":");
494                 String key = String.format("%s%s", BOOTLOADER_PREFIX, bootKeyVal[0]);
495 
496                 bootloaderTime
497                         .computeIfAbsent(key, k -> new ArrayList<>())
498                         .add(Double.parseDouble(bootKeyVal[1]));
499                 // SW is the time spent on the warning screen. So ignore it in
500                 // final boot time calculation.
501                 if (BOOTLOADER_PHASE_SW.equalsIgnoreCase(bootKeyVal[0])) {
502                     continue;
503                 }
504                 bootLoaderTotalTime += Double.parseDouble(bootKeyVal[1]);
505             }
506             bootloaderTime
507                     .computeIfAbsent(BOOTLOADER_TIME, k -> new ArrayList<>())
508                     .add(bootLoaderTotalTime);
509         }
510     }
511 
getDmesgBootTime()512     private Double getDmesgBootTime() throws Exception {
513 
514         CommandRunner android = new CommandRunner(getDevice());
515         String result = android.run("dmesg");
516         Pattern pattern = Pattern.compile("\\[(.*)].*sys.boot_completed=1.*");
517         for (String line : result.split("[\r\n]+")) {
518             Matcher matcher = pattern.matcher(line);
519             if (matcher.find()) {
520                 return Double.valueOf(matcher.group(1));
521             }
522         }
523         throw new IllegalArgumentException("Failed to get boot time info.");
524     }
525 
composTestHelper(boolean isWithCompos, String osKey)526     private void composTestHelper(boolean isWithCompos, String osKey) throws Exception {
527         assumeFalse("Skip on CF; too slow", isCuttlefish());
528         if (isWithCompos) {
529             assumeKernelSupported(osKey);
530             assumeVmTypeSupported(osKey, true);
531         } else {
532             assertThat(osKey).isNull();
533         }
534 
535         List<Double> bootDmesgTime = new ArrayList<>(ROUND_COUNT);
536 
537         for (int round = 0; round < ROUND_COUNT; ++round) {
538             reInstallApex(REINSTALL_APEX_TIMEOUT_SEC);
539             try {
540                 if (isWithCompos) {
541                     String os = SUPPORTED_OSES.get(osKey);
542                     compileStagedApex(COMPILE_STAGED_APEX_TIMEOUT_SEC, os);
543                 }
544             } finally {
545                 // If compilation fails, we still have a staged APEX, and we need to reboot to
546                 // clean that up for further tests.
547                 getDevice().nonBlockingReboot();
548                 waitForBootCompleted();
549             }
550 
551             double elapsedSec = getDmesgBootTime();
552             bootDmesgTime.add(elapsedSec);
553         }
554 
555         String suffix = "";
556         if (isWithCompos) {
557             suffix = "with_compos";
558         } else {
559             suffix = "without_compos";
560         }
561 
562         reportMetric(bootDmesgTime, "dmesg_boot_time_" + suffix, "s");
563     }
564 
reportMetric(List<Double> data, String name, String unit)565     private void reportMetric(List<Double> data, String name, String unit) {
566         CLog.d("Report metric " + name + "(" + unit + ") : " + data.toString());
567         Map<String, Double> stats = mMetricsProcessor.computeStats(data, name, unit);
568         for (Map.Entry<String, Double> entry : stats.entrySet()) {
569             CLog.d("Add test metrics " + entry.getKey() + " : " + entry.getValue().toString());
570             mMetrics.addTestMetric(entry.getKey(), entry.getValue().toString());
571         }
572     }
573 
waitForBootCompleted()574     private void waitForBootCompleted() throws Exception {
575         getDevice().waitForDeviceOnline(BOOT_COMPLETE_TIMEOUT_MS);
576         getDevice().waitForBootComplete(BOOT_COMPLETE_TIMEOUT_MS);
577         getDevice().enableAdbRoot();
578     }
579 
compileStagedApex(int timeoutSec, String os)580     private void compileStagedApex(int timeoutSec, String os) throws Exception {
581 
582         long timeStart = System.currentTimeMillis();
583         long timeEnd = timeStart + timeoutSec * 1000L;
584 
585         while (true) {
586 
587             try {
588                 CommandRunner android = new CommandRunner(getDevice());
589 
590                 String result =
591                         android.runWithTimeout(
592                                 3 * 60 * 1000, COMPOSD_CMD_BIN + " staged-apex-compile --os " + os);
593                 assertWithMessage("Failed to compile staged APEX. Reason: " + result)
594                         .that(result)
595                         .ignoringCase()
596                         .contains("all ok");
597 
598                 CLog.i("Success to compile staged APEX. Result: " + result);
599 
600                 break;
601             } catch (AssertionError e) {
602                 CLog.i("Gets AssertionError when compile staged APEX. Detail: " + e);
603             }
604 
605             if (System.currentTimeMillis() > timeEnd) {
606                 CLog.e("Try to compile staged APEX several times but all fail.");
607                 throw new AssertionError("Failed to compile staged APEX.");
608             }
609 
610             Thread.sleep(COMPILE_STAGED_APEX_RETRY_INTERVAL_MS);
611         }
612     }
613 
reInstallApex(int timeoutSec)614     private void reInstallApex(int timeoutSec) throws Exception {
615 
616         long timeStart = System.currentTimeMillis();
617         long timeEnd = timeStart + timeoutSec * 1000L;
618 
619         while (true) {
620 
621             try {
622                 CommandRunner android = new CommandRunner(getDevice());
623 
624                 String packagesOutput = android.run("pm list packages -f --apex-only");
625 
626                 Pattern p =
627                         Pattern.compile(
628                                 "package:(.*)=(com(?:\\.google)?\\.android\\.art)$",
629                                 Pattern.MULTILINE);
630                 Matcher m = p.matcher(packagesOutput);
631                 assertWithMessage("ART module not found. Packages are:\n" + packagesOutput)
632                         .that(m.find())
633                         .isTrue();
634 
635                 String artApexPath = m.group(1);
636 
637                 CommandResult result = android.runForResult("pm install --apex " + artApexPath);
638                 assertWithMessage("Failed to install APEX. Reason: " + result)
639                         .that(result.getExitCode())
640                         .isEqualTo(0);
641 
642                 CLog.i("Success to install APEX. Result: " + result);
643 
644                 break;
645             } catch (AssertionError e) {
646                 CLog.i("Gets AssertionError when reinstall art APEX. Detail: " + e);
647             }
648 
649             if (System.currentTimeMillis() > timeEnd) {
650                 CLog.e("Try to reinstall art APEX several times but all fail.");
651                 throw new AssertionError("Failed to reinstall art APEX.");
652             }
653 
654             Thread.sleep(REINSTALL_APEX_RETRY_INTERVAL_MS);
655         }
656     }
657 }
658