[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;
+}