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 package com.android.microdroid.test.device; 17 18 import static android.content.pm.PackageManager.FEATURE_VIRTUALIZATION_FRAMEWORK; 19 20 import static com.google.common.truth.Truth.assertThat; 21 import static com.google.common.truth.TruthJUnit.assume; 22 23 import android.app.Instrumentation; 24 import android.app.UiAutomation; 25 import android.content.Context; 26 import android.os.ParcelFileDescriptor; 27 import android.os.SystemProperties; 28 import android.system.Os; 29 import android.system.virtualmachine.VirtualMachine; 30 import android.system.virtualmachine.VirtualMachineCallback; 31 import android.system.virtualmachine.VirtualMachineConfig; 32 import android.system.virtualmachine.VirtualMachineException; 33 import android.system.virtualmachine.VirtualMachineManager; 34 import android.util.Log; 35 36 import androidx.annotation.CallSuper; 37 import androidx.test.core.app.ApplicationProvider; 38 import androidx.test.platform.app.InstrumentationRegistry; 39 40 import com.android.microdroid.test.common.DeviceProperties; 41 import com.android.microdroid.test.common.MetricsProcessor; 42 import com.android.microdroid.testservice.ITestService; 43 44 import java.io.BufferedReader; 45 import java.io.ByteArrayOutputStream; 46 import java.io.IOException; 47 import java.io.InputStream; 48 import java.io.InputStreamReader; 49 import java.util.OptionalLong; 50 import java.util.concurrent.CompletableFuture; 51 import java.util.concurrent.ExecutorService; 52 import java.util.concurrent.Executors; 53 import java.util.concurrent.TimeUnit; 54 55 public abstract class MicrodroidDeviceTestBase { 56 private static final String TAG = "MicrodroidDeviceTestBase"; 57 private final String MAX_PERFORMANCE_TASK_PROFILE = "CPUSET_SP_TOP_APP"; 58 isCuttlefish()59 public static boolean isCuttlefish() { 60 return getDeviceProperties().isCuttlefish(); 61 } 62 isUserBuild()63 public static boolean isUserBuild() { 64 return getDeviceProperties().isUserBuild(); 65 } 66 getMetricPrefix()67 public static String getMetricPrefix() { 68 return MetricsProcessor.getMetricPrefix(getDeviceProperties().getMetricsTag()); 69 } 70 getDeviceProperties()71 private static DeviceProperties getDeviceProperties() { 72 return DeviceProperties.create(SystemProperties::get); 73 } 74 grantPermission(String permission)75 protected final void grantPermission(String permission) { 76 Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation(); 77 UiAutomation uiAutomation = instrumentation.getUiAutomation(); 78 uiAutomation.grantRuntimePermission(instrumentation.getContext().getPackageName(), 79 permission); 80 } 81 revokePermission(String permission)82 protected final void revokePermission(String permission) { 83 Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation(); 84 UiAutomation uiAutomation = instrumentation.getUiAutomation(); 85 uiAutomation.revokeRuntimePermission(instrumentation.getContext().getPackageName(), 86 permission); 87 } 88 setMaxPerformanceTaskProfile()89 protected final void setMaxPerformanceTaskProfile() throws IOException { 90 Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation(); 91 UiAutomation uiAutomation = instrumentation.getUiAutomation(); 92 String cmd = "settaskprofile " + Os.gettid() + " " + MAX_PERFORMANCE_TASK_PROFILE; 93 String out = runInShell(TAG, uiAutomation, cmd).trim(); 94 String expect = "Profile " + MAX_PERFORMANCE_TASK_PROFILE + " is applied successfully!"; 95 if (!expect.equals(out)) { 96 throw new IOException("Could not apply max performance task profile: " + out); 97 } 98 } 99 100 private Context mCtx; 101 private boolean mProtectedVm; 102 getContext()103 protected Context getContext() { 104 return mCtx; 105 } 106 getVirtualMachineManager()107 public VirtualMachineManager getVirtualMachineManager() { 108 return mCtx.getSystemService(VirtualMachineManager.class); 109 } 110 newVmConfigBuilder()111 public VirtualMachineConfig.Builder newVmConfigBuilder() { 112 return new VirtualMachineConfig.Builder(mCtx).setProtectedVm(mProtectedVm); 113 } 114 isProtectedVm()115 protected final boolean isProtectedVm() { 116 return mProtectedVm; 117 } 118 119 /** 120 * Creates a new virtual machine, potentially removing an existing virtual machine with given 121 * name. 122 */ forceCreateNewVirtualMachine(String name, VirtualMachineConfig config)123 public VirtualMachine forceCreateNewVirtualMachine(String name, VirtualMachineConfig config) 124 throws VirtualMachineException { 125 final VirtualMachineManager vmm = getVirtualMachineManager(); 126 VirtualMachine existingVm = vmm.get(name); 127 if (existingVm != null) { 128 vmm.delete(name); 129 } 130 return vmm.create(name, config); 131 } 132 prepareTestSetup(boolean protectedVm)133 public void prepareTestSetup(boolean protectedVm) { 134 mCtx = ApplicationProvider.getApplicationContext(); 135 assume().withMessage("Device doesn't support AVF") 136 .that(mCtx.getPackageManager().hasSystemFeature(FEATURE_VIRTUALIZATION_FRAMEWORK)) 137 .isTrue(); 138 139 mProtectedVm = protectedVm; 140 141 int capabilities = getVirtualMachineManager().getCapabilities(); 142 if (protectedVm) { 143 assume().withMessage("Skip where protected VMs aren't supported") 144 .that(capabilities & VirtualMachineManager.CAPABILITY_PROTECTED_VM) 145 .isNotEqualTo(0); 146 } else { 147 assume().withMessage("Skip where VMs aren't supported") 148 .that(capabilities & VirtualMachineManager.CAPABILITY_NON_PROTECTED_VM) 149 .isNotEqualTo(0); 150 } 151 } 152 153 public abstract static class VmEventListener implements VirtualMachineCallback { 154 private ExecutorService mExecutorService = Executors.newSingleThreadExecutor(); 155 private OptionalLong mVcpuStartedNanoTime = OptionalLong.empty(); 156 private OptionalLong mKernelStartedNanoTime = OptionalLong.empty(); 157 private OptionalLong mInitStartedNanoTime = OptionalLong.empty(); 158 private OptionalLong mPayloadStartedNanoTime = OptionalLong.empty(); 159 private StringBuilder mConsoleOutput = new StringBuilder(); 160 private StringBuilder mLogOutput = new StringBuilder(); 161 private boolean mProcessedBootTimeMetrics = false; 162 processBootTimeMetrics(String log)163 private void processBootTimeMetrics(String log) { 164 if (!mVcpuStartedNanoTime.isPresent()) { 165 mVcpuStartedNanoTime = OptionalLong.of(System.nanoTime()); 166 } 167 if (log.contains("Starting payload...") && !mKernelStartedNanoTime.isPresent()) { 168 mKernelStartedNanoTime = OptionalLong.of(System.nanoTime()); 169 } 170 if (log.contains("Run /init as init process") && !mInitStartedNanoTime.isPresent()) { 171 mInitStartedNanoTime = OptionalLong.of(System.nanoTime()); 172 } 173 if (log.contains("microdroid_manager") && log.contains("executing main task") 174 && !mPayloadStartedNanoTime.isPresent()) { 175 mPayloadStartedNanoTime = OptionalLong.of(System.nanoTime()); 176 } 177 } 178 logVmOutputAndMonitorBootTimeMetrics( String tag, InputStream vmOutputStream, String name, StringBuilder result, boolean monitorEvents)179 private void logVmOutputAndMonitorBootTimeMetrics( 180 String tag, 181 InputStream vmOutputStream, 182 String name, 183 StringBuilder result, 184 boolean monitorEvents) { 185 mProcessedBootTimeMetrics |= monitorEvents; 186 new Thread( 187 () -> { 188 try { 189 BufferedReader reader = 190 new BufferedReader( 191 new InputStreamReader(vmOutputStream)); 192 String line; 193 while ((line = reader.readLine()) != null 194 && !Thread.interrupted()) { 195 if (monitorEvents) processBootTimeMetrics(line); 196 Log.i(tag, name + ": " + line); 197 result.append(line + "\n"); 198 } 199 } catch (Exception e) { 200 Log.w(tag, name, e); 201 } 202 }) 203 .start(); 204 } 205 logVmOutputAndMonitorBootTimeMetrics( String tag, InputStream vmOutputStream, String name, StringBuilder result)206 private void logVmOutputAndMonitorBootTimeMetrics( 207 String tag, InputStream vmOutputStream, String name, StringBuilder result) { 208 logVmOutputAndMonitorBootTimeMetrics(tag, vmOutputStream, name, result, true); 209 } 210 211 /** Copy output from the VM to logcat. This is helpful when things go wrong. */ logVmOutput( String tag, InputStream vmOutputStream, String name, StringBuilder result)212 protected void logVmOutput( 213 String tag, InputStream vmOutputStream, String name, StringBuilder result) { 214 logVmOutputAndMonitorBootTimeMetrics(tag, vmOutputStream, name, result, false); 215 } 216 runToFinish(String logTag, VirtualMachine vm)217 public void runToFinish(String logTag, VirtualMachine vm) 218 throws VirtualMachineException, InterruptedException { 219 vm.setCallback(mExecutorService, this); 220 vm.run(); 221 if (vm.getConfig().isVmOutputCaptured()) { 222 logVmOutputAndMonitorBootTimeMetrics( 223 logTag, vm.getConsoleOutput(), "Console", mConsoleOutput); 224 logVmOutput(logTag, vm.getLogOutput(), "Log", mLogOutput); 225 } 226 mExecutorService.awaitTermination(300, TimeUnit.SECONDS); 227 } 228 getVcpuStartedNanoTime()229 public OptionalLong getVcpuStartedNanoTime() { 230 return mVcpuStartedNanoTime; 231 } 232 getKernelStartedNanoTime()233 public OptionalLong getKernelStartedNanoTime() { 234 return mKernelStartedNanoTime; 235 } 236 getInitStartedNanoTime()237 public OptionalLong getInitStartedNanoTime() { 238 return mInitStartedNanoTime; 239 } 240 getPayloadStartedNanoTime()241 public OptionalLong getPayloadStartedNanoTime() { 242 return mPayloadStartedNanoTime; 243 } 244 getConsoleOutput()245 public String getConsoleOutput() { 246 return mConsoleOutput.toString(); 247 } 248 getLogOutput()249 public String getLogOutput() { 250 return mLogOutput.toString(); 251 } 252 hasProcessedBootTimeMetrics()253 public boolean hasProcessedBootTimeMetrics() { 254 return mProcessedBootTimeMetrics; 255 } 256 forceStop(VirtualMachine vm)257 protected void forceStop(VirtualMachine vm) { 258 try { 259 vm.stop(); 260 } catch (VirtualMachineException e) { 261 throw new RuntimeException(e); 262 } 263 } 264 265 @Override onPayloadStarted(VirtualMachine vm)266 public void onPayloadStarted(VirtualMachine vm) {} 267 268 @Override onPayloadReady(VirtualMachine vm)269 public void onPayloadReady(VirtualMachine vm) {} 270 271 @Override onPayloadFinished(VirtualMachine vm, int exitCode)272 public void onPayloadFinished(VirtualMachine vm, int exitCode) {} 273 274 @Override onError(VirtualMachine vm, int errorCode, String message)275 public void onError(VirtualMachine vm, int errorCode, String message) {} 276 277 @Override 278 @CallSuper onStopped(VirtualMachine vm, int reason)279 public void onStopped(VirtualMachine vm, int reason) { 280 vm.clearCallback(); 281 mExecutorService.shutdown(); 282 } 283 } 284 285 public enum BootTimeMetric { 286 TOTAL, 287 VM_START, 288 BOOTLOADER, 289 KERNEL, 290 USERSPACE, 291 } 292 293 public static class BootResult { 294 public final boolean payloadStarted; 295 public final int deathReason; 296 public final long apiCallNanoTime; 297 public final long endToEndNanoTime; 298 299 public final boolean processedBootTimeMetrics; 300 public final OptionalLong vcpuStartedNanoTime; 301 public final OptionalLong kernelStartedNanoTime; 302 public final OptionalLong initStartedNanoTime; 303 public final OptionalLong payloadStartedNanoTime; 304 305 public final String consoleOutput; 306 public final String logOutput; 307 BootResult( boolean payloadStarted, int deathReason, long apiCallNanoTime, long endToEndNanoTime, boolean processedBootTimeMetrics, OptionalLong vcpuStartedNanoTime, OptionalLong kernelStartedNanoTime, OptionalLong initStartedNanoTime, OptionalLong payloadStartedNanoTime, String consoleOutput, String logOutput)308 BootResult( 309 boolean payloadStarted, 310 int deathReason, 311 long apiCallNanoTime, 312 long endToEndNanoTime, 313 boolean processedBootTimeMetrics, 314 OptionalLong vcpuStartedNanoTime, 315 OptionalLong kernelStartedNanoTime, 316 OptionalLong initStartedNanoTime, 317 OptionalLong payloadStartedNanoTime, 318 String consoleOutput, 319 String logOutput) { 320 this.apiCallNanoTime = apiCallNanoTime; 321 this.payloadStarted = payloadStarted; 322 this.deathReason = deathReason; 323 this.endToEndNanoTime = endToEndNanoTime; 324 this.processedBootTimeMetrics = processedBootTimeMetrics; 325 this.vcpuStartedNanoTime = vcpuStartedNanoTime; 326 this.kernelStartedNanoTime = kernelStartedNanoTime; 327 this.initStartedNanoTime = initStartedNanoTime; 328 this.payloadStartedNanoTime = payloadStartedNanoTime; 329 this.consoleOutput = consoleOutput; 330 this.logOutput = logOutput; 331 } 332 getVcpuStartedNanoTime()333 private long getVcpuStartedNanoTime() { 334 return vcpuStartedNanoTime.getAsLong(); 335 } 336 getKernelStartedNanoTime()337 private long getKernelStartedNanoTime() { 338 // pvmfw emits log at the end which is used to estimate the kernelStart time. 339 // In case of no pvmfw run(non-protected mode), use vCPU started time instead. 340 return kernelStartedNanoTime.orElse(vcpuStartedNanoTime.getAsLong()); 341 } 342 getInitStartedNanoTime()343 private long getInitStartedNanoTime() { 344 return initStartedNanoTime.getAsLong(); 345 } 346 getPayloadStartedNanoTime()347 private long getPayloadStartedNanoTime() { 348 return payloadStartedNanoTime.getAsLong(); 349 } 350 getVMStartingElapsedNanoTime()351 public long getVMStartingElapsedNanoTime() { 352 return getVcpuStartedNanoTime() - apiCallNanoTime; 353 } 354 getBootloaderElapsedNanoTime()355 public long getBootloaderElapsedNanoTime() { 356 return getKernelStartedNanoTime() - getVcpuStartedNanoTime(); 357 } 358 getKernelElapsedNanoTime()359 public long getKernelElapsedNanoTime() { 360 return getInitStartedNanoTime() - getKernelStartedNanoTime(); 361 } 362 getUserspaceElapsedNanoTime()363 public long getUserspaceElapsedNanoTime() { 364 return getPayloadStartedNanoTime() - getInitStartedNanoTime(); 365 } 366 getBootTimeMetricNanoTime(BootTimeMetric metric)367 public OptionalLong getBootTimeMetricNanoTime(BootTimeMetric metric) { 368 if (metric == BootTimeMetric.TOTAL) { 369 return OptionalLong.of(endToEndNanoTime); 370 } 371 372 if (processedBootTimeMetrics) { 373 switch (metric) { 374 case VM_START: 375 return OptionalLong.of(getVMStartingElapsedNanoTime()); 376 case BOOTLOADER: 377 return OptionalLong.of(getBootloaderElapsedNanoTime()); 378 case KERNEL: 379 return OptionalLong.of(getKernelElapsedNanoTime()); 380 case USERSPACE: 381 return OptionalLong.of(getUserspaceElapsedNanoTime()); 382 } 383 } 384 385 return OptionalLong.empty(); 386 } 387 } 388 tryBootVm(String logTag, String vmName)389 public BootResult tryBootVm(String logTag, String vmName) 390 throws VirtualMachineException, InterruptedException { 391 VirtualMachine vm = getVirtualMachineManager().get(vmName); 392 final CompletableFuture<Boolean> payloadStarted = new CompletableFuture<>(); 393 final CompletableFuture<Integer> deathReason = new CompletableFuture<>(); 394 final CompletableFuture<Long> endTime = new CompletableFuture<>(); 395 VmEventListener listener = 396 new VmEventListener() { 397 @Override 398 public void onPayloadStarted(VirtualMachine vm) { 399 endTime.complete(System.nanoTime()); 400 payloadStarted.complete(true); 401 forceStop(vm); 402 } 403 404 @Override 405 public void onStopped(VirtualMachine vm, int reason) { 406 deathReason.complete(reason); 407 super.onStopped(vm, reason); 408 } 409 }; 410 long apiCallNanoTime = System.nanoTime(); 411 listener.runToFinish(logTag, vm); 412 return new BootResult( 413 payloadStarted.getNow(false), 414 deathReason.getNow(VmEventListener.STOP_REASON_INFRASTRUCTURE_ERROR), 415 apiCallNanoTime, 416 endTime.getNow(apiCallNanoTime) - apiCallNanoTime, 417 listener.hasProcessedBootTimeMetrics(), 418 listener.getVcpuStartedNanoTime(), 419 listener.getKernelStartedNanoTime(), 420 listener.getInitStartedNanoTime(), 421 listener.getPayloadStartedNanoTime(), 422 listener.getConsoleOutput(), 423 listener.getLogOutput()); 424 } 425 426 /** Execute a command. Returns stdout. */ runInShell(String tag, UiAutomation uiAutomation, String command)427 protected String runInShell(String tag, UiAutomation uiAutomation, String command) { 428 try (InputStream is = 429 new ParcelFileDescriptor.AutoCloseInputStream( 430 uiAutomation.executeShellCommand(command)); 431 ByteArrayOutputStream out = new ByteArrayOutputStream()) { 432 is.transferTo(out); 433 String stdout = out.toString("UTF-8"); 434 Log.i(tag, "Got stdout : " + stdout); 435 return stdout; 436 } catch (IOException e) { 437 Log.e(tag, "Error executing: " + command, e); 438 throw new RuntimeException("Failed to run the command.", e); 439 } 440 } 441 442 /** Execute a command. Returns the concatenation of stdout and stderr. */ runInShellWithStderr(String tag, UiAutomation uiAutomation, String command)443 protected String runInShellWithStderr(String tag, UiAutomation uiAutomation, String command) { 444 ParcelFileDescriptor[] files = uiAutomation.executeShellCommandRwe(command); 445 try (InputStream stdout = new ParcelFileDescriptor.AutoCloseInputStream(files[0]); 446 InputStream stderr = new ParcelFileDescriptor.AutoCloseInputStream(files[2]); 447 ByteArrayOutputStream out = new ByteArrayOutputStream()) { 448 files[1].close(); // The command's stdin 449 stdout.transferTo(out); 450 stderr.transferTo(out); 451 String output = out.toString("UTF-8"); 452 Log.i(tag, "Got output : " + stdout); 453 return output; 454 } catch (IOException e) { 455 Log.e(tag, "Error executing: " + command, e); 456 throw new RuntimeException("Failed to run the command.", e); 457 } 458 } 459 460 protected static class TestResults { 461 public Exception mException; 462 public Integer mAddInteger; 463 public String mAppRunProp; 464 public String mSublibRunProp; 465 public String mExtraApkTestProp; 466 public String mApkContentsPath; 467 public String mEncryptedStoragePath; 468 public String[] mEffectiveCapabilities; 469 public String mFileContent; 470 public byte[] mBcc; 471 public long[] mTimings; 472 public int mFileMode; 473 public int mMountFlags; 474 assertNoException()475 public void assertNoException() { 476 if (mException != null) { 477 // Rethrow, wrapped in a new exception, so we get stack traces of the original 478 // failure as well as the body of the test. 479 throw new RuntimeException(mException); 480 } 481 } 482 } 483 runVmTestService( String logTag, VirtualMachine vm, RunTestsAgainstTestService testsToRun)484 protected TestResults runVmTestService( 485 String logTag, VirtualMachine vm, RunTestsAgainstTestService testsToRun) 486 throws Exception { 487 CompletableFuture<Boolean> payloadStarted = new CompletableFuture<>(); 488 CompletableFuture<Boolean> payloadReady = new CompletableFuture<>(); 489 CompletableFuture<Boolean> payloadFinished = new CompletableFuture<>(); 490 TestResults testResults = new TestResults(); 491 VmEventListener listener = 492 new VmEventListener() { 493 ITestService mTestService = null; 494 495 private void initializeTestService(VirtualMachine vm) { 496 try { 497 mTestService = 498 ITestService.Stub.asInterface( 499 vm.connectToVsockServer(ITestService.SERVICE_PORT)); 500 // Make sure linkToDeath works, and include it in the log in case it's 501 // helpful. 502 mTestService 503 .asBinder() 504 .linkToDeath( 505 () -> Log.i(logTag, "ITestService binder died"), 0); 506 } catch (Exception e) { 507 testResults.mException = e; 508 } 509 } 510 511 private void testVMService(VirtualMachine vm) { 512 try { 513 if (mTestService == null) initializeTestService(vm); 514 testsToRun.runTests(mTestService, testResults); 515 } catch (Exception e) { 516 testResults.mException = e; 517 } 518 } 519 520 private void quitVMService() { 521 try { 522 mTestService.quit(); 523 } catch (Exception e) { 524 testResults.mException = e; 525 } 526 } 527 528 @Override 529 public void onPayloadReady(VirtualMachine vm) { 530 Log.i(logTag, "onPayloadReady"); 531 payloadReady.complete(true); 532 testVMService(vm); 533 quitVMService(); 534 } 535 536 @Override 537 public void onPayloadStarted(VirtualMachine vm) { 538 Log.i(logTag, "onPayloadStarted"); 539 payloadStarted.complete(true); 540 } 541 542 @Override 543 public void onPayloadFinished(VirtualMachine vm, int exitCode) { 544 Log.i(logTag, "onPayloadFinished: " + exitCode); 545 payloadFinished.complete(true); 546 forceStop(vm); 547 } 548 }; 549 550 listener.runToFinish(logTag, vm); 551 assertThat(payloadStarted.getNow(false)).isTrue(); 552 assertThat(payloadReady.getNow(false)).isTrue(); 553 assertThat(payloadFinished.getNow(false)).isTrue(); 554 return testResults; 555 } 556 557 @FunctionalInterface 558 protected interface RunTestsAgainstTestService { runTests(ITestService testService, TestResults testResults)559 void runTests(ITestService testService, TestResults testResults) throws Exception; 560 } 561 } 562