diff options
Diffstat (limited to 'apct-tests/perftests/windowmanager/src')
5 files changed, 846 insertions, 0 deletions
diff --git a/apct-tests/perftests/windowmanager/src/android/wm/InternalWindowOperationPerfTest.java b/apct-tests/perftests/windowmanager/src/android/wm/InternalWindowOperationPerfTest.java new file mode 100644 index 000000000000..4ed3b4e09d11 --- /dev/null +++ b/apct-tests/perftests/windowmanager/src/android/wm/InternalWindowOperationPerfTest.java @@ -0,0 +1,121 @@ +/* + * 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.StatsReport; + +import android.os.ParcelFileDescriptor; +import android.os.SystemClock; +import android.perftests.utils.ManualBenchmarkState; +import android.perftests.utils.ManualBenchmarkState.ManualBenchmarkTest; +import android.perftests.utils.PerfManualStatusReporter; +import android.perftests.utils.TraceMarkParser; +import android.perftests.utils.TraceMarkParser.TraceMarkSlice; +import android.util.Log; + +import androidx.test.filters.LargeTest; +import androidx.test.runner.lifecycle.Stage; + +import org.junit.Rule; +import org.junit.Test; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.util.concurrent.TimeUnit; + +/** Measure the performance of internal methods in window manager service by trace tag. */ +@LargeTest +public class InternalWindowOperationPerfTest extends WindowManagerPerfTestBase { + private static final String TAG = InternalWindowOperationPerfTest.class.getSimpleName(); + + @Rule + public final PerfManualStatusReporter mPerfStatusReporter = new PerfManualStatusReporter(); + + @Rule + public final PerfTestActivityRule mActivityRule = new PerfTestActivityRule(); + + private final TraceMarkParser mTraceMarkParser = new TraceMarkParser( + "applyPostLayoutPolicy", + "applySurfaceChanges", + "AppTransitionReady", + "closeSurfaceTransactiom", + "openSurfaceTransaction", + "performLayout", + "performSurfacePlacement", + "prepareSurfaces", + "updateInputWindows", + "WSA#startAnimation", + "activityIdle", + "activityPaused", + "activityStopped", + "activityDestroyed", + "finishActivity", + "startActivityInner"); + + @Test + @ManualBenchmarkTest( + targetTestDurationNs = 20 * TIME_1_S_IN_NS, + statsReport = @StatsReport( + flags = StatsReport.FLAG_ITERATION | StatsReport.FLAG_MEAN + | StatsReport.FLAG_MAX | StatsReport.FLAG_COEFFICIENT_VAR)) + public void testLaunchAndFinishActivity() throws Throwable { + final ManualBenchmarkState state = mPerfStatusReporter.getBenchmarkState(); + long measuredTimeNs = 0; + boolean isTraceStarted = false; + + while (state.keepRunning(measuredTimeNs)) { + if (!isTraceStarted && !state.isWarmingUp()) { + startAsyncAtrace(); + isTraceStarted = true; + } + final long startTime = SystemClock.elapsedRealtimeNanos(); + mActivityRule.launchActivity(); + mActivityRule.finishActivity(); + mActivityRule.waitForIdleSync(Stage.DESTROYED); + measuredTimeNs = SystemClock.elapsedRealtimeNanos() - startTime; + } + + stopAsyncAtrace(); + + mTraceMarkParser.forAllSlices((key, slices) -> { + for (TraceMarkSlice slice : slices) { + state.addExtraResult(key, (long) (slice.getDurationInSeconds() * NANOS_PER_S)); + } + }); + + Log.i(TAG, String.valueOf(mTraceMarkParser)); + } + + private void startAsyncAtrace() throws IOException { + sUiAutomation.executeShellCommand("atrace -b 32768 --async_start wm"); + // Avoid atrace isn't ready immediately. + SystemClock.sleep(TimeUnit.NANOSECONDS.toMillis(TIME_1_S_IN_NS)); + } + + private void stopAsyncAtrace() throws IOException { + final ParcelFileDescriptor pfd = sUiAutomation.executeShellCommand("atrace --async_stop"); + final InputStream inputStream = new ParcelFileDescriptor.AutoCloseInputStream(pfd); + try (BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream))) { + String line; + while ((line = reader.readLine()) != null) { + mTraceMarkParser.visit(line); + } + } + } +} diff --git a/apct-tests/perftests/windowmanager/src/android/wm/RecentsAnimationPerfTest.java b/apct-tests/perftests/windowmanager/src/android/wm/RecentsAnimationPerfTest.java new file mode 100644 index 000000000000..1667c1658a07 --- /dev/null +++ b/apct-tests/perftests/windowmanager/src/android/wm/RecentsAnimationPerfTest.java @@ -0,0 +1,229 @@ +/* + * 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.StatsReport; + +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.ActivityManager.TaskSnapshot; +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.util.Pair; +import android.view.IRecentsAnimationController; +import android.view.IRecentsAnimationRunner; +import android.view.RemoteAnimationTarget; + +import androidx.test.filters.LargeTest; +import androidx.test.runner.lifecycle.Stage; + +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 PerfTestActivityRule mActivityRule = + new PerfTestActivityRule(true /* launchActivity */); + + private long mMeasuredTimeNs; + + /** + * Used to skip each test method if there is error. It cannot be raised in static setup because + * that will break the amount of target method count. + */ + private static Exception sSetUpClassException; + + @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) { + sSetUpClassException = e; + } + } + + @AfterClass + public static void tearDownClass() { + sSetUpClassException = null; + sUiAutomation.dropShellPermissionIdentity(); + } + + @Before + public void setUp() { + Assume.assumeNoException(sSetUpClassException); + } + + /** 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, + statsReport = @StatsReport(flags = StatsReport.FLAG_ITERATION | StatsReport.FLAG_MEAN + | StatsReport.FLAG_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, RemoteAnimationTarget[] wallpapers, + 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) { + mActivityRule.waitForIdleSync(Stage.STOPPED); + + startTime = SystemClock.elapsedRealtimeNanos(); + atm.startActivityFromRecents(testActivityTaskId, null /* options */); + final long elapsedTimeNs = SystemClock.elapsedRealtimeNanos() - startTime; + mMeasuredTimeNs += elapsedTimeNs; + state.addExtraResult("startFromRecents", elapsedTimeNs); + + mActivityRule.waitForIdleSync(Stage.RESUMED); + } + + makeInterval(); + recentsSemaphore.release(); + } + + @Override + public void onAnimationCanceled(TaskSnapshot taskSnapshot) throws RemoteException { + Assume.assumeNoException( + new AssertionError("onAnimationCanceled should not be called")); + } + + @Override + public void onTaskAppeared(RemoteAnimationTarget app) throws RemoteException { + /* no-op */ + } + }; + + recentsSemaphore.tryAcquire(); + while (state.keepRunning(mMeasuredTimeNs)) { + mMeasuredTimeNs = 0; + + 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 animation callback is done. + Assume.assumeTrue(recentsSemaphore.tryAcquire(TIME_5_S_IN_NS, TimeUnit.NANOSECONDS)); + } + } +} diff --git a/apct-tests/perftests/windowmanager/src/android/wm/RelayoutPerfTest.java b/apct-tests/perftests/windowmanager/src/android/wm/RelayoutPerfTest.java new file mode 100644 index 000000000000..8139a2e963c5 --- /dev/null +++ b/apct-tests/perftests/windowmanager/src/android/wm/RelayoutPerfTest.java @@ -0,0 +1,162 @@ +/* + * 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.Activity; +import android.content.Context; +import android.graphics.Point; +import android.graphics.Rect; +import android.os.RemoteException; +import android.perftests.utils.BenchmarkState; +import android.perftests.utils.PerfStatusReporter; +import android.perftests.utils.PerfTestActivity; +import android.util.MergedConfiguration; +import android.view.DisplayCutout; +import android.view.IWindow; +import android.view.IWindowSession; +import android.view.InsetsSourceControl; +import android.view.InsetsState; +import android.view.SurfaceControl; +import android.view.View; +import android.view.WindowManager; +import android.view.WindowManagerGlobal; +import android.widget.LinearLayout; + +import androidx.test.filters.LargeTest; +import androidx.test.rule.ActivityTestRule; + +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; + +import java.util.Arrays; +import java.util.Collection; +import java.util.function.IntSupplier; + +@RunWith(Parameterized.class) +@LargeTest +public class RelayoutPerfTest extends WindowManagerPerfTestBase { + private int mIteration; + + @Rule + public final PerfStatusReporter mPerfStatusReporter = new PerfStatusReporter(); + + @Rule + public final ActivityTestRule<PerfTestActivity> mActivityRule = + new ActivityTestRule<>(PerfTestActivity.class); + + /** This is only a placement to match the input parameters from {@link #getParameters}. */ + @Parameterized.Parameter(0) + public String testName; + + /** The visibilities to loop for relayout. */ + @Parameterized.Parameter(1) + public int[] visibilities; + + /** + * Each row will be mapped into {@link #testName} and {@link #visibilities} of a new test + * instance according to the index of the parameter. + */ + @Parameterized.Parameters(name = "{0}") + public static Collection<Object[]> getParameters() { + return Arrays.asList(new Object[][] { + { "Visible", new int[] { View.VISIBLE } }, + { "Invisible~Visible", new int[] { View.INVISIBLE, View.VISIBLE } }, + { "Gone~Visible", new int[] { View.GONE, View.VISIBLE } }, + { "Gone~Invisible", new int[] { View.GONE, View.INVISIBLE } } + }); + } + + @Test + public void testRelayout() throws Throwable { + final Activity activity = mActivityRule.getActivity(); + final ContentView contentView = new ContentView(activity); + mActivityRule.runOnUiThread(() -> { + activity.getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); + activity.setContentView(contentView); + }); + getInstrumentation().waitForIdleSync(); + + final RelayoutRunner relayoutRunner = new RelayoutRunner(activity, contentView.getWindow(), + () -> visibilities[mIteration++ % visibilities.length]); + relayoutRunner.runBenchmark(mPerfStatusReporter.getBenchmarkState()); + } + + /** A dummy view to get IWindow. */ + private static class ContentView extends LinearLayout { + ContentView(Context context) { + super(context); + } + + @Override + protected IWindow getWindow() { + return super.getWindow(); + } + } + + private static class RelayoutRunner { + final Rect mOutFrame = new Rect(); + final Rect mOutContentInsets = new Rect(); + final Rect mOutVisibleInsets = new Rect(); + final Rect mOutStableInsets = new Rect(); + final Rect mOutBackDropFrame = new Rect(); + final DisplayCutout.ParcelableWrapper mOutDisplayCutout = + new DisplayCutout.ParcelableWrapper(DisplayCutout.NO_CUTOUT); + final MergedConfiguration mOutMergedConfiguration = new MergedConfiguration(); + final InsetsState mOutInsetsState = new InsetsState(); + final InsetsSourceControl[] mOutControls = new InsetsSourceControl[0]; + final IWindow mWindow; + final View mView; + final WindowManager.LayoutParams mParams; + final int mWidth; + final int mHeight; + final Point mOutSurfaceSize = new Point(); + final SurfaceControl mOutSurfaceControl; + final SurfaceControl mOutBlastSurfaceControl = new SurfaceControl(); + + final IntSupplier mViewVisibility; + + int mSeq; + int mFrameNumber; + int mFlags; + + RelayoutRunner(Activity activity, IWindow window, IntSupplier visibilitySupplier) { + mWindow = window; + mView = activity.getWindow().getDecorView(); + mParams = (WindowManager.LayoutParams) mView.getLayoutParams(); + mWidth = mView.getMeasuredWidth(); + mHeight = mView.getMeasuredHeight(); + mOutSurfaceControl = mView.getViewRootImpl().getSurfaceControl(); + mViewVisibility = visibilitySupplier; + } + + void runBenchmark(BenchmarkState state) throws RemoteException { + final IWindowSession session = WindowManagerGlobal.getWindowSession(); + while (state.keepRunning()) { + session.relayout(mWindow, mSeq, mParams, mWidth, mHeight, + mViewVisibility.getAsInt(), mFlags, mFrameNumber, mOutFrame, + mOutContentInsets, mOutVisibleInsets, mOutStableInsets, + mOutBackDropFrame, mOutDisplayCutout, mOutMergedConfiguration, + mOutSurfaceControl, mOutInsetsState, mOutControls, mOutSurfaceSize, + mOutBlastSurfaceControl); + } + } + } +} diff --git a/apct-tests/perftests/windowmanager/src/android/wm/WindowAddRemovePerfTest.java b/apct-tests/perftests/windowmanager/src/android/wm/WindowAddRemovePerfTest.java new file mode 100644 index 000000000000..c72cc9d635e0 --- /dev/null +++ b/apct-tests/perftests/windowmanager/src/android/wm/WindowAddRemovePerfTest.java @@ -0,0 +1,128 @@ +/* + * 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.InsetsSourceControl; +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 + implements ManualBenchmarkState.CustomizedIterationListener { + + private static final int PROFILED_ITERATIONS = 2; + + @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(); + } + + /** The last {@link #PROFILED_ITERATIONS} will provide the information of method profiling. */ + @Override + public void onStart(int iteration) { + startProfiling(WindowAddRemovePerfTest.class.getSimpleName() + + "_MethodTracing_" + iteration + ".trace"); + } + + @Override + public void onFinished(int iteration) { + stopProfiling(); + } + + @Test + @ManualBenchmarkTest(warmupDurationNs = TIME_1_S_IN_NS, targetTestDurationNs = TIME_5_S_IN_NS) + public void testAddRemoveWindow() throws Throwable { + final ManualBenchmarkState state = mPerfStatusReporter.getBenchmarkState(); + state.setCustomizedIterations(PROFILED_ITERATIONS, this); + new TestWindow().runBenchmark(state); + } + + 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 DisplayCutout.ParcelableWrapper mOutDisplayCutout = + new DisplayCutout.ParcelableWrapper(); + final InsetsState mOutInsetsState = new InsetsState(); + final InsetsSourceControl[] mOutControls = new InsetsSourceControl[0]; + + 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, + mOutDisplayCutout, inputChannel, mOutInsetsState, mOutControls); + 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; + inputChannel.dispose(); + } + } + } +} diff --git a/apct-tests/perftests/windowmanager/src/android/wm/WindowManagerPerfTestBase.java b/apct-tests/perftests/windowmanager/src/android/wm/WindowManagerPerfTestBase.java new file mode 100644 index 000000000000..9e17e940a06b --- /dev/null +++ b/apct-tests/perftests/windowmanager/src/android/wm/WindowManagerPerfTestBase.java @@ -0,0 +1,206 @@ +/* + * 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.Activity; +import android.app.UiAutomation; +import android.content.Context; +import android.content.Intent; +import android.os.BatteryManager; +import android.os.ParcelFileDescriptor; +import android.perftests.utils.PerfTestActivity; +import android.provider.Settings; + +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.AfterClass; +import org.junit.BeforeClass; +import org.junit.runner.Description; +import org.junit.runners.model.Statement; + +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.util.concurrent.TimeUnit; + +public class WindowManagerPerfTestBase { + static final UiAutomation sUiAutomation = getInstrumentation().getUiAutomation(); + static final long NANOS_PER_S = 1000L * 1000 * 1000; + static final long TIME_1_S_IN_NS = 1 * NANOS_PER_S; + static final long TIME_5_S_IN_NS = 5 * NANOS_PER_S; + + /** + * The out directory matching the directory-keys of collector in AndroidTest.xml. The directory + * is in /data because while enabling method profling of system server, it cannot write the + * trace to external storage. + */ + static final File BASE_OUT_PATH = new File("/data/local/CorePerfTests"); + + private static int sOriginalStayOnWhilePluggedIn; + + @BeforeClass + public static void setUpOnce() { + final Context context = getInstrumentation().getContext(); + sOriginalStayOnWhilePluggedIn = Settings.Global.getInt(context.getContentResolver(), + Settings.Global.STAY_ON_WHILE_PLUGGED_IN, 0); + // Keep the device awake during testing. + setStayOnWhilePluggedIn(BatteryManager.BATTERY_PLUGGED_USB); + + if (!BASE_OUT_PATH.exists()) { + executeShellCommand("mkdir -p " + BASE_OUT_PATH); + } + // In order to be closer to the real use case. + executeShellCommand("input keyevent KEYCODE_WAKEUP"); + executeShellCommand("wm dismiss-keyguard"); + context.startActivity(new Intent(Intent.ACTION_MAIN) + .addCategory(Intent.CATEGORY_HOME).setFlags(Intent.FLAG_ACTIVITY_NEW_TASK)); + } + + @AfterClass + public static void tearDownOnce() { + setStayOnWhilePluggedIn(sOriginalStayOnWhilePluggedIn); + } + + private static void setStayOnWhilePluggedIn(int value) { + executeShellCommand(String.format("settings put global %s %d", + Settings.Global.STAY_ON_WHILE_PLUGGED_IN, value)); + } + + /** + * Executes shell command with reading the output. It may also used to block until the current + * command is completed. + */ + static ByteArrayOutputStream executeShellCommand(String command) { + final ParcelFileDescriptor pfd = sUiAutomation.executeShellCommand(command); + final byte[] buf = new byte[512]; + final ByteArrayOutputStream bytes = new ByteArrayOutputStream(); + int bytesRead; + try (FileInputStream fis = new ParcelFileDescriptor.AutoCloseInputStream(pfd)) { + while ((bytesRead = fis.read(buf)) != -1) { + bytes.write(buf, 0, bytesRead); + } + } catch (IOException e) { + throw new RuntimeException(e); + } + return bytes; + } + + /** Starts method tracing on system server. */ + void startProfiling(String subPath) { + executeShellCommand("am profile start system " + new File(BASE_OUT_PATH, subPath)); + } + + void stopProfiling() { + executeShellCommand("am profile stop system"); + } + + /** + * Provides an activity that keeps screen on and is able to wait for a stable lifecycle stage. + */ + static class PerfTestActivityRule extends ActivityTestRule<PerfTestActivity> { + private final Intent mStartIntent = + new Intent(getInstrumentation().getTargetContext(), PerfTestActivity.class); + private final LifecycleListener mLifecycleListener = new LifecycleListener(); + + PerfTestActivityRule() { + this(false /* launchActivity */); + } + + PerfTestActivityRule(boolean launchActivity) { + super(PerfTestActivity.class, false /* initialTouchMode */, launchActivity); + } + + @Override + public Statement apply(Statement base, Description description) { + final Statement wrappedStatement = new Statement() { + @Override + public void evaluate() throws Throwable { + ActivityLifecycleMonitorRegistry.getInstance() + .addLifecycleCallback(mLifecycleListener); + base.evaluate(); + ActivityLifecycleMonitorRegistry.getInstance() + .removeLifecycleCallback(mLifecycleListener); + } + }; + return super.apply(wrappedStatement, description); + } + + @Override + protected Intent getActivityIntent() { + return mStartIntent; + } + + @Override + public PerfTestActivity launchActivity(Intent intent) { + final PerfTestActivity activity = super.launchActivity(intent); + mLifecycleListener.setTargetActivity(activity); + return activity; + } + + PerfTestActivity launchActivity() { + return launchActivity(mStartIntent); + } + + void waitForIdleSync(Stage state) { + mLifecycleListener.waitForIdleSync(state); + } + } + + static class LifecycleListener implements ActivityLifecycleCallback { + private Activity mTargetActivity; + private Stage mWaitingStage; + private Stage mReceivedStage; + + void setTargetActivity(Activity activity) { + mTargetActivity = activity; + mReceivedStage = mWaitingStage = null; + } + + void waitForIdleSync(Stage stage) { + synchronized (this) { + if (stage != mReceivedStage) { + mWaitingStage = stage; + 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(); + } + } + } + } +} |