Merge "Microdroid low memory test."
diff --git a/OWNERS b/OWNERS
index 7e479c4..5bd97f3 100644
--- a/OWNERS
+++ b/OWNERS
@@ -12,6 +12,7 @@
ardb@google.com
ascull@google.com
inseob@google.com
+jeffv@google.com
jooyung@google.com
mzyngier@google.com
ptosi@google.com
diff --git a/TEST_MAPPING b/TEST_MAPPING
index cf90c76..33498d3 100644
--- a/TEST_MAPPING
+++ b/TEST_MAPPING
@@ -25,6 +25,9 @@
},
{
"name": "MicrodroidBenchmarkApp"
+ },
+ {
+ "name": "ComposBenchmarkApp"
}
],
"imports": [
diff --git a/apkdmverity/Android.bp b/apkdmverity/Android.bp
index 06d4500..d7aa921 100644
--- a/apkdmverity/Android.bp
+++ b/apkdmverity/Android.bp
@@ -11,7 +11,7 @@
rustlibs: [
"libanyhow",
"libbitflags",
- "libclap",
+ "libclap_deprecated",
"libdata_model",
"libidsig",
"libitertools",
diff --git a/authfs/fd_server/Android.bp b/authfs/fd_server/Android.bp
index 9499cd2..c4aa1d8 100644
--- a/authfs/fd_server/Android.bp
+++ b/authfs/fd_server/Android.bp
@@ -13,7 +13,7 @@
"libbinder_common",
"libbinder_rpc_unstable_bindgen",
"libbinder_rs",
- "libclap",
+ "libclap_deprecated",
"liblibc",
"liblog_rust",
"libnix",
diff --git a/authfs/tests/Android.bp b/authfs/tests/Android.bp
index 61a6ef5..a886d10 100644
--- a/authfs/tests/Android.bp
+++ b/authfs/tests/Android.bp
@@ -11,7 +11,7 @@
"compatibility-host-util",
],
static_libs: [
- "VirtualizationTestHelper",
+ "MicrodroidHostTestHelper",
],
test_suites: ["general-tests"],
//TODO(b/235263148) use data_device_bins_64
@@ -31,7 +31,7 @@
rustlibs: [
"libandroid_logger",
"libanyhow",
- "libclap",
+ "libclap_deprecated",
"libcommand_fds",
"liblog_rust",
"libnix",
diff --git a/authfs/tests/java/src/com/android/fs/AuthFsHostTest.java b/authfs/tests/java/src/com/android/fs/AuthFsHostTest.java
index f5254bc..fcd6753 100644
--- a/authfs/tests/java/src/com/android/fs/AuthFsHostTest.java
+++ b/authfs/tests/java/src/com/android/fs/AuthFsHostTest.java
@@ -16,9 +16,8 @@
package com.android.virt.fs;
-import static android.virt.test.CommandResultSubject.assertThat;
-import static android.virt.test.LogArchiver.archiveLogThenDelete;
-
+import static com.android.microdroid.test.CommandResultSubject.assertThat;
+import static com.android.microdroid.test.LogArchiver.archiveLogThenDelete;
import static com.android.tradefed.device.TestDevice.MicrodroidBuilder;
import static com.android.tradefed.testtype.DeviceJUnit4ClassRunner.TestLogData;
@@ -30,10 +29,10 @@
import static org.junit.Assume.assumeTrue;
import android.platform.test.annotations.RootPermissionTest;
-import android.virt.test.CommandRunner;
import com.android.compatibility.common.tradefed.build.CompatibilityBuildHelper;
import com.android.compatibility.common.util.PollingCheck;
+import com.android.microdroid.test.CommandRunner;
import com.android.tradefed.build.IBuildInfo;
import com.android.tradefed.device.DeviceNotAvailableException;
import com.android.tradefed.device.ITestDevice;
diff --git a/avmd/Android.bp b/avmd/Android.bp
new file mode 100644
index 0000000..e31e103
--- /dev/null
+++ b/avmd/Android.bp
@@ -0,0 +1,38 @@
+package {
+ default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+rust_defaults {
+ name: "libavmd_defaults",
+ crate_name: "avmd",
+ host_supported: true,
+ srcs: ["src/lib.rs"],
+ prefer_rlib: true,
+ rustlibs: [
+ "libserde",
+ "libapexutil_rust", // TODO(b/239413416): Remove this after adding hex
+ ],
+}
+
+rust_library {
+ name: "libavmd",
+ defaults: ["libavmd_defaults"],
+}
+
+rust_binary {
+ name: "avmdtool",
+ srcs: ["src/main.rs"],
+ required: ["avbtool"],
+ host_supported: true,
+ prefer_rlib: true,
+ rustlibs: [
+ "libanyhow",
+ "libapexutil_rust",
+ "libapkverify",
+ "libavmd",
+ "libclap_deprecated",
+ "libserde",
+ "libserde_cbor",
+ "libvbmeta_rust",
+ ],
+}
diff --git a/avmd/README.md b/avmd/README.md
new file mode 100644
index 0000000..ae813a0
--- /dev/null
+++ b/avmd/README.md
@@ -0,0 +1,48 @@
+# The AVMD image format
+---
+
+The AVMD image format is used to descibe the verified code that a VM will
+load. This repository contains tools and libraries for working with the AVMD
+image format.
+
+# What is it?
+
+When a VM boots, it loads and verifies a set of images that control execution
+within the VM. Therefore, describing what executes in a VM means describing
+what is loaded. The AVMD image format is designed, for this purpose, to
+describe the closure of images that can be loaded and how they should be
+verified.
+
+# Caveats
+
+The AVMD image format will only allow Android supported signing formats. The
+supported formats are currently limited to [AVB][] and [APK][].
+
+[AVB]: https://android.googlesource.com/platform/external/avb/+/master/README.md
+[APK]: https://source.android.com/security/apksigning#schemes
+
+Verification of the images as they are loaded is the responsibility of the VM.
+The VM is required to only load the images described and to verify them against
+the included parameters. If the VM does not follow this requirement, the
+description of the VM may not be accurate and must not be trusted. Validating
+that the VM behaves as expected requires audit of all boot stages of the VM.
+
+# Using avmdtool
+
+The `.avmd` file can be created as follows
+
+```bash
+avmdtool create /tmp/out.avmd \
+ --vbmeta pvmfw preload u-boot.bin \
+ --vbmeta uboot env_vbmeta disk1/vbmeta.imb \
+ --vbmeta uboot vbmeta micordoid/vbmeta.img \
+ --apk microdroid payload compos.apk \
+ --apk microdroid extra_apk extra_apk.apk \
+ --apex-payload microdroid art_apex art.apex
+```
+
+You can read the `.avmd` file with
+
+```bash
+avmdtool dump /tmp/out.avmd
+```
diff --git a/avmd/src/avmd.rs b/avmd/src/avmd.rs
new file mode 100644
index 0000000..50cdfdf
--- /dev/null
+++ b/avmd/src/avmd.rs
@@ -0,0 +1,154 @@
+// 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.
+
+extern crate alloc;
+
+use alloc::{
+ string::{String, ToString},
+ vec::Vec,
+};
+use apexutil::to_hex_string;
+use core::fmt;
+use serde::{Deserialize, Serialize};
+
+/// An Avmd struct contains
+/// - A header with version information that allows rollback when needed.
+/// - A list of descriptors that describe different images.
+#[derive(Serialize, Deserialize, Debug, Clone)]
+pub struct Avmd {
+ header: Header,
+ descriptors: Vec<Descriptor>,
+}
+
+impl fmt::Display for Avmd {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ writeln!(f, "Descriptors:")?;
+ for descriptor in &self.descriptors {
+ write!(f, "{}", descriptor)?;
+ }
+ Ok(())
+ }
+}
+
+impl Avmd {
+ /// Creates an instance of Avmd with a given list of descriptors.
+ pub fn new(descriptors: Vec<Descriptor>) -> Avmd {
+ Avmd { header: Header::default(), descriptors }
+ }
+}
+
+static AVMD_MAGIC: u32 = 0x444d5641;
+static AVMD_VERSION_MAJOR: u16 = 1;
+static AVMD_VERSION_MINOR: u16 = 0;
+
+/// Header information for AVMD.
+#[derive(Serialize, Deserialize, Debug, Clone)]
+struct Header {
+ magic: u32,
+ version_major: u16,
+ version_minor: u16,
+}
+
+impl Default for Header {
+ fn default() -> Self {
+ Header {
+ magic: AVMD_MAGIC,
+ version_major: AVMD_VERSION_MAJOR,
+ version_minor: AVMD_VERSION_MINOR,
+ }
+ }
+}
+
+/// AVMD descriptor.
+#[derive(Serialize, Deserialize, Debug, Clone)]
+pub enum Descriptor {
+ /// Descriptor type for the VBMeta images.
+ VbMeta(VbMetaDescriptor),
+ /// Descriptor type for APK.
+ Apk(ApkDescriptor),
+}
+
+impl fmt::Display for Descriptor {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ match self {
+ Descriptor::VbMeta(descriptor) => write!(f, "{}", descriptor),
+ Descriptor::Apk(descriptor) => write!(f, "{}", descriptor),
+ }
+ }
+}
+
+/// VbMeta descriptor.
+#[derive(Serialize, Deserialize, Debug, Clone)]
+pub struct VbMetaDescriptor {
+ /// The identifier of this resource.
+ #[serde(flatten)]
+ pub resource: ResourceIdentifier,
+ /// The SHA-512 [VBMeta digest][] calculated from the top-level VBMeta image.
+ ///
+ /// [VBMeta digest]: https://android.googlesource.com/platform/external/avb/+/master/README.md#the-vbmeta-digest
+ pub vbmeta_digest: Vec<u8>,
+}
+
+impl fmt::Display for VbMetaDescriptor {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ writeln!(f, " VBMeta descriptor:")?;
+ writeln!(f, " namespace: {}", self.resource.namespace)?;
+ writeln!(f, " name: {}", self.resource.name)?;
+ writeln!(f, " vbmeta digest: {}", to_hex_string(&self.vbmeta_digest))?;
+ Ok(())
+ }
+}
+
+/// APK descriptor.
+#[derive(Serialize, Deserialize, Debug, Clone)]
+pub struct ApkDescriptor {
+ /// The identifier of this resource.
+ #[serde(flatten)]
+ pub resource: ResourceIdentifier,
+ /// The ID of the algoithm used to sign the APK.
+ /// It should be one of the algorithms in the [list][].
+ ///
+ /// [list]: https://source.android.com/security/apksigning/v2#signature-algorithm-ids
+ pub signature_algorithm_id: u32,
+ /// Digest of the APK's v3 signing block. TODO: fix
+ pub apk_digest: Vec<u8>,
+}
+
+impl fmt::Display for ApkDescriptor {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ writeln!(f, " APK descriptor:")?;
+ writeln!(f, " namespace: {}", self.resource.namespace)?;
+ writeln!(f, " name: {}", self.resource.name)?;
+ writeln!(f, " Signing algorithm ID: {:#x}", self.signature_algorithm_id)?;
+ writeln!(f, " APK digest: {}", to_hex_string(&self.apk_digest))?;
+ Ok(())
+ }
+}
+
+/// Resource identifier regroups information to identify resources.
+#[derive(Serialize, Deserialize, Debug, Clone)]
+pub struct ResourceIdentifier {
+ /// Namespace of the resource.
+ namespace: String,
+ /// Name of the resource.
+ name: String,
+}
+
+impl ResourceIdentifier {
+ /// Creates an instance of ResourceIdentifier with the given
+ /// namespace and name.
+ pub fn new(namespace: &str, name: &str) -> ResourceIdentifier {
+ ResourceIdentifier { namespace: namespace.to_string(), name: name.to_string() }
+ }
+}
diff --git a/avmd/src/lib.rs b/avmd/src/lib.rs
new file mode 100644
index 0000000..7a06e6a
--- /dev/null
+++ b/avmd/src/lib.rs
@@ -0,0 +1,21 @@
+// 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.
+
+//! Library for handling AVMD blobs.
+
+#![no_std]
+
+mod avmd;
+
+pub use avmd::{ApkDescriptor, Avmd, Descriptor, ResourceIdentifier, VbMetaDescriptor};
diff --git a/avmd/src/main.rs b/avmd/src/main.rs
new file mode 100644
index 0000000..b156a66
--- /dev/null
+++ b/avmd/src/main.rs
@@ -0,0 +1,157 @@
+// 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.
+
+//! Tool for handling AVMD blobs.
+
+use anyhow::{anyhow, bail, Result};
+use apexutil::get_payload_vbmeta_image_hash;
+use apkverify::pick_v4_apk_digest;
+use avmd::{ApkDescriptor, Avmd, Descriptor, ResourceIdentifier, VbMetaDescriptor};
+use clap::{App, AppSettings, Arg, ArgMatches, SubCommand};
+use serde::ser::Serialize;
+use std::fs::File;
+use vbmeta::VbMetaImage;
+
+fn get_vbmeta_image_hash(file: &str) -> Result<Vec<u8>> {
+ let img = VbMetaImage::verify_path(file)?;
+ Ok(img.hash().ok_or_else(|| anyhow!("No hash as VBMeta image isn't signed"))?.to_vec())
+}
+
+/// Iterate over a set of argument values, that could be empty or come in
+/// (<index>, <namespace>, <name>, <file>) tuple.
+struct NamespaceNameFileIterator<'a> {
+ indices: Option<clap::Indices<'a>>,
+ values: Option<clap::Values<'a>>,
+}
+
+impl<'a> NamespaceNameFileIterator<'a> {
+ fn new(args: &'a ArgMatches, name: &'a str) -> Self {
+ NamespaceNameFileIterator { indices: args.indices_of(name), values: args.values_of(name) }
+ }
+}
+
+impl<'a> Iterator for NamespaceNameFileIterator<'a> {
+ type Item = (usize, &'a str, &'a str, &'a str);
+
+ fn next(&mut self) -> Option<Self::Item> {
+ match (self.indices.as_mut(), self.values.as_mut()) {
+ (Some(indices), Some(values)) => {
+ match (indices.nth(2), values.next(), values.next(), values.next()) {
+ (Some(index), Some(namespace), Some(name), Some(file)) => {
+ Some((index, namespace, name, file))
+ }
+ _ => None,
+ }
+ }
+ _ => None,
+ }
+ }
+}
+
+fn create(args: &ArgMatches) -> Result<()> {
+ // Store descriptors in the order they were given in the arguments
+ // TODO: instead, group them by namespace?
+ let mut descriptors = std::collections::BTreeMap::new();
+ for (i, namespace, name, file) in NamespaceNameFileIterator::new(args, "vbmeta") {
+ descriptors.insert(
+ i,
+ Descriptor::VbMeta(VbMetaDescriptor {
+ resource: ResourceIdentifier::new(namespace, name),
+ vbmeta_digest: get_vbmeta_image_hash(file)?,
+ }),
+ );
+ }
+ for (i, namespace, name, file) in NamespaceNameFileIterator::new(args, "apk") {
+ let file = File::open(file)?;
+ let (signature_algorithm_id, apk_digest) = pick_v4_apk_digest(file)?;
+ descriptors.insert(
+ i,
+ Descriptor::Apk(ApkDescriptor {
+ resource: ResourceIdentifier::new(namespace, name),
+ signature_algorithm_id,
+ apk_digest: apk_digest.to_vec(),
+ }),
+ );
+ }
+ for (i, namespace, name, file) in NamespaceNameFileIterator::new(args, "apex-payload") {
+ descriptors.insert(
+ i,
+ Descriptor::VbMeta(VbMetaDescriptor {
+ resource: ResourceIdentifier::new(namespace, name),
+ vbmeta_digest: get_payload_vbmeta_image_hash(file)?,
+ }),
+ );
+ }
+ let avmd = Avmd::new(descriptors.into_values().collect());
+ let mut bytes = Vec::new();
+ avmd.serialize(
+ &mut serde_cbor::Serializer::new(&mut serde_cbor::ser::IoWrite::new(&mut bytes))
+ .packed_format()
+ .legacy_enums(),
+ )?;
+ std::fs::write(args.value_of("file").unwrap(), &bytes)?;
+ Ok(())
+}
+
+fn dump(args: &ArgMatches) -> Result<()> {
+ let file = std::fs::read(args.value_of("file").unwrap())?;
+ let avmd: Avmd = serde_cbor::from_slice(&file)?;
+ println!("{}", avmd);
+ Ok(())
+}
+
+fn main() -> Result<()> {
+ let namespace_name_file = ["namespace", "name", "file"];
+ let app = App::new("avmdtool")
+ .setting(AppSettings::SubcommandRequiredElseHelp)
+ .subcommand(
+ SubCommand::with_name("create")
+ .setting(AppSettings::ArgRequiredElseHelp)
+ .arg(Arg::with_name("file").required(true).takes_value(true))
+ .arg(
+ Arg::with_name("vbmeta")
+ .long("vbmeta")
+ .takes_value(true)
+ .value_names(&namespace_name_file)
+ .multiple(true),
+ )
+ .arg(
+ Arg::with_name("apk")
+ .long("apk")
+ .takes_value(true)
+ .value_names(&namespace_name_file)
+ .multiple(true),
+ )
+ .arg(
+ Arg::with_name("apex-payload")
+ .long("apex-payload")
+ .takes_value(true)
+ .value_names(&namespace_name_file)
+ .multiple(true),
+ ),
+ )
+ .subcommand(
+ SubCommand::with_name("dump")
+ .setting(AppSettings::ArgRequiredElseHelp)
+ .arg(Arg::with_name("file").required(true).takes_value(true)),
+ );
+
+ let args = app.get_matches();
+ match args.subcommand() {
+ ("create", Some(sub_args)) => create(sub_args)?,
+ ("dump", Some(sub_args)) => dump(sub_args)?,
+ _ => bail!("Invalid arguments"),
+ }
+ Ok(())
+}
diff --git a/compos/Android.bp b/compos/Android.bp
index 69b22d6..7ef9e75 100644
--- a/compos/Android.bp
+++ b/compos/Android.bp
@@ -14,7 +14,7 @@
"libbinder_common",
"libbinder_rpc_unstable_bindgen",
"libbinder_rs",
- "libclap",
+ "libclap_deprecated",
"libcompos_common",
"liblibc",
"liblog_rust",
diff --git a/compos/aidl/com/android/compos/ICompOsService.aidl b/compos/aidl/com/android/compos/ICompOsService.aidl
index 9cf99be..4df22da 100644
--- a/compos/aidl/com/android/compos/ICompOsService.aidl
+++ b/compos/aidl/com/android/compos/ICompOsService.aidl
@@ -74,4 +74,10 @@
* hardware/interfaces/security/dice/aidl/android/hardware/security/dice/Bcc.aidl.
*/
byte[] getAttestationChain();
+
+ /**
+ * Request the service to exit, triggering the termination of the VM. This may cause any
+ * requests in flight to fail.
+ */
+ oneway void quit();
}
diff --git a/compos/benchmark/Android.bp b/compos/benchmark/Android.bp
index 10f8015..9353771 100644
--- a/compos/benchmark/Android.bp
+++ b/compos/benchmark/Android.bp
@@ -11,6 +11,7 @@
static_libs: [
"androidx.test.runner",
"androidx.test.ext.junit",
+ "MicroroidDeviceTestHelper",
"truth-prebuilt",
],
platform_apis: true,
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 e4fb5ff..b4f55f9 100644
--- a/compos/benchmark/src/java/com/android/compos/benchmark/ComposBenchmark.java
+++ b/compos/benchmark/src/java/com/android/compos/benchmark/ComposBenchmark.java
@@ -17,6 +17,8 @@
import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
+import static com.google.common.truth.TruthJUnit.assume;
+
import static org.junit.Assert.assertTrue;
import android.app.Instrumentation;
@@ -24,7 +26,8 @@
import android.os.ParcelFileDescriptor;
import android.util.Log;
-import org.junit.After;
+import com.android.microdroid.test.MicrodroidDeviceTestBase;
+
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -37,17 +40,18 @@
import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
-import java.time.Duration;
import java.util.Date;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
@RunWith(JUnit4.class)
-public class ComposBenchmark {
+public class ComposBenchmark extends MicrodroidDeviceTestBase {
private static final String TAG = "ComposBenchmark";
private static final int BUFFER_SIZE = 1024;
- private static final int ROUND_COUNT = 10;
+ 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 Instrumentation mInstrumentation;
@@ -56,9 +60,29 @@
mInstrumentation = getInstrumentation();
}
- @After
- public void cleanup() {
+ private void reportMetric(String name, String unit, double[] values) {
+ double sum = 0;
+ double squareSum = 0;
+ double min = Double.MAX_VALUE;
+ double max = Double.MIN_VALUE;
+ for (double val : values) {
+ sum += val;
+ squareSum += val * val;
+ min = val < min ? val : min;
+ max = val > max ? val : max;
+ }
+
+ double average = sum / values.length;
+ double variance = squareSum / values.length - average * average;
+ double stdev = Math.sqrt(variance);
+
+ Bundle bundle = new Bundle();
+ bundle.putDouble(METRIC_PREFIX + name + "_average_" + unit, average);
+ bundle.putDouble(METRIC_PREFIX + name + "_min_" + unit, min);
+ bundle.putDouble(METRIC_PREFIX + name + "_max_" + unit, max);
+ bundle.putDouble(METRIC_PREFIX + name + "_stdev_" + unit, stdev);
+ mInstrumentation.sendStatus(0, bundle);
}
public byte[] executeCommandBlocking(String command) {
@@ -98,34 +122,26 @@
}
@Test
- public void testCompilationInVM()
- throws InterruptedException, IOException {
+ public void testGuestCompileTime() throws InterruptedException, IOException {
+ assume().withMessage("Skip on CF; too slow").that(isCuttlefish()).isFalse();
final String command = "/apex/com.android.compos/bin/composd_cmd test-compile";
- Long[] compileSecArray = new Long[ROUND_COUNT];
+ double[] compileTime = new double[ROUND_COUNT];
for (int round = 0; round < ROUND_COUNT; ++round) {
Long compileStartTime = System.nanoTime();
String output = executeCommand(command);
Long compileEndTime = System.nanoTime();
- Long compileSec = Duration.ofNanos(compileEndTime - compileStartTime).getSeconds();
Pattern pattern = Pattern.compile("All Ok");
Matcher matcher = pattern.matcher(output);
assertTrue(matcher.find());
- compileSecArray[round] = compileSec;
+ compileTime[round] = (compileEndTime - compileStartTime) / NANOS_IN_SEC;
}
- Long compileSecSum = 0L;
- for (Long num: compileSecArray) {
- compileSecSum += num;
- }
-
- Bundle bundle = new Bundle();
- bundle.putLong("compliation_in_vm_elapse_second", compileSecSum / compileSecArray.length);
- mInstrumentation.sendStatus(0, bundle);
+ reportMetric("guest_compile_time", "s", compileTime);
}
private Timestamp getLatestDex2oatSuccessTime()
@@ -133,7 +149,7 @@
final String command = "logcat -d -e dex2oat";
String output = executeCommand(command);
- String latestTime = "";
+ String latestTime = null;
for (String line : output.split("[\r\n]+")) {
Pattern pattern = Pattern.compile("dex2oat64: dex2oat took");
@@ -143,6 +159,10 @@
}
}
+ if (latestTime == null) {
+ return null;
+ }
+
DateFormat formatter = new SimpleDateFormat("MM-dd hh:mm:ss.SSS");
Date date = formatter.parse(latestTime);
Timestamp timeStampDate = new Timestamp(date.getTime());
@@ -151,35 +171,28 @@
}
@Test
- public void testCompilationInAndroid()
+ public void testHostCompileTime()
throws InterruptedException, IOException, ParseException {
final String command = "/apex/com.android.art/bin/odrefresh --force-compile";
- Long[] compileSecArray = new Long[ROUND_COUNT];
+ double[] compileTime = new double[ROUND_COUNT];
for (int round = 0; round < ROUND_COUNT; ++round) {
Timestamp beforeCompileLatestTime = getLatestDex2oatSuccessTime();
Long compileStartTime = System.nanoTime();
String output = executeCommand(command);
Long compileEndTime = System.nanoTime();
- Long compileSec = Duration.ofNanos(compileEndTime - compileStartTime).getSeconds();
Timestamp afterCompileLatestTime = getLatestDex2oatSuccessTime();
- assertTrue(beforeCompileLatestTime.before(afterCompileLatestTime));
+ assertTrue(afterCompileLatestTime != null);
+ assertTrue(beforeCompileLatestTime == null
+ || beforeCompileLatestTime.before(afterCompileLatestTime));
- compileSecArray[round] = compileSec;
+ compileTime[round] = (compileEndTime - compileStartTime) / NANOS_IN_SEC;
}
- Long compileSecSum = 0L;
- for (Long num: compileSecArray) {
- compileSecSum += num;
- }
-
- Bundle bundle = new Bundle();
- bundle.putLong("compliation_in_android_elapse_second",
- compileSecSum / compileSecArray.length);
- mInstrumentation.sendStatus(0, bundle);
+ reportMetric("host_compile_time", "s", compileTime);
}
}
diff --git a/compos/common/Android.bp b/compos/common/Android.bp
index d1b45c4..1a69b1a 100644
--- a/compos/common/Android.bp
+++ b/compos/common/Android.bp
@@ -13,6 +13,7 @@
"libanyhow",
"libbinder_common",
"libbinder_rpc_unstable_bindgen",
+ "liblazy_static",
"liblog_rust",
"libnested_virt",
"libnum_traits",
diff --git a/compos/common/compos_client.rs b/compos/common/compos_client.rs
index 23cd505..30c55b3 100644
--- a/compos/common/compos_client.rs
+++ b/compos/common/compos_client.rs
@@ -16,7 +16,7 @@
//! Support for starting CompOS in a VM and connecting to the service
-use crate::timeouts::timeouts;
+use crate::timeouts::TIMEOUTS;
use crate::{COMPOS_APEX_ROOT, COMPOS_DATA_ROOT, COMPOS_VSOCK_PORT, DEFAULT_VM_CONFIG_PATH};
use android_system_virtualizationservice::aidl::android::system::virtualizationservice::{
DeathReason::DeathReason,
@@ -37,7 +37,7 @@
use std::num::NonZeroU32;
use std::path::{Path, PathBuf};
use std::thread;
-use vmclient::VmInstance;
+use vmclient::{VmInstance, VmWaitError};
/// This owns an instance of the CompOS VM.
pub struct ComposClient(VmInstance);
@@ -128,14 +128,37 @@
instance.start()?;
- instance.wait_until_ready(timeouts()?.vm_max_time_to_ready)?;
+ let ready = instance.wait_until_ready(TIMEOUTS.vm_max_time_to_ready);
+ if ready == Err(VmWaitError::Finished) && debug_level != DebugLevel::NONE {
+ // The payload has (unexpectedly) finished, but the VM is still running. Give it
+ // some time to shutdown to maximize our chances of getting useful logs.
+ if let Some(death_reason) =
+ instance.wait_for_death_with_timeout(TIMEOUTS.vm_max_time_to_exit)
+ {
+ bail!("VM died during startup - reason {:?}", death_reason);
+ }
+ }
+ ready?;
Ok(Self(instance))
}
/// Create and return an RPC Binder connection to the Comp OS service in the VM.
- pub fn get_service(&self) -> Result<Strong<dyn ICompOsService>> {
- self.0.get_service(COMPOS_VSOCK_PORT).context("Connecting to CompOS service")
+ pub fn connect_service(&self) -> Result<Strong<dyn ICompOsService>> {
+ self.0.connect_service(COMPOS_VSOCK_PORT).context("Connecting to CompOS service")
+ }
+
+ /// Wait for the instance to shut down. If it fails to shutdown within a reasonable time the
+ /// instance is dropped, which forcibly terminates it.
+ /// This should only be called when the instance has been requested to quit, or we believe that
+ /// it is already in the process of exiting due to some failure.
+ pub fn wait_for_shutdown(self) {
+ let death_reason = self.0.wait_for_death_with_timeout(TIMEOUTS.vm_max_time_to_exit);
+ match death_reason {
+ Some(vmclient::DeathReason::Shutdown) => info!("VM has exited normally"),
+ Some(reason) => warn!("VM died with reason {:?}", reason),
+ None => warn!("VM failed to exit, dropping"),
+ }
}
}
diff --git a/compos/common/timeouts.rs b/compos/common/timeouts.rs
index d0d107f..952be0a 100644
--- a/compos/common/timeouts.rs
+++ b/compos/common/timeouts.rs
@@ -17,7 +17,7 @@
//! Timeouts for common situations, with support for longer timeouts when using nested
//! virtualization.
-use anyhow::Result;
+use lazy_static::lazy_static;
use std::time::Duration;
/// Holder for the various timeouts we use.
@@ -27,27 +27,32 @@
pub odrefresh_max_execution_time: Duration,
/// Time allowed for the CompOS VM to start up and become ready.
pub vm_max_time_to_ready: Duration,
+ /// Time we wait for a VM to exit once the payload has finished.
+ pub vm_max_time_to_exit: Duration,
}
-/// Return the timeouts that are appropriate on the current platform.
-pub fn timeouts() -> Result<&'static Timeouts> {
+lazy_static! {
+/// The timeouts that are appropriate on the current platform.
+pub static ref TIMEOUTS: Timeouts = if nested_virt::is_nested_virtualization().unwrap() {
// Nested virtualization is slow.
- if nested_virt::is_nested_virtualization()? {
- Ok(&EXTENDED_TIMEOUTS)
- } else {
- Ok(&NORMAL_TIMEOUTS)
- }
+ EXTENDED_TIMEOUTS
+} else {
+ NORMAL_TIMEOUTS
+};
}
/// The timeouts that we use normally.
const NORMAL_TIMEOUTS: Timeouts = Timeouts {
- // Note: the source of truth for these odrefresh timeouts is art/odrefresh/odr_config.h.
+ // Note: the source of truth for this odrefresh timeout is art/odrefresh/odrefresh.cc.
odrefresh_max_execution_time: Duration::from_secs(300),
vm_max_time_to_ready: Duration::from_secs(15),
+ vm_max_time_to_exit: Duration::from_secs(3),
};
-/// The timeouts that we use when need_extra_time() returns true.
+/// The timeouts that we use when running under nested virtualization.
const EXTENDED_TIMEOUTS: Timeouts = Timeouts {
+ // Note: the source of truth for this odrefresh timeout is art/odrefresh/odrefresh.cc.
odrefresh_max_execution_time: Duration::from_secs(480),
vm_max_time_to_ready: Duration::from_secs(120),
+ vm_max_time_to_exit: Duration::from_secs(10),
};
diff --git a/compos/composd/src/instance_starter.rs b/compos/composd/src/instance_starter.rs
index 340e8b7..6e6253e 100644
--- a/compos/composd/src/instance_starter.rs
+++ b/compos/composd/src/instance_starter.rs
@@ -113,7 +113,7 @@
&self.vm_parameters,
)
.context("Starting VM")?;
- let service = vm_instance.get_service().context("Connecting to CompOS")?;
+ let service = vm_instance.connect_service().context("Connecting to CompOS")?;
Ok(CompOsInstance { vm_instance, service, lazy_service_guard: Default::default() })
}
diff --git a/compos/composd_cmd/Android.bp b/compos/composd_cmd/Android.bp
index c230e13..1ede0ba 100644
--- a/compos/composd_cmd/Android.bp
+++ b/compos/composd_cmd/Android.bp
@@ -10,7 +10,7 @@
"android.system.composd-rust",
"libanyhow",
"libbinder_rs",
- "libclap",
+ "libclap_deprecated",
"libcompos_common",
],
prefer_rlib: true,
diff --git a/compos/composd_cmd/composd_cmd.rs b/compos/composd_cmd/composd_cmd.rs
index 6afd711..c6a5479 100644
--- a/compos/composd_cmd/composd_cmd.rs
+++ b/compos/composd_cmd/composd_cmd.rs
@@ -31,7 +31,7 @@
},
};
use anyhow::{bail, Context, Result};
-use compos_common::timeouts::timeouts;
+use compos_common::timeouts::TIMEOUTS;
use std::sync::{Arc, Condvar, Mutex};
use std::time::Duration;
@@ -147,7 +147,7 @@
println!("Waiting");
- match state.wait(timeouts()?.odrefresh_max_execution_time) {
+ match state.wait(TIMEOUTS.odrefresh_max_execution_time) {
Ok(Outcome::Succeeded) => Ok(()),
Ok(Outcome::TaskDied) => bail!("Compilation task died"),
Ok(Outcome::Failed(reason, message)) => {
diff --git a/compos/src/compsvc.rs b/compos/src/compsvc.rs
index 91415bb..9fa68d6 100644
--- a/compos/src/compsvc.rs
+++ b/compos/src/compsvc.rs
@@ -151,6 +151,12 @@
fn getAttestationChain(&self) -> BinderResult<Vec<u8>> {
to_binder_result(compos_key::get_attestation_chain())
}
+
+ fn quit(&self) -> BinderResult<()> {
+ // TODO(b/236581575) Consider shutting down the binder server a bit more gracefully.
+ // When our process exits, Microdroid will shut down the VM.
+ std::process::exit(0);
+ }
}
fn add_artifacts(target_dir: &Path, artifact_signer: &mut ArtifactSigner) -> Result<()> {
diff --git a/compos/tests/Android.bp b/compos/tests/Android.bp
index b77a7e4..72da97f 100644
--- a/compos/tests/Android.bp
+++ b/compos/tests/Android.bp
@@ -12,7 +12,7 @@
],
data_native_bins: ["bcc_validator"],
static_libs: [
- "VirtualizationTestHelper",
+ "MicrodroidHostTestHelper",
],
test_suites: [
"general-tests",
diff --git a/compos/tests/java/android/compos/test/ComposTestCase.java b/compos/tests/java/android/compos/test/ComposTestCase.java
index 51f0a1f..4f33afd 100644
--- a/compos/tests/java/android/compos/test/ComposTestCase.java
+++ b/compos/tests/java/android/compos/test/ComposTestCase.java
@@ -16,18 +16,17 @@
package android.compos.test;
-import static android.virt.test.CommandResultSubject.assertThat;
-import static android.virt.test.CommandResultSubject.command_results;
-
+import static com.android.microdroid.test.CommandResultSubject.assertThat;
+import static com.android.microdroid.test.CommandResultSubject.command_results;
import static com.android.tradefed.testtype.DeviceJUnit4ClassRunner.TestLogData;
import static com.google.common.truth.Truth.assertThat;
import static com.google.common.truth.Truth.assertWithMessage;
import android.platform.test.annotations.RootPermissionTest;
-import android.virt.test.CommandRunner;
-import android.virt.test.VirtualizationTestCaseBase;
+import com.android.microdroid.test.CommandRunner;
+import com.android.microdroid.test.MicrodroidHostTestCaseBase;
import com.android.tradefed.log.LogUtil.CLog;
import com.android.tradefed.result.FileInputStreamSource;
import com.android.tradefed.result.LogDataType;
@@ -46,7 +45,7 @@
@RootPermissionTest
@RunWith(DeviceJUnit4ClassRunner.class)
-public final class ComposTestCase extends VirtualizationTestCaseBase {
+public final class ComposTestCase extends MicrodroidHostTestCaseBase {
// Binaries used in test. (These paths are valid both in host and Microdroid.)
private static final String ODREFRESH_BIN = "/apex/com.android.art/bin/odrefresh";
diff --git a/compos/verify/Android.bp b/compos/verify/Android.bp
index 5c74e4f..38edf1c 100644
--- a/compos/verify/Android.bp
+++ b/compos/verify/Android.bp
@@ -11,7 +11,7 @@
"libandroid_logger",
"libanyhow",
"libbinder_rs",
- "libclap",
+ "libclap_deprecated",
"libcompos_common",
"libcompos_verify_native_rust",
"liblog_rust",
diff --git a/compos/verify/verify.rs b/compos/verify/verify.rs
index 64ae75f..e6848c7 100644
--- a/compos/verify/verify.rs
+++ b/compos/verify/verify.rs
@@ -106,11 +106,15 @@
&idsig_manifest_apk,
&VmParameters { debug_mode, ..Default::default() },
)?;
- let service = vm_instance.get_service()?;
- let public_key = service.getPublicKey().context("Getting public key")?;
+ let service = vm_instance.connect_service()?;
+ let public_key = service.getPublicKey().context("Getting public key");
- if !compos_verify_native::verify(&public_key, &signature, &info) {
+ // Shut down the VM cleanly, giving time for any relevant logs to be written
+ let _ = service.quit(); // If this fails, the VM is probably dying anyway
+ vm_instance.wait_for_shutdown();
+
+ if !compos_verify_native::verify(&public_key?, &signature, &info) {
bail!("Signature verification failed");
}
diff --git a/tests/aidl/com/android/microdroid/testservice/IBenchmarkService.aidl b/tests/aidl/com/android/microdroid/testservice/IBenchmarkService.aidl
new file mode 100644
index 0000000..afcf989
--- /dev/null
+++ b/tests/aidl/com/android/microdroid/testservice/IBenchmarkService.aidl
@@ -0,0 +1,25 @@
+/*
+ * 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.microdroid.testservice;
+
+/** {@hide} */
+interface IBenchmarkService {
+ const int SERVICE_PORT = 5677;
+
+ /** Reads a file and returns the elapsed seconds for the reading. */
+ double readFile(String filename, long fileSizeBytes, boolean isRand);
+}
diff --git a/tests/benchmark/Android.bp b/tests/benchmark/Android.bp
index e6d5b83..2111620 100644
--- a/tests/benchmark/Android.bp
+++ b/tests/benchmark/Android.bp
@@ -12,6 +12,7 @@
"MicroroidDeviceTestHelper",
"androidx.test.runner",
"androidx.test.ext.junit",
+ "com.android.microdroid.testservice-java",
"truth-prebuilt",
],
libs: ["android.system.virtualmachine"],
@@ -24,4 +25,12 @@
cc_library_shared {
name: "MicrodroidBenchmarkNativeLib",
srcs: ["src/native/benchmarkbinary.cpp"],
+ shared_libs: [
+ "android.system.virtualmachineservice-ndk",
+ "com.android.microdroid.testservice-ndk",
+ "libbase",
+ "libbinder_ndk",
+ "libbinder_rpc_unstable",
+ "liblog",
+ ],
}
diff --git a/tests/benchmark/assets/vm_config.json b/tests/benchmark/assets/vm_config.json
index 67e3d21..e8f43e0 100644
--- a/tests/benchmark/assets/vm_config.json
+++ b/tests/benchmark/assets/vm_config.json
@@ -4,7 +4,10 @@
},
"task": {
"type": "microdroid_launcher",
- "command": "MicrodroidBenchmarkNativeLib.so"
+ "command": "MicrodroidBenchmarkNativeLib.so",
+ "args": [
+ "no_io"
+ ]
},
"export_tombstones": true
}
diff --git a/tests/benchmark/assets/vm_config_io.json b/tests/benchmark/assets/vm_config_io.json
new file mode 100644
index 0000000..1a5a9e5
--- /dev/null
+++ b/tests/benchmark/assets/vm_config_io.json
@@ -0,0 +1,18 @@
+{
+ "os": {
+ "name": "microdroid"
+ },
+ "task": {
+ "type": "microdroid_launcher",
+ "command": "MicrodroidBenchmarkNativeLib.so",
+ "args": [
+ "io"
+ ]
+ },
+ "apexes": [
+ {
+ "name": "com.android.virt"
+ }
+ ],
+ "export_tombstones": true
+}
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 e96f58b..7ee2d39 100644
--- a/tests/benchmark/src/java/com/android/microdroid/benchmark/MicrodroidBenchmarks.java
+++ b/tests/benchmark/src/java/com/android/microdroid/benchmark/MicrodroidBenchmarks.java
@@ -13,6 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
+
package com.android.microdroid.benchmark;
import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
@@ -22,14 +23,14 @@
import android.app.Instrumentation;
import android.os.Bundle;
-import android.os.SystemProperties;
+import android.system.virtualmachine.VirtualMachine;
import android.system.virtualmachine.VirtualMachineConfig;
import android.system.virtualmachine.VirtualMachineConfig.DebugLevel;
import android.system.virtualmachine.VirtualMachineException;
import com.android.microdroid.test.MicrodroidDeviceTestBase;
+import com.android.microdroid.testservice.IBenchmarkService;
-import org.junit.After;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
@@ -37,24 +38,23 @@
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
+import java.io.File;
import java.io.IOException;
+import java.nio.file.Files;
+import java.util.ArrayList;
+import java.util.List;
@RunWith(Parameterized.class)
public class MicrodroidBenchmarks extends MicrodroidDeviceTestBase {
private static final String TAG = "MicrodroidBenchmarks";
+ private static final int VIRTIO_BLK_TRIAL_COUNT = 5;
@Rule public Timeout globalTimeout = Timeout.seconds(300);
- private static final String KERNEL_VERSION = SystemProperties.get("ro.kernel.version");
-
- private boolean isCuttlefish() {
- String productName = SystemProperties.get("ro.product.name");
- return (null != productName)
- && (productName.startsWith("aosp_cf_x86")
- || productName.startsWith("aosp_cf_arm")
- || productName.startsWith("cf_x86")
- || productName.startsWith("cf_arm"));
- }
+ private static final String APEX_ETC_FS = "/apex/com.android.virt/etc/fs/";
+ private static final double SIZE_MB = 1024.0 * 1024.0;
+ private static final String MICRODROID_IMG_PREFIX = "microdroid_";
+ private static final String MICRODROID_IMG_SUFFIX = ".img";
@Parameterized.Parameters(name = "protectedVm={0}")
public static Object[] protectedVmConfigs() {
@@ -71,11 +71,6 @@
mInstrumentation = getInstrumentation();
}
- @After
- public void cleanup() throws VirtualMachineException {
- cleanupTestSetup();
- }
-
private boolean canBootMicrodroidWithMemory(int mem)
throws VirtualMachineException, InterruptedException, IOException {
final int trialCount = 5;
@@ -85,7 +80,7 @@
VirtualMachineConfig.Builder builder =
mInner.newVmConfigBuilder("assets/vm_config.json");
VirtualMachineConfig normalConfig =
- builder.debugLevel(DebugLevel.FULL).memoryMib(mem).build();
+ builder.debugLevel(DebugLevel.NONE).memoryMib(mem).build();
mInner.forceCreateNewVirtualMachine("test_vm_minimum_memory", normalConfig);
if (tryBootVm(TAG, "test_vm_minimum_memory").payloadStarted) return true;
@@ -159,4 +154,111 @@
bundle.putDouble("avf_perf/microdroid/boot_time_stdev_ms", stdev);
mInstrumentation.sendStatus(0, bundle);
}
+
+ @Test
+ public void testMicrodroidImageSize() throws IOException {
+ Bundle bundle = new Bundle();
+ for (File file : new File(APEX_ETC_FS).listFiles()) {
+ String name = file.getName();
+
+ if (!name.startsWith(MICRODROID_IMG_PREFIX) || !name.endsWith(MICRODROID_IMG_SUFFIX)) {
+ continue;
+ }
+
+ String base =
+ name.substring(
+ MICRODROID_IMG_PREFIX.length(),
+ name.length() - MICRODROID_IMG_SUFFIX.length());
+ String metric = "avf_perf/microdroid/img_size_" + base + "_MB" + "+" + name;
+ double size = Files.size(file.toPath()) / SIZE_MB;
+ bundle.putDouble(metric, size);
+ }
+ mInstrumentation.sendStatus(0, bundle);
+ }
+
+ @Test
+ public void testVirtioBlkSeqReadRate() throws Exception {
+ testVirtioBlkReadRate(/*isRand=*/ false);
+ }
+
+ @Test
+ public void testVirtioBlkRandReadRate() throws Exception {
+ testVirtioBlkReadRate(/*isRand=*/ true);
+ }
+
+ private void testVirtioBlkReadRate(boolean isRand) throws Exception {
+ VirtualMachineConfig.Builder builder =
+ mInner.newVmConfigBuilder("assets/vm_config_io.json");
+ VirtualMachineConfig config = builder.debugLevel(DebugLevel.FULL).build();
+ List<Double> readRates = new ArrayList<>();
+
+ for (int i = 0; i < VIRTIO_BLK_TRIAL_COUNT; ++i) {
+ String vmName = "test_vm_io_" + i;
+ mInner.forceCreateNewVirtualMachine(vmName, config);
+ VirtualMachine vm = mInner.getVirtualMachineManager().get(vmName);
+ VirtioBlkVmEventListener listener = new VirtioBlkVmEventListener(readRates, isRand);
+ listener.runToFinish(TAG, vm);
+ }
+ reportMetrics(readRates, isRand);
+ }
+
+ private void reportMetrics(List<Double> readRates, boolean isRand) {
+ double sum = 0;
+ for (double rate : readRates) {
+ sum += rate;
+ }
+ double mean = sum / readRates.size();
+ double sqSum = 0;
+ for (double rate : readRates) {
+ sqSum += (rate - mean) * (rate - mean);
+ }
+ double stdDev = Math.sqrt(sqSum / (readRates.size() - 1));
+
+ Bundle bundle = new Bundle();
+ String metricNamePrefix =
+ "avf_perf/virtio-blk/"
+ + (mProtectedVm ? "protected-vm/" : "unprotected-vm/")
+ + (isRand ? "rand_read_" : "seq_read_");
+ String unit = "_mb_per_sec";
+
+ bundle.putDouble(metricNamePrefix + "mean" + unit, mean);
+ bundle.putDouble(metricNamePrefix + "std" + unit, stdDev);
+ mInstrumentation.sendStatus(0, bundle);
+ }
+
+ private static class VirtioBlkVmEventListener extends VmEventListener {
+ private static final String FILENAME = APEX_ETC_FS + "microdroid_super.img";
+
+ private final long mFileSizeBytes;
+ private final List<Double> mReadRates;
+ private final boolean mIsRand;
+
+ VirtioBlkVmEventListener(List<Double> readRates, boolean isRand) {
+ File file = new File(FILENAME);
+ try {
+ mFileSizeBytes = Files.size(file.toPath());
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ assertThat(mFileSizeBytes).isGreaterThan((long) SIZE_MB);
+ mReadRates = readRates;
+ mIsRand = isRand;
+ }
+
+ @Override
+ public void onPayloadReady(VirtualMachine vm) {
+ try {
+ IBenchmarkService benchmarkService =
+ IBenchmarkService.Stub.asInterface(
+ vm.connectToVsockServer(IBenchmarkService.SERVICE_PORT).get());
+ double elapsedSeconds =
+ benchmarkService.readFile(FILENAME, mFileSizeBytes, mIsRand);
+ double fileSizeMb = mFileSizeBytes / SIZE_MB;
+ mReadRates.add(fileSizeMb / elapsedSeconds);
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ forceStop(vm);
+ }
+ }
}
diff --git a/tests/benchmark/src/native/benchmarkbinary.cpp b/tests/benchmark/src/native/benchmarkbinary.cpp
index b5ec49c..6a5b764 100644
--- a/tests/benchmark/src/native/benchmarkbinary.cpp
+++ b/tests/benchmark/src/native/benchmarkbinary.cpp
@@ -13,11 +13,123 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
+
+#include <aidl/android/system/virtualmachineservice/IVirtualMachineService.h>
+#include <aidl/com/android/microdroid/testservice/BnBenchmarkService.h>
+#include <android-base/result.h>
+#include <android-base/unique_fd.h>
+#include <fcntl.h>
+#include <linux/vm_sockets.h>
+#include <stdio.h>
#include <unistd.h>
-extern "C" int android_native_main([[maybe_unused]] int argc, [[maybe_unused]] char* argv[]) {
- // do nothing for now; just leave it alive. good night.
- for (;;) {
- sleep(1000);
+#include <binder_rpc_unstable.hpp>
+#include <chrono>
+#include <random>
+#include <string>
+
+#include "android-base/logging.h"
+
+using aidl::android::system::virtualmachineservice::IVirtualMachineService;
+using android::base::ErrnoError;
+using android::base::Error;
+using android::base::Result;
+using android::base::unique_fd;
+
+namespace {
+constexpr uint64_t kBlockSizeBytes = 4096;
+
+class IOBenchmarkService : public aidl::com::android::microdroid::testservice::BnBenchmarkService {
+public:
+ ndk::ScopedAStatus readFile(const std::string& filename, int64_t fileSizeBytes, bool isRand,
+ double* out) override {
+ if (auto res = read_file(filename, fileSizeBytes, isRand); res.ok()) {
+ *out = res.value();
+ } else {
+ std::stringstream error;
+ error << "Failed reading file: " << res.error();
+ return ndk::ScopedAStatus::fromExceptionCodeWithMessage(EX_ILLEGAL_ARGUMENT,
+ error.str().c_str());
+ }
+ return ndk::ScopedAStatus::ok();
}
+
+private:
+ /** Returns the elapsed seconds for reading the file. */
+ Result<double> read_file(const std::string& filename, int64_t fileSizeBytes, bool is_rand) {
+ const int64_t block_count = fileSizeBytes / kBlockSizeBytes;
+ std::vector<uint64_t> offsets;
+ if (is_rand) {
+ std::mt19937 rd{std::random_device{}()};
+ offsets.reserve(block_count);
+ for (auto i = 0; i < block_count; ++i) offsets.push_back(i * kBlockSizeBytes);
+ std::shuffle(offsets.begin(), offsets.end(), rd);
+ }
+ char buf[kBlockSizeBytes];
+
+ clock_t start = clock();
+ unique_fd fd(open(filename.c_str(), O_RDONLY));
+ if (fd.get() == -1) {
+ return ErrnoError() << "Read: opening " << filename << " failed";
+ }
+ for (auto i = 0; i < block_count; ++i) {
+ if (is_rand) {
+ if (lseek(fd.get(), offsets[i], SEEK_SET) == -1) {
+ return ErrnoError() << "failed to lseek";
+ }
+ }
+ auto bytes = read(fd.get(), buf, kBlockSizeBytes);
+ if (bytes == 0) {
+ return Error() << "unexpected end of file";
+ } else if (bytes == -1) {
+ return ErrnoError() << "failed to read";
+ }
+ }
+ return {((double)clock() - start) / CLOCKS_PER_SEC};
+ }
+};
+
+Result<void> run_io_benchmark_tests() {
+ auto test_service = ndk::SharedRefBase::make<IOBenchmarkService>();
+ auto callback = []([[maybe_unused]] void* param) {
+ // Tell microdroid_manager that we're ready.
+ // If we can't, abort in order to fail fast - the host won't proceed without
+ // receiving the onReady signal.
+ ndk::SpAIBinder binder(
+ RpcClient(VMADDR_CID_HOST, IVirtualMachineService::VM_BINDER_SERVICE_PORT));
+ auto vm_service = IVirtualMachineService::fromBinder(binder);
+ if (vm_service == nullptr) {
+ LOG(ERROR) << "failed to connect VirtualMachineService\n";
+ abort();
+ }
+ if (auto status = vm_service->notifyPayloadReady(); !status.isOk()) {
+ LOG(ERROR) << "failed to notify payload ready to virtualizationservice: "
+ << status.getDescription();
+ abort();
+ }
+ };
+
+ if (!RunRpcServerCallback(test_service->asBinder().get(), test_service->SERVICE_PORT, callback,
+ nullptr)) {
+ return Error() << "RPC Server failed to run";
+ }
+ return {};
+}
+} // Anonymous namespace
+
+extern "C" int android_native_main([[maybe_unused]] int argc, char* argv[]) {
+ if (strcmp(argv[1], "no_io") == 0) {
+ // do nothing for now; just leave it alive. good night.
+ for (;;) {
+ sleep(1000);
+ }
+ } else if (strcmp(argv[1], "io") == 0) {
+ if (auto res = run_io_benchmark_tests(); res.ok()) {
+ return 0;
+ } else {
+ LOG(ERROR) << "IO benchmark test failed: " << res.error() << "\n";
+ return 1;
+ }
+ }
+ return 0;
}
diff --git a/tests/helper/Android.bp b/tests/helper/Android.bp
index 679fbfe..e7760e2 100644
--- a/tests/helper/Android.bp
+++ b/tests/helper/Android.bp
@@ -3,11 +3,18 @@
}
java_library_static {
+ name: "VirtualizationTestHelper",
+ srcs: ["src/java/com/android/virt/**/*.java"],
+ host_supported: true,
+}
+
+java_library_static {
name: "MicroroidDeviceTestHelper",
- srcs: ["src/java/**/*.java"],
+ srcs: ["src/java/com/android/microdroid/**/*.java"],
static_libs: [
"androidx.test.runner",
"androidx.test.ext.junit",
+ "VirtualizationTestHelper",
"truth-prebuilt",
],
libs: ["android.system.virtualmachine"],
diff --git a/tests/helper/src/java/com/android/microdroid/test/MicrodroidDeviceTestBase.java b/tests/helper/src/java/com/android/microdroid/test/MicrodroidDeviceTestBase.java
index 87c53a7..1f57634 100644
--- a/tests/helper/src/java/com/android/microdroid/test/MicrodroidDeviceTestBase.java
+++ b/tests/helper/src/java/com/android/microdroid/test/MicrodroidDeviceTestBase.java
@@ -21,6 +21,7 @@
import android.content.Context;
import android.os.ParcelFileDescriptor;
+import android.os.SystemProperties;
import android.sysprop.HypervisorProperties;
import android.system.virtualizationservice.DeathReason;
import android.system.virtualmachine.VirtualMachine;
@@ -33,6 +34,8 @@
import androidx.annotation.CallSuper;
import androidx.test.core.app.ApplicationProvider;
+import com.android.virt.VirtualizationTestHelper;
+
import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;
@@ -60,13 +63,15 @@
}).start();
}
- private boolean mPkvmSupported;
+ public static boolean isCuttlefish() {
+ return VirtualizationTestHelper.isCuttlefish(SystemProperties.get("ro.product.name"));
+ }
// TODO(b/220920264): remove Inner class; this is a hack to hide virt APEX types
protected static class Inner {
- private boolean mProtectedVm;
- private Context mContext;
- private VirtualMachineManager mVmm;
+ private final boolean mProtectedVm;
+ private final Context mContext;
+ private final VirtualMachineManager mVmm;
public Inner(Context context, boolean protectedVm, VirtualMachineManager vmm) {
mProtectedVm = protectedVm;
@@ -114,7 +119,6 @@
// classes, check the existence of a class in the package and skip this test if not exist.
try {
Class.forName("android.system.virtualmachine.VirtualMachineManager");
- mPkvmSupported = true;
} catch (ClassNotFoundException e) {
assumeNoException(e);
return;
@@ -132,16 +136,10 @@
mInner = new Inner(context, protectedVm, VirtualMachineManager.getInstance(context));
}
- public void cleanupTestSetup() throws VirtualMachineException {
- if (!mPkvmSupported) {
- return;
- }
- }
-
protected abstract static class VmEventListener implements VirtualMachineCallback {
private ExecutorService mExecutorService = Executors.newSingleThreadExecutor();
- void runToFinish(String logTag, VirtualMachine vm)
+ public void runToFinish(String logTag, VirtualMachine vm)
throws VirtualMachineException, InterruptedException {
vm.setCallback(mExecutorService, this);
vm.run();
@@ -150,7 +148,7 @@
mExecutorService.awaitTermination(300, TimeUnit.SECONDS);
}
- void forceStop(VirtualMachine vm) {
+ protected void forceStop(VirtualMachine vm) {
try {
vm.clearCallback();
vm.stop();
diff --git a/tests/helper/src/java/com/android/virt/VirtualizationTestHelper.java b/tests/helper/src/java/com/android/virt/VirtualizationTestHelper.java
new file mode 100644
index 0000000..c6c0ad0
--- /dev/null
+++ b/tests/helper/src/java/com/android/virt/VirtualizationTestHelper.java
@@ -0,0 +1,26 @@
+/*
+ * 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 com.android.virt;
+
+public abstract class VirtualizationTestHelper {
+ public static boolean isCuttlefish(String productName) {
+ return (null != productName)
+ && (productName.startsWith("aosp_cf_x86")
+ || productName.startsWith("aosp_cf_arm")
+ || productName.startsWith("cf_x86")
+ || productName.startsWith("cf_arm"));
+ }
+}
diff --git a/tests/hostside/Android.bp b/tests/hostside/Android.bp
index dfc2f2b..24288ee 100644
--- a/tests/hostside/Android.bp
+++ b/tests/hostside/Android.bp
@@ -13,7 +13,7 @@
"tradefed",
],
static_libs: [
- "VirtualizationTestHelper",
+ "MicrodroidHostTestHelper",
],
per_testcase_directory: true,
data: [
diff --git a/tests/hostside/helper/Android.bp b/tests/hostside/helper/Android.bp
index 6ab02f8..af88bb6 100644
--- a/tests/hostside/helper/Android.bp
+++ b/tests/hostside/helper/Android.bp
@@ -3,11 +3,14 @@
}
java_library_host {
- name: "VirtualizationTestHelper",
+ name: "MicrodroidHostTestHelper",
srcs: ["java/**/*.java"],
libs: [
"compatibility-tradefed",
"tradefed",
"truth-prebuilt",
],
+ static_libs: [
+ "VirtualizationTestHelper",
+ ],
}
diff --git a/tests/hostside/helper/java/android/virt/test/CommandResultSubject.java b/tests/hostside/helper/java/com/android/microdroid/test/CommandResultSubject.java
similarity index 98%
rename from tests/hostside/helper/java/android/virt/test/CommandResultSubject.java
rename to tests/hostside/helper/java/com/android/microdroid/test/CommandResultSubject.java
index 5312f5a..2271325 100644
--- a/tests/hostside/helper/java/android/virt/test/CommandResultSubject.java
+++ b/tests/hostside/helper/java/com/android/microdroid/test/CommandResultSubject.java
@@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package android.virt.test;
+package com.android.microdroid.test;
import static com.google.common.truth.Truth.assertAbout;
diff --git a/tests/hostside/helper/java/android/virt/test/CommandRunner.java b/tests/hostside/helper/java/com/android/microdroid/test/CommandRunner.java
similarity index 98%
rename from tests/hostside/helper/java/android/virt/test/CommandRunner.java
rename to tests/hostside/helper/java/com/android/microdroid/test/CommandRunner.java
index f1bdb8e..2f9b3df 100644
--- a/tests/hostside/helper/java/android/virt/test/CommandRunner.java
+++ b/tests/hostside/helper/java/com/android/microdroid/test/CommandRunner.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package android.virt.test;
+package com.android.microdroid.test;
import static org.hamcrest.CoreMatchers.is;
import static org.junit.Assert.fail;
diff --git a/tests/hostside/helper/java/android/virt/test/LogArchiver.java b/tests/hostside/helper/java/com/android/microdroid/test/LogArchiver.java
similarity index 97%
rename from tests/hostside/helper/java/android/virt/test/LogArchiver.java
rename to tests/hostside/helper/java/com/android/microdroid/test/LogArchiver.java
index b6cae95..be638ab 100644
--- a/tests/hostside/helper/java/android/virt/test/LogArchiver.java
+++ b/tests/hostside/helper/java/com/android/microdroid/test/LogArchiver.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package android.virt.test;
+package com.android.microdroid.test;
import static com.android.tradefed.testtype.DeviceJUnit4ClassRunner.TestLogData;
diff --git a/tests/hostside/helper/java/android/virt/test/VirtualizationTestCaseBase.java b/tests/hostside/helper/java/com/android/microdroid/test/MicrodroidHostTestCaseBase.java
similarity index 96%
rename from tests/hostside/helper/java/android/virt/test/VirtualizationTestCaseBase.java
rename to tests/hostside/helper/java/com/android/microdroid/test/MicrodroidHostTestCaseBase.java
index 10d1e8e..0712323 100644
--- a/tests/hostside/helper/java/android/virt/test/VirtualizationTestCaseBase.java
+++ b/tests/hostside/helper/java/com/android/microdroid/test/MicrodroidHostTestCaseBase.java
@@ -14,11 +14,10 @@
* limitations under the License.
*/
-package android.virt.test;
+package com.android.microdroid.test;
-import static android.virt.test.CommandResultSubject.assertThat;
-import static android.virt.test.CommandResultSubject.command_results;
-
+import static com.android.microdroid.test.CommandResultSubject.assertThat;
+import static com.android.microdroid.test.CommandResultSubject.command_results;
import static com.android.tradefed.testtype.DeviceJUnit4ClassRunner.TestLogData;
import static com.google.common.truth.Truth.assertWithMessage;
@@ -35,6 +34,7 @@
import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test;
import com.android.tradefed.util.CommandResult;
import com.android.tradefed.util.RunUtil;
+import com.android.virt.VirtualizationTestHelper;
import java.io.File;
import java.io.FileNotFoundException;
@@ -46,7 +46,7 @@
import java.util.regex.Matcher;
import java.util.regex.Pattern;
-public abstract class VirtualizationTestCaseBase extends BaseHostJUnit4Test {
+public abstract class MicrodroidHostTestCaseBase extends BaseHostJUnit4Test {
protected static final String TEST_ROOT = "/data/local/tmp/virt/";
protected static final String VIRT_APEX = "/apex/com.android.virt/";
protected static final String LOG_PATH = TEST_ROOT + "log.txt";
@@ -92,6 +92,10 @@
android.tryRun("rm", "-rf", "/data/misc/virtualizationservice/*");
}
+ protected boolean isCuttlefish() throws Exception {
+ return VirtualizationTestHelper.isCuttlefish(getDevice().getProperty("ro.product.name"));
+ }
+
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/android/virt/test/MicrodroidTestCase.java b/tests/hostside/java/com/android/microdroid/test/MicrodroidTestCase.java
similarity index 88%
rename from tests/hostside/java/android/virt/test/MicrodroidTestCase.java
rename to tests/hostside/java/com/android/microdroid/test/MicrodroidTestCase.java
index d25868e..c4ea80a 100644
--- a/tests/hostside/java/android/virt/test/MicrodroidTestCase.java
+++ b/tests/hostside/java/com/android/microdroid/test/MicrodroidTestCase.java
@@ -14,10 +14,9 @@
* limitations under the License.
*/
-package android.virt.test;
+package com.android.microdroid.test;
-import static android.virt.test.CommandResultSubject.command_results;
-
+import static com.android.microdroid.test.CommandResultSubject.command_results;
import static com.android.tradefed.testtype.DeviceJUnit4ClassRunner.TestLogData;
import static com.google.common.truth.Truth.assertThat;
@@ -27,7 +26,6 @@
import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.CoreMatchers.nullValue;
import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
@@ -63,7 +61,7 @@
import java.util.regex.Pattern;
@RunWith(DeviceJUnit4ClassRunner.class)
-public class MicrodroidTestCase extends VirtualizationTestCaseBase {
+public class MicrodroidTestCase extends MicrodroidHostTestCaseBase {
private static final String APK_NAME = "MicrodroidTestApp.apk";
private static final String PACKAGE_NAME = "com.android.microdroid.test";
private static final String SHELL_PACKAGE_NAME = "com.android.shell";
@@ -78,16 +76,6 @@
@Rule public TestLogData mTestLogs = new TestLogData();
@Rule public TestName mTestName = new TestName();
- // TODO(b/176805428): remove this
- private boolean isCuttlefish() throws Exception {
- String productName = getDevice().getProperty("ro.product.name");
- return (null != productName)
- && (productName.startsWith("aosp_cf_x86")
- || productName.startsWith("aosp_cf_arm")
- || productName.startsWith("cf_x86")
- || productName.startsWith("cf_arm"));
- }
-
private int minMemorySize() throws DeviceNotAvailableException {
CommandRunner android = new CommandRunner(getDevice());
String abi = android.run("getprop", "ro.product.cpu.abi");
@@ -404,53 +392,6 @@
}
@Test
- public void testTombstonesAreBeingForwarded() throws Exception {
- // This test requires rooting. Skip on user builds where rooting is impossible.
- final String buildType = getDevice().getProperty("ro.build.type");
- assumeTrue("userdebug".equals(buildType) || "eng".equals(buildType));
-
- // Note this test relies on logcat values being printed by tombstone_transmit on
- // and the reeceiver on host (virtualization_service)
- final String configPath = "assets/vm_config.json"; // path inside the APK
- final String cid =
- startMicrodroid(
- getDevice(),
- getBuild(),
- APK_NAME,
- PACKAGE_NAME,
- configPath,
- /* debug */ true,
- minMemorySize(),
- Optional.of(NUM_VCPUS),
- Optional.of(CPU_AFFINITY));
- adbConnectToMicrodroid(getDevice(), cid);
- waitForBootComplete();
- runOnMicrodroidRetryingOnFailure(
- MICRODROID_COMMAND_TIMEOUT_MILLIS, MICRODROID_ADB_CONNECT_MAX_ATTEMPTS, "true");
- // We need root permission to write to /data/tombstones/
- rootMicrodroid();
- // Write a test tombstone file in /data/tombstones
- runOnMicrodroid("echo -n \'Test tombstone in VM with 34 bytes\'"
- + "> /data/tombstones/transmit.txt");
- // check if the tombstone have been tranferred from VM. This is a bit flaky - increasing
- // timeout to 30s can result in SIGKILL inside microdroid due to logcat memory issue
- CommandRunner android = new CommandRunner(getDevice());
- android.runWithTimeout(
- 15000,
- "grep",
- "-m",
- "1",
- "'tombstone_transmit.microdroid:.*data/tombstones/transmit.txt'",
- LOG_PATH);
-
- // Confirm that tombstone is received (from host logcat)
- assertNotEquals(runOnHost("adb", "-s", getDevice().getSerialNumber(),
- "logcat", "-d", "-e",
- "Received 34 bytes from guest & wrote to tombstone file.*"),
- "");
- }
-
- @Test
public void testMicrodroidBoots() throws Exception {
final String configPath = "assets/vm_config.json"; // path inside the APK
final String cid =
diff --git a/tests/testapk/src/java/com/android/microdroid/test/MicrodroidTests.java b/tests/testapk/src/java/com/android/microdroid/test/MicrodroidTests.java
index 448f150..b429e4d 100644
--- a/tests/testapk/src/java/com/android/microdroid/test/MicrodroidTests.java
+++ b/tests/testapk/src/java/com/android/microdroid/test/MicrodroidTests.java
@@ -32,7 +32,6 @@
import com.android.microdroid.testservice.ITestService;
-import org.junit.After;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
@@ -77,11 +76,6 @@
prepareTestSetup(mProtectedVm);
}
- @After
- public void cleanup() throws VirtualMachineException {
- cleanupTestSetup();
- }
-
private static final int MIN_MEM_ARM64 = 150;
private static final int MIN_MEM_X86_64 = 196;
diff --git a/tests/vsock_guest.cc b/tests/vsock_guest.cc
index 7a72e11..884c8a4 100644
--- a/tests/vsock_guest.cc
+++ b/tests/vsock_guest.cc
@@ -62,6 +62,16 @@
PLOG(ERROR) << "WriteStringToFd";
return EXIT_FAILURE;
}
+ shutdown(fd.get(), SHUT_WR); // close socket for writing
+
+ // Must not shut down until the server ACKs the message. Shutting down
+ // the VM would otherwise terminate the VMM and reset the server's socket.
+ LOG(INFO) << "Waiting for ACK from the server...";
+ if (!ReadFdToString(fd, &msg)) {
+ PLOG(ERROR) << "ReadFdToString";
+ return EXIT_FAILURE;
+ }
+ shutdown(fd.get(), SHUT_RD); // close socket for reading
LOG(INFO) << "Exiting...";
return EXIT_SUCCESS;
diff --git a/tests/vsock_test.cc b/tests/vsock_test.cc
index 0fc451d..1460660 100644
--- a/tests/vsock_test.cc
+++ b/tests/vsock_test.cc
@@ -48,6 +48,7 @@
static constexpr const char kVmInitrdPath[] = "/data/local/tmp/virt-test/initramfs";
static constexpr const char kVmParams[] = "rdinit=/bin/init bin/vsock_client 2 45678 HelloWorld";
static constexpr const char kTestMessage[] = "HelloWorld";
+static constexpr const char kAckMessage[] = "ACK";
static constexpr const char kPlatformVersion[] = "~1.0";
/** Returns true if the kernel supports unprotected VMs. */
@@ -108,9 +109,13 @@
LOG(INFO) << "Reading message from the client...";
std::string msg;
- ASSERT_TRUE(ReadFdToString(client_fd, &msg));
-
+ ASSERT_TRUE(ReadFdToString(client_fd, &msg)) << strerror(errno);
LOG(INFO) << "Received message: " << msg;
+
+ // The client is waiting for a response to signal it can shut down.
+ LOG(INFO) << "Replying with '" << kAckMessage << "'...";
+ ASSERT_TRUE(WriteStringToFd(kAckMessage, client_fd));
+
ASSERT_EQ(msg, kTestMessage);
}
diff --git a/vmbase/Android.bp b/vmbase/Android.bp
index 6400ccd..fb7f6a6 100644
--- a/vmbase/Android.bp
+++ b/vmbase/Android.bp
@@ -43,9 +43,9 @@
cc_defaults {
name: "vmbase_elf_defaults",
defaults: ["vmbase_cc_defaults"],
- system_shared_libs: ["libc"],
static_executable: true,
static_libs: [
+ "libarm-optimized-routines-mem",
"libvmbase_entry",
],
}
diff --git a/vmclient/src/errors.rs b/vmclient/src/errors.rs
index 532706d..43db7f9 100644
--- a/vmclient/src/errors.rs
+++ b/vmclient/src/errors.rs
@@ -17,7 +17,7 @@
use thiserror::Error;
/// An error while waiting for a VM to do something.
-#[derive(Clone, Debug, Error)]
+#[derive(Clone, Debug, Eq, Error, PartialEq)]
pub enum VmWaitError {
/// Timed out waiting for the VM.
#[error("Timed out waiting for VM.")]
@@ -33,9 +33,9 @@
Finished,
}
-/// An error connection to a VM RPC Binder service.
-#[derive(Clone, Debug, Error)]
-pub enum GetServiceError {
+/// An error connecting to a VM RPC Binder service.
+#[derive(Clone, Debug, Eq, Error, PartialEq)]
+pub enum ConnectServiceError {
/// The RPC binder connection failed.
#[error("Vsock connection to RPC binder failed.")]
ConnectionFailed,
diff --git a/vmclient/src/lib.rs b/vmclient/src/lib.rs
index 867c3a7..9b5b8dd 100644
--- a/vmclient/src/lib.rs
+++ b/vmclient/src/lib.rs
@@ -20,7 +20,7 @@
mod sync;
pub use crate::death_reason::DeathReason;
-pub use crate::errors::{GetServiceError, VmWaitError};
+pub use crate::errors::{ConnectServiceError, VmWaitError};
use crate::{rpc_binder::VsockFactory, sync::Monitor};
use android_system_virtualizationservice::{
aidl::android::system::virtualizationservice::{
@@ -111,6 +111,15 @@
self.state.wait_while(|state| state.death_reason.is_none()).unwrap().death_reason.unwrap()
}
+ /// Blocks until the VM or the VirtualizationService itself dies, or the given timeout expires.
+ /// Returns the reason why it died if it did so.
+ pub fn wait_for_death_with_timeout(&self, timeout: Duration) -> Option<DeathReason> {
+ let (state, _timeout_result) =
+ self.state.wait_timeout_while(timeout, |state| state.death_reason.is_none()).unwrap();
+ // We don't care if it timed out - we just return the reason if there now is one
+ state.death_reason
+ }
+
/// Waits until the VM reports that it is ready.
///
/// Returns an error if the VM dies first, or the `timeout` elapses before the VM is ready.
@@ -133,15 +142,15 @@
}
/// Tries to connect to an RPC Binder service provided by the VM on the given vsock port.
- pub fn get_service<T: FromIBinder + ?Sized>(
+ pub fn connect_service<T: FromIBinder + ?Sized>(
&self,
port: u32,
- ) -> Result<Strong<T>, GetServiceError> {
+ ) -> Result<Strong<T>, ConnectServiceError> {
let mut vsock_factory = VsockFactory::new(&*self.vm, port);
let ibinder = vsock_factory.connect_rpc_client()?;
- FromIBinder::try_from(ibinder).map_err(GetServiceError::WrongServiceType)
+ FromIBinder::try_from(ibinder).map_err(ConnectServiceError::WrongServiceType)
}
/// Get ramdump
diff --git a/vmclient/src/rpc_binder.rs b/vmclient/src/rpc_binder.rs
index fee643f..7c2992b 100644
--- a/vmclient/src/rpc_binder.rs
+++ b/vmclient/src/rpc_binder.rs
@@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-use crate::errors::GetServiceError;
+use crate::errors::ConnectServiceError;
use android_system_virtualizationservice::{
aidl::android::system::virtualizationservice::IVirtualMachine::IVirtualMachine,
};
@@ -30,7 +30,7 @@
Self { vm, port }
}
- pub fn connect_rpc_client(&mut self) -> Result<binder::SpIBinder, GetServiceError> {
+ pub fn connect_rpc_client(&mut self) -> Result<binder::SpIBinder, ConnectServiceError> {
let param = self.as_void_ptr();
unsafe {
@@ -41,7 +41,7 @@
let binder =
binder_rpc_unstable_bindgen::RpcPreconnectedClient(Some(Self::request_fd), param)
as *mut AIBinder;
- new_spibinder(binder).ok_or(GetServiceError::ConnectionFailed)
+ new_spibinder(binder).ok_or(ConnectServiceError::ConnectionFailed)
}
}
diff --git a/zipfuse/Android.bp b/zipfuse/Android.bp
index e10fc31..d07a8e1 100644
--- a/zipfuse/Android.bp
+++ b/zipfuse/Android.bp
@@ -10,7 +10,7 @@
prefer_rlib: true,
rustlibs: [
"libanyhow",
- "libclap",
+ "libclap_deprecated",
"libfuse_rust",
"liblibc",
"libzip",