Wait for CPU idle between multiuser perf test iterations.

Bug: 342399089
Bug: 340812874
Test: atest UserLifecycleTests
Flag: None
Change-Id: I91579d8af35b4a7df76d6bc1bcfad328c107d7c2
diff --git a/apct-tests/perftests/multiuser/src/android/multiuser/BenchmarkRunner.java b/apct-tests/perftests/multiuser/src/android/multiuser/BenchmarkRunner.java
index 515ddc8..a4128a3 100644
--- a/apct-tests/perftests/multiuser/src/android/multiuser/BenchmarkRunner.java
+++ b/apct-tests/perftests/multiuser/src/android/multiuser/BenchmarkRunner.java
@@ -19,13 +19,16 @@
 import android.os.Bundle;
 import android.os.SystemClock;
 import android.perftests.utils.ShellHelper;
+import android.util.Log;
 
 import java.util.ArrayList;
+import java.util.concurrent.TimeoutException;
 
 // Based on //platform/frameworks/base/apct-tests/perftests/utils/BenchmarkState.java
 public class BenchmarkRunner {
-
-    private static final long COOL_OFF_PERIOD_MS = 1000;
+    private static final String TAG = BenchmarkRunner.class.getSimpleName();
+    private static final int TIMEOUT_IN_SECONDS = 45;
+    private static final int CPU_IDLE_THRESHOLD_PERCENTAGE = 90;
 
     private static final int NUM_ITERATIONS = 4;
 
@@ -79,8 +82,7 @@
     }
 
     private void prepareForNextRun() {
-        SystemClock.sleep(COOL_OFF_PERIOD_MS);
-        ShellHelper.runShellCommand("am wait-for-broadcast-idle --flush-broadcast-loopers");
+        waitCoolDownPeriod();
         mStartTimeNs = System.nanoTime();
         mPausedDurationNs = 0;
     }
@@ -102,7 +104,7 @@
      * to avoid unnecessary waiting.
      */
     public void resumeTiming() {
-        ShellHelper.runShellCommand("am wait-for-broadcast-idle --flush-broadcast-loopers");
+        waitCoolDownPeriod();
         resumeTimer();
     }
 
@@ -162,4 +164,58 @@
         }
         return null;
     }
+
+    /** Waits for the CPU cores and the broadcast queue to be idle. */
+    public void waitCoolDownPeriod() {
+        waitForCpuIdle();
+        waitForBroadcastIdle();
+    }
+
+    private void waitForBroadcastIdle() {
+        try {
+            ShellHelper.runShellCommandWithTimeout(
+                    "am wait-for-broadcast-idle --flush-broadcast-loopers", TIMEOUT_IN_SECONDS);
+        } catch (TimeoutException e) {
+            Log.e(TAG, "Ending waitForBroadcastIdle because it didn't finish in "
+                    + TIMEOUT_IN_SECONDS + " seconds", e);
+        }
+    }
+
+    private void waitForCpuIdle() {
+        int count = 0;
+        int idleCpuPercentage;
+        while (count++ < TIMEOUT_IN_SECONDS) {
+            idleCpuPercentage = getIdleCpuPercentage();
+            Log.d(TAG, "Waiting for CPU idle #" + count + "=" + idleCpuPercentage + "%");
+            if (idleCpuPercentage > CPU_IDLE_THRESHOLD_PERCENTAGE) {
+                return;
+            }
+            SystemClock.sleep(1000);
+        }
+        Log.e(TAG, "Ending waitForCpuIdle because it didn't finish in "
+                + TIMEOUT_IN_SECONDS + " seconds");
+    }
+
+    private int getIdleCpuPercentage() {
+        String output = ShellHelper.runShellCommand("top -m 1 -n 1");
+
+        String[] tokens = output.split("\\s+");
+
+        float totalCpu = -1;
+        float idleCpu = -1;
+        for (String token : tokens) {
+            if (token.contains("%cpu")) {
+                totalCpu = Float.parseFloat(token.split("%")[0]);
+            } else if (token.contains("%idle")) {
+                idleCpu = Float.parseFloat(token.split("%")[0]);
+            }
+        }
+
+        if (totalCpu < 0 || idleCpu < 0) {
+            Log.e(TAG, "Could not get idle cpu percentage, output=" + output);
+            return -1;
+        }
+
+        return (int) (100 * idleCpu / totalCpu);
+    }
 }
\ No newline at end of file
diff --git a/apct-tests/perftests/multiuser/src/android/multiuser/UserLifecycleTests.java b/apct-tests/perftests/multiuser/src/android/multiuser/UserLifecycleTests.java
index 762e2af..98ab0c2 100644
--- a/apct-tests/perftests/multiuser/src/android/multiuser/UserLifecycleTests.java
+++ b/apct-tests/perftests/multiuser/src/android/multiuser/UserLifecycleTests.java
@@ -188,21 +188,6 @@
         }
     }
 
-    /** Tests creating a new user, with wait times between iterations. */
-    @Test(timeout = TIMEOUT_MAX_TEST_TIME_MS)
-    public void createUser_realistic() throws RemoteException {
-        while (mRunner.keepRunning()) {
-            Log.i(TAG, "Starting timer");
-            final int userId = createUserNoFlags();
-
-            mRunner.pauseTiming();
-            Log.i(TAG, "Stopping timer");
-            removeUser(userId);
-            waitCoolDownPeriod();
-            mRunner.resumeTimingForNextIteration();
-        }
-    }
-
     /** Tests creating and starting a new user. */
     @Test(timeout = TIMEOUT_MAX_TEST_TIME_MS)
     public void createAndStartUser() throws RemoteException {
@@ -239,7 +224,6 @@
             mRunner.pauseTiming();
             Log.d(TAG, "Stopping timer");
             removeUser(userId);
-            waitCoolDownPeriod();
             mRunner.resumeTimingForNextIteration();
         }
     }
@@ -254,7 +238,6 @@
             mRunner.pauseTiming();
             final int userId = createUserNoFlags();
 
-            waitForBroadcastIdle();
             runThenWaitForBroadcasts(userId, () -> {
                 mRunner.resumeTiming();
                 Log.i(TAG, "Starting timer");
@@ -309,9 +292,6 @@
 
             preStartUser(userId, numberOfIterationsToSkip);
 
-            waitForBroadcastIdle();
-            waitCoolDownPeriod();
-
             runThenWaitForBroadcasts(userId, () -> {
                 mRunner.resumeTiming();
                 Log.i(TAG, "Starting timer");
@@ -353,9 +333,6 @@
         while (mRunner.keepRunning()) {
             mRunner.pauseTiming();
 
-            waitForBroadcastIdle();
-            waitCoolDownPeriod();
-
             runThenWaitForBroadcasts(userId, () -> {
                 mRunner.resumeTiming();
                 Log.i(TAG, "Starting timer");
@@ -420,7 +397,6 @@
         while (mRunner.keepRunning()) {
             mRunner.pauseTiming();
 
-            waitCoolDownPeriod();
             mRunner.resumeTiming();
             Log.i(TAG, "Starting timer");
 
@@ -454,7 +430,6 @@
             mRunner.pauseTiming();
             Log.d(TAG, "Stopping timer");
             removeUser(userId);
-            waitCoolDownPeriod();
             mRunner.resumeTimingForNextIteration();
         }
     }
@@ -466,6 +441,7 @@
             mRunner.pauseTiming();
             final int startUser = mAm.getCurrentUser();
             final int userId = createUserNoFlags();
+
             mRunner.resumeTiming();
             Log.i(TAG, "Starting timer");
 
@@ -479,27 +455,6 @@
         }
     }
 
-    /** Tests switching to an uninitialized user with wait times between iterations. */
-    @Test(timeout = TIMEOUT_MAX_TEST_TIME_MS)
-    public void switchUser_realistic() throws Exception {
-        while (mRunner.keepRunning()) {
-            mRunner.pauseTiming();
-            final int startUser = ActivityManager.getCurrentUser();
-            final int userId = createUserNoFlags();
-            waitCoolDownPeriod();
-            Log.d(TAG, "Starting timer");
-            mRunner.resumeTiming();
-
-            switchUser(userId);
-
-            mRunner.pauseTiming();
-            Log.d(TAG, "Stopping timer");
-            switchUserNoCheck(startUser);
-            removeUser(userId);
-            mRunner.resumeTimingForNextIteration();
-        }
-    }
-
     /** Tests switching to a previously-started, but no-longer-running, user. */
     @Test(timeout = TIMEOUT_MAX_TEST_TIME_MS)
     public void switchUser_stopped() throws RemoteException {
@@ -507,6 +462,7 @@
             mRunner.pauseTiming();
             final int startUser = mAm.getCurrentUser();
             final int testUser = initializeNewUserAndSwitchBack(/* stopNewUser */ true);
+
             mRunner.resumeTiming();
             Log.i(TAG, "Starting timer");
 
@@ -536,7 +492,6 @@
 
         while (mRunner.keepRunning()) {
             mRunner.pauseTiming();
-            waitCoolDownPeriod();
             Log.d(TAG, "Starting timer");
             mRunner.resumeTiming();
 
@@ -562,7 +517,6 @@
                 /* useStaticWallpaper */true);
         while (mRunner.keepRunning()) {
             mRunner.pauseTiming();
-            waitCoolDownPeriod();
             Log.d(TAG, "Starting timer");
             mRunner.resumeTiming();
 
@@ -606,7 +560,6 @@
         final int testUser = initializeNewUserAndSwitchBack(/* stopNewUser */ false);
         while (mRunner.keepRunning()) {
             mRunner.pauseTiming();
-            waitCoolDownPeriod();
             Log.d(TAG, "Starting timer");
             mRunner.resumeTiming();
 
@@ -614,7 +567,6 @@
 
             mRunner.pauseTiming();
             Log.d(TAG, "Stopping timer");
-            waitForBroadcastIdle();
             switchUserNoCheck(startUser);
             mRunner.resumeTimingForNextIteration();
         }
@@ -631,7 +583,6 @@
                 /* useStaticWallpaper */ true);
         while (mRunner.keepRunning()) {
             mRunner.pauseTiming();
-            waitCoolDownPeriod();
             Log.d(TAG, "Starting timer");
             mRunner.resumeTiming();
 
@@ -639,7 +590,6 @@
 
             mRunner.pauseTiming();
             Log.d(TAG, "Stopping timer");
-            waitForBroadcastIdle();
             switchUserNoCheck(startUser);
             mRunner.resumeTimingForNextIteration();
         }
@@ -675,13 +625,11 @@
     @Test(timeout = TIMEOUT_MAX_TEST_TIME_MS)
     public void stopUser_realistic() throws RemoteException {
         final int userId = createUserNoFlags();
-        waitCoolDownPeriod();
         while (mRunner.keepRunning()) {
             mRunner.pauseTiming();
             runThenWaitForBroadcasts(userId, ()-> {
                 mIam.startUserInBackground(userId);
             }, Intent.ACTION_USER_STARTED, Intent.ACTION_MEDIA_MOUNTED);
-            waitCoolDownPeriod();
             Log.d(TAG, "Starting timer");
             mRunner.resumeTiming();
 
@@ -703,7 +651,7 @@
             final int startUser = mAm.getCurrentUser();
             final int userId = createUserNoFlags();
 
-            waitForBroadcastIdle();
+            mRunner.waitCoolDownPeriod();
             mUserSwitchWaiter.runThenWaitUntilBootCompleted(userId, () -> {
                 mRunner.resumeTiming();
                 Log.i(TAG, "Starting timer");
@@ -726,7 +674,7 @@
             final int startUser = ActivityManager.getCurrentUser();
             final int userId = createUserNoFlags();
 
-            waitCoolDownPeriod();
+            mRunner.waitCoolDownPeriod();
             mUserSwitchWaiter.runThenWaitUntilBootCompleted(userId, () -> {
                 mRunner.resumeTiming();
                 Log.d(TAG, "Starting timer");
@@ -752,7 +700,7 @@
                 switchUser(userId);
             }, Intent.ACTION_MEDIA_MOUNTED);
 
-            waitForBroadcastIdle();
+            mRunner.waitCoolDownPeriod();
             mUserSwitchWaiter.runThenWaitUntilSwitchCompleted(startUser, () -> {
                 runThenWaitForBroadcasts(userId, () -> {
                     mRunner.resumeTiming();
@@ -781,7 +729,7 @@
                 switchUser(userId);
             }, Intent.ACTION_MEDIA_MOUNTED);
 
-            waitCoolDownPeriod();
+            mRunner.waitCoolDownPeriod();
             mUserSwitchWaiter.runThenWaitUntilSwitchCompleted(startUser, () -> {
                 runThenWaitForBroadcasts(userId, () -> {
                     mRunner.resumeTiming();
@@ -827,7 +775,6 @@
             Log.d(TAG, "Stopping timer");
             attestTrue("Failed creating profile " + userId, mUm.isManagedProfile(userId));
             removeUser(userId);
-            waitCoolDownPeriod();
             mRunner.resumeTimingForNextIteration();
         }
     }
@@ -868,7 +815,6 @@
             mRunner.pauseTiming();
             Log.d(TAG, "Stopping timer");
             removeUser(userId);
-            waitCoolDownPeriod();
             mRunner.resumeTimingForNextIteration();
         }
     }
@@ -913,7 +859,6 @@
 
             mRunner.pauseTiming();
             Log.d(TAG, "Stopping timer");
-            waitCoolDownPeriod();
             mRunner.resumeTimingForNextIteration();
         }
         removeUser(userId);
@@ -965,7 +910,6 @@
             mRunner.pauseTiming();
             Log.d(TAG, "Stopping timer");
             removeUser(userId);
-            waitCoolDownPeriod();
             mRunner.resumeTimingForNextIteration();
         }
     }
@@ -1030,7 +974,6 @@
             mRunner.pauseTiming();
             Log.d(TAG, "Stopping timer");
             removeUser(userId);
-            waitCoolDownPeriod();
             mRunner.resumeTimingForNextIteration();
         }
     }
@@ -1071,7 +1014,6 @@
             mRunner.pauseTiming();
             Log.d(TAG, "Stopping timer");
             removeUser(userId);
-            waitCoolDownPeriod();
             mRunner.resumeTimingForNextIteration();
         }
     }
@@ -1124,7 +1066,6 @@
             mRunner.pauseTiming();
             Log.d(TAG, "Stopping timer");
             removeUser(userId);
-            waitCoolDownPeriod();
             mRunner.resumeTimingForNextIteration();
         }
     }
@@ -1164,7 +1105,6 @@
             runThenWaitForBroadcasts(userId, () -> {
                 startUserInBackgroundAndWaitForUnlock(userId);
             }, Intent.ACTION_MEDIA_MOUNTED);
-            waitCoolDownPeriod();
             mRunner.resumeTiming();
             Log.d(TAG, "Starting timer");
 
@@ -1280,6 +1220,7 @@
      * If lack of success should fail the test, use {@link #switchUser(int)} instead.
      */
     private boolean switchUserNoCheck(int userId) throws RemoteException {
+        mRunner.waitCoolDownPeriod();
         final boolean[] success = {true};
         mUserSwitchWaiter.runThenWaitUntilSwitchCompleted(userId, () -> {
             mAm.switchUser(userId);
@@ -1296,7 +1237,7 @@
      */
     private void stopUserAfterWaitingForBroadcastIdle(int userId)
             throws RemoteException {
-        waitForBroadcastIdle();
+        mRunner.waitCoolDownPeriod();
         stopUser(userId);
     }
 
@@ -1438,6 +1379,8 @@
      */
     private void runThenWaitForBroadcasts(int userId, FunctionalUtils.ThrowingRunnable runnable,
             String... actions) {
+        mRunner.waitCoolDownPeriod();
+
         final String unreceivedAction =
                 mBroadcastWaiter.runThenWaitForBroadcasts(userId, runnable, actions);
 
@@ -1538,28 +1481,4 @@
         assertEquals("", ShellHelper.runShellCommand("setprop " + name + " " + value));
         return TextUtils.firstNotEmpty(oldValue, "invalid");
     }
-
-    private void waitForBroadcastIdle() {
-        try {
-            ShellHelper.runShellCommandWithTimeout(
-                    "am wait-for-broadcast-idle --flush-broadcast-loopers", TIMEOUT_IN_SECOND);
-        } catch (TimeoutException e) {
-            Log.e(TAG, "Ending waitForBroadcastIdle because it is taking too long", e);
-        }
-    }
-
-    private void sleep(long ms) {
-        try {
-            Thread.sleep(ms);
-        } catch (InterruptedException e) {
-            // Ignore
-        }
-    }
-
-    private void waitCoolDownPeriod() {
-        // Heuristic value based on local tests. Stability increased compared to no waiting.
-        final int tenSeconds = 1000 * 10;
-        waitForBroadcastIdle();
-        sleep(tenSeconds);
-    }
 }