Add read/write perf tests for BlobStore.

These tests will help us understand the difference
between local reads/writes vs blobstore reads/writes.

Bug: 226954650
Test: atest ./apct-tests/perftests/blobstore/src/com/android/perftests/blob/BlobStorePerfTests.java
Change-Id: Ibcc8497b4c32e90824b89e836716bdeb7490227b
diff --git a/apct-tests/perftests/blobstore/Android.bp b/apct-tests/perftests/blobstore/Android.bp
index 25c4250..9064b44 100644
--- a/apct-tests/perftests/blobstore/Android.bp
+++ b/apct-tests/perftests/blobstore/Android.bp
@@ -22,17 +22,18 @@
 }
 
 android_test {
-  name: "BlobStorePerfTests",
-  srcs: ["src/**/*.java"],
-  static_libs: [
-    "BlobStoreTestUtils",
-    "androidx.test.rules",
-    "androidx.annotation_annotation",
-    "apct-perftests-utils",
-    "ub-uiautomator",
-    "collector-device-lib-platform",
-  ],
-  platform_apis: true,
-  test_suites: ["device-tests"],
-  certificate: "platform",
+    name: "BlobStorePerfTests",
+    srcs: ["src/**/*.java"],
+    static_libs: [
+        "BlobStoreTestUtils",
+        "androidx.test.rules",
+        "androidx.annotation_annotation",
+        "apct-perftests-utils",
+        "ub-uiautomator",
+        "collector-device-lib-platform",
+        "androidx.benchmark_benchmark-macro",
+    ],
+    platform_apis: true,
+    test_suites: ["device-tests"],
+    certificate: "platform",
 }
diff --git a/apct-tests/perftests/blobstore/src/com/android/perftests/blob/BlobStorePerfTests.java b/apct-tests/perftests/blobstore/src/com/android/perftests/blob/BlobStorePerfTests.java
index faf61a7..665e986 100644
--- a/apct-tests/perftests/blobstore/src/com/android/perftests/blob/BlobStorePerfTests.java
+++ b/apct-tests/perftests/blobstore/src/com/android/perftests/blob/BlobStorePerfTests.java
@@ -15,15 +15,20 @@
  */
 package com.android.perftests.blob;
 
+import static com.android.utils.blob.Utils.acquireLease;
+
 import android.app.blob.BlobHandle;
 import android.app.blob.BlobStoreManager;
 import android.content.Context;
+import android.os.ParcelFileDescriptor;
 import android.perftests.utils.ManualBenchmarkState;
 import android.perftests.utils.PerfManualStatusReporter;
 import android.perftests.utils.TraceMarkParser;
 import android.perftests.utils.TraceMarkParser.TraceMarkSlice;
 import android.support.test.uiautomator.UiDevice;
+import android.util.DataUnit;
 
+import androidx.benchmark.macro.MacrobenchmarkScope;
 import androidx.test.filters.LargeTest;
 import androidx.test.platform.app.InstrumentationRegistry;
 
@@ -36,12 +41,16 @@
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
 
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
 import java.io.IOException;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Base64;
 import java.util.Collection;
 import java.util.List;
+import java.util.Random;
 import java.util.concurrent.CompletableFuture;
 import java.util.concurrent.TimeUnit;
 
@@ -53,6 +62,8 @@
     // From f/b/apex/blobstore/service/java/com/android/server/blob/BlobStoreSession.java
     private static final String ATRACE_COMPUTE_DIGEST_PREFIX = "computeBlobDigest-";
 
+    public static final int BUFFER_SIZE_BYTES = 16 * 1024;
+
     private Context mContext;
     private BlobStoreManager mBlobStoreManager;
     private AtraceUtils mAtraceUtils;
@@ -85,6 +96,8 @@
 
     @After
     public void tearDown() {
+        mContext.getFilesDir().delete();
+        runShellCommand("cmd package clear " + mContext.getPackageName());
         runShellCommand("cmd blob_store idle-maintenance");
     }
 
@@ -109,6 +122,110 @@
         }
     }
 
+    @Test
+    public void testDirectReads() throws Exception {
+        final File file = new File(mContext.getDataDir(), "test_read_file");
+        final long sizeBytes = DataUnit.MEBIBYTES.toBytes(fileSizeInMb);
+        try (FileOutputStream outputStream = new FileOutputStream(file)) {
+            writeData(outputStream, sizeBytes);
+        }
+
+        long durationNs = 0;
+        while (mState.keepRunning(durationNs)) {
+            dropCache();
+            try (FileInputStream inputStream = new FileInputStream(file)) {
+                final long startTimeNs = System.nanoTime();
+                readData(inputStream, sizeBytes);
+                durationNs = System.nanoTime() - startTimeNs;
+            }
+        }
+    }
+
+    @Test
+    public void testBlobStoreReads() throws Exception {
+        final FakeBlobData blobData = prepareDataBlob(fileSizeInMb);
+        commitBlob(blobData);
+        acquireLease(mContext, blobData.getBlobHandle(), "Test Desc");
+        final long sizeBytes = DataUnit.MEBIBYTES.toBytes(fileSizeInMb);
+
+        long durationNs = 0;
+        while (mState.keepRunning(durationNs)) {
+            dropCache();
+            try (FileInputStream inputStream = new ParcelFileDescriptor.AutoCloseInputStream(
+                    mBlobStoreManager.openBlob(blobData.getBlobHandle()))) {
+                final long startTimeNs = System.nanoTime();
+                readData(inputStream, sizeBytes);
+                durationNs = System.nanoTime() - startTimeNs;
+            }
+        }
+
+        deleteBlob(blobData.getBlobHandle());
+    }
+
+    @Test
+    public void testDirectWrites() throws Exception {
+        final File file = new File(mContext.getDataDir(), "test_write_file");
+        final long sizeBytes = DataUnit.MEBIBYTES.toBytes(fileSizeInMb);
+
+        long durationNs = 0;
+        while (mState.keepRunning(durationNs)) {
+            file.delete();
+            dropCache();
+            try (FileOutputStream outputStream = new FileOutputStream(file)) {
+                final long startTimeNs = System.nanoTime();
+                writeData(outputStream, sizeBytes);
+                durationNs = System.nanoTime() - startTimeNs;
+            }
+        }
+    }
+
+    @Test
+    public void testBlobStoreWrites() throws Exception {
+        final FakeBlobData blobData = prepareDataBlob(fileSizeInMb);
+        final long sizeBytes = DataUnit.MEBIBYTES.toBytes(fileSizeInMb);
+
+        long durationNs = 0;
+        while (mState.keepRunning(durationNs)) {
+            final long sessionId = mBlobStoreManager.createSession(blobData.getBlobHandle());
+            try (BlobStoreManager.Session session = mBlobStoreManager.openSession(sessionId)) {
+                dropCache();
+                try (FileOutputStream outputStream = new ParcelFileDescriptor
+                        .AutoCloseOutputStream(session.openWrite(0, sizeBytes))) {
+                    final long startTimeNs = System.nanoTime();
+                    writeData(outputStream, sizeBytes);
+                    durationNs = System.nanoTime() - startTimeNs;
+                }
+            }
+            mBlobStoreManager.abandonSession(sessionId);
+        }
+    }
+
+    private void readData(FileInputStream inputStream, long sizeBytes) throws Exception {
+        final byte[] buffer = new byte[BUFFER_SIZE_BYTES];
+        long bytesRead = 0;
+        while (bytesRead < sizeBytes) {
+            bytesRead += inputStream.read(buffer);
+        }
+    }
+
+    private void writeData(FileOutputStream outputStream, long sizeBytes) throws Exception {
+        final byte[] buffer = new byte[BUFFER_SIZE_BYTES];
+        long bytesWritten = 0;
+        final Random random = new Random(0);
+        while (bytesWritten < sizeBytes) {
+            random.nextBytes(buffer);
+            final int toWrite = (bytesWritten + buffer.length <= sizeBytes)
+                    ? buffer.length : (int) (sizeBytes - bytesWritten);
+            outputStream.write(buffer, 0, toWrite);
+            bytesWritten += toWrite;
+        }
+    }
+
+    private void dropCache() {
+        final MacrobenchmarkScope scope = new MacrobenchmarkScope(mContext.getPackageName(), false);
+        scope.dropKernelPageCache();
+    }
+
     private void collectDigestDurationsFromTrace(TraceMarkParser parser, List<Long> durations) {
         mAtraceUtils.performDump(parser, (key, slices) -> {
             for (TraceMarkSlice slice : slices) {
@@ -119,7 +236,7 @@
 
     private FakeBlobData prepareDataBlob(int fileSizeInMb) throws Exception {
         final FakeBlobData blobData = new FakeBlobData.Builder(mContext)
-                .setFileSize(fileSizeInMb * 1024 * 1024 /* bytes */)
+                .setFileSize(DataUnit.MEBIBYTES.toBytes(fileSizeInMb))
                 .build();
         blobData.prepare();
         return blobData;
diff --git a/tests/BlobStoreTestUtils/src/com/android/utils/blob/FakeBlobData.java b/tests/BlobStoreTestUtils/src/com/android/utils/blob/FakeBlobData.java
index 56db4f9..f469298 100644
--- a/tests/BlobStoreTestUtils/src/com/android/utils/blob/FakeBlobData.java
+++ b/tests/BlobStoreTestUtils/src/com/android/utils/blob/FakeBlobData.java
@@ -17,6 +17,7 @@
 
 import static com.android.utils.blob.Utils.BUFFER_SIZE_BYTES;
 import static com.android.utils.blob.Utils.copy;
+import static com.android.utils.blob.Utils.writeRandomData;
 
 import static com.google.common.truth.Truth.assertThat;
 
@@ -123,7 +124,7 @@
 
     public void prepare() throws Exception {
         try (RandomAccessFile file = new RandomAccessFile(mFile, "rw")) {
-            writeRandomData(file, mFileSize);
+            writeRandomData(file, mRandom, mFileSize);
         }
         mFileDigest = FileUtils.digest(mFile, "SHA-256");
         mExpiryTimeMs = System.currentTimeMillis() + mExpiryDurationMs;
@@ -239,18 +240,4 @@
         }
         return digest.digest();
     }
-
-    private void writeRandomData(RandomAccessFile file, long fileSize)
-            throws Exception {
-        long bytesWritten = 0;
-        final byte[] buffer = new byte[BUFFER_SIZE_BYTES];
-        while (bytesWritten < fileSize) {
-            mRandom.nextBytes(buffer);
-            final int toWrite = (bytesWritten + buffer.length <= fileSize)
-                    ? buffer.length : (int) (fileSize - bytesWritten);
-            file.seek(bytesWritten);
-            file.write(buffer, 0, toWrite);
-            bytesWritten += toWrite;
-        }
-    }
 }
diff --git a/tests/BlobStoreTestUtils/src/com/android/utils/blob/Utils.java b/tests/BlobStoreTestUtils/src/com/android/utils/blob/Utils.java
index 2d230a7..f6c0e6d 100644
--- a/tests/BlobStoreTestUtils/src/com/android/utils/blob/Utils.java
+++ b/tests/BlobStoreTestUtils/src/com/android/utils/blob/Utils.java
@@ -30,11 +30,14 @@
 import androidx.test.platform.app.InstrumentationRegistry;
 import androidx.test.uiautomator.UiDevice;
 
+import java.io.File;
 import java.io.FileInputStream;
 import java.io.FileOutputStream;
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.OutputStream;
+import java.io.RandomAccessFile;
+import java.util.Random;
 
 public class Utils {
     public static final String TAG = "BlobStoreTest";
@@ -164,6 +167,25 @@
         runShellCmd("cmd blob_store idle-maintenance");
     }
 
+    public static void writeRandomData(File file, long fileSizeBytes)
+            throws Exception {
+        writeRandomData(new RandomAccessFile(file, "rw"), new Random(0), fileSizeBytes);
+    }
+
+    public static void writeRandomData(RandomAccessFile file, Random random, long fileSizeBytes)
+            throws Exception {
+        long bytesWritten = 0;
+        final byte[] buffer = new byte[BUFFER_SIZE_BYTES];
+        while (bytesWritten < fileSizeBytes) {
+            random.nextBytes(buffer);
+            final int toWrite = (bytesWritten + buffer.length <= fileSizeBytes)
+                    ? buffer.length : (int) (fileSizeBytes - bytesWritten);
+            file.seek(bytesWritten);
+            file.write(buffer, 0, toWrite);
+            bytesWritten += toWrite;
+        }
+    }
+
     public static String runShellCmd(String cmd) throws IOException {
         final UiDevice uiDevice = UiDevice.getInstance(
                 InstrumentationRegistry.getInstrumentation());