Add performance test of add/remove window

Also add support of multiple stats for manual benchmark.
So it can provide more details of separated steps.

Bug: 131727899
Test: atest WindowAddRemovePerfTest

Change-Id: Ia0b49c4a5e139449ee313c6ccbbe500e13327b8a
diff --git a/apct-tests/perftests/core/src/android/wm/RelayoutPerfTest.java b/apct-tests/perftests/core/src/android/wm/RelayoutPerfTest.java
index 1f12ae6..f0c474b 100644
--- a/apct-tests/perftests/core/src/android/wm/RelayoutPerfTest.java
+++ b/apct-tests/perftests/core/src/android/wm/RelayoutPerfTest.java
@@ -39,7 +39,6 @@
 import androidx.test.filters.LargeTest;
 import androidx.test.rule.ActivityTestRule;
 
-import org.junit.Before;
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -51,9 +50,7 @@
 
 @RunWith(Parameterized.class)
 @LargeTest
-public class RelayoutPerfTest {
-    private static final IWindowSession sSession = WindowManagerGlobal.getWindowSession();
-
+public class RelayoutPerfTest extends WindowManagerPerfTestBase {
     private int mIteration;
 
     @Rule
@@ -85,12 +82,6 @@
         });
     }
 
-    @Before
-    public void setUp() {
-        getInstrumentation().getUiAutomation().executeShellCommand("input keyevent KEYCODE_WAKEUP");
-        getInstrumentation().getUiAutomation().executeShellCommand("wm dismiss-keyguard");
-    }
-
     @Test
     public void testRelayout() throws Throwable {
         final Activity activity = mActivityRule.getActivity();
@@ -154,8 +145,9 @@
         }
 
         void runBenchmark(BenchmarkState state) throws RemoteException {
+            final IWindowSession session = WindowManagerGlobal.getWindowSession();
             while (state.keepRunning()) {
-                sSession.relayout(mWindow, mSeq, mParams, mWidth, mHeight,
+                session.relayout(mWindow, mSeq, mParams, mWidth, mHeight,
                         mViewVisibility.getAsInt(), mFlags, mFrameNumber, mOutFrame,
                         mOutOverscanInsets, mOutContentInsets, mOutVisibleInsets, mOutStableInsets,
                         mOutOutsets, mOutBackDropFrame, mOutDisplayCutout, mOutMergedConfiguration,
diff --git a/apct-tests/perftests/core/src/android/wm/WindowAddRemovePerfTest.java b/apct-tests/perftests/core/src/android/wm/WindowAddRemovePerfTest.java
new file mode 100644
index 0000000..a951869
--- /dev/null
+++ b/apct-tests/perftests/core/src/android/wm/WindowAddRemovePerfTest.java
@@ -0,0 +1,108 @@
+/*
+ * Copyright (C) 2019 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.
+ */
+
+package android.wm;
+
+import static android.view.WindowManager.LayoutParams.FLAG_LAYOUT_INSET_DECOR;
+import static android.view.WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN;
+
+import android.graphics.Rect;
+import android.os.RemoteException;
+import android.os.SystemClock;
+import android.perftests.utils.ManualBenchmarkState;
+import android.perftests.utils.ManualBenchmarkState.ManualBenchmarkTest;
+import android.perftests.utils.PerfManualStatusReporter;
+import android.view.Display;
+import android.view.DisplayCutout;
+import android.view.IWindowSession;
+import android.view.InputChannel;
+import android.view.InsetsState;
+import android.view.View;
+import android.view.WindowManager;
+import android.view.WindowManagerGlobal;
+
+import androidx.test.filters.LargeTest;
+
+import com.android.internal.view.BaseIWindow;
+
+import org.junit.AfterClass;
+import org.junit.BeforeClass;
+import org.junit.Rule;
+import org.junit.Test;
+
+@LargeTest
+public class WindowAddRemovePerfTest extends WindowManagerPerfTestBase {
+    @Rule
+    public final PerfManualStatusReporter mPerfStatusReporter = new PerfManualStatusReporter();
+
+    @BeforeClass
+    public static void setUpClass() {
+        // Get the permission to use most window types.
+        sUiAutomation.adoptShellPermissionIdentity();
+    }
+
+    @AfterClass
+    public static void tearDownClass() {
+        sUiAutomation.dropShellPermissionIdentity();
+    }
+
+    @Test
+    @ManualBenchmarkTest(warmupDurationNs = WARMUP_DURATION, targetTestDurationNs = TEST_DURATION)
+    public void testAddRemoveWindow() throws Throwable {
+        new TestWindow().runBenchmark(mPerfStatusReporter.getBenchmarkState());
+    }
+
+    private static class TestWindow extends BaseIWindow {
+        final WindowManager.LayoutParams mLayoutParams = new WindowManager.LayoutParams();
+        final Rect mOutFrame = new Rect();
+        final Rect mOutContentInsets = new Rect();
+        final Rect mOutStableInsets = new Rect();
+        final Rect mOutOutsets = new Rect();
+        final DisplayCutout.ParcelableWrapper mOutDisplayCutout =
+                new DisplayCutout.ParcelableWrapper();
+        final InsetsState mOutInsetsState = new InsetsState();
+
+        TestWindow() {
+            mLayoutParams.setTitle(TestWindow.class.getName());
+            mLayoutParams.type = WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
+            // Simulate as common phone window.
+            mLayoutParams.flags = FLAG_LAYOUT_IN_SCREEN | FLAG_LAYOUT_INSET_DECOR;
+        }
+
+        void runBenchmark(ManualBenchmarkState state) throws RemoteException {
+            final IWindowSession session = WindowManagerGlobal.getWindowSession();
+            long elapsedTimeNs = 0;
+            while (state.keepRunning(elapsedTimeNs)) {
+                // InputChannel cannot be reused.
+                final InputChannel inputChannel = new InputChannel();
+
+                long startTime = SystemClock.elapsedRealtimeNanos();
+                session.addToDisplay(this, mSeq, mLayoutParams, View.VISIBLE,
+                        Display.DEFAULT_DISPLAY, mOutFrame, mOutContentInsets, mOutStableInsets,
+                        mOutOutsets, mOutDisplayCutout, inputChannel, mOutInsetsState);
+                final long elapsedTimeNsOfAdd = SystemClock.elapsedRealtimeNanos() - startTime;
+                state.addExtraResult("add", elapsedTimeNsOfAdd);
+
+                startTime = SystemClock.elapsedRealtimeNanos();
+                session.remove(this);
+                final long elapsedTimeNsOfRemove = SystemClock.elapsedRealtimeNanos() - startTime;
+                state.addExtraResult("remove", elapsedTimeNsOfRemove);
+
+                elapsedTimeNs = elapsedTimeNsOfAdd + elapsedTimeNsOfRemove;
+            }
+        }
+    }
+}
diff --git a/apct-tests/perftests/core/src/android/wm/WindowManagerPerfTestBase.java b/apct-tests/perftests/core/src/android/wm/WindowManagerPerfTestBase.java
new file mode 100644
index 0000000..b2c6168
--- /dev/null
+++ b/apct-tests/perftests/core/src/android/wm/WindowManagerPerfTestBase.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2019 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.
+ */
+
+package android.wm;
+
+import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
+
+import android.app.UiAutomation;
+
+import org.junit.Before;
+
+public class WindowManagerPerfTestBase {
+    static final UiAutomation sUiAutomation = getInstrumentation().getUiAutomation();
+    static final long NANOS_PER_S = 1000L * 1000 * 1000;
+    static final long WARMUP_DURATION = 1 * NANOS_PER_S;
+    static final long TEST_DURATION = 5 * NANOS_PER_S;
+
+    @Before
+    public void setUp() {
+        // In order to be closer to the real use case.
+        sUiAutomation.executeShellCommand("input keyevent KEYCODE_WAKEUP");
+        sUiAutomation.executeShellCommand("wm dismiss-keyguard");
+    }
+}
diff --git a/apct-tests/perftests/utils/src/android/perftests/utils/ManualBenchmarkState.java b/apct-tests/perftests/utils/src/android/perftests/utils/ManualBenchmarkState.java
index 40778de..dd43ae7 100644
--- a/apct-tests/perftests/utils/src/android/perftests/utils/ManualBenchmarkState.java
+++ b/apct-tests/perftests/utils/src/android/perftests/utils/ManualBenchmarkState.java
@@ -19,8 +19,13 @@
 import android.app.Activity;
 import android.app.Instrumentation;
 import android.os.Bundle;
+import android.util.ArrayMap;
 import android.util.Log;
 
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
 import java.util.ArrayList;
 import java.util.concurrent.TimeUnit;
 
@@ -71,6 +76,8 @@
 
     private int mState = NOT_STARTED;  // Current benchmark state.
 
+    private long mWarmupDurationNs = WARMUP_DURATION_NS;
+    private long mTargetTestDurationNs = TARGET_TEST_DURATION_NS;
     private long mWarmupStartTime = 0;
     private int mWarmupIterations = 0;
 
@@ -79,12 +86,30 @@
     // Individual duration in nano seconds.
     private ArrayList<Long> mResults = new ArrayList<>();
 
+    /** @see #addExtraResult(String, long) */
+    private ArrayMap<String, ArrayList<Long>> mExtraResults;
+
     // Statistics. These values will be filled when the benchmark has finished.
     // The computation needs double precision, but long int is fine for final reporting.
     private Stats mStats;
 
+    void configure(ManualBenchmarkTest testAnnotation) {
+        if (testAnnotation == null) {
+            return;
+        }
+
+        final long warmupDurationNs = testAnnotation.warmupDurationNs();
+        if (warmupDurationNs >= 0) {
+            mWarmupDurationNs = warmupDurationNs;
+        }
+        final long targetTestDurationNs = testAnnotation.targetTestDurationNs();
+        if (targetTestDurationNs >= 0) {
+            mTargetTestDurationNs = targetTestDurationNs;
+        }
+    }
+
     private void beginBenchmark(long warmupDuration, int iterations) {
-        mMaxIterations = (int) (TARGET_TEST_DURATION_NS / (warmupDuration / iterations));
+        mMaxIterations = (int) (mTargetTestDurationNs / (warmupDuration / iterations));
         mMaxIterations = Math.min(MAX_TEST_ITERATIONS,
                 Math.max(mMaxIterations, MIN_TEST_ITERATIONS));
         mState = RUNNING;
@@ -108,7 +133,7 @@
                 final long timeSinceStartingWarmup = System.nanoTime() - mWarmupStartTime;
                 ++mWarmupIterations;
                 if (mWarmupIterations >= WARMUP_MIN_ITERATIONS
-                        && timeSinceStartingWarmup >= WARMUP_DURATION_NS) {
+                        && timeSinceStartingWarmup >= mWarmupDurationNs) {
                     beginBenchmark(timeSinceStartingWarmup, mWarmupIterations);
                 }
                 return true;
@@ -129,31 +154,69 @@
         }
     }
 
-    private String summaryLine() {
-        final StringBuilder sb = new StringBuilder();
-        sb.append("Summary: ");
-        sb.append("median=").append(mStats.getMedian()).append("ns, ");
-        sb.append("mean=").append(mStats.getMean()).append("ns, ");
-        sb.append("min=").append(mStats.getMin()).append("ns, ");
-        sb.append("max=").append(mStats.getMax()).append("ns, ");
-        sb.append("sigma=").append(mStats.getStandardDeviation()).append(", ");
-        sb.append("iteration=").append(mResults.size()).append(", ");
-        sb.append("values=").append(mResults.toString());
+    /**
+     * Adds additional result while this benchmark is running. It is used when a sequence of
+     * operations is executed consecutively, the duration of each operation can also be recorded.
+     */
+    public void addExtraResult(String key, long duration) {
+        if (mState != RUNNING) {
+            return;
+        }
+        if (mExtraResults == null) {
+            mExtraResults = new ArrayMap<>();
+        }
+        mExtraResults.computeIfAbsent(key, k -> new ArrayList<>()).add(duration);
+    }
+
+    private static String summaryLine(String key, Stats stats, ArrayList<Long> results) {
+        final StringBuilder sb = new StringBuilder(key);
+        sb.append(" Summary: ");
+        sb.append("median=").append(stats.getMedian()).append("ns, ");
+        sb.append("mean=").append(stats.getMean()).append("ns, ");
+        sb.append("min=").append(stats.getMin()).append("ns, ");
+        sb.append("max=").append(stats.getMax()).append("ns, ");
+        sb.append("sigma=").append(stats.getStandardDeviation()).append(", ");
+        sb.append("iteration=").append(results.size()).append(", ");
+        sb.append("values=");
+        if (results.size() > 100) {
+            sb.append(results.subList(0, 100)).append(" ...");
+        } else {
+            sb.append(results);
+        }
         return sb.toString();
     }
 
+    private static void fillStatus(Bundle status, String key, Stats stats) {
+        status.putLong(key + "_median", stats.getMedian());
+        status.putLong(key + "_mean", (long) stats.getMean());
+        status.putLong(key + "_percentile90", stats.getPercentile90());
+        status.putLong(key + "_percentile95", stats.getPercentile95());
+        status.putLong(key + "_stddev", (long) stats.getStandardDeviation());
+    }
+
     public void sendFullStatusReport(Instrumentation instrumentation, String key) {
         if (mState != FINISHED) {
             throw new IllegalStateException("The benchmark hasn't finished");
         }
-        Log.i(TAG, key + summaryLine());
+        Log.i(TAG, summaryLine(key, mStats, mResults));
         final Bundle status = new Bundle();
-        status.putLong(key + "_median", mStats.getMedian());
-        status.putLong(key + "_mean", (long) mStats.getMean());
-        status.putLong(key + "_percentile90", mStats.getPercentile90());
-        status.putLong(key + "_percentile95", mStats.getPercentile95());
-        status.putLong(key + "_stddev", (long) mStats.getStandardDeviation());
+        fillStatus(status, key, mStats);
+        if (mExtraResults != null) {
+            for (int i = 0; i < mExtraResults.size(); i++) {
+                final String subKey = key + "_" + mExtraResults.keyAt(i);
+                final Stats stats = new Stats(mExtraResults.valueAt(i));
+                Log.i(TAG, summaryLine(subKey, mStats, mResults));
+                fillStatus(status, subKey, stats);
+            }
+        }
         instrumentation.sendStatus(Activity.RESULT_OK, status);
     }
-}
 
+    /** The annotation to customize the test, e.g. the duration of warm-up and target test. */
+    @Target(ElementType.METHOD)
+    @Retention(RetentionPolicy.RUNTIME)
+    public @interface ManualBenchmarkTest {
+        long warmupDurationNs() default -1;
+        long targetTestDurationNs() default -1;
+    }
+}
diff --git a/apct-tests/perftests/utils/src/android/perftests/utils/PerfManualStatusReporter.java b/apct-tests/perftests/utils/src/android/perftests/utils/PerfManualStatusReporter.java
index 8187c6f..8ff6a16 100644
--- a/apct-tests/perftests/utils/src/android/perftests/utils/PerfManualStatusReporter.java
+++ b/apct-tests/perftests/utils/src/android/perftests/utils/PerfManualStatusReporter.java
@@ -16,7 +16,7 @@
 
 package android.perftests.utils;
 
-import androidx.test.InstrumentationRegistry;
+import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
 
 import org.junit.rules.TestRule;
 import org.junit.runner.Description;
@@ -59,15 +59,15 @@
 
     @Override
     public Statement apply(Statement base, Description description) {
+        mState.configure(description.getAnnotation(ManualBenchmarkState.ManualBenchmarkTest.class));
+
         return new Statement() {
             @Override
             public void evaluate() throws Throwable {
                 base.evaluate();
 
-                mState.sendFullStatusReport(InstrumentationRegistry.getInstrumentation(),
-                        description.getMethodName());
+                mState.sendFullStatusReport(getInstrumentation(), description.getMethodName());
             }
         };
     }
 }
-