Merge changes Ifec1533f,I66267892,I45ab976c into main

* changes:
  Test PSCI MEM_PROTECT
  Add Event class for KvmHypTracer
  Generalize KvmHypTracer
diff --git a/tests/benchmark_hostside/java/android/avf/test/AVFHostTestCase.java b/tests/benchmark_hostside/java/android/avf/test/AVFHostTestCase.java
index 0280652..4a61016 100644
--- a/tests/benchmark_hostside/java/android/avf/test/AVFHostTestCase.java
+++ b/tests/benchmark_hostside/java/android/avf/test/AVFHostTestCase.java
@@ -47,6 +47,7 @@
 import org.junit.runner.RunWith;
 
 import java.util.ArrayList;
+import java.util.Collections;
 import java.util.List;
 import java.util.Map;
 import java.util.regex.Matcher;
@@ -121,9 +122,14 @@
 
     @Test
     public void testNoLongHypSections() throws Exception {
-        assumeTrue("Skip without hypervisor tracing", KvmHypTracer.isSupported(getDevice()));
+        String[] hypEvents = {
+            "hyp_enter", "hyp_exit"
+        };
 
-        KvmHypTracer tracer = new KvmHypTracer(getDevice());
+        assumeTrue("Skip without hypervisor tracing",
+            KvmHypTracer.isSupported(getDevice(), hypEvents));
+
+        KvmHypTracer tracer = new KvmHypTracer(getDevice(), hypEvents);
         String result = tracer.run(COMPOSD_CMD_BIN + " test-compile");
         assertWithMessage("Failed to test compilation VM.")
                 .that(result).ignoringCase().contains("all ok");
@@ -134,6 +140,36 @@
     }
 
     @Test
+    public void testPsciMemProtect() throws Exception {
+        String[] hypEvents = {
+            "psci_mem_protect"
+        };
+
+        assumeTrue("Skip without hypervisor tracing",
+            KvmHypTracer.isSupported(getDevice(), hypEvents));
+        KvmHypTracer tracer = new KvmHypTracer(getDevice(), hypEvents);
+
+        /* We need to wait for crosvm to die so all the VM pages are reclaimed */
+        String result = tracer.run(COMPOSD_CMD_BIN + " test-compile && killall -w crosvm || true");
+        assertWithMessage("Failed to test compilation VM.")
+                .that(result).ignoringCase().contains("all ok");
+
+        List<Integer> values = tracer.getPsciMemProtect();
+
+        assertWithMessage("PSCI MEM_PROTECT events not recorded")
+            .that(values.size()).isGreaterThan(2);
+
+        assertWithMessage("PSCI MEM_PROTECT counter not starting from 0")
+            .that(values.get(0)).isEqualTo(0);
+
+        assertWithMessage("PSCI MEM_PROTECT counter not ending with 0")
+            .that(values.get(values.size() - 1)).isEqualTo(0);
+
+        assertWithMessage("PSCI MEM_PROTECT counter didn't increment")
+            .that(Collections.max(values)).isGreaterThan(0);
+    }
+
+    @Test
     public void testCameraAppStartupTime() throws Exception {
         String[] launchIntentPackages = {
             "com.android.camera2",
diff --git a/tests/hostside/helper/java/com/android/microdroid/test/host/KvmHypTracer.java b/tests/hostside/helper/java/com/android/microdroid/test/host/KvmHypTracer.java
index 0d8ee96..5c72358 100644
--- a/tests/hostside/helper/java/com/android/microdroid/test/host/KvmHypTracer.java
+++ b/tests/hostside/helper/java/com/android/microdroid/test/host/KvmHypTracer.java
@@ -30,24 +30,63 @@
 import java.text.ParseException;
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.List;
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
 import javax.annotation.Nonnull;
 
+class KvmHypEvent {
+    public final int cpu;
+    public final double timestamp;
+    public final String name;
+    public final String args;
+    public final boolean valid;
+
+    private static final Pattern LOST_EVENT_PATTERN = Pattern.compile(
+            "^CPU:[0-9]* \\[LOST ([0-9]*) EVENTS\\]");
+
+    public KvmHypEvent(String str) {
+        Matcher matcher = LOST_EVENT_PATTERN.matcher(str);
+        if (matcher.find())
+            throw new OutOfMemoryError("Lost " + matcher.group(1) + " events");
+
+        Pattern pattern = Pattern.compile(
+                "^\\[([0-9]*)\\][ \t]*([0-9]*\\.[0-9]*): (\\S+) (.*)");
+
+        matcher = pattern.matcher(str);
+        if (!matcher.find()) {
+            valid = false;
+            cpu = 0;
+            timestamp = 0;
+            name = "";
+            args = "";
+            CLog.w("Failed to parse hyp event: " + str);
+            return;
+        }
+
+        cpu = Integer.parseInt(matcher.group(1));
+        timestamp = Double.parseDouble(matcher.group(2));
+        name = matcher.group(3);
+        args = matcher.group(4);
+        valid = true;
+    }
+
+    public String toString() {
+        return String.format(
+                "[%03d]\t%f: %s %s", cpu, timestamp, name, args);
+    }
+}
+
 /** This class provides utilities to interact with the hyp tracing subsystem */
 public final class KvmHypTracer {
 
     private static final String HYP_TRACING_ROOT = "/sys/kernel/tracing/hyp/";
-    private static final String HYP_EVENTS[] = { "hyp_enter", "hyp_exit" };
     private static final int DEFAULT_BUF_SIZE_KB = 4 * 1024;
-    private static final Pattern LOST_EVENT_PATTERN = Pattern.compile(
-            "^CPU:[0-9]* \\[LOST ([0-9]*) EVENTS\\]");
-    private static final Pattern EVENT_PATTERN = Pattern.compile(
-            "^\\[([0-9]*)\\][ \t]*([0-9]*\\.[0-9]*): (" + String.join("|", HYP_EVENTS) + ") (.*)");
 
     private final CommandRunner mRunner;
     private final ITestDevice mDevice;
     private final int mNrCpus;
+    private final String mHypEvents[];
 
     private final ArrayList<File> mTraces;
 
@@ -59,22 +98,23 @@
         return "events/hyp/" + event + "/";
     }
 
-    public static boolean isSupported(ITestDevice device) throws Exception {
-        for (String event: HYP_EVENTS) {
+    public static boolean isSupported(ITestDevice device, String[] events) throws Exception {
+        for (String event: events) {
             if (!device.doesFileExist(HYP_TRACING_ROOT + eventDir(event) + "/enable"))
                 return false;
         }
         return true;
     }
 
-    public KvmHypTracer(@Nonnull ITestDevice device) throws Exception {
-        assertWithMessage("Hypervisor tracing not supported")
-                .that(isSupported(device)).isTrue();
+    public KvmHypTracer(@Nonnull ITestDevice device, String[] events) throws Exception {
+        assertWithMessage("Hypervisor events " + String.join(",", events) + " not supported")
+            .that(isSupported(device, events)).isTrue();
 
         mDevice = device;
         mRunner = new CommandRunner(mDevice);
         mTraces = new ArrayList<File>();
         mNrCpus = Integer.parseInt(mRunner.run("nproc"));
+        mHypEvents = events;
     }
 
     public String run(String payload_cmd) throws Exception {
@@ -83,7 +123,7 @@
         setNode("tracing_on", 0);
         mRunner.run("echo 0 | tee " + HYP_TRACING_ROOT + "events/*/*/enable");
         setNode("buffer_size_kb", DEFAULT_BUF_SIZE_KB);
-        for (String event: HYP_EVENTS)
+        for (String event: mHypEvents)
             setNode(eventDir(event) + "/enable", 1);
         setNode("trace", 0);
 
@@ -96,15 +136,21 @@
             cmd += "CPU" + i + "_TRACE_PIPE_PID=$!;";
         }
 
+        String cmd_script = mRunner.run("mktemp -t cmd_script.XXXXXXXXXX");
+        mRunner.run("echo '" + payload_cmd + "' > " + cmd_script);
+
         /* Run the payload with tracing enabled */
         cmd += "echo 1 > tracing_on;";
         String cmd_stdout = mRunner.run("mktemp -t cmd_stdout.XXXXXXXXXX");
-        cmd += payload_cmd + " > " + cmd_stdout + ";";
+        cmd += "sh " + cmd_script + " > " + cmd_stdout + ";";
         cmd += "echo 0 > tracing_on;";
 
-        /* Actively kill the cat subprocesses as trace_pipe is blocking */
-        for (int i = 0; i < mNrCpus; i++)
+        /* Wait for cat to finish reading the pipe interface before killing it */
+        for (int i = 0; i < mNrCpus; i++) {
+            cmd += "while $(test '$(ps -o S -p $CPU" + i
+                + "_TRACE_PIPE_PID | tail -n 1)' = 'R'); do sleep 1; done;";
             cmd += "kill -9 $CPU" + i + "_TRACE_PIPE_PID;";
+        }
         cmd += "wait";
 
         /*
@@ -116,6 +162,8 @@
          */
         mRunner.run(cmd);
 
+        mRunner.run("rm -f " + cmd_script);
+
         for (String t: trace_pipes) {
             File trace = mDevice.pullFile(t);
             assertNotNull(trace);
@@ -128,37 +176,57 @@
         return res;
     }
 
+    private boolean hasEvents(String[] events) {
+        for (String event : events) {
+            if (!Arrays.asList(mHypEvents).contains(event)) {
+                return false;
+            }
+        }
+
+        return true;
+    }
+
+    private KvmHypEvent getNextEvent(BufferedReader br) throws Exception {
+        KvmHypEvent event;
+        String l;
+
+        if ((l = br.readLine()) == null)
+            return null;
+
+        event = new KvmHypEvent(l);
+        if (!event.valid)
+            return null;
+
+        return event;
+    }
+
     public SimpleStats getDurationStats() throws Exception {
+        String[] reqEvents = {"hyp_enter", "hyp_exit"};
         SimpleStats stats = new SimpleStats();
 
+        assertWithMessage("KvmHypTracer() is missing events " + String.join(",", reqEvents))
+            .that(hasEvents(reqEvents)).isTrue();
+
         for (File trace: mTraces) {
             BufferedReader br = new BufferedReader(new FileReader(trace));
             double last = 0.0, hyp_enter = 0.0;
-            String l, prev_event = "";
-            while ((l = br.readLine()) != null) {
-                Matcher matcher = LOST_EVENT_PATTERN.matcher(l);
-                if (matcher.find())
-                    throw new OutOfMemoryError("Lost " + matcher.group(1) + " events");
+            String prev_event = "";
+            KvmHypEvent hypEvent;
 
-                matcher = EVENT_PATTERN.matcher(l);
-                if (!matcher.find()) {
-                    CLog.w("Failed to parse hyp event: " + l);
-                    continue;
-                }
-
-                int cpu = Integer.parseInt(matcher.group(1));
+            while ((hypEvent = getNextEvent(br)) != null) {
+                int cpu = hypEvent.cpu;
                 if (cpu < 0 || cpu >= mNrCpus)
                     throw new ParseException("Incorrect CPU number: " + cpu, 0);
 
-                double cur = Double.parseDouble(matcher.group(2));
+                double cur = hypEvent.timestamp;
                 if (cur < last)
                     throw new ParseException("Time must not go backward: " + cur, 0);
                 last = cur;
 
-                String event = matcher.group(3);
+                String event = hypEvent.name;
                 if (event.equals(prev_event)) {
-                    throw new ParseException("Hyp event found twice in a row: " + trace + " - " + l,
-                                             0);
+                    throw new ParseException("Hyp event found twice in a row: " +
+                                             trace + " - " + hypEvent, 0);
                 }
 
                 switch (event) {
@@ -170,7 +238,7 @@
                         hyp_enter = cur;
                         break;
                     default:
-                        throw new ParseException("Unexpected line in trace" + l, 0);
+                        throw new ParseException("Unexpected line in trace " + hypEvent, 0);
                 }
                 prev_event = event;
             }
@@ -178,4 +246,55 @@
 
         return stats;
     }
+
+    public List<Integer> getPsciMemProtect() throws Exception {
+        String[] reqEvents = {"psci_mem_protect"};
+        List<Integer> psciMemProtect = new ArrayList<>();
+
+        assertWithMessage("KvmHypTracer() is missing events " + String.join(",", reqEvents))
+            .that(hasEvents(reqEvents)).isTrue();
+
+        BufferedReader[] brs = new BufferedReader[mTraces.size()];
+        KvmHypEvent[] next = new KvmHypEvent[mTraces.size()];
+
+        for (int i = 0; i < mTraces.size(); i++) {
+            brs[i] = new BufferedReader(new FileReader(mTraces.get(i)));
+            next[i] = getNextEvent(brs[i]);
+        }
+
+        while (true) {
+            double oldest = Double.MAX_VALUE;
+            int oldestIdx = -1;
+
+            for (int i = 0; i < mTraces.size(); i ++) {
+                if ((next[i] != null) && (next[i].timestamp < oldest)) {
+                    oldest = next[i].timestamp;
+                    oldestIdx = i;
+                }
+            }
+
+            if (oldestIdx < 0)
+                break;
+
+            Pattern pattern = Pattern.compile(
+                "count=([0-9]*) was=([0-9]*)");
+            Matcher matcher = pattern.matcher(next[oldestIdx].args);
+            if (!matcher.find()) {
+                throw new ParseException("Unexpected psci_mem_protect event: " +
+                                         next[oldestIdx], 0);
+            }
+
+            int count = Integer.parseInt(matcher.group(1));
+            int was = Integer.parseInt(matcher.group(2));
+
+            if (psciMemProtect.isEmpty()) {
+                psciMemProtect.add(was);
+            }
+
+            psciMemProtect.add(count);
+            next[oldestIdx] = getNextEvent(brs[oldestIdx]);
+        }
+
+        return psciMemProtect;
+    }
 }