benchmark:Measure virtio-blk read rate
Bug: 236123069
Test: atest MicrodroidBenchmarks
Change-Id: Ia46ea8d1b4425fa280171adcce7f82a76f1a329e
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 3731e13..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,11 +23,13 @@
import android.app.Instrumentation;
import android.os.Bundle;
+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.Before;
import org.junit.Rule;
@@ -38,10 +41,13 @@
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);
@@ -159,12 +165,100 @@
continue;
}
- String base = name.substring(MICRODROID_IMG_PREFIX.length(),
- name.length() - MICRODROID_IMG_SUFFIX.length());
- String metric = "avf_perf/microdroid/img_size_" + base + "_MB";
+ 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/src/java/com/android/microdroid/test/MicrodroidDeviceTestBase.java b/tests/helper/src/java/com/android/microdroid/test/MicrodroidDeviceTestBase.java
index a2c43d7..1f57634 100644
--- a/tests/helper/src/java/com/android/microdroid/test/MicrodroidDeviceTestBase.java
+++ b/tests/helper/src/java/com/android/microdroid/test/MicrodroidDeviceTestBase.java
@@ -139,7 +139,7 @@
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();
@@ -148,7 +148,7 @@
mExecutorService.awaitTermination(300, TimeUnit.SECONDS);
}
- void forceStop(VirtualMachine vm) {
+ protected void forceStop(VirtualMachine vm) {
try {
vm.clearCallback();
vm.stop();