Merge changes from topic "kernelboot"

* changes:
  Remove UBoot related Microdroid tests
  bootloader_time should exclude time spent in ABL
  Fix Microdroid tests to work with kernelboot method
  Change the vbmeta image for kernelboot mode
  Boot VM without bootloader - using init ramdisk
diff --git a/.prebuilt_info/prebuilt_info_pvmfw_pvmfw_img.asciipb b/.prebuilt_info/prebuilt_info_pvmfw_pvmfw_img.asciipb
index 2fa867a..ba1e6b9 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: "8966862"
+    build_id: "9107967"
     target: "u-boot_pvmfw"
     source_file: "pvmfw.img"
   }
diff --git a/apkdmverity/Android.bp b/apkdmverity/Android.bp
index 97a6174..51ef4dd 100644
--- a/apkdmverity/Android.bp
+++ b/apkdmverity/Android.bp
@@ -43,4 +43,8 @@
     rustlibs: [
         "libtempfile",
     ],
+    data: [
+        "testdata/*.apk",
+        "testdata/*.idsig",
+    ],
 }
diff --git a/apkdmverity/src/main.rs b/apkdmverity/src/main.rs
index de7f5bb..b99ed1c 100644
--- a/apkdmverity/src/main.rs
+++ b/apkdmverity/src/main.rs
@@ -31,7 +31,6 @@
 use itertools::Itertools;
 use std::fmt::Debug;
 use std::fs;
-use std::fs::File;
 use std::os::unix::fs::FileTypeExt;
 use std::path::{Path, PathBuf};
 
@@ -104,9 +103,7 @@
     // Parse the idsig file to locate the merkle tree in it, then attach the file to a loop device
     // with the offset so that the start of the merkle tree becomes the beginning of the loop
     // device.
-    let sig = V4Signature::from(
-        File::open(&idsig).context(format!("Failed to open idsig file {:?}", &idsig))?,
-    )?;
+    let sig = V4Signature::from_idsig_path(&idsig)?;
     let offset = sig.merkle_tree_offset;
     let size = sig.merkle_tree_size as u64;
     // Due to unknown reason(b/191344832), we can't enable "direct IO" for the IDSIG file (backing
@@ -143,8 +140,8 @@
 #[cfg(test)]
 mod tests {
     use crate::*;
-    use std::fs::OpenOptions;
-    use std::io::{Cursor, Write};
+    use std::fs::{File, OpenOptions};
+    use std::io::Write;
     use std::os::unix::fs::FileExt;
 
     struct TestContext<'a> {
@@ -251,7 +248,9 @@
         let idsig = include_bytes!("../testdata/test.apk.idsig");
 
         // Make a single-byte change to the merkle tree
-        let offset = V4Signature::from(Cursor::new(&idsig)).unwrap().merkle_tree_offset as usize;
+        let offset = V4Signature::from_idsig_path("testdata/test.apk.idsig")
+            .unwrap()
+            .merkle_tree_offset as usize;
 
         let mut modified_idsig = Vec::new();
         modified_idsig.extend_from_slice(idsig);
@@ -354,7 +353,10 @@
     fn correct_custom_roothash() {
         let apk = include_bytes!("../testdata/test.apk");
         let idsig = include_bytes!("../testdata/test.apk.idsig");
-        let roothash = V4Signature::from(Cursor::new(&idsig)).unwrap().hashing_info.raw_root_hash;
+        let roothash = V4Signature::from_idsig_path("testdata/test.apk.idsig")
+            .unwrap()
+            .hashing_info
+            .raw_root_hash;
         run_test_with_hash(
             apk.as_ref(),
             idsig.as_ref(),
diff --git a/authfs/tests/Android.bp b/authfs/tests/Android.bp
index 72685ad..b662bee 100644
--- a/authfs/tests/Android.bp
+++ b/authfs/tests/Android.bp
@@ -14,11 +14,14 @@
         "MicrodroidHostTestHelper",
     ],
     test_suites: ["general-tests"],
-    //TODO(b/235263148) use data_device_bins_64
-    data_device_bins_first: ["open_then_run"],
+    data_device_bins_first: [
+        "open_then_run",
+        "fsverity",
+    ],
     per_testcase_directory: true,
     data: [
         ":authfs_test_files",
+        ":CtsApkVerityTestPrebuiltFiles",
         ":MicrodroidTestApp",
     ],
 }
diff --git a/authfs/tests/AndroidTest.xml b/authfs/tests/AndroidTest.xml
index cc358f2..2ccc45f 100644
--- a/authfs/tests/AndroidTest.xml
+++ b/authfs/tests/AndroidTest.xml
@@ -34,6 +34,7 @@
 
         <!-- Test executable -->
         <option name="push-file" key="open_then_run" value="/data/local/tmp/open_then_run" />
+        <option name="push-file" key="fsverity" value="/data/local/tmp/fsverity" />
 
         <!-- Test data files -->
         <option name="push-file" key="cert.der" value="/data/local/tmp/authfs/cert.der" />
@@ -48,6 +49,19 @@
             value="/data/local/tmp/authfs/input.4k.fsv_meta" />
         <option name="push-file" key="input.4m.fsv_meta.bad_merkle"
             value="/data/local/tmp/authfs/input.4m.fsv_meta.bad_merkle" />
+
+        <!-- Just pick a file with signature that can be trused on the device. -->
+        <option name="push-file" key="CtsApkVerityTestAppPrebuilt.apk"
+            value="/data/local/tmp/authfs/input.apk" />
+        <option name="push-file" key="CtsApkVerityTestAppPrebuilt.apk.fsv_sig"
+            value="/data/local/tmp/authfs/input.apk.fsv_sig" />
+    </target_preparer>
+
+    <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
+        <option name="throw-if-cmd-fail" value="true" />
+        <!-- Now that the files are pushed to the device, enable fs-verity for the targeting file.
+             It works because the signature is trusted on all CTS compatible devices. -->
+        <option name="run-command" value="cd /data/local/tmp/authfs; ../fsverity enable input.apk --signature=input.apk.fsv_sig" />
     </target_preparer>
 
     <test class="com.android.compatibility.common.tradefed.testtype.JarHostTest" >
diff --git a/authfs/tests/java/src/com/android/fs/AuthFsHostTest.java b/authfs/tests/java/src/com/android/fs/AuthFsHostTest.java
index 0ade0ba..46b9e77 100644
--- a/authfs/tests/java/src/com/android/fs/AuthFsHostTest.java
+++ b/authfs/tests/java/src/com/android/fs/AuthFsHostTest.java
@@ -80,6 +80,9 @@
     /** Path to open_then_run on Android */
     private static final String OPEN_THEN_RUN_BIN = "/data/local/tmp/open_then_run";
 
+    /** Path to fsverity on Android */
+    private static final String FSVERITY_BIN = "/data/local/tmp/fsverity";
+
     /** Mount point of authfs on Microdroid during the test */
     private static final String MOUNT_DIR = "/data/local/tmp";
 
@@ -252,6 +255,23 @@
     }
 
     @Test
+    public void testReadWithFsverityVerification_FdServerUsesRealFsverityData() throws Exception {
+        // Setup (fs-verity is enabled for input.apk in AndroidTest.xml)
+        runFdServerOnAndroid("--open-ro 3:input.apk", "--ro-fds 3");
+        String expectedDigest = sAndroid.run(
+                FSVERITY_BIN + " digest --compact " + TEST_DIR + "/input.apk");
+        runAuthFsOnMicrodroid(
+                "--remote-ro-file 3:sha256-" + expectedDigest + " --cid " + VMADDR_CID_HOST);
+
+        // Action
+        String actualHash = computeFileHash(sMicrodroid, MOUNT_DIR + "/3");
+
+        // Verify
+        String expectedHash = computeFileHash(sAndroid, TEST_DIR + "/input.apk");
+        assertEquals("Inconsistent hash from /authfs/3: ", expectedHash, actualHash);
+    }
+
+    @Test
     public void testWriteThroughCorrectly() throws Exception {
         // Setup
         runFdServerOnAndroid("--open-rw 3:" + TEST_OUTPUT_DIR + "/out.file", "--rw-fds 3");
diff --git a/avmd/src/main.rs b/avmd/src/main.rs
index a3dee15..fc18225 100644
--- a/avmd/src/main.rs
+++ b/avmd/src/main.rs
@@ -74,7 +74,7 @@
     }
     for (i, namespace, name, file) in NamespaceNameFileIterator::new(args, "apk") {
         let file = File::open(file)?;
-        let (signature_algorithm_id, apk_digest) = get_apk_digest(file, /*verify=*/ false)?;
+        let (signature_algorithm_id, apk_digest) = get_apk_digest(file, /*verify=*/ true)?;
         descriptors.insert(
             i,
             Descriptor::Apk(ApkDescriptor {
diff --git a/compos/aidl/com/android/compos/ICompOsService.aidl b/compos/aidl/com/android/compos/ICompOsService.aidl
index 9b45e13..df8c91e 100644
--- a/compos/aidl/com/android/compos/ICompOsService.aidl
+++ b/compos/aidl/com/android/compos/ICompOsService.aidl
@@ -17,6 +17,7 @@
 package com.android.compos;
 
 /** {@hide} */
+@SuppressWarnings(value={"mixed-oneway"})
 interface ICompOsService {
     /**
      * Initializes system properties. ART expects interesting properties that have to be passed from
@@ -41,28 +42,41 @@
         TEST_COMPILE = 1,
     }
 
+    /** Arguments to run odrefresh */
+    parcelable OdrefreshArgs {
+        /** The type of compilation to be performed */
+        CompilationMode compilationMode = CompilationMode.NORMAL_COMPILE;
+        /** An fd referring to /system */
+        int systemDirFd = -1;
+        /** An optional fd referring to /system_ext. Negative number means none. */
+        int systemExtDirFd = -1;
+        /** An fd referring to the output directory, ART_APEX_DATA */
+        int outputDirFd = -1;
+        /** An fd referring to the staging directory, e.g. ART_APEX_DATA/staging */
+        int stagingDirFd = -1;
+        /**
+         * The sub-directory of the output directory to which artifacts are to be written (e.g.
+         * dalvik-cache)
+         */
+        String targetDirName;
+        /** The zygote architecture (ro.zygote) */
+        String zygoteArch;
+        /** The compiler filter used to compile system server */
+        String systemServerCompilerFilter;
+    }
+
     /**
      * Run odrefresh in the VM context.
      *
      * The execution is based on the VM's APEX mounts, files on Android's /system and optionally
-     * /system_ext (by accessing through systemDirFd and systemExtDirFd over AuthFS), and
-     * *CLASSPATH derived in the VM, to generate the same odrefresh output artifacts to the output
-     * directory (through outputDirFd).
+     * /system_ext (by accessing through OdrefreshArgs.systemDirFd and OdrefreshArgs.systemExtDirFd
+     * over AuthFS), and *CLASSPATH derived in the VM, to generate the same odrefresh output
+     * artifacts to the output directory (through OdrefreshArgs.outputDirFd).
      *
-     * @param compilationMode The type of compilation to be performed
-     * @param systemDirFd An fd referring to /system
-     * @param systemExtDirFd An optional fd referring to /system_ext. Negative number means none.
-     * @param outputDirFd An fd referring to the output directory, ART_APEX_DATA
-     * @param stagingDirFd An fd referring to the staging directory, e.g. ART_APEX_DATA/staging
-     * @param targetDirName The sub-directory of the output directory to which artifacts are to be
-     *                      written (e.g. dalvik-cache)
-     * @param zygoteArch The zygote architecture (ro.zygote)
-     * @param systemServerCompilerFilter The compiler filter used to compile system server
+     * @param args Arguments to configure the odrefresh context
      * @return odrefresh exit code
      */
-    byte odrefresh(CompilationMode compilation_mode, int systemDirFd, int systemExtDirFd,
-            int outputDirFd, int stagingDirFd, String targetDirName, String zygoteArch,
-            String systemServerCompilerFilter);
+    byte odrefresh(in OdrefreshArgs args);
 
     /**
      * Returns the current VM's signing key, as an Ed25519 public key
diff --git a/compos/benchmark/src/java/com/android/compos/benchmark/ComposBenchmark.java b/compos/benchmark/src/java/com/android/compos/benchmark/ComposBenchmark.java
index dd113a6..c25de71 100644
--- a/compos/benchmark/src/java/com/android/compos/benchmark/ComposBenchmark.java
+++ b/compos/benchmark/src/java/com/android/compos/benchmark/ComposBenchmark.java
@@ -55,7 +55,7 @@
     private static final int BUFFER_SIZE = 1024;
     private static final int ROUND_COUNT = 5;
     private static final double NANOS_IN_SEC = 1_000_000_000.0;
-    private static final String METRIC_PREFIX = "avf_perf/compos/";
+    private static final String METRIC_PREFIX = getMetricPrefix() + "compos/";
 
     private final MetricsProcessor mMetricsProcessor = new MetricsProcessor(METRIC_PREFIX);
 
diff --git a/compos/composd/src/odrefresh_task.rs b/compos/composd/src/odrefresh_task.rs
index 9276fb1..3a699ab 100644
--- a/compos/composd/src/odrefresh_task.rs
+++ b/compos/composd/src/odrefresh_task.rs
@@ -25,7 +25,7 @@
 use anyhow::{Context, Result};
 use binder::{Interface, Result as BinderResult, Strong};
 use compos_aidl_interface::aidl::com::android::compos::ICompOsService::{
-    CompilationMode::CompilationMode, ICompOsService,
+    CompilationMode::CompilationMode, ICompOsService, OdrefreshArgs::OdrefreshArgs,
 };
 use compos_common::odrefresh::{
     is_system_property_interesting, ExitCode, ODREFRESH_OUTPUT_ROOT_DIR,
@@ -180,16 +180,18 @@
     let zygote_arch = system_properties::read("ro.zygote")?.context("ro.zygote not set")?;
     let system_server_compiler_filter =
         system_properties::read("dalvik.vm.systemservercompilerfilter")?.unwrap_or_default();
-    let exit_code = service.odrefresh(
-        compilation_mode,
-        system_dir_raw_fd,
-        system_ext_dir_raw_fd,
-        output_dir_raw_fd,
-        staging_dir_raw_fd,
-        target_dir_name,
-        &zygote_arch,
-        &system_server_compiler_filter,
-    )?;
+
+    let args = OdrefreshArgs {
+        compilationMode: compilation_mode,
+        systemDirFd: system_dir_raw_fd,
+        systemExtDirFd: system_ext_dir_raw_fd,
+        outputDirFd: output_dir_raw_fd,
+        stagingDirFd: staging_dir_raw_fd,
+        targetDirName: target_dir_name.to_string(),
+        zygoteArch: zygote_arch,
+        systemServerCompilerFilter: system_server_compiler_filter,
+    };
+    let exit_code = service.odrefresh(&args)?;
 
     drop(fd_server_raii);
     ExitCode::from_i32(exit_code.into())
diff --git a/compos/src/compilation.rs b/compos/src/compilation.rs
index d165599..2872d95 100644
--- a/compos/src/compilation.rs
+++ b/compos/src/compilation.rs
@@ -33,95 +33,65 @@
     IAuthFsService::IAuthFsService,
 };
 use binder::Strong;
-use compos_aidl_interface::aidl::com::android::compos::ICompOsService::CompilationMode::CompilationMode;
+use compos_aidl_interface::aidl::com::android::compos::ICompOsService::{
+    CompilationMode::CompilationMode, OdrefreshArgs::OdrefreshArgs,
+};
 use compos_common::odrefresh::ExitCode;
 
 const FD_SERVER_PORT: i32 = 3264; // TODO: support dynamic port
 
-pub struct OdrefreshContext<'a> {
-    compilation_mode: CompilationMode,
-    system_dir_fd: i32,
-    system_ext_dir_fd: Option<i32>,
-    output_dir_fd: i32,
-    staging_dir_fd: i32,
-    target_dir_name: &'a str,
-    zygote_arch: &'a str,
-    system_server_compiler_filter: &'a str,
-}
-
-impl<'a> OdrefreshContext<'a> {
-    #[allow(clippy::too_many_arguments)]
-    pub fn new(
-        compilation_mode: CompilationMode,
-        system_dir_fd: i32,
-        system_ext_dir_fd: Option<i32>,
-        output_dir_fd: i32,
-        staging_dir_fd: i32,
-        target_dir_name: &'a str,
-        zygote_arch: &'a str,
-        system_server_compiler_filter: &'a str,
-    ) -> Result<Self> {
-        if compilation_mode != CompilationMode::NORMAL_COMPILE {
-            // Conservatively check debuggability.
-            let debuggable =
-                system_properties::read_bool("ro.boot.microdroid.app_debuggable", false)
-                    .unwrap_or(false);
-            if !debuggable {
-                bail!("Requested compilation mode only available in debuggable VMs");
-            }
+fn validate_args(args: &OdrefreshArgs) -> Result<()> {
+    if args.compilationMode != CompilationMode::NORMAL_COMPILE {
+        // Conservatively check debuggability.
+        let debuggable = system_properties::read_bool("ro.boot.microdroid.app_debuggable", false)
+            .unwrap_or(false);
+        if !debuggable {
+            bail!("Requested compilation mode only available in debuggable VMs");
         }
-
-        if system_dir_fd < 0 || output_dir_fd < 0 || staging_dir_fd < 0 {
-            bail!("The remote FDs are expected to be non-negative");
-        }
-        if !matches!(zygote_arch, "zygote64" | "zygote64_32") {
-            bail!("Invalid zygote arch");
-        }
-        // Disallow any sort of path traversal
-        if target_dir_name.contains(path::MAIN_SEPARATOR) {
-            bail!("Invalid target directory {}", target_dir_name);
-        }
-
-        // We're not validating/allowlisting the compiler filter, and just assume the compiler will
-        // reject an invalid string. We need to accept "verify" filter anyway, and potential
-        // performance degration by the attacker is not currently in scope. This also allows ART to
-        // specify new compiler filter and configure through system property without change to
-        // CompOS.
-
-        Ok(Self {
-            compilation_mode,
-            system_dir_fd,
-            system_ext_dir_fd,
-            output_dir_fd,
-            staging_dir_fd,
-            target_dir_name,
-            zygote_arch,
-            system_server_compiler_filter,
-        })
     }
+
+    if args.systemDirFd < 0 || args.outputDirFd < 0 || args.stagingDirFd < 0 {
+        bail!("The remote FDs are expected to be non-negative");
+    }
+    if !matches!(&args.zygoteArch[..], "zygote64" | "zygote64_32") {
+        bail!("Invalid zygote arch");
+    }
+    // Disallow any sort of path traversal
+    if args.targetDirName.contains(path::MAIN_SEPARATOR) {
+        bail!("Invalid target directory {}", args.targetDirName);
+    }
+
+    // We're not validating/allowlisting the compiler filter, and just assume the compiler will
+    // reject an invalid string. We need to accept "verify" filter anyway, and potential
+    // performance degration by the attacker is not currently in scope. This also allows ART to
+    // specify new compiler filter and configure through system property without change to
+    // CompOS.
+    Ok(())
 }
 
 pub fn odrefresh<F>(
     odrefresh_path: &Path,
-    context: OdrefreshContext,
+    args: &OdrefreshArgs,
     authfs_service: Strong<dyn IAuthFsService>,
     success_fn: F,
 ) -> Result<ExitCode>
 where
     F: FnOnce(PathBuf) -> Result<()>,
 {
+    validate_args(args)?;
+
     // Mount authfs (via authfs_service). The authfs instance unmounts once the `authfs` variable
     // is out of scope.
 
     let mut input_dir_fd_annotations = vec![InputDirFdAnnotation {
-        fd: context.system_dir_fd,
+        fd: args.systemDirFd,
         // Use the 0th APK of the extra_apks in compos/apk/assets/vm_config*.json
         manifestPath: "/mnt/extra-apk/0/assets/build_manifest.pb".to_string(),
         prefix: "system/".to_string(),
     }];
-    if let Some(fd) = context.system_ext_dir_fd {
+    if args.systemExtDirFd >= 0 {
         input_dir_fd_annotations.push(InputDirFdAnnotation {
-            fd,
+            fd: args.systemExtDirFd,
             // Use the 1st APK of the extra_apks in compos/apk/assets/vm_config_system_ext_*.json
             manifestPath: "/mnt/extra-apk/1/assets/build_manifest.pb".to_string(),
             prefix: "system_ext/".to_string(),
@@ -132,8 +102,8 @@
         port: FD_SERVER_PORT,
         inputDirFdAnnotations: input_dir_fd_annotations,
         outputDirFdAnnotations: vec![
-            OutputDirFdAnnotation { fd: context.output_dir_fd },
-            OutputDirFdAnnotation { fd: context.staging_dir_fd },
+            OutputDirFdAnnotation { fd: args.outputDirFd },
+            OutputDirFdAnnotation { fd: args.stagingDirFd },
         ],
         ..Default::default()
     };
@@ -144,52 +114,50 @@
     let mut odrefresh_vars = EnvMap::from_current_env();
 
     let mut android_root = mountpoint.clone();
-    android_root.push(context.system_dir_fd.to_string());
+    android_root.push(args.systemDirFd.to_string());
     android_root.push("system");
     odrefresh_vars.set("ANDROID_ROOT", path_to_str(&android_root)?);
     debug!("ANDROID_ROOT={:?}", &android_root);
 
-    if let Some(fd) = context.system_ext_dir_fd {
+    if args.systemExtDirFd >= 0 {
         let mut system_ext_root = mountpoint.clone();
-        system_ext_root.push(fd.to_string());
+        system_ext_root.push(args.systemExtDirFd.to_string());
         system_ext_root.push("system_ext");
         odrefresh_vars.set("SYSTEM_EXT_ROOT", path_to_str(&system_ext_root)?);
         debug!("SYSTEM_EXT_ROOT={:?}", &system_ext_root);
     }
 
-    let art_apex_data = mountpoint.join(context.output_dir_fd.to_string());
+    let art_apex_data = mountpoint.join(args.outputDirFd.to_string());
     odrefresh_vars.set("ART_APEX_DATA", path_to_str(&art_apex_data)?);
     debug!("ART_APEX_DATA={:?}", &art_apex_data);
 
-    let staging_dir = mountpoint.join(context.staging_dir_fd.to_string());
+    let staging_dir = mountpoint.join(args.stagingDirFd.to_string());
 
     set_classpaths(&mut odrefresh_vars, &android_root)?;
 
-    let mut args = vec![
+    let mut command_line_args = vec![
         "odrefresh".to_string(),
         "--compilation-os-mode".to_string(),
-        format!("--zygote-arch={}", context.zygote_arch),
-        format!("--dalvik-cache={}", context.target_dir_name),
+        format!("--zygote-arch={}", args.zygoteArch),
+        format!("--dalvik-cache={}", args.targetDirName),
         format!("--staging-dir={}", staging_dir.display()),
         "--no-refresh".to_string(),
     ];
 
-    if !context.system_server_compiler_filter.is_empty() {
-        args.push(format!(
-            "--system-server-compiler-filter={}",
-            context.system_server_compiler_filter
-        ));
+    if !args.systemServerCompilerFilter.is_empty() {
+        command_line_args
+            .push(format!("--system-server-compiler-filter={}", args.systemServerCompilerFilter));
     }
 
-    let compile_flag = match context.compilation_mode {
+    let compile_flag = match args.compilationMode {
         CompilationMode::NORMAL_COMPILE => "--compile",
         CompilationMode::TEST_COMPILE => "--force-compile",
         other => bail!("Unknown compilation mode {:?}", other),
     };
-    args.push(compile_flag.to_string());
+    command_line_args.push(compile_flag.to_string());
 
-    debug!("Running odrefresh with args: {:?}", &args);
-    let jail = spawn_jailed_task(odrefresh_path, &args, &odrefresh_vars.into_env())
+    debug!("Running odrefresh with args: {:?}", &command_line_args);
+    let jail = spawn_jailed_task(odrefresh_path, &command_line_args, &odrefresh_vars.into_env())
         .context("Spawn odrefresh")?;
     let exit_code = match jail.wait() {
         Ok(_) => 0,
@@ -201,7 +169,7 @@
     info!("odrefresh exited with {:?}", exit_code);
 
     if exit_code == ExitCode::CompilationSuccess {
-        let target_dir = art_apex_data.join(context.target_dir_name);
+        let target_dir = art_apex_data.join(&args.targetDirName);
         success_fn(target_dir)?;
     }
 
diff --git a/compos/src/compsvc.rs b/compos/src/compsvc.rs
index 3dbb4da..4330bbf 100644
--- a/compos/src/compsvc.rs
+++ b/compos/src/compsvc.rs
@@ -28,11 +28,11 @@
 use std::sync::RwLock;
 
 use crate::artifact_signer::ArtifactSigner;
-use crate::compilation::{odrefresh, OdrefreshContext};
+use crate::compilation::odrefresh;
 use crate::compos_key;
 use binder::{BinderFeatures, ExceptionCode, Interface, Result as BinderResult, Status, Strong};
 use compos_aidl_interface::aidl::com::android::compos::ICompOsService::{
-    BnCompOsService, CompilationMode::CompilationMode, ICompOsService,
+    BnCompOsService, ICompOsService, OdrefreshArgs::OdrefreshArgs,
 };
 use compos_common::binder::to_binder_result;
 use compos_common::odrefresh::{is_system_property_interesting, ODREFRESH_PATH};
@@ -98,17 +98,7 @@
         Ok(())
     }
 
-    fn odrefresh(
-        &self,
-        compilation_mode: CompilationMode,
-        system_dir_fd: i32,
-        system_ext_dir_fd: i32,
-        output_dir_fd: i32,
-        staging_dir_fd: i32,
-        target_dir_name: &str,
-        zygote_arch: &str,
-        system_server_compiler_filter: &str,
-    ) -> BinderResult<i8> {
+    fn odrefresh(&self, args: &OdrefreshArgs) -> BinderResult<i8> {
         let initialized = *self.initialized.read().unwrap();
         if !initialized.unwrap_or(false) {
             return Err(Status::new_exception_str(
@@ -117,18 +107,7 @@
             ));
         }
 
-        let context = OdrefreshContext::new(
-            compilation_mode,
-            system_dir_fd,
-            if system_ext_dir_fd >= 0 { Some(system_ext_dir_fd) } else { None },
-            output_dir_fd,
-            staging_dir_fd,
-            target_dir_name,
-            zygote_arch,
-            system_server_compiler_filter,
-        );
-
-        to_binder_result(context.and_then(|c| self.do_odrefresh(c)))
+        to_binder_result(self.do_odrefresh(args))
     }
 
     fn getPublicKey(&self) -> BinderResult<Vec<u8>> {
@@ -147,10 +126,10 @@
 }
 
 impl CompOsService {
-    fn do_odrefresh(&self, context: OdrefreshContext) -> Result<i8> {
+    fn do_odrefresh(&self, args: &OdrefreshArgs) -> Result<i8> {
         let authfs_service = binder::get_interface(AUTHFS_SERVICE_NAME)
             .context("Unable to connect to AuthFS service")?;
-        let exit_code = odrefresh(&self.odrefresh_path, context, authfs_service, |output_dir| {
+        let exit_code = odrefresh(&self.odrefresh_path, args, authfs_service, |output_dir| {
             // authfs only shows us the files we created, so it's ok to just sign everything
             // under the output directory.
             let mut artifact_signer = ArtifactSigner::new(&output_dir);
diff --git a/libs/apexutil/src/lib.rs b/libs/apexutil/src/lib.rs
index 1183aea..999f16d 100644
--- a/libs/apexutil/src/lib.rs
+++ b/libs/apexutil/src/lib.rs
@@ -145,8 +145,9 @@
     use super::*;
 
     #[test]
-    fn test_open_apex() {
+    fn apex_verification_returns_valid_result() {
         let res = verify("tests/data/test.apex").unwrap();
+        // The expected hex is generated when we ran the method the first time.
         assert_eq!(
             to_hex_string(&res.root_digest),
             "fe11ab17da0a3a738b54bdc3a13f6139cbdf91ec32f001f8d4bbbf8938e04e39"
@@ -154,7 +155,7 @@
     }
 
     #[test]
-    fn test_payload_vbmeta_image_hash() {
+    fn payload_vbmeta_has_valid_image_hash() {
         let result = get_payload_vbmeta_image_hash("tests/data/test.apex").unwrap();
         assert_eq!(
             to_hex_string(&result),
diff --git a/libs/apkverify/src/v3.rs b/libs/apkverify/src/v3.rs
index fac0a7f..db7d8cc 100644
--- a/libs/apkverify/src/v3.rs
+++ b/libs/apkverify/src/v3.rs
@@ -76,9 +76,9 @@
 }
 
 #[derive(Debug)]
-struct Signature {
+pub(crate) struct Signature {
     /// Option is used here to allow us to ignore unsupported algorithm.
-    signature_algorithm_id: Option<SignatureAlgorithmID>,
+    pub(crate) signature_algorithm_id: Option<SignatureAlgorithmID>,
     signature: LengthPrefixed<Bytes>,
 }
 
@@ -127,9 +127,9 @@
 }
 
 impl Signer {
-    /// Select the signature that uses the strongest algorithm according to the preferences of the
-    /// v4 signing scheme.
-    fn strongest_signature(&self) -> Result<&Signature> {
+    /// Selects the signature that has the strongest supported `SignatureAlgorithmID`.
+    /// The strongest signature is used in both v3 verification and v4 apk digest computation.
+    pub(crate) fn strongest_signature(&self) -> Result<&Signature> {
         Ok(self
             .signatures
             .iter()
@@ -138,14 +138,13 @@
             .context("No supported signatures found")?)
     }
 
-    pub(crate) fn pick_v4_apk_digest(&self) -> Result<(SignatureAlgorithmID, Box<[u8]>)> {
-        let strongest_algorithm_id = self
-            .strongest_signature()?
-            .signature_algorithm_id
-            .context("Strongest signature should contain a valid signature algorithm.")?;
+    pub(crate) fn find_digest_by_algorithm(
+        &self,
+        algorithm_id: SignatureAlgorithmID,
+    ) -> Result<Box<[u8]>> {
         let signed_data: SignedData = self.signed_data.slice(..).read()?;
-        let digest = signed_data.find_digest_by_algorithm(strongest_algorithm_id)?;
-        Ok((strongest_algorithm_id, digest.digest.as_ref().to_vec().into_boxed_slice()))
+        let digest = signed_data.find_digest_by_algorithm(algorithm_id)?;
+        Ok(digest.digest.as_ref().to_vec().into_boxed_slice())
     }
 
     /// Verifies the strongest signature from signatures against signed data using public key.
diff --git a/libs/apkverify/src/v4.rs b/libs/apkverify/src/v4.rs
index d0522a7..9012479 100644
--- a/libs/apkverify/src/v4.rs
+++ b/libs/apkverify/src/v4.rs
@@ -18,7 +18,7 @@
 //!
 //! [v4]: https://source.android.com/security/apksigning/v4
 
-use anyhow::{ensure, Result};
+use anyhow::{ensure, Context, Result};
 use std::io::{Read, Seek};
 
 use crate::algorithms::SignatureAlgorithmID;
@@ -34,13 +34,17 @@
     verify: bool,
 ) -> Result<(SignatureAlgorithmID, Box<[u8]>)> {
     let (signer, mut sections) = extract_signer_and_apk_sections(apk)?;
-    let (signature_algorithm_id, extracted_digest) = signer.pick_v4_apk_digest()?;
+    let strongest_algorithm_id = signer
+        .strongest_signature()?
+        .signature_algorithm_id
+        .context("Strongest signature should contain a valid signature algorithm.")?;
+    let extracted_digest = signer.find_digest_by_algorithm(strongest_algorithm_id)?;
     if verify {
-        let computed_digest = sections.compute_digest(signature_algorithm_id)?;
+        let computed_digest = sections.compute_digest(strongest_algorithm_id)?;
         ensure!(
             computed_digest == extracted_digest.as_ref(),
             "Computed digest does not match the extracted digest."
         );
     }
-    Ok((signature_algorithm_id, extracted_digest))
+    Ok((strongest_algorithm_id, extracted_digest))
 }
diff --git a/libs/idsig/Android.bp b/libs/idsig/Android.bp
index 9f7d377..615d70e 100644
--- a/libs/idsig/Android.bp
+++ b/libs/idsig/Android.bp
@@ -31,5 +31,7 @@
     compile_multilib: "first",
     data: [
         "testdata/input.*",
+        "testdata/*.apk",
+        "testdata/*.idsig",
     ],
 }
diff --git a/libs/idsig/src/apksigv4.rs b/libs/idsig/src/apksigv4.rs
index 29def3b..3f73df3 100644
--- a/libs/idsig/src/apksigv4.rs
+++ b/libs/idsig/src/apksigv4.rs
@@ -19,7 +19,9 @@
 use byteorder::{LittleEndian, ReadBytesExt, WriteBytesExt};
 use num_derive::{FromPrimitive, ToPrimitive};
 use num_traits::{FromPrimitive, ToPrimitive};
+use std::fs;
 use std::io::{copy, Cursor, Read, Seek, SeekFrom, Write};
+use std::path::Path;
 
 use crate::hashtree::*;
 
@@ -114,9 +116,17 @@
     }
 }
 
+impl V4Signature<fs::File> {
+    /// Creates a `V4Signature` struct from the given idsig path.
+    pub fn from_idsig_path<P: AsRef<Path>>(idsig_path: P) -> Result<Self> {
+        let idsig = fs::File::open(idsig_path).context("Cannot find idsig file")?;
+        Self::from_idsig(idsig)
+    }
+}
+
 impl<R: Read + Seek> V4Signature<R> {
     /// Consumes a stream for an idsig file into a `V4Signature` struct.
-    pub fn from(mut r: R) -> Result<V4Signature<R>> {
+    pub fn from_idsig(mut r: R) -> Result<V4Signature<R>> {
         Ok(V4Signature {
             version: Version::from(r.read_u32::<LittleEndian>()?)?,
             hashing_info: HashingInfo::from(&mut r)?,
@@ -293,14 +303,15 @@
     use super::*;
     use std::io::Cursor;
 
+    const TEST_APK_PATH: &str = "testdata/v4-digest-v3-Sha256withEC.apk";
+
     fn hexstring_from(s: &[u8]) -> String {
         s.iter().map(|byte| format!("{:02x}", byte)).reduce(|i, j| i + &j).unwrap_or_default()
     }
 
     #[test]
     fn parse_idsig_file() {
-        let idsig = Cursor::new(include_bytes!("../testdata/v4-digest-v3-Sha256withEC.apk.idsig"));
-        let parsed = V4Signature::from(idsig).unwrap();
+        let parsed = V4Signature::from_idsig_path(format!("{}.idsig", TEST_APK_PATH)).unwrap();
 
         assert_eq!(Version::V2, parsed.version);
 
@@ -334,13 +345,13 @@
     /// the input file.
     #[test]
     fn parse_and_compose() {
-        let input = Cursor::new(include_bytes!("../testdata/v4-digest-v3-Sha256withEC.apk.idsig"));
-        let mut parsed = V4Signature::from(input.clone()).unwrap();
+        let idsig_path = format!("{}.idsig", TEST_APK_PATH);
+        let mut v4_signature = V4Signature::from_idsig_path(&idsig_path).unwrap();
 
         let mut output = Cursor::new(Vec::new());
-        parsed.write_into(&mut output).unwrap();
+        v4_signature.write_into(&mut output).unwrap();
 
-        assert_eq!(input.get_ref().as_ref(), output.get_ref().as_slice());
+        assert_eq!(fs::read(&idsig_path).unwrap(), output.get_ref().as_slice());
     }
 
     /// Create V4Signature by hashing an APK. Merkle tree and the root hash should be the same
@@ -351,8 +362,7 @@
         let mut created =
             V4Signature::create(&mut input, 4096, &[], HashAlgorithm::SHA256).unwrap();
 
-        let golden = Cursor::new(include_bytes!("../testdata/v4-digest-v3-Sha256withEC.apk.idsig"));
-        let mut golden = V4Signature::from(golden).unwrap();
+        let mut golden = V4Signature::from_idsig_path(format!("{}.idsig", TEST_APK_PATH)).unwrap();
 
         // Compare the root hash
         assert_eq!(
diff --git a/microdroid_manager/src/main.rs b/microdroid_manager/src/main.rs
index 7629291..c50bcbe 100644
--- a/microdroid_manager/src/main.rs
+++ b/microdroid_manager/src/main.rs
@@ -481,7 +481,7 @@
         .map(|(i, extra_idsig)| {
             (
                 format!("extra-apk-{}", i),
-                get_apk_root_hash_from_idsig(extra_idsig.to_str().unwrap())
+                get_apk_root_hash_from_idsig(extra_idsig)
                     .expect("Can't find root hash from extra idsig"),
             )
         })
@@ -601,10 +601,8 @@
     Ok(())
 }
 
-fn get_apk_root_hash_from_idsig(path: &str) -> Result<Box<RootHash>> {
-    let mut idsig = File::open(path)?;
-    let idsig = V4Signature::from(&mut idsig)?;
-    Ok(idsig.hashing_info.raw_root_hash)
+fn get_apk_root_hash_from_idsig<P: AsRef<Path>>(idsig_path: P) -> Result<Box<RootHash>> {
+    Ok(V4Signature::from_idsig_path(idsig_path)?.hashing_info.raw_root_hash)
 }
 
 fn get_public_key_from_apk(apk: &str, root_hash_trustful: bool) -> Result<Box<[u8]>> {
diff --git a/pvmfw/pvmfw.img b/pvmfw/pvmfw.img
index dc0d4d7..7f32a9a 100644
--- a/pvmfw/pvmfw.img
+++ b/pvmfw/pvmfw.img
Binary files differ
diff --git a/tests/benchmark/src/java/com/android/microdroid/benchmark/MicrodroidBenchmarks.java b/tests/benchmark/src/java/com/android/microdroid/benchmark/MicrodroidBenchmarks.java
index 7bf3c4e..263956b 100644
--- a/tests/benchmark/src/java/com/android/microdroid/benchmark/MicrodroidBenchmarks.java
+++ b/tests/benchmark/src/java/com/android/microdroid/benchmark/MicrodroidBenchmarks.java
@@ -32,6 +32,7 @@
 import android.util.Log;
 
 import com.android.microdroid.test.common.MetricsProcessor;
+import com.android.microdroid.test.common.ProcessUtil;
 import com.android.microdroid.test.device.MicrodroidDeviceTestBase;
 import com.android.microdroid.testservice.IBenchmarkService;
 
@@ -49,11 +50,12 @@
 import java.util.List;
 import java.util.Map;
 import java.util.concurrent.atomic.AtomicReference;
+import java.util.function.Function;
 
 @RunWith(Parameterized.class)
 public class MicrodroidBenchmarks extends MicrodroidDeviceTestBase {
     private static final String TAG = "MicrodroidBenchmarks";
-    private static final String METRIC_NAME_PREFIX = "avf_perf/microdroid/";
+    private static final String METRIC_NAME_PREFIX = getMetricPrefix() + "microdroid/";
     private static final int IO_TEST_TRIAL_COUNT = 5;
 
     @Rule public Timeout globalTimeout = Timeout.seconds(300);
@@ -273,6 +275,10 @@
         }
     }
 
+    private String executeCommand(String command) {
+        return runInShell(TAG, mInstrumentation.getUiAutomation(), command);
+    }
+
     @Test
     public void testMemoryUsage() throws Exception {
         final String vmName = "test_vm_mem_usage";
@@ -283,7 +289,7 @@
                         .build();
         mInner.forceCreateNewVirtualMachine(vmName, config);
         VirtualMachine vm = mInner.getVirtualMachineManager().get(vmName);
-        MemoryUsageListener listener = new MemoryUsageListener();
+        MemoryUsageListener listener = new MemoryUsageListener(this::executeCommand);
         BenchmarkVmListener.create(listener).runToFinish(TAG, vm);
 
         double mem_overall = 256.0;
@@ -293,6 +299,10 @@
         double mem_buffers = (double) listener.mBuffers / 1024.0;
         double mem_cached = (double) listener.mCached / 1024.0;
         double mem_slab = (double) listener.mSlab / 1024.0;
+        double mem_crosvm_host_rss = (double) listener.mCrosvmHostRss / 1024.0;
+        double mem_crosvm_host_pss = (double) listener.mCrosvmHostPss / 1024.0;
+        double mem_crosvm_guest_rss = (double) listener.mCrosvmGuestRss / 1024.0;
+        double mem_crosvm_guest_pss = (double) listener.mCrosvmGuestPss / 1024.0;
 
         double mem_kernel = mem_overall - mem_total;
         double mem_used = mem_total - mem_free - mem_buffers - mem_cached - mem_slab;
@@ -305,10 +315,20 @@
         bundle.putDouble(METRIC_NAME_PREFIX + "mem_cached_MB", mem_cached);
         bundle.putDouble(METRIC_NAME_PREFIX + "mem_slab_MB", mem_slab);
         bundle.putDouble(METRIC_NAME_PREFIX + "mem_unreclaimable_MB", mem_unreclaimable);
+        bundle.putDouble(METRIC_NAME_PREFIX + "mem_crosvm_host_rss_MB", mem_crosvm_host_rss);
+        bundle.putDouble(METRIC_NAME_PREFIX + "mem_crosvm_host_pss_MB", mem_crosvm_host_pss);
+        bundle.putDouble(METRIC_NAME_PREFIX + "mem_crosvm_guest_rss_MB", mem_crosvm_guest_rss);
+        bundle.putDouble(METRIC_NAME_PREFIX + "mem_crosvm_guest_pss_MB", mem_crosvm_guest_pss);
         mInstrumentation.sendStatus(0, bundle);
     }
 
     private static class MemoryUsageListener implements BenchmarkVmListener.InnerListener {
+        MemoryUsageListener(Function<String, String> shellExecutor) {
+            mShellExecutor = shellExecutor;
+        }
+
+        public Function<String, String> mShellExecutor;
+
         public long mMemTotal;
         public long mMemFree;
         public long mMemAvailable;
@@ -316,6 +336,11 @@
         public long mCached;
         public long mSlab;
 
+        public long mCrosvmHostRss;
+        public long mCrosvmHostPss;
+        public long mCrosvmGuestRss;
+        public long mCrosvmGuestPss;
+
         @Override
         public void onPayloadReady(VirtualMachine vm, IBenchmarkService service)
                 throws RemoteException {
@@ -325,6 +350,39 @@
             mBuffers = service.getMemInfoEntry("Buffers");
             mCached = service.getMemInfoEntry("Cached");
             mSlab = service.getMemInfoEntry("Slab");
+
+            try {
+                List<Integer> crosvmPids =
+                        ProcessUtil.getProcessMap(mShellExecutor).entrySet().stream()
+                                .filter(e -> e.getValue().contains("crosvm"))
+                                .map(e -> e.getKey())
+                                .collect(java.util.stream.Collectors.toList());
+                if (crosvmPids.size() != 1) {
+                    throw new RuntimeException(
+                            "expected to find exactly one crosvm processes, found "
+                                    + crosvmPids.size());
+                }
+
+                mCrosvmHostRss = 0;
+                mCrosvmHostPss = 0;
+                mCrosvmGuestRss = 0;
+                mCrosvmGuestPss = 0;
+                for (ProcessUtil.SMapEntry entry :
+                        ProcessUtil.getProcessSmaps(crosvmPids.get(0), mShellExecutor)) {
+                    long rss = entry.metrics.get("Rss");
+                    long pss = entry.metrics.get("Pss");
+                    if (entry.name.contains("crosvm_guest")) {
+                        mCrosvmGuestRss += rss;
+                        mCrosvmGuestPss += pss;
+                    } else {
+                        mCrosvmHostRss += rss;
+                        mCrosvmHostPss += pss;
+                    }
+                }
+            } catch (Exception e) {
+                Log.e(TAG, "Error inside onPayloadReady():" + e);
+                throw new RuntimeException(e);
+            }
         }
     }
 
diff --git a/tests/benchmark_hostside/java/android/avf/test/AVFHostTestCase.java b/tests/benchmark_hostside/java/android/avf/test/AVFHostTestCase.java
index e5eee27..efba60b 100644
--- a/tests/benchmark_hostside/java/android/avf/test/AVFHostTestCase.java
+++ b/tests/benchmark_hostside/java/android/avf/test/AVFHostTestCase.java
@@ -70,14 +70,14 @@
     private static final int BOOT_COMPLETE_TIMEOUT_MS = 10 * 60 * 1000;
     private static final double NANOS_IN_SEC = 1_000_000_000.0;
     private static final int ROUND_COUNT = 5;
-    private static final String METRIC_PREFIX = "avf_perf/hostside/";
 
-    private final MetricsProcessor mMetricsProcessor = new MetricsProcessor(METRIC_PREFIX);
+    private MetricsProcessor mMetricsProcessor;
     @Rule public TestMetrics mMetrics = new TestMetrics();
 
     @Before
     public void setUp() throws Exception {
         testIfDeviceIsCapable(getDevice());
+        mMetricsProcessor = new MetricsProcessor(getMetricPrefix() + "hostside/");
     }
 
     @After
diff --git a/tests/helper/Android.bp b/tests/helper/Android.bp
index f77dae5..60d4be1 100644
--- a/tests/helper/Android.bp
+++ b/tests/helper/Android.bp
@@ -20,6 +20,7 @@
     static_libs: [
         "androidx.test.runner",
         "androidx.test.ext.junit",
+        "MicrodroidTestHelper",
         "VirtualizationTestHelper",
         "truth-prebuilt",
     ],
diff --git a/tests/helper/src/java/com/android/microdroid/test/common/MetricsProcessor.java b/tests/helper/src/java/com/android/microdroid/test/common/MetricsProcessor.java
index 41534f1..b6bc479 100644
--- a/tests/helper/src/java/com/android/microdroid/test/common/MetricsProcessor.java
+++ b/tests/helper/src/java/com/android/microdroid/test/common/MetricsProcessor.java
@@ -24,6 +24,12 @@
 public final class MetricsProcessor {
     private final String mPrefix;
 
+    public static String getMetricPrefix(String debugTag) {
+        return "avf_perf"
+            + ((debugTag != null && !debugTag.isEmpty()) ? "[" + debugTag + "]" : "")
+            + "/";
+    }
+
     public MetricsProcessor(String prefix) {
         mPrefix = prefix;
     }
diff --git a/tests/helper/src/java/com/android/microdroid/test/common/ProcessUtil.java b/tests/helper/src/java/com/android/microdroid/test/common/ProcessUtil.java
index c5aad6e..611a572 100644
--- a/tests/helper/src/java/com/android/microdroid/test/common/ProcessUtil.java
+++ b/tests/helper/src/java/com/android/microdroid/test/common/ProcessUtil.java
@@ -17,18 +17,41 @@
 package com.android.microdroid.test.common;
 
 import java.io.IOException;
+import java.util.ArrayList;
 import java.util.HashMap;
+import java.util.List;
 import java.util.Map;
 import java.util.function.Function;
 
 /** This class provides process utility for both device tests and host tests. */
 public final class ProcessUtil {
 
+    /** A memory map entry from /proc/{pid}/smaps */
+    public static class SMapEntry {
+        public String name;
+        public Map<String, Long> metrics;
+    }
+
+    /** Gets metrics key and values mapping of specified process id */
+    public static List<SMapEntry> getProcessSmaps(int pid, Function<String, String> shellExecutor)
+            throws IOException {
+        String path = "/proc/" + pid + "/smaps";
+        return parseSmaps(shellExecutor.apply("cat " + path + " || true"));
+    }
+
     /** Gets metrics key and values mapping of specified process id */
     public static Map<String, Long> getProcessSmapsRollup(
             int pid, Function<String, String> shellExecutor) throws IOException {
         String path = "/proc/" + pid + "/smaps_rollup";
-        return parseMemoryInfo(skipFirstLine(shellExecutor.apply("cat " + path + " || true")));
+        List<SMapEntry> entries = parseSmaps(shellExecutor.apply("cat " + path + " || true"));
+        if (entries.size() > 1) {
+            throw new RuntimeException(
+                    "expected at most one entry in smaps_rollup, got " + entries.size());
+        }
+        if (entries.size() == 1) {
+            return entries.get(0).metrics;
+        }
+        return new HashMap<String, Long>();
     }
 
     /** Gets process id and process name mapping of the device */
@@ -54,21 +77,47 @@
     // To ensures that only one object is created at a time.
     private ProcessUtil() {}
 
-    private static Map<String, Long> parseMemoryInfo(String file) {
-        Map<String, Long> stats = new HashMap<>();
-        for (String line : file.split("[\r\n]+")) {
+    private static List<SMapEntry> parseSmaps(String file) {
+        List<SMapEntry> entries = new ArrayList<SMapEntry>();
+        for (String line : file.split("\n")) {
             line = line.trim();
             if (line.length() == 0) {
                 continue;
             }
-            // Each line is '<metrics>:        <number> kB'.
-            // EX : Pss_Anon:        70712 kB
-            if (line.endsWith(" kB")) line = line.substring(0, line.length() - 3);
-
-            String[] elems = line.split(":");
-            stats.put(elems[0].trim(), Long.parseLong(elems[1].trim()));
+            if (line.contains(": ")) {
+                if (entries.size() == 0) {
+                    throw new RuntimeException("unexpected line: " + line);
+                }
+                // Each line is '<metrics>:        <number> kB'.
+                // EX : Pss_Anon:        70712 kB
+                if (line.endsWith(" kB")) line = line.substring(0, line.length() - 3);
+                String[] elems = line.split(":");
+                String name = elems[0].trim();
+                try {
+                    entries.get(entries.size() - 1)
+                            .metrics
+                            .put(name, Long.parseLong(elems[1].trim()));
+                } catch (java.lang.NumberFormatException e) {
+                    // Some entries, like "VmFlags", aren't numbers, just ignore.
+                }
+                continue;
+            }
+            // Parse the header and create a new entry for it.
+            // Some header examples:
+            //     7f644098a000-7f644098c000 rw-p 00000000 00:00 0
+            //     00400000-0048a000 r-xp 00000000 fd:03 960637   /bin/bash
+            //     75e42af000-75f42af000 rw-s 00000000 00:01 235  /memfd:crosvm_guest (deleted)
+            SMapEntry entry = new SMapEntry();
+            String[] parts = line.split("\\s+", 6);
+            if (parts.length >= 6) {
+                entry.name = parts[5];
+            } else {
+                entry.name = "";
+            }
+            entry.metrics = new HashMap<String, Long>();
+            entries.add(entry);
         }
-        return stats;
+        return entries;
     }
 
     private static String skipFirstLine(String str) {
diff --git a/tests/helper/src/java/com/android/microdroid/test/device/MicrodroidDeviceTestBase.java b/tests/helper/src/java/com/android/microdroid/test/device/MicrodroidDeviceTestBase.java
index 33de61c..a1dee6d 100644
--- a/tests/helper/src/java/com/android/microdroid/test/device/MicrodroidDeviceTestBase.java
+++ b/tests/helper/src/java/com/android/microdroid/test/device/MicrodroidDeviceTestBase.java
@@ -33,6 +33,7 @@
 import androidx.annotation.CallSuper;
 import androidx.test.core.app.ApplicationProvider;
 
+import com.android.microdroid.test.common.MetricsProcessor;
 import com.android.virt.VirtualizationTestHelper;
 
 import java.io.BufferedReader;
@@ -51,6 +52,11 @@
         return VirtualizationTestHelper.isCuttlefish(SystemProperties.get("ro.product.name"));
     }
 
+    public static String getMetricPrefix() {
+        return MetricsProcessor.getMetricPrefix(
+                SystemProperties.get("debug.hypervisor.metrics_tag"));
+    }
+
     // TODO(b/220920264): remove Inner class; this is a hack to hide virt APEX types
     protected static class Inner {
         private final boolean mProtectedVm;
diff --git a/tests/hostside/helper/Android.bp b/tests/hostside/helper/Android.bp
index af88bb6..b2333ab 100644
--- a/tests/hostside/helper/Android.bp
+++ b/tests/hostside/helper/Android.bp
@@ -11,6 +11,7 @@
         "truth-prebuilt",
     ],
     static_libs: [
+        "MicrodroidTestHelper",
         "VirtualizationTestHelper",
     ],
 }
diff --git a/tests/hostside/helper/java/com/android/microdroid/test/host/MicrodroidHostTestCaseBase.java b/tests/hostside/helper/java/com/android/microdroid/test/host/MicrodroidHostTestCaseBase.java
index 875d89f..0417123 100644
--- a/tests/hostside/helper/java/com/android/microdroid/test/host/MicrodroidHostTestCaseBase.java
+++ b/tests/hostside/helper/java/com/android/microdroid/test/host/MicrodroidHostTestCaseBase.java
@@ -27,6 +27,7 @@
 import static org.junit.Assume.assumeTrue;
 
 import com.android.compatibility.common.tradefed.build.CompatibilityBuildHelper;
+import com.android.microdroid.test.common.MetricsProcessor;
 import com.android.tradefed.build.IBuildInfo;
 import com.android.tradefed.device.DeviceNotAvailableException;
 import com.android.tradefed.device.ITestDevice;
@@ -97,6 +98,11 @@
         return VirtualizationTestHelper.isCuttlefish(getDevice().getProperty("ro.product.name"));
     }
 
+    protected String getMetricPrefix() throws Exception {
+        return MetricsProcessor.getMetricPrefix(
+                getDevice().getProperty("debug.hypervisor.metrics_tag"));
+    }
+
     public static void testIfDeviceIsCapable(ITestDevice androidDevice) throws Exception {
         assumeTrue("Need an actual TestDevice", androidDevice instanceof TestDevice);
         TestDevice testDevice = (TestDevice) androidDevice;
diff --git a/tests/hostside/java/com/android/microdroid/test/MicrodroidTestCase.java b/tests/hostside/java/com/android/microdroid/test/MicrodroidTestCase.java
index a4e46a1..928261a 100644
--- a/tests/hostside/java/com/android/microdroid/test/MicrodroidTestCase.java
+++ b/tests/hostside/java/com/android/microdroid/test/MicrodroidTestCase.java
@@ -94,6 +94,8 @@
     @Rule public TestName mTestName = new TestName();
     @Rule public TestMetrics mMetrics = new TestMetrics();
 
+    private String mMetricPrefix;
+
     private int minMemorySize() throws DeviceNotAvailableException {
         CommandRunner android = new CommandRunner(getDevice());
         String abi = android.run("getprop", "ro.product.cpu.abi");
@@ -775,7 +777,7 @@
 
         for (Map.Entry<String, Long> stat : getProcMemInfo().entrySet()) {
             mMetrics.addTestMetric(
-                    "avf_perf/microdroid/meminfo/" + stat.getKey().toLowerCase(),
+                    mMetricPrefix + "meminfo/" + stat.getKey().toLowerCase(),
                     stat.getValue().toString());
         }
 
@@ -783,7 +785,7 @@
             for (Map.Entry<String, Long> stat : getProcSmapsRollup(proc.mPid).entrySet()) {
                 String name = stat.getKey().toLowerCase();
                 mMetrics.addTestMetric(
-                        "avf_perf/microdroid/smaps/" + name + "/" + proc.mName,
+                        mMetricPrefix + "smaps/" + name + "/" + proc.mName,
                         stat.getValue().toString());
             }
         }
@@ -828,6 +830,7 @@
     @Before
     public void setUp() throws Exception {
         testIfDeviceIsCapable(getDevice());
+        mMetricPrefix = getMetricPrefix() + "microdroid/";
 
         prepareVirtualizationTestSetup(getDevice());