Merge "Orderly VM shutdown, part 1"
diff --git a/TEST_MAPPING b/TEST_MAPPING
index cf90c76..33498d3 100644
--- a/TEST_MAPPING
+++ b/TEST_MAPPING
@@ -25,6 +25,9 @@
     },
     {
       "name": "MicrodroidBenchmarkApp"
+    },
+    {
+      "name": "ComposBenchmarkApp"
     }
   ],
   "imports": [
diff --git a/compos/benchmark/src/java/com/android/compos/benchmark/ComposBenchmark.java b/compos/benchmark/src/java/com/android/compos/benchmark/ComposBenchmark.java
index e4fb5ff..be9bb50 100644
--- a/compos/benchmark/src/java/com/android/compos/benchmark/ComposBenchmark.java
+++ b/compos/benchmark/src/java/com/android/compos/benchmark/ComposBenchmark.java
@@ -24,7 +24,6 @@
 import android.os.ParcelFileDescriptor;
 import android.util.Log;
 
-import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -37,7 +36,6 @@
 import java.text.DateFormat;
 import java.text.ParseException;
 import java.text.SimpleDateFormat;
-import java.time.Duration;
 import java.util.Date;
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
@@ -47,7 +45,9 @@
 public class ComposBenchmark {
     private static final String TAG = "ComposBenchmark";
     private static final int BUFFER_SIZE = 1024;
-    private static final int ROUND_COUNT = 10;
+    private static final int ROUND_COUNT = 5;
+    private static final double NANOS_IN_SEC = 1_000_000_000.0;
+    private static final String METRIC_PREFIX = "avf_perf/compos/";
 
     private Instrumentation mInstrumentation;
 
@@ -56,9 +56,29 @@
         mInstrumentation = getInstrumentation();
     }
 
-    @After
-    public void cleanup() {
+    private void reportMetric(String name, String unit, double[] values) {
+        double sum = 0;
+        double squareSum = 0;
+        double min = Double.MAX_VALUE;
+        double max = Double.MIN_VALUE;
 
+        for (double val : values) {
+            sum += val;
+            squareSum += val * val;
+            min = val < min ? val : min;
+            max = val > max ? val : max;
+        }
+
+        double average = sum / values.length;
+        double variance = squareSum / values.length - average * average;
+        double stdev = Math.sqrt(variance);
+
+        Bundle bundle = new Bundle();
+        bundle.putDouble(METRIC_PREFIX + name + "_average_" + unit, average);
+        bundle.putDouble(METRIC_PREFIX + name + "_min_" + unit, min);
+        bundle.putDouble(METRIC_PREFIX + name + "_max_" + unit, max);
+        bundle.putDouble(METRIC_PREFIX + name + "_stdev_" + unit, stdev);
+        mInstrumentation.sendStatus(0, bundle);
     }
 
     public byte[] executeCommandBlocking(String command) {
@@ -98,34 +118,25 @@
     }
 
     @Test
-    public void testCompilationInVM()
-            throws InterruptedException, IOException {
+    public void testGuestCompileTime() throws InterruptedException, IOException {
 
         final String command = "/apex/com.android.compos/bin/composd_cmd test-compile";
 
-        Long[] compileSecArray = new Long[ROUND_COUNT];
+        double[] compileTime = new double[ROUND_COUNT];
 
         for (int round = 0; round < ROUND_COUNT; ++round) {
             Long compileStartTime = System.nanoTime();
             String output = executeCommand(command);
             Long compileEndTime = System.nanoTime();
-            Long compileSec = Duration.ofNanos(compileEndTime - compileStartTime).getSeconds();
 
             Pattern pattern = Pattern.compile("All Ok");
             Matcher matcher = pattern.matcher(output);
             assertTrue(matcher.find());
 
-            compileSecArray[round] = compileSec;
+            compileTime[round] = (compileEndTime - compileStartTime) / NANOS_IN_SEC;
         }
 
-        Long compileSecSum = 0L;
-        for (Long num: compileSecArray) {
-           compileSecSum += num;
-        }
-
-        Bundle bundle = new Bundle();
-        bundle.putLong("compliation_in_vm_elapse_second", compileSecSum / compileSecArray.length);
-        mInstrumentation.sendStatus(0, bundle);
+        reportMetric("guest_compile_time", "s", compileTime);
     }
 
     private Timestamp getLatestDex2oatSuccessTime()
@@ -133,7 +144,7 @@
 
         final String command = "logcat -d -e dex2oat";
         String output = executeCommand(command);
-        String latestTime = "";
+        String latestTime = null;
 
         for (String line : output.split("[\r\n]+")) {
             Pattern pattern = Pattern.compile("dex2oat64: dex2oat took");
@@ -143,6 +154,10 @@
             }
         }
 
+        if (latestTime == null) {
+            return null;
+        }
+
         DateFormat formatter = new SimpleDateFormat("MM-dd hh:mm:ss.SSS");
         Date date = formatter.parse(latestTime);
         Timestamp timeStampDate = new Timestamp(date.getTime());
@@ -151,35 +166,28 @@
     }
 
     @Test
-    public void testCompilationInAndroid()
+    public void testHostCompileTime()
             throws InterruptedException, IOException, ParseException {
 
         final String command = "/apex/com.android.art/bin/odrefresh --force-compile";
 
-        Long[] compileSecArray = new Long[ROUND_COUNT];
+        double[] compileTime = new double[ROUND_COUNT];
 
         for (int round = 0; round < ROUND_COUNT; ++round) {
             Timestamp beforeCompileLatestTime = getLatestDex2oatSuccessTime();
             Long compileStartTime = System.nanoTime();
             String output = executeCommand(command);
             Long compileEndTime = System.nanoTime();
-            Long compileSec = Duration.ofNanos(compileEndTime - compileStartTime).getSeconds();
             Timestamp afterCompileLatestTime = getLatestDex2oatSuccessTime();
 
-            assertTrue(beforeCompileLatestTime.before(afterCompileLatestTime));
+            assertTrue(afterCompileLatestTime != null);
+            assertTrue(beforeCompileLatestTime == null
+                    || beforeCompileLatestTime.before(afterCompileLatestTime));
 
-            compileSecArray[round] = compileSec;
+            compileTime[round] = (compileEndTime - compileStartTime) / NANOS_IN_SEC;
         }
 
-        Long compileSecSum = 0L;
-        for (Long num: compileSecArray) {
-            compileSecSum += num;
-        }
-
-        Bundle bundle = new Bundle();
-        bundle.putLong("compliation_in_android_elapse_second",
-                compileSecSum / compileSecArray.length);
-        mInstrumentation.sendStatus(0, bundle);
+        reportMetric("host_compile_time", "s", compileTime);
     }
 
 }
diff --git a/tests/benchmark/src/java/com/android/microdroid/benchmark/MicrodroidBenchmarks.java b/tests/benchmark/src/java/com/android/microdroid/benchmark/MicrodroidBenchmarks.java
index e96f58b..625b638 100644
--- a/tests/benchmark/src/java/com/android/microdroid/benchmark/MicrodroidBenchmarks.java
+++ b/tests/benchmark/src/java/com/android/microdroid/benchmark/MicrodroidBenchmarks.java
@@ -37,7 +37,9 @@
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
 
+import java.io.File;
 import java.io.IOException;
+import java.nio.file.Files;
 
 @RunWith(Parameterized.class)
 public class MicrodroidBenchmarks extends MicrodroidDeviceTestBase {
@@ -47,6 +49,11 @@
 
     private static final String KERNEL_VERSION = SystemProperties.get("ro.kernel.version");
 
+    private static final String APEX_ETC_FS = "/apex/com.android.virt/etc/fs/";
+    private static final double SIZE_MB = 1024.0 * 1024.0;
+    private static final String MICRODROID_IMG_PREFIX = "microdroid_";
+    private static final String MICRODROID_IMG_SUFFIX = ".img";
+
     private boolean isCuttlefish() {
         String productName = SystemProperties.get("ro.product.name");
         return (null != productName)
@@ -85,7 +92,7 @@
             VirtualMachineConfig.Builder builder =
                     mInner.newVmConfigBuilder("assets/vm_config.json");
             VirtualMachineConfig normalConfig =
-                    builder.debugLevel(DebugLevel.FULL).memoryMib(mem).build();
+                    builder.debugLevel(DebugLevel.NONE).memoryMib(mem).build();
             mInner.forceCreateNewVirtualMachine("test_vm_minimum_memory", normalConfig);
 
             if (tryBootVm(TAG, "test_vm_minimum_memory").payloadStarted) return true;
@@ -159,4 +166,23 @@
         bundle.putDouble("avf_perf/microdroid/boot_time_stdev_ms", stdev);
         mInstrumentation.sendStatus(0, bundle);
     }
+
+    @Test
+    public void testMicrodroidImageSize() throws IOException {
+        Bundle bundle = new Bundle();
+        for (File file : new File(APEX_ETC_FS).listFiles()) {
+            String name = file.getName();
+
+            if (!name.startsWith(MICRODROID_IMG_PREFIX) || !name.endsWith(MICRODROID_IMG_SUFFIX)) {
+                continue;
+            }
+
+            String base = name.substring(MICRODROID_IMG_PREFIX.length(),
+                                         name.length() - MICRODROID_IMG_SUFFIX.length());
+            String metric = "avf_perf/microdroid/img_size_" + base + "_MB";
+            double size = Files.size(file.toPath()) / SIZE_MB;
+            bundle.putDouble(metric, size);
+        }
+        mInstrumentation.sendStatus(0, bundle);
+    }
 }
diff --git a/tests/vsock_guest.cc b/tests/vsock_guest.cc
index 7a72e11..884c8a4 100644
--- a/tests/vsock_guest.cc
+++ b/tests/vsock_guest.cc
@@ -62,6 +62,16 @@
         PLOG(ERROR) << "WriteStringToFd";
         return EXIT_FAILURE;
     }
+    shutdown(fd.get(), SHUT_WR); // close socket for writing
+
+    // Must not shut down until the server ACKs the message. Shutting down
+    // the VM would otherwise terminate the VMM and reset the server's socket.
+    LOG(INFO) << "Waiting for ACK from the server...";
+    if (!ReadFdToString(fd, &msg)) {
+        PLOG(ERROR) << "ReadFdToString";
+        return EXIT_FAILURE;
+    }
+    shutdown(fd.get(), SHUT_RD); // close socket for reading
 
     LOG(INFO) << "Exiting...";
     return EXIT_SUCCESS;
diff --git a/tests/vsock_test.cc b/tests/vsock_test.cc
index 0fc451d..1460660 100644
--- a/tests/vsock_test.cc
+++ b/tests/vsock_test.cc
@@ -48,6 +48,7 @@
 static constexpr const char kVmInitrdPath[] = "/data/local/tmp/virt-test/initramfs";
 static constexpr const char kVmParams[] = "rdinit=/bin/init bin/vsock_client 2 45678 HelloWorld";
 static constexpr const char kTestMessage[] = "HelloWorld";
+static constexpr const char kAckMessage[] = "ACK";
 static constexpr const char kPlatformVersion[] = "~1.0";
 
 /** Returns true if the kernel supports unprotected VMs. */
@@ -108,9 +109,13 @@
 
     LOG(INFO) << "Reading message from the client...";
     std::string msg;
-    ASSERT_TRUE(ReadFdToString(client_fd, &msg));
-
+    ASSERT_TRUE(ReadFdToString(client_fd, &msg)) << strerror(errno);
     LOG(INFO) << "Received message: " << msg;
+
+    // The client is waiting for a response to signal it can shut down.
+    LOG(INFO) << "Replying with '" << kAckMessage << "'...";
+    ASSERT_TRUE(WriteStringToFd(kAckMessage, client_fd));
+
     ASSERT_EQ(msg, kTestMessage);
 }