Merge "Use psci crate for PSCI calls."
diff --git a/.prebuilt_info/prebuilt_info_pvmfw_pvmfw_img.asciipb b/.prebuilt_info/prebuilt_info_pvmfw_pvmfw_img.asciipb
index eee2b20..33dfb62 100644
--- a/.prebuilt_info/prebuilt_info_pvmfw_pvmfw_img.asciipb
+++ b/.prebuilt_info/prebuilt_info_pvmfw_pvmfw_img.asciipb
@@ -1,6 +1,6 @@
 drops {
   android_build_drop {
-    build_id: "8351176"
+    build_id: "8494941"
     target: "u-boot_pvmfw"
     source_file: "pvmfw.img"
   }
diff --git a/OWNERS b/OWNERS
index c72e030..7e479c4 100644
--- a/OWNERS
+++ b/OWNERS
@@ -18,5 +18,6 @@
 qperret@google.com
 qwandor@google.com
 serbanc@google.com
+shikhapanwar@google.com
 tabba@google.com
 victorhsieh@google.com
diff --git a/authfs/tests/java/src/com/android/fs/AuthFsHostTest.java b/authfs/tests/java/src/com/android/fs/AuthFsHostTest.java
index 64658a9..31f8458 100644
--- a/authfs/tests/java/src/com/android/fs/AuthFsHostTest.java
+++ b/authfs/tests/java/src/com/android/fs/AuthFsHostTest.java
@@ -16,6 +16,8 @@
 
 package com.android.virt.fs;
 
+import static android.virt.test.LogArchiver.archiveLogThenDelete;
+
 import static com.android.tradefed.device.TestDevice.MicrodroidBuilder;
 import static com.android.tradefed.testtype.DeviceJUnit4ClassRunner.TestLogData;
 
@@ -26,11 +28,10 @@
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
-import static org.junit.Assume.assumeFalse;
+import static org.junit.Assume.assumeTrue;
 
 import android.platform.test.annotations.RootPermissionTest;
 import android.virt.test.CommandRunner;
-import android.virt.test.VirtualizationTestCaseBase;
 
 import com.android.compatibility.common.tradefed.build.CompatibilityBuildHelper;
 import com.android.compatibility.common.util.PollingCheck;
@@ -42,12 +43,12 @@
 import com.android.tradefed.log.LogUtil.CLog;
 import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
 import com.android.tradefed.testtype.junit4.AfterClassWithInfo;
+import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test;
 import com.android.tradefed.testtype.junit4.BeforeClassWithInfo;
 import com.android.tradefed.util.CommandResult;
 import com.android.tradefed.util.CommandStatus;
 
 import org.junit.After;
-import org.junit.AssumptionViolatedException;
 import org.junit.Before;
 import org.junit.Rule;
 import org.junit.Test;
@@ -62,7 +63,7 @@
 
 @RootPermissionTest
 @RunWith(DeviceJUnit4ClassRunner.class)
-public final class AuthFsHostTest extends VirtualizationTestCaseBase {
+public final class AuthFsHostTest extends BaseHostJUnit4Test {
 
     /** Test directory on Android where data are located */
     private static final String TEST_DIR = "/data/local/tmp/authfs";
@@ -70,6 +71,9 @@
     /** Output directory where the test can generate output on Android */
     private static final String TEST_OUTPUT_DIR = "/data/local/tmp/authfs/output_dir";
 
+    /** VM's log file */
+    private static final String LOG_PATH = TEST_OUTPUT_DIR + "/log.txt";
+
     /** File name of the test APK */
     private static final String TEST_APK_NAME = "MicrodroidTestApp.apk";
 
@@ -122,20 +126,18 @@
     @BeforeClassWithInfo
     public static void beforeClassWithDevice(TestInformation testInfo) throws Exception {
         assertNotNull(testInfo.getDevice());
-        ITestDevice androidDevice = testInfo.getDevice();
+        if (!(testInfo.getDevice() instanceof TestDevice)) {
+            CLog.w("Unexpected type of ITestDevice. Skipping.");
+            return;
+        }
+        TestDevice androidDevice = (TestDevice) testInfo.getDevice();
         sAndroid = new CommandRunner(androidDevice);
 
-        try {
-            testIfDeviceIsCapable(androidDevice);
-        } catch (AssumptionViolatedException e) {
-            // NB: The assumption exception is NOT handled by the test infra when it is thrown from
-            // a class method (see b/37502066). This has not only caused the loss of log, but also
-            // prevented the test cases to be reported at all and thus confused the test infra.
-            //
-            // Since we want to avoid the big overhead to start the VM repeatedly on CF, let's catch
-            // AssumptionViolatedException and emulate it artifitially.
-            CLog.e("Assumption failed: " + e);
-            sAssumptionFailed = true;
+        // NB: We can't use assumeTrue because the assumption exception is NOT handled by the test
+        // infra when it is thrown from a class method (see b/37502066). We need to skip both here
+        // and in setUp.
+        if (!androidDevice.supportsMicrodroid()) {
+            CLog.i("Microdroid not supported. Skipping.");
             return;
         }
 
@@ -148,11 +150,12 @@
                         .addExtraIdsigPath(EXTRA_IDSIG_PATH)
                         .build((TestDevice) androidDevice);
 
+        // From this point on, we need to tear down the Microdroid instance
+        sMicrodroid = new CommandRunner(microdroidDevice);
+
         // Root because authfs (started from shell in this test) currently require root to open
         // /dev/fuse and mount the FUSE.
         assertThat(microdroidDevice.enableAdbRoot()).isTrue();
-
-        sMicrodroid = new CommandRunner(microdroidDevice);
     }
 
     @AfterClassWithInfo
@@ -171,7 +174,7 @@
 
     @Before
     public void setUp() throws Exception {
-        assumeFalse(sAssumptionFailed);
+        assumeTrue(((TestDevice) getDevice()).supportsMicrodroid());
         sAndroid.run("mkdir " + TEST_OUTPUT_DIR);
     }
 
@@ -208,11 +211,11 @@
                         + VMADDR_CID_HOST);
 
         // Action
-        String actualHashUnverified4m = computeFileHashOnMicrodroid(MOUNT_DIR + "/6");
-        String actualHash4m = computeFileHashOnMicrodroid(MOUNT_DIR + "/3");
+        String actualHashUnverified4m = computeFileHash(sMicrodroid, MOUNT_DIR + "/6");
+        String actualHash4m = computeFileHash(sMicrodroid, MOUNT_DIR + "/3");
 
         // Verify
-        String expectedHash4m = computeFileHashOnAndroid(TEST_DIR + "/input.4m");
+        String expectedHash4m = computeFileHash(sAndroid, TEST_DIR + "/input.4m");
 
         assertEquals("Inconsistent hash from /authfs/6: ", expectedHash4m, actualHashUnverified4m);
         assertEquals("Inconsistent hash from /authfs/3: ", expectedHash4m, actualHash4m);
@@ -232,12 +235,12 @@
                 + VMADDR_CID_HOST);
 
         // Action
-        String actualHash4k = computeFileHashOnMicrodroid(MOUNT_DIR + "/3");
-        String actualHash4k1 = computeFileHashOnMicrodroid(MOUNT_DIR + "/6");
+        String actualHash4k = computeFileHash(sMicrodroid, MOUNT_DIR + "/3");
+        String actualHash4k1 = computeFileHash(sMicrodroid, MOUNT_DIR + "/6");
 
         // Verify
-        String expectedHash4k = computeFileHashOnAndroid(TEST_DIR + "/input.4k");
-        String expectedHash4k1 = computeFileHashOnAndroid(TEST_DIR + "/input.4k1");
+        String expectedHash4k = computeFileHash(sAndroid, TEST_DIR + "/input.4k");
+        String expectedHash4k1 = computeFileHash(sAndroid, TEST_DIR + "/input.4k1");
 
         assertEquals("Inconsistent hash from /authfs/3: ", expectedHash4k, actualHash4k);
         assertEquals("Inconsistent hash from /authfs/6: ", expectedHash4k1, actualHash4k1);
@@ -252,7 +255,7 @@
         runAuthFsOnMicrodroid("--remote-ro-file 3:" + DIGEST_4M + " --cid " + VMADDR_CID_HOST);
 
         // Verify
-        assertFalse(copyFileOnMicrodroid(MOUNT_DIR + "/3", "/dev/null"));
+        assertFalse(copyFile(sMicrodroid, MOUNT_DIR + "/3", "/dev/null"));
     }
 
     @Test
@@ -265,10 +268,10 @@
         String srcPath = "/system/bin/linker64";
         String destPath = MOUNT_DIR + "/3";
         String backendPath = TEST_OUTPUT_DIR + "/out.file";
-        assertTrue(copyFileOnMicrodroid(srcPath, destPath));
+        assertTrue(copyFile(sMicrodroid, srcPath, destPath));
 
         // Verify
-        String expectedHash = computeFileHashOnMicrodroid(srcPath);
+        String expectedHash = computeFileHash(sMicrodroid, srcPath);
         expectBackingFileConsistency(destPath, backendPath, expectedHash);
     }
 
@@ -281,30 +284,32 @@
         String srcPath = "/system/bin/linker64";
         String destPath = MOUNT_DIR + "/3";
         String backendPath = TEST_OUTPUT_DIR + "/out.file";
-        assertTrue(copyFileOnMicrodroid(srcPath, destPath));
+        assertTrue(copyFile(sMicrodroid, srcPath, destPath));
 
         // Action
         // Tampering with the first 2 4K-blocks of the backing file.
-        zeroizeFileOnAndroid(backendPath, /* size */ 8192, /* offset */ 0);
+        assertTrue(
+                writeZerosAtFileOffset(sAndroid, backendPath,
+                        /* offset */ 0, /* number */ 8192, /* writeThrough */ false));
 
         // Verify
         // Write to a block partially requires a read back to calculate the new hash. It should fail
         // when the content is inconsistent to the known hash. Use direct I/O to avoid simply
         // writing to the filesystem cache.
         assertFalse(
-                writeZerosAtFileOffsetOnMicrodroid(
-                        destPath, /* offset */ 0, /* number */ 1024, /* writeThrough */ true));
+                writeZerosAtFileOffset(sMicrodroid, destPath,
+                        /* offset */ 0, /* number */ 1024, /* writeThrough */ true));
 
         // A full 4K write does not require to read back, so write can succeed even if the backing
         // block has already been tampered.
         assertTrue(
-                writeZerosAtFileOffsetOnMicrodroid(
-                        destPath, /* offset */ 4096, /* number */ 4096, /* writeThrough */ false));
+                writeZerosAtFileOffset(sMicrodroid, destPath,
+                        /* offset */ 4096, /* number */ 4096, /* writeThrough */ false));
 
         // Otherwise, a partial write with correct backing file should still succeed.
         assertTrue(
-                writeZerosAtFileOffsetOnMicrodroid(
-                        destPath, /* offset */ 8192, /* number */ 1024, /* writeThrough */ false));
+                writeZerosAtFileOffset(sMicrodroid, destPath,
+                        /* offset */ 8192, /* number */ 1024, /* writeThrough */ false));
     }
 
     @Test
@@ -316,20 +321,20 @@
         String srcPath = "/system/bin/linker64";
         String destPath = MOUNT_DIR + "/3";
         String backendPath = TEST_OUTPUT_DIR + "/out.file";
-        assertTrue(copyFileOnMicrodroid(srcPath, destPath));
+        assertTrue(copyFile(sMicrodroid, srcPath, destPath));
 
         // Action
         // Tampering with the first 4K-block of the backing file.
-        zeroizeFileOnAndroid(backendPath, /* size */ 4096, /* offset */ 0);
+        assertTrue(
+                writeZerosAtFileOffset(sAndroid, backendPath,
+                        /* offset */ 0, /* number */ 4096, /* writeThrough */ false));
 
         // Verify
         // Force dropping the page cache, so that the next read can be validated.
         sMicrodroid.run("echo 1 > /proc/sys/vm/drop_caches");
         // A read will fail if the backing data has been tampered.
-        assertFalse(checkReadAtFileOffsetOnMicrodroid(
-                destPath, /* offset */ 0, /* number */ 4096));
-        assertTrue(checkReadAtFileOffsetOnMicrodroid(
-                destPath, /* offset */ 4096, /* number */ 4096));
+        assertFalse(checkReadAt(sMicrodroid, destPath, /* offset */ 0, /* number */ 4096));
+        assertTrue(checkReadAt(sMicrodroid, destPath, /* offset */ 4096, /* number */ 4096));
     }
 
     @Test
@@ -340,17 +345,19 @@
 
         String outputPath = MOUNT_DIR + "/3";
         String backendPath = TEST_OUTPUT_DIR + "/out.file";
-        createFileWithOnesOnMicrodroid(outputPath, 8192);
+        createFileWithOnes(sMicrodroid, outputPath, 8192);
 
         // Action
         // Tampering with the last 4K-block of the backing file.
-        zeroizeFileOnAndroid(backendPath, /* size */ 1, /* offset */ 4096);
+        assertTrue(
+                writeZerosAtFileOffset(sAndroid, backendPath,
+                        /* offset */ 4096, /* number */ 1, /* writeThrough */ false));
 
         // Verify
         // A resize (to a non-multiple of 4K) will fail if the last backing chunk has been
         // tampered. The original data is necessary (and has to be verified) to calculate the new
         // hash with shorter data.
-        assertFalse(resizeFileOnMicrodroid(outputPath, 8000));
+        assertFalse(resizeFile(sMicrodroid, outputPath, 8000));
     }
 
     @Test
@@ -362,22 +369,22 @@
         String backendPath = TEST_OUTPUT_DIR + "/out.file";
 
         // Action & Verify
-        createFileWithOnesOnMicrodroid(outputPath, 10000);
-        assertEquals(getFileSizeInBytesOnMicrodroid(outputPath), 10000);
+        createFileWithOnes(sMicrodroid, outputPath, 10000);
+        assertEquals(getFileSizeInBytes(sMicrodroid, outputPath), 10000);
         expectBackingFileConsistency(
                 outputPath,
                 backendPath,
                 "684ad25fdc2bbb80cbc910dd1bde6d5499ccf860ca6ee44704b77ec445271353");
 
-        assertTrue(resizeFileOnMicrodroid(outputPath, 15000));
-        assertEquals(getFileSizeInBytesOnMicrodroid(outputPath), 15000);
+        assertTrue(resizeFile(sMicrodroid, outputPath, 15000));
+        assertEquals(getFileSizeInBytes(sMicrodroid, outputPath), 15000);
         expectBackingFileConsistency(
                 outputPath,
                 backendPath,
                 "567c89f62586e0d33369157afdfe99a2fa36cdffb01e91dcdc0b7355262d610d");
 
-        assertTrue(resizeFileOnMicrodroid(outputPath, 5000));
-        assertEquals(getFileSizeInBytesOnMicrodroid(outputPath), 5000);
+        assertTrue(resizeFile(sMicrodroid, outputPath, 5000));
+        assertEquals(getFileSizeInBytes(sMicrodroid, outputPath), 5000);
         expectBackingFileConsistency(
                 outputPath,
                 backendPath,
@@ -397,16 +404,16 @@
         // Can create a new file to write.
         String expectedAndroidPath = androidOutputDir + "/file";
         String authfsPath = authfsOutputDir + "/file";
-        createFileWithOnesOnMicrodroid(authfsPath, 10000);
-        assertEquals(getFileSizeInBytesOnMicrodroid(authfsPath), 10000);
+        createFileWithOnes(sMicrodroid, authfsPath, 10000);
+        assertEquals(getFileSizeInBytes(sMicrodroid, authfsPath), 10000);
         expectBackingFileConsistency(
                 authfsPath,
                 expectedAndroidPath,
                 "684ad25fdc2bbb80cbc910dd1bde6d5499ccf860ca6ee44704b77ec445271353");
 
         // Regular file operations work, e.g. resize.
-        assertTrue(resizeFileOnMicrodroid(authfsPath, 15000));
-        assertEquals(getFileSizeInBytesOnMicrodroid(authfsPath), 15000);
+        assertTrue(resizeFile(sMicrodroid, authfsPath, 15000));
+        assertEquals(getFileSizeInBytes(sMicrodroid, authfsPath), 15000);
         expectBackingFileConsistency(
                 authfsPath,
                 expectedAndroidPath,
@@ -426,21 +433,21 @@
         // Can create nested directories and can create a file in one.
         sMicrodroid.run("mkdir " + authfsOutputDir + "/new_dir");
         sMicrodroid.run("mkdir -p " + authfsOutputDir + "/we/need/to/go/deeper");
-        createFileWithOnesOnMicrodroid(authfsOutputDir + "/new_dir/file1", 10000);
-        createFileWithOnesOnMicrodroid(authfsOutputDir + "/we/need/file2", 10000);
+        createFileWithOnes(sMicrodroid, authfsOutputDir + "/new_dir/file1", 10000);
+        createFileWithOnes(sMicrodroid, authfsOutputDir + "/we/need/file2", 10000);
 
         // Verify
         // Directories show up in Android.
         sAndroid.run("test -d " + androidOutputDir + "/new_dir");
         sAndroid.run("test -d " + androidOutputDir + "/we/need/to/go/deeper");
         // Files exist in Android. Hashes on Microdroid and Android are consistent.
-        assertEquals(getFileSizeInBytesOnMicrodroid(authfsOutputDir + "/new_dir/file1"), 10000);
+        assertEquals(getFileSizeInBytes(sMicrodroid, authfsOutputDir + "/new_dir/file1"), 10000);
         expectBackingFileConsistency(
                 authfsOutputDir + "/new_dir/file1",
                 androidOutputDir + "/new_dir/file1",
                 "684ad25fdc2bbb80cbc910dd1bde6d5499ccf860ca6ee44704b77ec445271353");
         // Same to file in a nested directory.
-        assertEquals(getFileSizeInBytesOnMicrodroid(authfsOutputDir + "/we/need/file2"), 10000);
+        assertEquals(getFileSizeInBytes(sMicrodroid, authfsOutputDir + "/we/need/file2"), 10000);
         expectBackingFileConsistency(
                 authfsOutputDir + "/we/need/file2",
                 androidOutputDir + "/we/need/file2",
@@ -458,10 +465,10 @@
 
         // Action & Verify
         sMicrodroid.run("echo -n foo > " + authfsOutputDir + "/file");
-        assertEquals(getFileSizeInBytesOnMicrodroid(authfsOutputDir + "/file"), 3);
+        assertEquals(getFileSizeInBytes(sMicrodroid, authfsOutputDir + "/file"), 3);
         // Can override a file and write normally.
-        createFileWithOnesOnMicrodroid(authfsOutputDir + "/file", 10000);
-        assertEquals(getFileSizeInBytesOnMicrodroid(authfsOutputDir + "/file"), 10000);
+        createFileWithOnes(sMicrodroid, authfsOutputDir + "/file", 10000);
+        assertEquals(getFileSizeInBytes(sMicrodroid, authfsOutputDir + "/file"), 10000);
         expectBackingFileConsistency(
                 authfsOutputDir + "/file",
                 androidOutputDir + "/file",
@@ -505,7 +512,7 @@
         sMicrodroid.run("test ! -d " + authfsOutputDir + "/dir/dir2");
         sAndroid.run("test ! -d " + androidOutputDir + "/dir/dir2");
         // Can only delete a directory if empty
-        assertFailedOnMicrodroid("rmdir " + authfsOutputDir + "/dir");
+        assertFailed(sMicrodroid, "rmdir " + authfsOutputDir + "/dir");
         sMicrodroid.run("test -d " + authfsOutputDir + "/dir");  // still there
         sMicrodroid.run("rm " + authfsOutputDir + "/dir/file");
         sMicrodroid.run("rmdir " + authfsOutputDir + "/dir");
@@ -529,10 +536,10 @@
 
         // Action & Verify
         // Cannot create directory if an entry with the same name already exists.
-        assertFailedOnMicrodroid("mkdir " + authfsOutputDir + "/some_file");
-        assertFailedOnMicrodroid("mkdir " + authfsOutputDir + "/some_dir");
-        assertFailedOnMicrodroid("mkdir " + authfsOutputDir + "/some_dir/file");
-        assertFailedOnMicrodroid("mkdir " + authfsOutputDir + "/some_dir/dir");
+        assertFailed(sMicrodroid, "mkdir " + authfsOutputDir + "/some_file");
+        assertFailed(sMicrodroid, "mkdir " + authfsOutputDir + "/some_dir");
+        assertFailed(sMicrodroid, "mkdir " + authfsOutputDir + "/some_dir/file");
+        assertFailed(sMicrodroid, "mkdir " + authfsOutputDir + "/some_dir/dir");
     }
 
     @Test
@@ -579,10 +586,10 @@
 
         // Action
         String actualHash =
-                computeFileHashOnMicrodroid(authfsInputDir + "/system/framework/framework.jar");
+                computeFileHash(sMicrodroid, authfsInputDir + "/system/framework/framework.jar");
 
         // Verify
-        String expectedHash = computeFileHashOnAndroid("/system/framework/framework.jar");
+        String expectedHash = computeFileHash(sAndroid, "/system/framework/framework.jar");
         assertEquals("Expect consistent hash through /authfs/3: ", expectedHash, actualHash);
     }
 
@@ -596,7 +603,7 @@
 
         // Verify
         sMicrodroid.run("test -f " + authfsInputDir + "/system/framework/services.jar");
-        assertFailedOnMicrodroid("test -f " + authfsInputDir + "/system/bin/sh");
+        assertFailed(sMicrodroid, "test -f " + authfsInputDir + "/system/bin/sh");
     }
 
     @Test
@@ -651,8 +658,8 @@
         sMicrodroid.run("chmod 321 " + MOUNT_DIR + "/3");
         expectFileMode("--wx-w---x", MOUNT_DIR + "/3", TEST_OUTPUT_DIR + "/file");
         // Can't set the disallowed bits
-        assertFailedOnMicrodroid("chmod +s " + MOUNT_DIR + "/3");
-        assertFailedOnMicrodroid("chmod +t " + MOUNT_DIR + "/3");
+        assertFailed(sMicrodroid, "chmod +s " + MOUNT_DIR + "/3");
+        assertFailed(sMicrodroid, "chmod +t " + MOUNT_DIR + "/3");
     }
 
     @Test
@@ -674,8 +681,8 @@
         sMicrodroid.run("chmod 321 " + authfsOutputDir + "/dir");
         expectFileMode("d-wx-w---x", authfsOutputDir + "/dir", TEST_OUTPUT_DIR + "/dir");
         // Can't set the disallowed bits
-        assertFailedOnMicrodroid("chmod +s " + authfsOutputDir + "/dir/dir2");
-        assertFailedOnMicrodroid("chmod +t " + authfsOutputDir + "/dir");
+        assertFailed(sMicrodroid, "chmod +s " + authfsOutputDir + "/dir/dir2");
+        assertFailed(sMicrodroid, "chmod +t " + authfsOutputDir + "/dir");
     }
 
     @Test
@@ -697,8 +704,8 @@
         sMicrodroid.run("chmod 321 " + authfsOutputDir + "/file2");
         expectFileMode("--wx-w---x", authfsOutputDir + "/file2", TEST_OUTPUT_DIR + "/file2");
         // Can't set the disallowed bits
-        assertFailedOnMicrodroid("chmod +s " + authfsOutputDir + "/file");
-        assertFailedOnMicrodroid("chmod +t " + authfsOutputDir + "/file2");
+        assertFailed(sMicrodroid, "chmod +s " + authfsOutputDir + "/file");
+        assertFailed(sMicrodroid, "chmod +t " + authfsOutputDir + "/file2");
     }
 
     @Test
@@ -725,35 +732,17 @@
     private void expectBackingFileConsistency(
             String authFsPath, String backendPath, String expectedHash)
             throws DeviceNotAvailableException {
-        String hashOnAuthFs = computeFileHashOnMicrodroid(authFsPath);
+        String hashOnAuthFs = computeFileHash(sMicrodroid, authFsPath);
         assertEquals("File hash is different to expectation", expectedHash, hashOnAuthFs);
 
-        String hashOfBackingFile = computeFileHashOnAndroid(backendPath);
+        String hashOfBackingFile = computeFileHash(sAndroid, backendPath);
         assertEquals(
                 "Inconsistent file hash on the backend storage", hashOnAuthFs, hashOfBackingFile);
     }
 
-    private String computeFileHashOnMicrodroid(String path) throws DeviceNotAvailableException {
-        String result = sMicrodroid.run("sha256sum " + path);
-        String[] tokens = result.split("\\s");
-        if (tokens.length > 0) {
-            return tokens[0];
-        } else {
-            CLog.e("Unrecognized output by sha256sum: " + result);
-            return "";
-        }
-    }
-
-    private boolean copyFileOnMicrodroid(String src, String dest)
+    private static String computeFileHash(CommandRunner runner, String path)
             throws DeviceNotAvailableException {
-        // TODO(b/182576497): cp returns error because close(2) returns ENOSYS in the current authfs
-        // implementation. We should probably fix that since programs can expect close(2) return 0.
-        String cmd = "cat " + src + " > " + dest;
-        return sMicrodroid.tryRun(cmd) != null;
-    }
-
-    private String computeFileHashOnAndroid(String path) throws DeviceNotAvailableException {
-        String result = sAndroid.run("sha256sum " + path);
+        String result = runner.run("sha256sum " + path);
         String[] tokens = result.split("\\s");
         if (tokens.length > 0) {
             return tokens[0];
@@ -763,6 +752,15 @@
         }
     }
 
+    private static boolean copyFile(CommandRunner runner, String src, String dest)
+            throws DeviceNotAvailableException {
+        // toybox's cp(1) implementation ignores most read(2) errors, and it's unclear what the
+        // canonical behavior should be (not mentioned in manpage). For this test, use cat(1) in
+        // order to fail on I/O error.
+        CommandResult result = runner.runForResult("cat " + src + " > " + dest);
+        return result.getStatus() == CommandStatus.SUCCESS;
+    }
+
     private void expectFileMode(String expected, String microdroidPath, String androidPath)
             throws DeviceNotAvailableException {
         String actual = sMicrodroid.run("stat -c '%A' " + microdroidPath);
@@ -772,34 +770,35 @@
         assertEquals("Inconsistent mode for " + androidPath + " (android)", expected, actual);
     }
 
-    private boolean resizeFileOnMicrodroid(String path, long size)
+    private static boolean resizeFile(CommandRunner runner, String path, long size)
             throws DeviceNotAvailableException {
-        CommandResult result = sMicrodroid.runForResult("truncate -c -s " + size + " " + path);
+        CommandResult result = runner.runForResult("truncate -c -s " + size + " " + path);
         return result.getStatus() == CommandStatus.SUCCESS;
     }
 
-    private long getFileSizeInBytesOnMicrodroid(String path) throws DeviceNotAvailableException {
-        return Long.parseLong(sMicrodroid.run("stat -c '%s' " + path));
+    private static long getFileSizeInBytes(CommandRunner runner, String path)
+            throws DeviceNotAvailableException {
+        return Long.parseLong(runner.run("stat -c '%s' " + path));
     }
 
-    private void createFileWithOnesOnMicrodroid(String filePath, long numberOfOnes)
+    private static void createFileWithOnes(CommandRunner runner, String filePath, long numberOfOnes)
             throws DeviceNotAvailableException {
-        sMicrodroid.run(
+        runner.run(
                 "yes $'\\x01' | tr -d '\\n' | dd bs=1 count=" + numberOfOnes + " of=" + filePath);
     }
 
-    private boolean checkReadAtFileOffsetOnMicrodroid(String filePath, long offset, long size)
-            throws DeviceNotAvailableException {
+    private static boolean checkReadAt(CommandRunner runner, String filePath, long offset,
+            long size) throws DeviceNotAvailableException {
         String cmd = "dd if=" + filePath + " of=/dev/null bs=1 count=" + size;
         if (offset > 0) {
             cmd += " skip=" + offset;
         }
-        CommandResult result = sMicrodroid.runForResult(cmd);
+        CommandResult result = runner.runForResult(cmd);
         return result.getStatus() == CommandStatus.SUCCESS;
     }
 
-    private boolean writeZerosAtFileOffsetOnMicrodroid(
-            String filePath, long offset, long numberOfZeros, boolean writeThrough)
+    private static boolean writeZerosAtFileOffset(CommandRunner runner, String filePath,
+            long offset, long numberOfZeros, boolean writeThrough)
             throws DeviceNotAvailableException {
         String cmd = "dd if=/dev/zero of=" + filePath + " bs=1 count=" + numberOfZeros
                 + " conv=notrunc";
@@ -809,14 +808,14 @@
         if (writeThrough) {
             cmd += " direct";
         }
-        CommandResult result = sMicrodroid.runForResult(cmd);
+        CommandResult result = runner.runForResult(cmd);
         return result.getStatus() == CommandStatus.SUCCESS;
     }
 
-    private void zeroizeFileOnAndroid(String filePath, long size, long offset)
+    private static void assertFailed(CommandRunner runner, String... cmd)
             throws DeviceNotAvailableException {
-        sAndroid.run("dd if=/dev/zero of=" + filePath + " bs=1 count=" + size + " conv=notrunc"
-                + " seek=" + offset);
+        CommandResult result = runner.runForResult(cmd);
+        assertThat(result.getStatus()).isEqualTo(CommandStatus.FAILED);
     }
 
     private void runAuthFsOnMicrodroid(String flags) {
diff --git a/compos/aidl/com/android/compos/ICompOsService.aidl b/compos/aidl/com/android/compos/ICompOsService.aidl
index ef48ccf..48a46b1 100644
--- a/compos/aidl/com/android/compos/ICompOsService.aidl
+++ b/compos/aidl/com/android/compos/ICompOsService.aidl
@@ -55,4 +55,11 @@
      * (https://datatracker.ietf.org/doc/html/rfc8032#section-5.1.5).
      */
     byte[] getPublicKey();
+
+    /**
+     * Returns the attestation certificate chain of the current VM. The result is in the form of a
+     * CBOR encoded Boot Certificate Chain (BCC) as defined in
+     * hardware/interfaces/security/dice/aidl/android/hardware/security/dice/Bcc.aidl.
+     */
+    byte[] getAttestationChain();
 }
diff --git a/compos/common/compos_client.rs b/compos/common/compos_client.rs
index 16dc2cf..839280c 100644
--- a/compos/common/compos_client.rs
+++ b/compos/common/compos_client.rs
@@ -64,6 +64,8 @@
     /// Comma separated list of host CPUs where vCPUs are assigned to. If None, any host CPU can be
     /// used to run any vCPU.
     pub cpu_set: Option<String>,
+    /// List of task profiles to apply to the VM
+    pub task_profiles: Vec<String>,
     /// If present, overrides the path to the VM config JSON file
     pub config_path: Option<String>,
     /// If present, overrides the amount of RAM to give the VM
@@ -137,6 +139,7 @@
             memoryMib: parameters.memory_mib.unwrap_or(0), // 0 means use the default
             numCpus: parameters.cpus.map_or(1, NonZeroU32::get) as i32,
             cpuAffinity: parameters.cpu_set.clone(),
+            taskProfiles: parameters.task_profiles.clone(),
         });
 
         let vm = service
diff --git a/compos/compos_key_helper/compos_key_main.cpp b/compos/compos_key_helper/compos_key_main.cpp
index a0d0b18..9ba9f8d 100644
--- a/compos/compos_key_helper/compos_key_main.cpp
+++ b/compos/compos_key_helper/compos_key_main.cpp
@@ -25,6 +25,7 @@
 
 #include "compos_key.h"
 
+using aidl::android::hardware::security::dice::Bcc;
 using aidl::android::hardware::security::dice::BccHandover;
 using aidl::android::hardware::security::dice::InputValues;
 using aidl::android::security::dice::IDiceNode;
@@ -68,6 +69,30 @@
     return 0;
 }
 
+int write_bcc() {
+    ndk::SpAIBinder binder{AServiceManager_getService("android.security.dice.IDiceNode")};
+    auto dice_node = IDiceNode::fromBinder(binder);
+    if (!dice_node) {
+        LOG(ERROR) << "Unable to connect to IDiceNode";
+        return 1;
+    }
+
+    const std::vector<InputValues> empty_input_values;
+    Bcc bcc;
+    auto status = dice_node->getAttestationChain(empty_input_values, &bcc);
+    if (!status.isOk()) {
+        LOG(ERROR) << "GetAttestationChain failed: " << status.getDescription();
+        return 1;
+    }
+
+    if (!WriteFully(STDOUT_FILENO, bcc.data.data(), bcc.data.size())) {
+        PLOG(ERROR) << "Write failed";
+        return 1;
+    }
+
+    return 0;
+}
+
 int sign_input() {
     std::string to_sign;
     if (!ReadFdToString(STDIN_FILENO, &to_sign)) {
@@ -103,6 +128,8 @@
     if (argc == 2) {
         if (argv[1] == "public_key"sv) {
             return write_public_key();
+        } else if (argv[1] == "bcc"sv) {
+            return write_bcc();
         } else if (argv[1] == "sign"sv) {
             return sign_input();
         }
diff --git a/compos/composd/aidl/android/system/composd/ICompilationTaskCallback.aidl b/compos/composd/aidl/android/system/composd/ICompilationTaskCallback.aidl
index b334d8b..569bba5 100644
--- a/compos/composd/aidl/android/system/composd/ICompilationTaskCallback.aidl
+++ b/compos/composd/aidl/android/system/composd/ICompilationTaskCallback.aidl
@@ -20,6 +20,13 @@
  * requested compilation task completes.
  */
 oneway interface ICompilationTaskCallback {
+    enum FailureReason {
+        /** We failed to successfully start the VM and run compilation in it. */
+        CompilationFailed,
+        /** We ran compilation in the VM, but it reported a problem. */
+        UnexpectedCompilationResult,
+    }
+
     /**
      * Called if a compilation task has ended successfully, generating all the required artifacts.
      */
@@ -28,5 +35,5 @@
     /**
      * Called if a compilation task has ended unsuccessfully.
      */
-    void onFailure();
+    void onFailure(FailureReason reason, String message);
 }
diff --git a/compos/composd/src/instance_manager.rs b/compos/composd/src/instance_manager.rs
index 587314c..60bf20f 100644
--- a/compos/composd/src/instance_manager.rs
+++ b/compos/composd/src/instance_manager.rs
@@ -98,7 +98,14 @@
         }
     };
     let cpu_set = system_properties::read(DEX2OAT_CPU_SET_PROP_NAME)?;
-    Ok(VmParameters { cpus, cpu_set, memory_mib: Some(VM_MEMORY_MIB), ..Default::default() })
+    let task_profiles = vec!["VMCompilationPerformance".to_string()];
+    Ok(VmParameters {
+        cpus,
+        cpu_set,
+        task_profiles,
+        memory_mib: Some(VM_MEMORY_MIB),
+        ..Default::default()
+    })
 }
 
 // Ensures we only run one instance at a time.
diff --git a/compos/composd/src/instance_starter.rs b/compos/composd/src/instance_starter.rs
index 4873d7a..f899497 100644
--- a/compos/composd/src/instance_starter.rs
+++ b/compos/composd/src/instance_starter.rs
@@ -87,7 +87,13 @@
         let _ = fs::remove_file(&self.idsig);
         let _ = fs::remove_file(&self.idsig_manifest_apk);
 
-        self.start_vm(virtualization_service)
+        let instance = self.start_vm(virtualization_service)?;
+
+        // Retrieve the VM's attestation chain as a BCC and save it in the instance directory.
+        let bcc = instance.service.getAttestationChain().context("Getting attestation chain")?;
+        fs::write(self.instance_root.join("bcc"), bcc).context("Writing BCC")?;
+
+        Ok(instance)
     }
 
     fn start_vm(
diff --git a/compos/composd/src/odrefresh_task.rs b/compos/composd/src/odrefresh_task.rs
index 9dec1c1..e06e5fe 100644
--- a/compos/composd/src/odrefresh_task.rs
+++ b/compos/composd/src/odrefresh_task.rs
@@ -19,7 +19,8 @@
 use crate::fd_server_helper::FdServerConfig;
 use crate::instance_starter::CompOsInstance;
 use android_system_composd::aidl::android::system::composd::{
-    ICompilationTask::ICompilationTask, ICompilationTaskCallback::ICompilationTaskCallback,
+    ICompilationTask::ICompilationTask,
+    ICompilationTaskCallback::{FailureReason::FailureReason, ICompilationTaskCallback},
 };
 use android_system_composd::binder::{Interface, Result as BinderResult, Strong};
 use anyhow::{Context, Result};
@@ -99,12 +100,15 @@
                         task.callback.onSuccess()
                     }
                     Ok(exit_code) => {
-                        error!("Unexpected odrefresh result: {:?}", exit_code);
-                        task.callback.onFailure()
+                        let message = format!("Unexpected odrefresh result: {:?}", exit_code);
+                        error!("{}", message);
+                        task.callback
+                            .onFailure(FailureReason::UnexpectedCompilationResult, &message)
                     }
                     Err(e) => {
-                        error!("Running odrefresh failed: {:?}", e);
-                        task.callback.onFailure()
+                        let message = format!("Running odrefresh failed: {:?}", e);
+                        error!("{}", message);
+                        task.callback.onFailure(FailureReason::CompilationFailed, &message)
                     }
                 };
                 if let Err(e) = result {
diff --git a/compos/composd_cmd/composd_cmd.rs b/compos/composd_cmd/composd_cmd.rs
index 9f535d5..6afd711 100644
--- a/compos/composd_cmd/composd_cmd.rs
+++ b/compos/composd_cmd/composd_cmd.rs
@@ -19,7 +19,9 @@
 use android_system_composd::{
     aidl::android::system::composd::{
         ICompilationTask::ICompilationTask,
-        ICompilationTaskCallback::{BnCompilationTaskCallback, ICompilationTaskCallback},
+        ICompilationTaskCallback::{
+            BnCompilationTaskCallback, FailureReason::FailureReason, ICompilationTaskCallback,
+        },
         IIsolatedCompilationService::ApexSource::ApexSource,
         IIsolatedCompilationService::IIsolatedCompilationService,
     },
@@ -68,10 +70,10 @@
     completed: Condvar,
 }
 
-#[derive(Copy, Clone)]
 enum Outcome {
     Succeeded,
-    Failed,
+    Failed(FailureReason, String),
+    TaskDied,
 }
 
 impl Interface for Callback {}
@@ -82,8 +84,8 @@
         Ok(())
     }
 
-    fn onFailure(&self) -> BinderResult<()> {
-        self.0.set_outcome(Outcome::Failed);
+    fn onFailure(&self, reason: FailureReason, message: &str) -> BinderResult<()> {
+        self.0.set_outcome(Outcome::Failed(reason, message.to_owned()));
         Ok(())
     }
 }
@@ -97,14 +99,14 @@
     }
 
     fn wait(&self, duration: Duration) -> Result<Outcome> {
-        let (outcome, result) = self
+        let (mut outcome, result) = self
             .completed
             .wait_timeout_while(self.mutex.lock().unwrap(), duration, |outcome| outcome.is_none())
             .unwrap();
         if result.timed_out() {
             bail!("Timed out waiting for compilation")
         }
-        Ok(outcome.unwrap())
+        Ok(outcome.take().unwrap())
     }
 }
 
@@ -138,7 +140,7 @@
     let state_clone = state.clone();
     let mut death_recipient = DeathRecipient::new(move || {
         eprintln!("CompilationTask died");
-        state_clone.set_outcome(Outcome::Failed);
+        state_clone.set_outcome(Outcome::TaskDied);
     });
     // Note that dropping death_recipient cancels this, so we can't use a temporary here.
     task.as_binder().link_to_death(&mut death_recipient)?;
@@ -147,7 +149,10 @@
 
     match state.wait(timeouts()?.odrefresh_max_execution_time) {
         Ok(Outcome::Succeeded) => Ok(()),
-        Ok(Outcome::Failed) => bail!("Compilation failed"),
+        Ok(Outcome::TaskDied) => bail!("Compilation task died"),
+        Ok(Outcome::Failed(reason, message)) => {
+            bail!("Compilation failed: {:?}: {}", reason, message)
+        }
         Err(e) => {
             if let Err(e) = task.cancel() {
                 eprintln!("Failed to cancel compilation: {:?}", e);
diff --git a/compos/service/java/com/android/server/compos/IsolatedCompilationJobService.java b/compos/service/java/com/android/server/compos/IsolatedCompilationJobService.java
index 75f5334..cf852e3 100644
--- a/compos/service/java/com/android/server/compos/IsolatedCompilationJobService.java
+++ b/compos/service/java/com/android/server/compos/IsolatedCompilationJobService.java
@@ -31,6 +31,9 @@
 import android.system.composd.IIsolatedCompilationService;
 import android.util.Log;
 
+import com.android.server.compos.IsolatedCompilationMetrics.CompilationResult;
+
+import java.util.NoSuchElementException;
 import java.util.concurrent.TimeUnit;
 import java.util.concurrent.atomic.AtomicReference;
 
@@ -41,26 +44,10 @@
  */
 public class IsolatedCompilationJobService extends JobService {
     private static final String TAG = IsolatedCompilationJobService.class.getName();
-    private static final int DAILY_JOB_ID = 5132250;
     private static final int STAGED_APEX_JOB_ID = 5132251;
 
     private final AtomicReference<CompilationJob> mCurrentJob = new AtomicReference<>();
 
-    static void scheduleDailyJob(JobScheduler scheduler) {
-        // TODO(b/205296305) Remove this
-        ComponentName serviceName =
-                new ComponentName("android", IsolatedCompilationJobService.class.getName());
-
-        int result = scheduler.schedule(new JobInfo.Builder(DAILY_JOB_ID, serviceName)
-                .setRequiresDeviceIdle(true)
-                .setRequiresCharging(true)
-                .setPeriodic(TimeUnit.DAYS.toMillis(1))
-                .build());
-        if (result != JobScheduler.RESULT_SUCCESS) {
-            Log.e(TAG, "Failed to schedule daily job");
-        }
-    }
-
     static void scheduleStagedApexJob(JobScheduler scheduler) {
         ComponentName serviceName =
                 new ComponentName("android", IsolatedCompilationJobService.class.getName());
@@ -73,7 +60,12 @@
                 .setRequiresCharging(true)
                 .setRequiresStorageNotLow(true)
                 .build());
-        if (result != JobScheduler.RESULT_SUCCESS) {
+        if (result == JobScheduler.RESULT_SUCCESS) {
+            IsolatedCompilationMetrics.onCompilationScheduled(
+                    IsolatedCompilationMetrics.SCHEDULING_SUCCESS);
+        } else {
+            IsolatedCompilationMetrics.onCompilationScheduled(
+                    IsolatedCompilationMetrics.SCHEDULING_FAILURE);
             Log.e(TAG, "Failed to schedule staged APEX job");
         }
     }
@@ -84,9 +76,7 @@
 
     @Override
     public boolean onStartJob(JobParameters params) {
-        int jobId = params.getJobId();
-
-        Log.i(TAG, "Starting job " + jobId);
+        Log.i(TAG, "Starting job");
 
         // This function (and onStopJob) are only ever called on the main thread, so we don't have
         // to worry about two starts at once, or start and stop happening at once. But onCompletion
@@ -99,8 +89,10 @@
             return false;  // Already finished
         }
 
+        IsolatedCompilationMetrics metrics = new IsolatedCompilationMetrics();
+
         CompilationJob newJob = new CompilationJob(IsolatedCompilationJobService.this::onCompletion,
-                params);
+                params, metrics);
         mCurrentJob.set(newJob);
 
         // This can take some time - we need to start up a VM - so we do it on a separate
@@ -110,9 +102,10 @@
             @Override
             public void run() {
                 try {
-                    newJob.start(jobId);
+                    newJob.start();
                 } catch (RuntimeException e) {
                     Log.e(TAG, "Starting CompilationJob failed", e);
+                    metrics.onCompilationEnded(IsolatedCompilationMetrics.RESULT_FAILED_TO_START);
                     mCurrentJob.set(null);
                     newJob.stop(); // Just in case it managed to start before failure
                     jobFinished(params, /*wantReschedule=*/ false);
@@ -153,18 +146,20 @@
 
     static class CompilationJob extends ICompilationTaskCallback.Stub
             implements IBinder.DeathRecipient {
+        private final IsolatedCompilationMetrics mMetrics;
         private final AtomicReference<ICompilationTask> mTask = new AtomicReference<>();
         private final CompilationCallback mCallback;
         private final JobParameters mParams;
         private volatile boolean mStopRequested = false;
-        private volatile boolean mCanceled = false;
 
-        CompilationJob(CompilationCallback callback, JobParameters params) {
+        CompilationJob(CompilationCallback callback, JobParameters params,
+                IsolatedCompilationMetrics metrics) {
             mCallback = requireNonNull(callback);
             mParams = params;
+            mMetrics = requireNonNull(metrics);
         }
 
-        void start(int jobId) {
+        void start() {
             IBinder binder = ServiceManager.waitForService("android.system.composd");
             IIsolatedCompilationService composd =
                     IIsolatedCompilationService.Stub.asInterface(binder);
@@ -174,13 +169,8 @@
             }
 
             try {
-                ICompilationTask composTask;
-                if (jobId == DAILY_JOB_ID) {
-                    composTask = composd.startTestCompile(
-                            IIsolatedCompilationService.ApexSource.NoStaged, this);
-                } else {
-                    composTask = composd.startStagedApexCompile(this);
-                }
+                ICompilationTask composTask = composd.startStagedApexCompile(this);
+                mMetrics.onCompilationStarted();
                 mTask.set(composTask);
                 composTask.asBinder().linkToDeath(this, 0);
             } catch (RemoteException e) {
@@ -201,38 +191,67 @@
 
         private void cancelTask() {
             ICompilationTask task = mTask.getAndSet(null);
-            if (task != null) {
-                mCanceled = true;
-                Log.i(TAG, "Cancelling task");
-                try {
-                    task.cancel();
-                } catch (RuntimeException | RemoteException e) {
-                    // If canceling failed we'll assume it means that the task has already failed;
-                    // there's nothing else we can do anyway.
-                    Log.w(TAG, "Failed to cancel CompilationTask", e);
-                }
+            if (task == null) {
+                return;
+            }
+
+            Log.i(TAG, "Cancelling task");
+            try {
+                task.cancel();
+            } catch (RuntimeException | RemoteException e) {
+                // If canceling failed we'll assume it means that the task has already failed;
+                // there's nothing else we can do anyway.
+                Log.w(TAG, "Failed to cancel CompilationTask", e);
+            }
+
+            mMetrics.onCompilationEnded(IsolatedCompilationMetrics.RESULT_JOB_CANCELED);
+            try {
+                task.asBinder().unlinkToDeath(this, 0);
+            } catch (NoSuchElementException e) {
+                // Harmless
             }
         }
 
         @Override
         public void binderDied() {
-            onFailure();
+            onCompletion(false, IsolatedCompilationMetrics.RESULT_COMPOSD_DIED);
         }
 
         @Override
         public void onSuccess() {
-            onCompletion(true);
+            onCompletion(true, IsolatedCompilationMetrics.RESULT_SUCCESS);
         }
 
         @Override
-        public void onFailure() {
-            onCompletion(false);
+        public void onFailure(byte reason, String message) {
+            int result;
+            switch (reason) {
+                case ICompilationTaskCallback.FailureReason.CompilationFailed:
+                    result = IsolatedCompilationMetrics.RESULT_COMPILATION_FAILED;
+                    break;
+
+                case ICompilationTaskCallback.FailureReason.UnexpectedCompilationResult:
+                    result = IsolatedCompilationMetrics.RESULT_UNEXPECTED_COMPILATION_RESULT;
+                    break;
+
+                default:
+                    result = IsolatedCompilationMetrics.RESULT_UNKNOWN_FAILURE;
+                    break;
+            }
+            Log.w(TAG, "Compilation failed: " + message);
+            onCompletion(false, result);
         }
 
-        private void onCompletion(boolean succeeded) {
-            mTask.set(null);
-            if (!mCanceled) {
+        private void onCompletion(boolean succeeded, @CompilationResult int result) {
+            ICompilationTask task = mTask.getAndSet(null);
+            if (task != null) {
+                mMetrics.onCompilationEnded(result);
                 mCallback.onCompletion(mParams, succeeded);
+                try {
+                    task.asBinder().unlinkToDeath(this, 0);
+                } catch (NoSuchElementException e) {
+                    // Harmless
+                }
             }
         }
     }
diff --git a/compos/service/java/com/android/server/compos/IsolatedCompilationMetrics.java b/compos/service/java/com/android/server/compos/IsolatedCompilationMetrics.java
new file mode 100644
index 0000000..0ed2305
--- /dev/null
+++ b/compos/service/java/com/android/server/compos/IsolatedCompilationMetrics.java
@@ -0,0 +1,97 @@
+/*
+ * Copyright 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 com.android.server.compos;
+
+import android.annotation.IntDef;
+import android.os.SystemClock;
+import android.util.Log;
+
+import com.android.internal.art.ArtStatsLog;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * A class that handles reporting metrics relating to Isolated Compilation to statsd.
+ *
+ * @hide
+ */
+class IsolatedCompilationMetrics {
+    private static final String TAG = IsolatedCompilationMetrics.class.getName();
+
+    // TODO(b/218525257): Move the definition of these enums to atoms.proto
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef({RESULT_UNKNOWN, RESULT_SUCCESS, RESULT_UNKNOWN_FAILURE, RESULT_FAILED_TO_START,
+            RESULT_JOB_CANCELED, RESULT_COMPILATION_FAILED, RESULT_UNEXPECTED_COMPILATION_RESULT,
+            RESULT_COMPOSD_DIED})
+    public @interface CompilationResult {}
+
+    // Keep this in sync with Result enum in IsolatedCompilationEnded in
+    // frameworks/proto_logging/stats/atoms.proto
+    public static final int RESULT_UNKNOWN =
+            ArtStatsLog.ISOLATED_COMPILATION_ENDED__COMPILATION_RESULT__RESULT_UNKNOWN;
+    public static final int RESULT_SUCCESS =
+            ArtStatsLog.ISOLATED_COMPILATION_ENDED__COMPILATION_RESULT__RESULT_SUCCESS;
+    public static final int RESULT_UNKNOWN_FAILURE =
+            ArtStatsLog.ISOLATED_COMPILATION_ENDED__COMPILATION_RESULT__RESULT_UNKNOWN_FAILURE;
+    public static final int RESULT_FAILED_TO_START =
+            ArtStatsLog.ISOLATED_COMPILATION_ENDED__COMPILATION_RESULT__RESULT_FAILED_TO_START;
+    public static final int RESULT_JOB_CANCELED =
+            ArtStatsLog.ISOLATED_COMPILATION_ENDED__COMPILATION_RESULT__RESULT_JOB_CANCELED;
+    public static final int RESULT_COMPILATION_FAILED = ArtStatsLog
+            .ISOLATED_COMPILATION_ENDED__COMPILATION_RESULT__RESULT_COMPILATION_FAILED;
+    public static final int RESULT_UNEXPECTED_COMPILATION_RESULT = ArtStatsLog
+            .ISOLATED_COMPILATION_ENDED__COMPILATION_RESULT__RESULT_UNEXPECTED_COMPILATION_RESULT;
+    public static final int RESULT_COMPOSD_DIED =
+            ArtStatsLog.ISOLATED_COMPILATION_ENDED__COMPILATION_RESULT__RESULT_COMPOSD_DIED;
+
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef({SCHEDULING_RESULT_UNKNOWN, SCHEDULING_SUCCESS, SCHEDULING_FAILURE})
+    public @interface ScheduleJobResult {}
+
+    // Keep this in sync with Result enum in IsolatedCompilationScheduled in
+    // frameworks/proto_logging/stats/atoms.proto
+
+    public static final int SCHEDULING_RESULT_UNKNOWN = ArtStatsLog
+            .ISOLATED_COMPILATION_SCHEDULED__SCHEDULING_RESULT__SCHEDULING_RESULT_UNKNOWN;
+    public static final int SCHEDULING_FAILURE =
+            ArtStatsLog.ISOLATED_COMPILATION_SCHEDULED__SCHEDULING_RESULT__SCHEDULING_FAILURE;
+    public static final int SCHEDULING_SUCCESS =
+            ArtStatsLog.ISOLATED_COMPILATION_SCHEDULED__SCHEDULING_RESULT__SCHEDULING_SUCCESS;
+
+    private long mCompilationStartTimeMs = 0;
+
+    public static void onCompilationScheduled(@ScheduleJobResult int result) {
+        // TODO(b/218525257): write to ArtStatsLog instead of logcat
+        ArtStatsLog.write(ArtStatsLog.ISOLATED_COMPILATION_SCHEDULED, result);
+        Log.i(TAG, "ISOLATED_COMPILATION_SCHEDULED: " + result);
+    }
+
+    public void onCompilationStarted() {
+        mCompilationStartTimeMs = SystemClock.elapsedRealtime();
+    }
+
+    public void onCompilationEnded(@CompilationResult int result) {
+        long compilationTime = mCompilationStartTimeMs == 0 ? -1
+                : SystemClock.elapsedRealtime() - mCompilationStartTimeMs;
+        mCompilationStartTimeMs = 0;
+
+        // TODO(b/218525257): write to ArtStatsLog instead of logcat
+        ArtStatsLog.write(ArtStatsLog.ISOLATED_COMPILATION_ENDED, compilationTime, result);
+        Log.i(TAG, "ISOLATED_COMPILATION_ENDED: " + result + ", " + compilationTime);
+    }
+}
diff --git a/compos/service/java/com/android/server/compos/IsolatedCompilationService.java b/compos/service/java/com/android/server/compos/IsolatedCompilationService.java
index 11e3743..b2fcbe0 100644
--- a/compos/service/java/com/android/server/compos/IsolatedCompilationService.java
+++ b/compos/service/java/com/android/server/compos/IsolatedCompilationService.java
@@ -67,7 +67,6 @@
             return;
         }
 
-        IsolatedCompilationJobService.scheduleDailyJob(scheduler);
         StagedApexObserver.registerForStagedApexUpdates(scheduler);
     }
 
diff --git a/compos/src/compos_key.rs b/compos/src/compos_key.rs
index eb6248f..faa9d67 100644
--- a/compos/src/compos_key.rs
+++ b/compos/src/compos_key.rs
@@ -21,8 +21,16 @@
 const COMPOS_KEY_HELPER_PATH: &str = "/apex/com.android.compos/bin/compos_key_helper";
 
 pub fn get_public_key() -> Result<Vec<u8>> {
+    get_data_from_helper("public_key")
+}
+
+pub fn get_attestation_chain() -> Result<Vec<u8>> {
+    get_data_from_helper("bcc")
+}
+
+fn get_data_from_helper(command: &str) -> Result<Vec<u8>> {
     let child = Command::new(COMPOS_KEY_HELPER_PATH)
-        .arg("public_key")
+        .arg(command)
         .stdin(Stdio::null())
         .stdout(Stdio::piped())
         .stderr(Stdio::piped())
diff --git a/compos/src/compsvc.rs b/compos/src/compsvc.rs
index 3a794ee..e21aa7d 100644
--- a/compos/src/compsvc.rs
+++ b/compos/src/compsvc.rs
@@ -86,6 +86,10 @@
     fn getPublicKey(&self) -> BinderResult<Vec<u8>> {
         to_binder_result(compos_key::get_public_key())
     }
+
+    fn getAttestationChain(&self) -> BinderResult<Vec<u8>> {
+        to_binder_result(compos_key::get_attestation_chain())
+    }
 }
 
 fn add_artifacts(target_dir: &Path, artifact_signer: &mut ArtifactSigner) -> Result<()> {
diff --git a/javalib/src/android/system/virtualmachine/VirtualMachineConfig.java b/javalib/src/android/system/virtualmachine/VirtualMachineConfig.java
index 3a2d581..7b5d5ab 100644
--- a/javalib/src/android/system/virtualmachine/VirtualMachineConfig.java
+++ b/javalib/src/android/system/virtualmachine/VirtualMachineConfig.java
@@ -238,6 +238,9 @@
         parcel.memoryMib = mMemoryMib;
         parcel.numCpus = mNumCpus;
         parcel.cpuAffinity = mCpuAffinity;
+        // Don't allow apps to set task profiles ... at last for now. Also, don't forget to
+        // validate the string because these are appended to the cmdline argument.
+        parcel.taskProfiles = new String[0];
         return parcel;
     }
 
diff --git a/microdroid/Android.bp b/microdroid/Android.bp
index b7d844f..a2ae144 100644
--- a/microdroid/Android.bp
+++ b/microdroid/Android.bp
@@ -119,6 +119,10 @@
     dirs: microdroid_rootdirs,
     symlinks: microdroid_symlinks,
     file_contexts: ":microdroid_file_contexts.gen",
+    // For deterministic output, use fake_timestamp, hard-coded uuid
+    fake_timestamp: "1611569676",
+    // python -c "import uuid; print(uuid.uuid5(uuid.NAMESPACE_URL, 'www.android.com/avf/microdroid/system'))"
+    uuid: "5fe079c6-f01a-52be-87d3-d415231a72ad",
 }
 
 prebuilt_etc {
@@ -189,6 +193,10 @@
     avb_private_key: ":microdroid_sign_key",
     avb_algorithm: "SHA256_RSA4096",
     file_contexts: ":microdroid_vendor_file_contexts.gen",
+    // For deterministic output, use fake_timestamp, hard-coded uuid
+    fake_timestamp: "1611569676",
+    // python -c "import uuid; print(uuid.uuid5(uuid.NAMESPACE_URL, 'www.android.com/avf/microdroid/vendor'))"
+    uuid: "156d40d7-8d8e-5c99-8913-ec82de549a70",
 }
 
 logical_partition {
@@ -382,6 +390,9 @@
     filename: "microdroid_bootconfig.full_debuggable",
 }
 
+// python -c "import hashlib; print(hashlib.sha256(b'bootconfig').hexdigest())"
+bootconfig_salt = "e158851fbebb402e1f18ea9372ea2f76b4dea23eceb5c4b92e5b27ade8537f5b"
+
 // TODO(jiyong): make a new module type that does the avb signing
 genrule {
     name: "microdroid_bootconfig_normal_gen",
@@ -394,6 +405,7 @@
     cmd: "cp $(location bootconfig.normal) $(out) && " +
         "$(location avbtool) add_hash_footer " +
         "--algorithm SHA256_RSA4096 " +
+        "--salt " + bootconfig_salt + " " +
         "--partition_name bootconfig " +
         "--key $(location :microdroid_sign_key) " +
         "--partition_size $$(( " + avb_hash_footer_kb + " * 1024 + ( $$(stat --format=%s $(out)) + 4096 - 1 ) / 4096 * 4096 )) " +
@@ -411,6 +423,7 @@
     cmd: "cp $(location bootconfig.app_debuggable) $(out) && " +
         "$(location avbtool) add_hash_footer " +
         "--algorithm SHA256_RSA4096 " +
+        "--salt " + bootconfig_salt + " " +
         "--partition_name bootconfig " +
         "--key $(location :microdroid_sign_key) " +
         "--partition_size $$(( " + avb_hash_footer_kb + " * 1024 + ( $$(stat --format=%s $(out)) + 4096 - 1 ) / 4096 * 4096 )) " +
@@ -428,6 +441,7 @@
     cmd: "cp $(location bootconfig.full_debuggable) $(out) && " +
         "$(location avbtool) add_hash_footer " +
         "--algorithm SHA256_RSA4096 " +
+        "--salt " + bootconfig_salt + " " +
         "--partition_name bootconfig " +
         "--key $(location :microdroid_sign_key) " +
         "--partition_size $$(( " + avb_hash_footer_kb + " * 1024 + ( $$(stat --format=%s $(out)) + 4096 - 1 ) / 4096 * 4096 )) " +
@@ -455,6 +469,9 @@
     filename: "microdroid_bootloader",
 }
 
+// python -c "import hashlib; print(hashlib.sha256(b'bootloader').hexdigest())"
+bootloader_salt = "3b4a12881d11f33cff968a24d7c53723a8232cde9a8d91e29fdbd6a95ae6adf0"
+
 genrule {
     name: "microdroid_bootloader_gen",
     tools: ["avbtool"],
@@ -473,6 +490,7 @@
         "if [ $$(stat --format=%s $(out)) -gt 4096 ]; then " +
         "$(location avbtool) add_hash_footer " +
         "--algorithm SHA256_RSA4096 " +
+        "--salt " + bootloader_salt + " " +
         "--partition_name bootloader " +
         "--key $(location :microdroid_sign_key) " +
         "--partition_size $$(( " + avb_hash_footer_kb + " * 1024 + ( $$(stat --format=%s $(out)) + 4096 - 1 ) / 4096 * 4096 )) " +
@@ -526,6 +544,9 @@
     filename: "uboot_env.img",
 }
 
+// python -c "import hashlib; print(hashlib.sha256(b'uboot_env').hexdigest())"
+uboot_env_salt = "cbf2d76827ece5ca8d176a40c94ac6355edcf6511b4b887364a8c0e05850df10"
+
 genrule {
     name: "microdroid_uboot_env_gen",
     tools: [
@@ -540,6 +561,7 @@
     cmd: "$(location mkenvimage_slim) -output_path $(out) -input_path $(location uboot-env.txt) && " +
         "$(location avbtool) add_hash_footer " +
         "--algorithm SHA256_RSA4096 " +
+        "--salt " + uboot_env_salt + " " +
         "--partition_name uboot_env " +
         "--key $(location :microdroid_sign_key) " +
         "--partition_size $$(( " + avb_hash_footer_kb + " * 1024 + ( $$(stat --format=%s $(out)) + 4096 - 1 ) / 4096 * 4096 )) " +
diff --git a/microdroid/build.prop b/microdroid/build.prop
index 8cbabff..0908a4c 100644
--- a/microdroid/build.prop
+++ b/microdroid/build.prop
@@ -4,10 +4,10 @@
 service.adb.listen_addrs=vsock:5555
 
 # TODO(b/189164487): support build related properties
-ro.build.version.codename=Tiramisu
-ro.build.version.release=12
-ro.build.version.sdk=32
-ro.build.version.security_patch=2021-12-05
+ro.build.version.codename=UpsideDownCake
+ro.build.version.release=13
+ro.build.version.sdk=33
+ro.build.version.security_patch=2022-06-05
 
 # Payload metadata partition
 apexd.payload_metadata.path=/dev/block/by-name/payload-metadata
diff --git a/microdroid/payload/mk_payload.cc b/microdroid/payload/mk_payload.cc
index 6e3f526..4dbcabf 100644
--- a/microdroid/payload/mk_payload.cc
+++ b/microdroid/payload/mk_payload.cc
@@ -166,11 +166,10 @@
     Metadata metadata;
     metadata.set_version(1);
 
-    int apex_index = 0;
     for (const auto& apex_config : config.apexes) {
         auto* apex = metadata.add_apexes();
         apex->set_name(apex_config.name);
-        apex->set_partition_name("microdroid-apex-" + std::to_string(apex_index++));
+        apex->set_partition_name(apex_config.name);
         apex->set_is_factory(true);
     }
 
@@ -303,4 +302,4 @@
     }
 
     return 0;
-}
\ No newline at end of file
+}
diff --git a/pvmfw/pvmfw.img b/pvmfw/pvmfw.img
index 7d8386b..6060f4b 100644
--- a/pvmfw/pvmfw.img
+++ b/pvmfw/pvmfw.img
Binary files differ
diff --git a/tests/hostside/helper/java/android/virt/test/LogArchiver.java b/tests/hostside/helper/java/android/virt/test/LogArchiver.java
new file mode 100644
index 0000000..b6cae95
--- /dev/null
+++ b/tests/hostside/helper/java/android/virt/test/LogArchiver.java
@@ -0,0 +1,46 @@
+/*
+ * 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.virt.test;
+
+import static com.android.tradefed.testtype.DeviceJUnit4ClassRunner.TestLogData;
+
+import com.android.tradefed.device.DeviceNotAvailableException;
+import com.android.tradefed.device.ITestDevice;
+import com.android.tradefed.result.FileInputStreamSource;
+import com.android.tradefed.result.LogDataType;
+
+import java.io.File;
+
+/** A helper class for archiving device log files to the host's tradefed output directory. */
+public abstract class LogArchiver {
+    /** Copy device log (then delete) to a tradefed output directory on the host.
+     *
+     * @param logs A {@link TestLogData} that needs to be owned by the actual test case.
+     * @param device The device to pull the log file from.
+     * @param remotePath The path on the device.
+     * @param localName Local file name to be copied to.
+     */
+    public static void archiveLogThenDelete(TestLogData logs, ITestDevice device, String remotePath,
+            String localName) throws DeviceNotAvailableException {
+        File logFile = device.pullFile(remotePath);
+        if (logFile != null) {
+            logs.addTestLog(localName, LogDataType.TEXT, new FileInputStreamSource(logFile));
+            // Delete to avoid confusing logs from a previous run, just in case.
+            device.deleteFile(remotePath);
+        }
+    }
+}
diff --git a/tests/hostside/helper/java/android/virt/test/VirtualizationTestCaseBase.java b/tests/hostside/helper/java/android/virt/test/VirtualizationTestCaseBase.java
index 440ae18..dc0284f 100644
--- a/tests/hostside/helper/java/android/virt/test/VirtualizationTestCaseBase.java
+++ b/tests/hostside/helper/java/android/virt/test/VirtualizationTestCaseBase.java
@@ -30,8 +30,6 @@
 import com.android.tradefed.device.ITestDevice;
 import com.android.tradefed.device.TestDevice;
 import com.android.tradefed.log.LogUtil.CLog;
-import com.android.tradefed.result.FileInputStreamSource;
-import com.android.tradefed.result.LogDataType;
 import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test;
 import com.android.tradefed.util.CommandResult;
 import com.android.tradefed.util.CommandStatus;
@@ -96,12 +94,7 @@
 
     public static void archiveLogThenDelete(TestLogData logs, ITestDevice device, String remotePath,
             String localName) throws DeviceNotAvailableException {
-        File logFile = device.pullFile(remotePath);
-        if (logFile != null) {
-            logs.addTestLog(localName, LogDataType.TEXT, new FileInputStreamSource(logFile));
-            // Delete to avoid confusing logs from a previous run, just in case.
-            device.deleteFile(remotePath);
-        }
+        LogArchiver.archiveLogThenDelete(logs, device, remotePath, localName);
     }
 
     // Run an arbitrary command in the host side and returns the result
diff --git a/virtualizationservice/aidl/android/system/virtualizationservice/VirtualMachineAppConfig.aidl b/virtualizationservice/aidl/android/system/virtualizationservice/VirtualMachineAppConfig.aidl
index c36e561..22b8a94 100644
--- a/virtualizationservice/aidl/android/system/virtualizationservice/VirtualMachineAppConfig.aidl
+++ b/virtualizationservice/aidl/android/system/virtualizationservice/VirtualMachineAppConfig.aidl
@@ -67,4 +67,9 @@
      * Default is no mask which means a vCPU can run on any host CPU.
      */
     @nullable String cpuAffinity;
+
+    /**
+     * List of task profile names to apply for the VM
+     */
+    String[] taskProfiles;
 }
diff --git a/virtualizationservice/aidl/android/system/virtualizationservice/VirtualMachineRawConfig.aidl b/virtualizationservice/aidl/android/system/virtualizationservice/VirtualMachineRawConfig.aidl
index dfd3bff..83a81a0 100644
--- a/virtualizationservice/aidl/android/system/virtualizationservice/VirtualMachineRawConfig.aidl
+++ b/virtualizationservice/aidl/android/system/virtualizationservice/VirtualMachineRawConfig.aidl
@@ -63,4 +63,9 @@
      * The format follows SemVer.
      */
     @utf8InCpp String platformVersion;
+
+    /**
+     * List of task profile names to apply for the VM
+     */
+    String[] taskProfiles;
 }
diff --git a/virtualizationservice/src/aidl.rs b/virtualizationservice/src/aidl.rs
index a2e856c..41cc4a5 100644
--- a/virtualizationservice/src/aidl.rs
+++ b/virtualizationservice/src/aidl.rs
@@ -467,6 +467,7 @@
             memory_mib: config.memoryMib.try_into().ok().and_then(NonZeroU32::new),
             cpus: config.numCpus.try_into().ok().and_then(NonZeroU32::new),
             cpu_affinity: config.cpuAffinity.clone(),
+            task_profiles: config.taskProfiles.clone(),
             console_fd,
             log_fd,
             indirect_files,
@@ -634,6 +635,7 @@
     vm_config.protectedVm = config.protectedVm;
     vm_config.numCpus = config.numCpus;
     vm_config.cpuAffinity = config.cpuAffinity.clone();
+    vm_config.taskProfiles = config.taskProfiles.clone();
 
     // Microdroid requires an additional payload disk image and the bootconfig partition.
     if os_name == "microdroid" {
diff --git a/virtualizationservice/src/crosvm.rs b/virtualizationservice/src/crosvm.rs
index f1b179e..b184dca 100644
--- a/virtualizationservice/src/crosvm.rs
+++ b/virtualizationservice/src/crosvm.rs
@@ -64,6 +64,7 @@
     pub memory_mib: Option<NonZeroU32>,
     pub cpus: Option<NonZeroU32>,
     pub cpu_affinity: Option<String>,
+    pub task_profiles: Vec<String>,
     pub console_fd: Option<File>,
     pub log_fd: Option<File>,
     pub indirect_files: Vec<File>,
@@ -326,6 +327,10 @@
         command.arg("--cpu-affinity").arg(cpu_affinity);
     }
 
+    if !config.task_profiles.is_empty() {
+        command.arg("--task-profiles").arg(config.task_profiles.join(","));
+    }
+
     // Keep track of what file descriptors should be mapped to the crosvm process.
     let mut preserved_fds = config.indirect_files.iter().map(|file| file.as_raw_fd()).collect();
 
diff --git a/vm/src/main.rs b/vm/src/main.rs
index 80ea9be..8b438b4 100644
--- a/vm/src/main.rs
+++ b/vm/src/main.rs
@@ -92,6 +92,10 @@
         #[structopt(long)]
         cpu_affinity: Option<String>,
 
+        /// Comma separated list of task profile names to apply to the VM
+        #[structopt(long)]
+        task_profiles: Vec<String>,
+
         /// Paths to extra idsig files.
         #[structopt(long = "extra-idsig")]
         extra_idsigs: Vec<PathBuf>,
@@ -118,6 +122,10 @@
         #[structopt(long)]
         cpu_affinity: Option<String>,
 
+        /// Comma separated list of task profile names to apply to the VM
+        #[structopt(long)]
+        task_profiles: Vec<String>,
+
         /// Path to file for VM console output.
         #[structopt(long)]
         console: Option<PathBuf>,
@@ -200,6 +208,7 @@
             mem,
             cpus,
             cpu_affinity,
+            task_profiles,
             extra_idsigs,
         } => command_run_app(
             service,
@@ -215,9 +224,10 @@
             mem,
             cpus,
             cpu_affinity,
+            task_profiles,
             &extra_idsigs,
         ),
-        Opt::Run { config, daemonize, cpus, cpu_affinity, console, log } => {
+        Opt::Run { config, daemonize, cpus, cpu_affinity, task_profiles, console, log } => {
             command_run(
                 service,
                 &config,
@@ -227,6 +237,7 @@
                 /* mem */ None,
                 cpus,
                 cpu_affinity,
+                task_profiles,
             )
         }
         Opt::Stop { cid } => command_stop(service, cid),
diff --git a/vm/src/run.rs b/vm/src/run.rs
index ef38d7d..3d3d703 100644
--- a/vm/src/run.rs
+++ b/vm/src/run.rs
@@ -54,6 +54,7 @@
     mem: Option<u32>,
     cpus: Option<u32>,
     cpu_affinity: Option<String>,
+    task_profiles: Vec<String>,
     extra_idsigs: &[PathBuf],
 ) -> Result<(), Error> {
     let extra_apks = parse_extra_apk_list(apk, config_path)?;
@@ -105,6 +106,7 @@
         memoryMib: mem.unwrap_or(0) as i32, // 0 means use the VM default
         numCpus: cpus.unwrap_or(1) as i32,
         cpuAffinity: cpu_affinity,
+        taskProfiles: task_profiles,
     });
     run(
         service,
@@ -127,6 +129,7 @@
     mem: Option<u32>,
     cpus: Option<u32>,
     cpu_affinity: Option<String>,
+    task_profiles: Vec<String>,
 ) -> Result<(), Error> {
     let config_file = File::open(config_path).context("Failed to open config file")?;
     let mut config =
@@ -138,6 +141,7 @@
         config.numCpus = cpus as i32;
     }
     config.cpuAffinity = cpu_affinity;
+    config.taskProfiles = task_profiles;
     run(
         service,
         &VirtualMachineConfig::RawConfig(config),