[benchmarks][authfs] Run AuthFsBenchmarks in postsubmit
Test: atest AuthFsHostTest AuthFsBenchmarks
Bug: 254050475
Change-Id: I2ff801532c73a8b7dcde89c0d87bdf339c447ab4
diff --git a/authfs/tests/benchmarks/Android.bp b/authfs/tests/benchmarks/Android.bp
new file mode 100644
index 0000000..b198328
--- /dev/null
+++ b/authfs/tests/benchmarks/Android.bp
@@ -0,0 +1,37 @@
+package {
+ default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+java_test_host {
+ name: "AuthFsBenchmarks",
+ srcs: ["src/java/com/android/fs/benchmarks/*.java"],
+ libs: [
+ "tradefed",
+ ],
+ static_libs: [
+ "AuthFsHostTestCommon",
+ "MicrodroidHostTestHelper",
+ ],
+ test_suites: ["general-tests"],
+ data_device_bins_first: [
+ "open_then_run",
+ "fsverity",
+ ],
+ per_testcase_directory: true,
+ data: [
+ ":authfs_test_files",
+ ":CtsApkVerityTestPrebuiltFiles",
+ ":MicrodroidTestApp",
+ ":measure_io",
+ ],
+}
+
+cc_binary {
+ name: "measure_io",
+ srcs: [
+ "src/measure_io.cpp",
+ ],
+ shared_libs: [
+ "libbase",
+ ],
+}
diff --git a/authfs/tests/benchmarks/AndroidTest.xml b/authfs/tests/benchmarks/AndroidTest.xml
new file mode 100644
index 0000000..7ca3a80
--- /dev/null
+++ b/authfs/tests/benchmarks/AndroidTest.xml
@@ -0,0 +1,64 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+
+<configuration description="Config for authfs tests">
+ <!-- Need root to start virtualizationservice -->
+ <target_preparer class="com.android.tradefed.targetprep.RootTargetPreparer"/>
+
+ <!-- Still need to define SELinux policy for authfs and fd_server properly. -->
+ <target_preparer class="com.android.tradefed.targetprep.DisableSELinuxTargetPreparer"/>
+
+ <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
+ <option name="throw-if-cmd-fail" value="true" />
+ <!-- Prepare test directories. -->
+ <option name="run-command" value="mkdir -p /data/local/tmp/authfs/mnt" />
+ <option name="teardown-command" value="rm -rf /data/local/tmp/authfs" />
+ </target_preparer>
+
+ <target_preparer class="com.android.tradefed.targetprep.PushFilePreparer">
+ <option name="cleanup" value="true" />
+ <option name="abort-on-push-failure" value="true" />
+
+ <!-- Test executable -->
+ <option name="push-file" key="open_then_run" value="/data/local/tmp/open_then_run" />
+ <option name="push-file" key="fsverity" value="/data/local/tmp/fsverity" />
+
+ <!-- Test data files -->
+ <option name="push-file" key="cert.der" value="/data/local/tmp/authfs/cert.der" />
+ <option name="push-file" key="input.4m" value="/data/local/tmp/authfs/input.4m" />
+ <option name="push-file" key="input.4m.fsv_meta"
+ value="/data/local/tmp/authfs/input.4m.fsv_meta" />
+
+ <!-- Just pick a file with signature that can be trused on the device. -->
+ <option name="push-file" key="CtsApkVerityTestAppPrebuilt.apk"
+ value="/data/local/tmp/authfs/input.apk" />
+ <option name="push-file" key="CtsApkVerityTestAppPrebuilt.apk.fsv_sig"
+ value="/data/local/tmp/authfs/input.apk.fsv_sig" />
+ </target_preparer>
+
+ <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
+ <option name="throw-if-cmd-fail" value="true" />
+ <!-- Now that the files are pushed to the device, enable fs-verity for the targeting file.
+ It works because the signature is trusted on all CTS compatible devices. -->
+ <option name="run-command"
+ value="cd /data/local/tmp/authfs;
+ ../fsverity enable input.apk --signature=input.apk.fsv_sig" />
+ </target_preparer>
+
+ <test class="com.android.compatibility.common.tradefed.testtype.JarHostTest" >
+ <option name="jar" value="AuthFsBenchmarks.jar" />
+ </test>
+</configuration>
diff --git a/authfs/tests/benchmarks/src/java/com/android/fs/benchmarks/AuthFsBenchmarks.java b/authfs/tests/benchmarks/src/java/com/android/fs/benchmarks/AuthFsBenchmarks.java
new file mode 100644
index 0000000..5e9073a
--- /dev/null
+++ b/authfs/tests/benchmarks/src/java/com/android/fs/benchmarks/AuthFsBenchmarks.java
@@ -0,0 +1,170 @@
+/*
+ * 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.fs.benchmarks;
+
+import static com.android.tradefed.testtype.DeviceJUnit4ClassRunner.TestMetrics;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.platform.test.annotations.RootPermissionTest;
+
+import com.android.fs.common.AuthFsTestRule;
+import com.android.microdroid.test.common.MetricsProcessor;
+import com.android.tradefed.device.DeviceNotAvailableException;
+import com.android.tradefed.invoker.TestInformation;
+import com.android.tradefed.metrics.proto.MetricMeasurement.DataType;
+import com.android.tradefed.metrics.proto.MetricMeasurement.Measurements;
+import com.android.tradefed.metrics.proto.MetricMeasurement.Metric;
+import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
+import com.android.tradefed.testtype.junit4.AfterClassWithInfo;
+import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test;
+import com.android.tradefed.testtype.junit4.BeforeClassWithInfo;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+
+@RootPermissionTest
+@RunWith(DeviceJUnit4ClassRunner.class)
+public class AuthFsBenchmarks extends BaseHostJUnit4Test {
+ private static final int TRIAL_COUNT = 5;
+
+ /** Name of the measure_io binary on host. */
+ private static final String MEASURE_IO_BIN_NAME = "measure_io";
+
+ /** Path to measure_io on Microdroid. */
+ private static final String MEASURE_IO_BIN_PATH = "/data/local/tmp/measure_io";
+
+ /** fs-verity digest (sha256) of testdata/input.4m */
+ private static final String DIGEST_4M =
+ "sha256-f18a268d565348fb4bbf11f10480b198f98f2922eb711de149857b3cecf98a8d";
+
+ @Rule public final AuthFsTestRule mAuthFsTestRule = new AuthFsTestRule();
+ @Rule public final TestMetrics mTestMetrics = new TestMetrics();
+ private MetricsProcessor mMetricsProcessor;
+
+ @BeforeClassWithInfo
+ public static void beforeClassWithDevice(TestInformation testInfo) throws Exception {
+ AuthFsTestRule.setUpAndroid(testInfo);
+ }
+
+ @Before
+ public void setUp() throws Exception {
+ String metricsPrefix =
+ MetricsProcessor.getMetricPrefix(
+ getDevice().getProperty("debug.hypervisor.metrics_tag"));
+ mMetricsProcessor = new MetricsProcessor(metricsPrefix + "authfs/");
+ AuthFsTestRule.startMicrodroid();
+ }
+
+ @After
+ public void tearDown() throws DeviceNotAvailableException {
+ AuthFsTestRule.shutdownMicrodroid();
+ }
+
+ @AfterClassWithInfo
+ public static void afterClassWithDevice(TestInformation testInfo) {
+ AuthFsTestRule.tearDownAndroid();
+ }
+
+ @Test
+ public void seqReadRemoteFile() throws Exception {
+ readRemoteFile("seq");
+ }
+
+ @Test
+ public void randReadRemoteFile() throws Exception {
+ readRemoteFile("rand");
+ }
+
+ @Test
+ public void seqWriteRemoteFile() throws Exception {
+ writeRemoteFile("seq");
+ }
+
+ @Test
+ public void randWriteRemoteFile() throws Exception {
+ writeRemoteFile("rand");
+ }
+
+ private void readRemoteFile(String mode) throws DeviceNotAvailableException {
+ pushMeasureIoBinToMicrodroid();
+ // Cache the file in memory for the host.
+ mAuthFsTestRule
+ .getAndroid()
+ .run("cat " + mAuthFsTestRule.TEST_DIR + "/input.4m > /dev/null");
+
+ String filePath = mAuthFsTestRule.MOUNT_DIR + "/3";
+ int fileSizeMb = 4;
+ String cmd = MEASURE_IO_BIN_PATH + " " + filePath + " " + fileSizeMb + " " + mode + " r";
+ List<Double> rates = new ArrayList<>(TRIAL_COUNT);
+ for (int i = 0; i < TRIAL_COUNT + 1; ++i) {
+ mAuthFsTestRule.runFdServerOnAndroid(
+ "--open-ro 3:input.4m --open-ro 4:input.4m.fsv_meta", "--ro-fds 3:4");
+ mAuthFsTestRule.runAuthFsOnMicrodroid("--remote-ro-file 3:" + DIGEST_4M);
+
+ String rate = mAuthFsTestRule.getMicrodroid().run(cmd);
+ rates.add(Double.parseDouble(rate));
+ }
+ reportMetrics(rates, mode + "_read", "mb_per_sec");
+ }
+
+ private void writeRemoteFile(String mode) throws DeviceNotAvailableException {
+ pushMeasureIoBinToMicrodroid();
+ String filePath = mAuthFsTestRule.MOUNT_DIR + "/5";
+ int fileSizeMb = 8;
+ String cmd = MEASURE_IO_BIN_PATH + " " + filePath + " " + fileSizeMb + " " + mode + " w";
+ List<Double> rates = new ArrayList<>(TRIAL_COUNT);
+ for (int i = 0; i < TRIAL_COUNT + 1; ++i) {
+ mAuthFsTestRule.runFdServerOnAndroid(
+ "--open-rw 5:" + mAuthFsTestRule.TEST_OUTPUT_DIR + "/out.file", "--rw-fds 5");
+ mAuthFsTestRule.runAuthFsOnMicrodroid("--remote-new-rw-file 5");
+
+ String rate = mAuthFsTestRule.getMicrodroid().run(cmd);
+ rates.add(Double.parseDouble(rate));
+ }
+ reportMetrics(rates, mode + "_write", "mb_per_sec");
+ }
+
+ private void pushMeasureIoBinToMicrodroid() throws DeviceNotAvailableException {
+ File measureReadBin = mAuthFsTestRule.findTestFile(getBuild(), MEASURE_IO_BIN_NAME);
+ assertThat(measureReadBin.exists()).isTrue();
+ mAuthFsTestRule.getMicrodroidDevice().pushFile(measureReadBin, MEASURE_IO_BIN_PATH);
+ assertThat(mAuthFsTestRule.getMicrodroid().run("ls " + MEASURE_IO_BIN_PATH))
+ .isEqualTo(MEASURE_IO_BIN_PATH);
+ }
+
+ private void reportMetrics(List<Double> metrics, String name, String unit) {
+ Map<String, Double> stats = mMetricsProcessor.computeStats(metrics, name, unit);
+ for (Map.Entry<String, Double> entry : stats.entrySet()) {
+ Metric metric =
+ Metric.newBuilder()
+ .setType(DataType.RAW)
+ .setMeasurements(
+ Measurements.newBuilder().setSingleDouble(entry.getValue()))
+ .build();
+ mTestMetrics.addTestMetric(entry.getKey(), metric);
+ }
+ }
+}
diff --git a/authfs/tests/benchmarks/src/measure_io.cpp b/authfs/tests/benchmarks/src/measure_io.cpp
new file mode 100644
index 0000000..e1f2fb8
--- /dev/null
+++ b/authfs/tests/benchmarks/src/measure_io.cpp
@@ -0,0 +1,77 @@
+/*
+ * 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.
+ */
+
+#include <android-base/unique_fd.h>
+#include <assert.h>
+#include <err.h>
+#include <fcntl.h>
+#include <string.h>
+#include <time.h>
+#include <unistd.h>
+
+#include <algorithm>
+#include <iomanip>
+#include <iostream>
+#include <random>
+
+using android::base::unique_fd;
+
+constexpr int kBlockSizeBytes = 4096;
+constexpr int kNumBytesPerMB = 1024 * 1024;
+
+int main(int argc, const char *argv[]) {
+ if (argc != 5 || !(strcmp(argv[3], "rand") == 0 || strcmp(argv[3], "seq") == 0) ||
+ !(strcmp(argv[4], "r") == 0 || strcmp(argv[4], "w") == 0)) {
+ errx(EXIT_FAILURE, "Usage: %s <filename> <file_size_mb> <rand|seq> <r|w>", argv[0]);
+ }
+ int file_size_mb = std::stoi(argv[2]);
+ bool is_rand = (strcmp(argv[3], "rand") == 0);
+ bool is_read = (strcmp(argv[4], "r") == 0);
+ const int block_count = file_size_mb * kNumBytesPerMB / kBlockSizeBytes;
+ std::vector<int> offsets(block_count);
+ for (auto i = 0; i < block_count; ++i) {
+ offsets[i] = i * kBlockSizeBytes;
+ }
+ if (is_rand) {
+ std::mt19937 rd{std::random_device{}()};
+ std::shuffle(offsets.begin(), offsets.end(), rd);
+ }
+ unique_fd fd(open(argv[1], (is_read ? O_RDONLY : O_WRONLY) | O_CLOEXEC));
+ if (fd.get() == -1) {
+ errx(EXIT_FAILURE, "failed to open file: %s", argv[1]);
+ }
+
+ char buf[kBlockSizeBytes];
+ clock_t start = clock();
+ for (auto i = 0; i < block_count; ++i) {
+ auto bytes = is_read ? pread(fd, buf, kBlockSizeBytes, offsets[i])
+ : pwrite(fd, buf, kBlockSizeBytes, offsets[i]);
+ if (bytes == 0) {
+ errx(EXIT_FAILURE, "unexpected end of file");
+ } else if (bytes == -1) {
+ errx(EXIT_FAILURE, "failed to read");
+ }
+ }
+ if (!is_read) {
+ // Writes all the buffered modifications to the open file.
+ assert(syncfs(fd) == 0);
+ }
+ double elapsed_seconds = ((double)clock() - start) / CLOCKS_PER_SEC;
+ double rate = (double)file_size_mb / elapsed_seconds;
+ std::cout << std::setprecision(12) << rate << std::endl;
+
+ return EXIT_SUCCESS;
+}