benchmark:Measure virtio-blk read rate

Bug: 236123069
Test: atest MicrodroidBenchmarks
Change-Id: Ia46ea8d1b4425fa280171adcce7f82a76f1a329e
This commit is contained in:
Alice Wang 2022-07-25 14:44:13 +00:00
parent b05832f408
commit 246108fdf9
7 changed files with 271 additions and 10 deletions

View File

@ -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);
}

View File

@ -12,6 +12,7 @@ android_test {
"MicroroidDeviceTestHelper",
"androidx.test.runner",
"androidx.test.ext.junit",
"com.android.microdroid.testservice-java",
"truth-prebuilt",
],
libs: ["android.system.virtualmachine"],
@ -24,4 +25,12 @@ android_test {
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",
],
}

View File

@ -4,7 +4,10 @@
},
"task": {
"type": "microdroid_launcher",
"command": "MicrodroidBenchmarkNativeLib.so"
"command": "MicrodroidBenchmarkNativeLib.so",
"args": [
"no_io"
]
},
"export_tombstones": true
}

View File

@ -0,0 +1,18 @@
{
"os": {
"name": "microdroid"
},
"task": {
"type": "microdroid_launcher",
"command": "MicrodroidBenchmarkNativeLib.so",
"args": [
"io"
]
},
"apexes": [
{
"name": "com.android.virt"
}
],
"export_tombstones": true
}

View File

@ -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 static com.google.common.truth.TruthJUnit.assume;
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 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);
@ -159,12 +165,100 @@ public class MicrodroidBenchmarks extends MicrodroidDeviceTestBase {
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);
}
}
}

View File

@ -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;
}

View File

@ -139,7 +139,7 @@ public abstract class MicrodroidDeviceTestBase {
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 @@ public abstract class MicrodroidDeviceTestBase {
mExecutorService.awaitTermination(300, TimeUnit.SECONDS);
}
void forceStop(VirtualMachine vm) {
protected void forceStop(VirtualMachine vm) {
try {
vm.clearCallback();
vm.stop();