Merge "[benchmark][authfs] Add benchmarks for authfs random read"
diff --git a/authfs/tests/Android.bp b/authfs/tests/Android.bp
index b662bee..0177254 100644
--- a/authfs/tests/Android.bp
+++ b/authfs/tests/Android.bp
@@ -23,6 +23,7 @@
         ":authfs_test_files",
         ":CtsApkVerityTestPrebuiltFiles",
         ":MicrodroidTestApp",
+        ":measure_io",
     ],
 }
 
@@ -42,3 +43,13 @@
     test_suites: ["general-tests"],
     test_harness: false,
 }
+
+cc_binary {
+    name: "measure_io",
+    srcs: [
+        "measure_io.cpp",
+    ],
+    shared_libs: [
+        "libbase",
+    ],
+}
diff --git a/authfs/tests/java/src/com/android/fs/AuthFsBenchmarks.java b/authfs/tests/java/src/com/android/fs/AuthFsBenchmarks.java
index 0dd4613..fb8c0cc 100644
--- a/authfs/tests/java/src/com/android/fs/AuthFsBenchmarks.java
+++ b/authfs/tests/java/src/com/android/fs/AuthFsBenchmarks.java
@@ -18,6 +18,8 @@
 
 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.microdroid.test.common.MetricsProcessor;
@@ -36,6 +38,7 @@
 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;
@@ -44,8 +47,13 @@
 @RunWith(DeviceJUnit4ClassRunner.class)
 public class AuthFsBenchmarks extends BaseHostJUnit4Test {
     private static final int TRIAL_COUNT = 5;
-    private static final double NANO_SECS_PER_SEC = 1_000_000_000.;
-    private static final double INPUT_SIZE_IN_MB = 4.;
+    private static final int FILE_SIZE_IN_MB = 4;
+
+    /** 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 =
@@ -76,27 +84,41 @@
 
     @Test
     public void seqReadRemoteFile() throws Exception {
-        List<Double> transferRates = new ArrayList<>(TRIAL_COUNT);
+        readRemoteFile("seq");
+    }
+
+    @Test
+    public void randReadRemoteFile() throws Exception {
+        readRemoteFile("rand");
+    }
+
+    private void readRemoteFile(String mode) throws DeviceNotAvailableException {
+        pushMeasureIoBinToMicrodroid();
         // Cache the file in memory for the host.
-        String cmd = "cat " + mAuthFsTestRule.TEST_DIR + "/input.4m > /dev/null";
-        mAuthFsTestRule.getAndroid().run(cmd);
+        mAuthFsTestRule
+                .getAndroid()
+                .run("cat " + mAuthFsTestRule.TEST_DIR + "/input.4m > /dev/null");
+
+        String filePath = mAuthFsTestRule.MOUNT_DIR + "/3";
+        String cmd = MEASURE_IO_BIN_PATH + " " + filePath + " " + FILE_SIZE_IN_MB + " " + mode;
+        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);
-            double elapsedSeconds = measureSeqReadOnMicrodroid("3");
-            transferRates.add(INPUT_SIZE_IN_MB / elapsedSeconds);
+
+            String rate = mAuthFsTestRule.getMicrodroid().run(cmd);
+            rates.add(Double.parseDouble(rate));
         }
-        reportMetrics(transferRates, "seq_read", "mb_per_sec");
+        reportMetrics(rates, mode + "_read", "mb_per_sec");
     }
 
-    private double measureSeqReadOnMicrodroid(String filename) throws DeviceNotAvailableException {
-        String cmd = "cat " + mAuthFsTestRule.MOUNT_DIR + "/" + filename + " > /dev/null";
-        // Ideally, we should measure the time in the VM to avoid the adb and host tests latency.
-        double startTime = System.nanoTime();
-        mAuthFsTestRule.getMicrodroid().run(cmd);
-        double elapsedSeconds = (System.nanoTime() - startTime) / NANO_SECS_PER_SEC;
-        return elapsedSeconds;
+    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) {
diff --git a/authfs/tests/java/src/com/android/fs/AuthFsHostTest.java b/authfs/tests/java/src/com/android/fs/AuthFsHostTest.java
index 2deb490..14dc88b 100644
--- a/authfs/tests/java/src/com/android/fs/AuthFsHostTest.java
+++ b/authfs/tests/java/src/com/android/fs/AuthFsHostTest.java
@@ -52,7 +52,7 @@
     private static final String FSVERITY_BIN = "/data/local/tmp/fsverity";
 
     /** Mount point of authfs on Microdroid during the test */
-    private static final String MOUNT_DIR = "/data/local/tmp";
+    private static final String MOUNT_DIR = AuthFsTestRule.MOUNT_DIR;
 
     /** Input manifest path in the VM. */
     private static final String INPUT_MANIFEST_PATH = "/mnt/apk/assets/input_manifest.pb";
diff --git a/authfs/tests/java/src/com/android/fs/AuthFsTestRule.java b/authfs/tests/java/src/com/android/fs/AuthFsTestRule.java
index 34a403e..ebeac4f 100644
--- a/authfs/tests/java/src/com/android/fs/AuthFsTestRule.java
+++ b/authfs/tests/java/src/com/android/fs/AuthFsTestRule.java
@@ -65,7 +65,7 @@
     private static final String TEST_OUTPUT_DIR = "/data/local/tmp/authfs/output_dir";
 
     /** Mount point of authfs on Microdroid during the test */
-    static final String MOUNT_DIR = "/data/local/tmp";
+    static final String MOUNT_DIR = "/data/local/tmp/mnt";
 
     /** VM's log file */
     private static final String LOG_PATH = TEST_OUTPUT_DIR + "/log.txt";
@@ -85,6 +85,7 @@
     private static final int VMADDR_CID_HOST = 2;
 
     private static TestInformation sTestInfo;
+    private static ITestDevice sMicrodroidDevice;
     private static CommandRunner sAndroid;
     private static CommandRunner sMicrodroid;
 
@@ -110,18 +111,21 @@
 
         // For each test case, boot and adb connect to a new Microdroid
         CLog.i("Starting the shared VM");
-        ITestDevice microdroidDevice =
+        sMicrodroidDevice =
                 MicrodroidBuilder.fromFile(
-                                findTestApk(testInfo.getBuildInfo()), VM_CONFIG_PATH_IN_APK)
+                                findTestFile(testInfo.getBuildInfo(), TEST_APK_NAME),
+                                VM_CONFIG_PATH_IN_APK)
                         .debugLevel("full")
                         .build((TestDevice) androidDevice);
 
         // From this point on, we need to tear down the Microdroid instance
-        sMicrodroid = new CommandRunner(microdroidDevice);
+        sMicrodroid = new CommandRunner(sMicrodroidDevice);
+
+        sMicrodroid.runForResult("mkdir -p " + MOUNT_DIR);
 
         // Root because authfs (started from shell in this test) currently require root to open
         // /dev/fuse and mount the FUSE.
-        assertThat(microdroidDevice.enableAdbRoot()).isTrue();
+        assertThat(sMicrodroidDevice.enableAdbRoot()).isTrue();
     }
 
     static void tearDownClass(TestInformation testInfo) throws DeviceNotAvailableException {
@@ -148,6 +152,11 @@
         return sMicrodroid;
     }
 
+    static ITestDevice getMicrodroidDevice() {
+        assertThat(sMicrodroidDevice).isNotNull();
+        return sMicrodroidDevice;
+    }
+
     @Override
     public Statement apply(final Statement base, Description description) {
         return super.apply(
@@ -205,11 +214,11 @@
         }
     }
 
-    private static File findTestApk(IBuildInfo buildInfo) {
+    static File findTestFile(IBuildInfo buildInfo, String fileName) {
         try {
-            return (new CompatibilityBuildHelper(buildInfo)).getTestFile(TEST_APK_NAME);
+            return (new CompatibilityBuildHelper(buildInfo)).getTestFile(fileName);
         } catch (FileNotFoundException e) {
-            fail("Missing test file: " + TEST_APK_NAME);
+            fail("Missing test file: " + fileName);
             return null;
         }
     }
diff --git a/authfs/tests/measure_io.cpp b/authfs/tests/measure_io.cpp
new file mode 100644
index 0000000..736c244
--- /dev/null
+++ b/authfs/tests/measure_io.cpp
@@ -0,0 +1,72 @@
+/*
+ * 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 <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 != 4 || !(strcmp(argv[3], "rand") == 0 || strcmp(argv[3], "seq") == 0)) {
+        std::cerr << "Usage: " << argv[0] << " <filename> <file_size_mb> <rand|seq>" << std::endl;
+        return EXIT_FAILURE;
+    }
+    int file_size_mb = std::stoi(argv[2]);
+    bool is_rand = (strcmp(argv[3], "rand") == 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], O_RDONLY | O_CLOEXEC));
+    if (fd.get() == -1) {
+        std::cerr << "failed to open file: " << argv[1] << std::endl;
+        return EXIT_FAILURE;
+    }
+
+    char buf[kBlockSizeBytes];
+    clock_t start = clock();
+    for (auto i = 0; i < block_count; ++i) {
+        auto bytes = pread(fd, buf, kBlockSizeBytes, offsets[i]);
+        if (bytes == 0) {
+            std::cerr << "unexpected end of file" << std::endl;
+            return EXIT_FAILURE;
+        } else if (bytes == -1) {
+            std::cerr << "failed to read" << std::endl;
+            return EXIT_FAILURE;
+        }
+    }
+    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;
+}