Merge "Add performance test for operations of recents activity"
This commit is contained in:
committed by
Android (Google) Code Review
commit
1639760252
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user