diff options
author | TreeHugger Robot <treehugger-gerrit@google.com> | 2019-07-30 05:24:37 +0000 |
---|---|---|
committer | Android (Google) Code Review <android-gerrit@google.com> | 2019-07-30 05:24:37 +0000 |
commit | 163976025250dc8a546ba19347bfcedd7b0b8ae2 (patch) | |
tree | 63562aeb2b0e9c3c8ce3f944ca34ab20a3f358ea | |
parent | 971b38d45ab36939a9315075a9abd70235afbff2 (diff) | |
parent | 5ef56dd6f36f904b1ad916cae54f22ac41759b14 (diff) |
Merge "Add performance test for operations of recents activity"
5 files changed, 352 insertions, 11 deletions
diff --git a/apct-tests/perftests/core/src/android/wm/RecentsAnimationPerfTest.java b/apct-tests/perftests/core/src/android/wm/RecentsAnimationPerfTest.java new file mode 100644 index 000000000000..4e2b281da1b4 --- /dev/null +++ b/apct-tests/perftests/core/src/android/wm/RecentsAnimationPerfTest.java @@ -0,0 +1,276 @@ +/* + * 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.perftests.utils.ManualBenchmarkState.STATS_REPORT_COEFFICIENT_VAR; +import static android.perftests.utils.ManualBenchmarkState.STATS_REPORT_ITERATION; +import static android.perftests.utils.ManualBenchmarkState.STATS_REPORT_MEAN; + +import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation; + +import static org.hamcrest.core.AnyOf.anyOf; +import static org.hamcrest.core.Is.is; + +import android.app.Activity; +import android.app.ActivityTaskManager; +import android.app.IActivityTaskManager; +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.content.pm.PackageManager; +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.perftests.utils.StubActivity; +import android.util.Pair; +import android.view.IRecentsAnimationController; +import android.view.IRecentsAnimationRunner; +import android.view.RemoteAnimationTarget; +import android.view.WindowManager; + +import androidx.test.filters.LargeTest; +import androidx.test.rule.ActivityTestRule; +import androidx.test.runner.lifecycle.ActivityLifecycleCallback; +import androidx.test.runner.lifecycle.ActivityLifecycleMonitorRegistry; +import androidx.test.runner.lifecycle.Stage; + +import org.junit.After; +import org.junit.AfterClass; +import org.junit.Assume; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.concurrent.Semaphore; +import java.util.concurrent.TimeUnit; + +@RunWith(Parameterized.class) +@LargeTest +public class RecentsAnimationPerfTest extends WindowManagerPerfTestBase { + private static Intent sRecentsIntent; + + @Rule + public final PerfManualStatusReporter mPerfStatusReporter = new PerfManualStatusReporter(); + + @Rule + public final ActivityTestRule<StubActivity> mActivityRule = new ActivityTestRule<>( + StubActivity.class, false /* initialTouchMode */, false /* launchActivity */); + + private long mMeasuredTimeNs; + private LifecycleListener mLifecycleListener; + + @Parameterized.Parameter(0) + public int intervalBetweenOperations; + + @Parameterized.Parameters(name = "interval{0}ms") + public static Collection<Object[]> getParameters() { + return Arrays.asList(new Object[][] { + { 0 }, + { 100 }, + { 300 }, + }); + } + + @BeforeClass + public static void setUpClass() { + // Get the permission to invoke startRecentsActivity. + sUiAutomation.adoptShellPermissionIdentity(); + + final Context context = getInstrumentation().getContext(); + final PackageManager pm = context.getPackageManager(); + final ComponentName defaultHome = pm.getHomeActivities(new ArrayList<>()); + + try { + final ComponentName recentsComponent = + ComponentName.unflattenFromString(context.getResources().getString( + com.android.internal.R.string.config_recentsComponentName)); + final int enabledState = pm.getComponentEnabledSetting(recentsComponent); + Assume.assumeThat(enabledState, anyOf( + is(PackageManager.COMPONENT_ENABLED_STATE_DEFAULT), + is(PackageManager.COMPONENT_ENABLED_STATE_ENABLED))); + + final boolean homeIsRecents = + recentsComponent.getPackageName().equals(defaultHome.getPackageName()); + sRecentsIntent = + new Intent().setComponent(homeIsRecents ? defaultHome : recentsComponent); + } catch (Exception e) { + Assume.assumeNoException(e); + } + } + + @AfterClass + public static void tearDownClass() { + sUiAutomation.dropShellPermissionIdentity(); + } + + @Before + @Override + public void setUp() { + super.setUp(); + final Activity testActivity = mActivityRule.launchActivity(null /* intent */); + try { + mActivityRule.runOnUiThread(() -> testActivity.getWindow() + .addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)); + } catch (Throwable ignored) { } + mLifecycleListener = new LifecycleListener(testActivity); + ActivityLifecycleMonitorRegistry.getInstance().addLifecycleCallback(mLifecycleListener); + } + + @After + public void tearDown() { + ActivityLifecycleMonitorRegistry.getInstance().removeLifecycleCallback(mLifecycleListener); + } + + /** Simulate the timing of touch. */ + private void makeInterval() { + SystemClock.sleep(intervalBetweenOperations); + } + + /** + * <pre> + * Steps: + * (1) Start recents activity (only make it visible). + * (2) Finish animation, take turns to execute (a), (b). + * (a) Move recents activity to top. + * ({@link com.android.server.wm.RecentsAnimationController#REORDER_MOVE_TO_TOP}) + * Move test app to top by startActivityFromRecents. + * (b) Cancel (it is similar to swipe a little distance and give up to enter recents). + * ({@link com.android.server.wm.RecentsAnimationController#REORDER_MOVE_TO_ORIGINAL_POSITION}) + * (3) Loop (1). + * </pre> + */ + @Test + @ManualBenchmarkTest( + warmupDurationNs = TIME_1_S_IN_NS, + targetTestDurationNs = TIME_5_S_IN_NS, + statsReportFlags = + STATS_REPORT_ITERATION | STATS_REPORT_MEAN | STATS_REPORT_COEFFICIENT_VAR) + public void testRecentsAnimation() throws Throwable { + final ManualBenchmarkState state = mPerfStatusReporter.getBenchmarkState(); + final IActivityTaskManager atm = ActivityTaskManager.getService(); + + final ArrayList<Pair<String, Boolean>> finishCases = new ArrayList<>(); + // Real launch the recents activity. + finishCases.add(new Pair<>("finishMoveToTop", true)); + // Return to the original top. + finishCases.add(new Pair<>("finishCancel", false)); + + // Ensure startRecentsActivity won't be called before finishing the animation. + final Semaphore recentsSemaphore = new Semaphore(1); + + final int testActivityTaskId = mActivityRule.getActivity().getTaskId(); + final IRecentsAnimationRunner.Stub anim = new IRecentsAnimationRunner.Stub() { + int mIteration; + + @Override + public void onAnimationStart(IRecentsAnimationController controller, + RemoteAnimationTarget[] apps, Rect homeContentInsets, + Rect minimizedHomeBounds) throws RemoteException { + final Pair<String, Boolean> finishCase = finishCases.get(mIteration++ % 2); + final boolean moveRecentsToTop = finishCase.second; + makeInterval(); + + long startTime = SystemClock.elapsedRealtimeNanos(); + controller.finish(moveRecentsToTop, false /* sendUserLeaveHint */); + final long elapsedTimeNsOfFinish = SystemClock.elapsedRealtimeNanos() - startTime; + mMeasuredTimeNs += elapsedTimeNsOfFinish; + state.addExtraResult(finishCase.first, elapsedTimeNsOfFinish); + + if (moveRecentsToTop) { + mLifecycleListener.waitForIdleSync(Stage.STOPPED); + + startTime = SystemClock.elapsedRealtimeNanos(); + atm.startActivityFromRecents(testActivityTaskId, null /* options */); + final long elapsedTimeNs = SystemClock.elapsedRealtimeNanos() - startTime; + mMeasuredTimeNs += elapsedTimeNs; + state.addExtraResult("startFromRecents", elapsedTimeNs); + + mLifecycleListener.waitForIdleSync(Stage.RESUMED); + } + + makeInterval(); + recentsSemaphore.release(); + } + + @Override + public void onAnimationCanceled(boolean deferredWithScreenshot) throws RemoteException { + Assume.assumeNoException( + new AssertionError("onAnimationCanceled should not be called")); + } + }; + + while (state.keepRunning(mMeasuredTimeNs)) { + Assume.assumeTrue(recentsSemaphore.tryAcquire(TIME_5_S_IN_NS, TimeUnit.NANOSECONDS)); + + final long startTime = SystemClock.elapsedRealtimeNanos(); + atm.startRecentsActivity(sRecentsIntent, null /* unused */, anim); + final long elapsedTimeNsOfStart = SystemClock.elapsedRealtimeNanos() - startTime; + mMeasuredTimeNs += elapsedTimeNsOfStart; + state.addExtraResult("start", elapsedTimeNsOfStart); + } + + // Ensure the last round of animation callback is done. + recentsSemaphore.tryAcquire(TIME_5_S_IN_NS, TimeUnit.NANOSECONDS); + recentsSemaphore.release(); + } + + private static class LifecycleListener implements ActivityLifecycleCallback { + private final Activity mTargetActivity; + private Stage mWaitingStage; + private Stage mReceivedStage; + + LifecycleListener(Activity activity) { + mTargetActivity = activity; + } + + void waitForIdleSync(Stage state) { + synchronized (this) { + if (state != mReceivedStage) { + mWaitingStage = state; + try { + wait(TimeUnit.NANOSECONDS.toMillis(TIME_5_S_IN_NS)); + } catch (InterruptedException impossible) { } + } + mWaitingStage = mReceivedStage = null; + } + getInstrumentation().waitForIdleSync(); + } + + @Override + public void onActivityLifecycleChanged(Activity activity, Stage stage) { + if (mTargetActivity != activity) { + return; + } + + synchronized (this) { + mReceivedStage = stage; + if (mWaitingStage == mReceivedStage) { + notifyAll(); + } + } + } + } +} diff --git a/apct-tests/perftests/core/src/android/wm/WindowAddRemovePerfTest.java b/apct-tests/perftests/core/src/android/wm/WindowAddRemovePerfTest.java index a95186916cea..27790e649a26 100644 --- a/apct-tests/perftests/core/src/android/wm/WindowAddRemovePerfTest.java +++ b/apct-tests/perftests/core/src/android/wm/WindowAddRemovePerfTest.java @@ -60,7 +60,7 @@ public class WindowAddRemovePerfTest extends WindowManagerPerfTestBase { } @Test - @ManualBenchmarkTest(warmupDurationNs = WARMUP_DURATION, targetTestDurationNs = TEST_DURATION) + @ManualBenchmarkTest(warmupDurationNs = TIME_1_S_IN_NS, targetTestDurationNs = TIME_5_S_IN_NS) public void testAddRemoveWindow() throws Throwable { new TestWindow().runBenchmark(mPerfStatusReporter.getBenchmarkState()); } diff --git a/apct-tests/perftests/core/src/android/wm/WindowManagerPerfTestBase.java b/apct-tests/perftests/core/src/android/wm/WindowManagerPerfTestBase.java index b2c61688ef59..4864da4b0195 100644 --- a/apct-tests/perftests/core/src/android/wm/WindowManagerPerfTestBase.java +++ b/apct-tests/perftests/core/src/android/wm/WindowManagerPerfTestBase.java @@ -25,8 +25,8 @@ 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; + static final long TIME_1_S_IN_NS = 1 * NANOS_PER_S; + static final long TIME_5_S_IN_NS = 5 * NANOS_PER_S; @Before public void setUp() { 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 dd43ae70cc5c..ffe39e8679e1 100644 --- a/apct-tests/perftests/utils/src/android/perftests/utils/ManualBenchmarkState.java +++ b/apct-tests/perftests/utils/src/android/perftests/utils/ManualBenchmarkState.java @@ -16,6 +16,7 @@ package android.perftests.utils; +import android.annotation.IntDef; import android.app.Activity; import android.app.Instrumentation; import android.os.Bundle; @@ -58,6 +59,28 @@ import java.util.concurrent.TimeUnit; public final class ManualBenchmarkState { private static final String TAG = ManualBenchmarkState.class.getSimpleName(); + @IntDef(prefix = {"STATS_REPORT"}, value = { + STATS_REPORT_MEDIAN, + STATS_REPORT_MEAN, + STATS_REPORT_MIN, + STATS_REPORT_MAX, + STATS_REPORT_PERCENTILE90, + STATS_REPORT_PERCENTILE95, + STATS_REPORT_STDDEV, + STATS_REPORT_ITERATION, + }) + public @interface StatsReport {} + + public static final int STATS_REPORT_MEDIAN = 0x00000001; + public static final int STATS_REPORT_MEAN = 0x00000002; + public static final int STATS_REPORT_MIN = 0x00000004; + public static final int STATS_REPORT_MAX = 0x00000008; + public static final int STATS_REPORT_PERCENTILE90 = 0x00000010; + public static final int STATS_REPORT_PERCENTILE95 = 0x00000020; + public static final int STATS_REPORT_STDDEV = 0x00000040; + public static final int STATS_REPORT_COEFFICIENT_VAR = 0x00000080; + public static final int STATS_REPORT_ITERATION = 0x00000100; + // TODO: Tune these values. // warm-up for duration private static final long WARMUP_DURATION_NS = TimeUnit.SECONDS.toNanos(5); @@ -93,6 +116,13 @@ public final class ManualBenchmarkState { // The computation needs double precision, but long int is fine for final reporting. private Stats mStats; + private int mStatsReportFlags = STATS_REPORT_MEDIAN | STATS_REPORT_MEAN + | STATS_REPORT_PERCENTILE90 | STATS_REPORT_PERCENTILE95 | STATS_REPORT_STDDEV; + + private boolean shouldReport(int statsReportFlag) { + return (mStatsReportFlags & statsReportFlag) != 0; + } + void configure(ManualBenchmarkTest testAnnotation) { if (testAnnotation == null) { return; @@ -106,6 +136,10 @@ public final class ManualBenchmarkState { if (targetTestDurationNs >= 0) { mTargetTestDurationNs = targetTestDurationNs; } + final int statsReportFlags = testAnnotation.statsReportFlags(); + if (statsReportFlags >= 0) { + mStatsReportFlags = statsReportFlags; + } } private void beginBenchmark(long warmupDuration, int iterations) { @@ -186,12 +220,35 @@ public final class ManualBenchmarkState { 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()); + private void fillStatus(Bundle status, String key, Stats stats) { + if (shouldReport(STATS_REPORT_ITERATION)) { + status.putLong(key + "_iteration", stats.getSize()); + } + if (shouldReport(STATS_REPORT_MEDIAN)) { + status.putLong(key + "_median", stats.getMedian()); + } + if (shouldReport(STATS_REPORT_MEAN)) { + status.putLong(key + "_mean", Math.round(stats.getMean())); + } + if (shouldReport(STATS_REPORT_MIN)) { + status.putLong(key + "_min", stats.getMin()); + } + if (shouldReport(STATS_REPORT_MAX)) { + status.putLong(key + "_max", stats.getMax()); + } + if (shouldReport(STATS_REPORT_PERCENTILE90)) { + status.putLong(key + "_percentile90", stats.getPercentile90()); + } + if (shouldReport(STATS_REPORT_PERCENTILE95)) { + status.putLong(key + "_percentile95", stats.getPercentile95()); + } + if (shouldReport(STATS_REPORT_STDDEV)) { + status.putLong(key + "_stddev", Math.round(stats.getStandardDeviation())); + } + if (shouldReport(STATS_REPORT_COEFFICIENT_VAR)) { + status.putLong(key + "_cv", + Math.round((100 * stats.getStandardDeviation() / stats.getMean()))); + } } public void sendFullStatusReport(Instrumentation instrumentation, String key) { @@ -204,8 +261,9 @@ public final class ManualBenchmarkState { 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)); + final ArrayList<Long> results = mExtraResults.valueAt(i); + final Stats stats = new Stats(results); + Log.i(TAG, summaryLine(subKey, stats, results)); fillStatus(status, subKey, stats); } } @@ -218,5 +276,6 @@ public final class ManualBenchmarkState { public @interface ManualBenchmarkTest { long warmupDurationNs() default -1; long targetTestDurationNs() default -1; + @StatsReport int statsReportFlags() default -1; } } diff --git a/apct-tests/perftests/utils/src/android/perftests/utils/Stats.java b/apct-tests/perftests/utils/src/android/perftests/utils/Stats.java index 5e50073c0674..f650e810c6de 100644 --- a/apct-tests/perftests/utils/src/android/perftests/utils/Stats.java +++ b/apct-tests/perftests/utils/src/android/perftests/utils/Stats.java @@ -23,6 +23,7 @@ import java.util.List; public class Stats { private long mMedian, mMin, mMax, mPercentile90, mPercentile95; private double mMean, mStandardDeviation; + private final int mSize; /* Calculate stats in constructor. */ public Stats(List<Long> values) { @@ -35,6 +36,7 @@ public class Stats { Collections.sort(values); + mSize = size; mMin = values.get(0); mMax = values.get(values.size() - 1); @@ -56,6 +58,10 @@ public class Stats { mStandardDeviation = Math.sqrt(mStandardDeviation / (double) (size - 1)); } + public int getSize() { + return mSize; + } + public double getMean() { return mMean; } |