Commit 5b77a9e0 authored by Wei-Yin Chen (陳威尹)'s avatar Wei-Yin Chen (陳威尹) Committed by Commit Bot

Add performance tests for GridTabSwitcherLayout

Frame rate and dirty time span in the Tab-to-Grid animation are
measured for the following use cases:
- From NTP
- From live tab, with total number of tabs = {1, 10, 100}

Bug: 964406
Change-Id: I2e181596c9f3145557257b7b060ffee4db6f8cdf
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1634395
Commit-Queue: Wei-Yin Chen (陳威尹) <wychen@chromium.org>
Reviewed-by: default avatarYusuf Ozuysal <yusufo@chromium.org>
Cr-Commit-Position: refs/heads/master@{#664543}
parent 7e809e11
......@@ -494,6 +494,7 @@ chrome_test_java_sources = [
"javatests/src/org/chromium/chrome/browser/webapps/WebappVisibilityTest.java",
"javatests/src/org/chromium/chrome/browser/webauth/AuthenticatorTest.java",
"javatests/src/org/chromium/chrome/browser/widget/DualControlLayoutTest.java",
"javatests/src/org/chromium/chrome/browser/widget/GridTabSwitcherLayoutPerfTest.java",
"javatests/src/org/chromium/chrome/browser/widget/OverviewListLayoutTest.java",
"javatests/src/org/chromium/chrome/browser/widget/PromoDialogTest.java",
"javatests/src/org/chromium/chrome/browser/widget/RadioButtonLayoutTest.java",
......
......@@ -9,6 +9,7 @@ import android.support.annotation.NonNull;
import org.chromium.chrome.browser.compositor.layouts.OverviewModeBehavior;
import org.chromium.chrome.browser.compositor.layouts.OverviewModeController;
import org.chromium.ui.resources.dynamics.ViewResourceAdapter;
/**
* Interface for the Grid Tab Switcher.
......@@ -33,6 +34,12 @@ public interface GridTabSwitcher {
*/
int getResourceId();
/**
* @return The timestamp of last dirty event of {@link ViewResourceAdapter} of
* {@link TabListRecyclerView}.
*/
long getLastDirtyTimeForTesting();
/**
* Before calling {@link OverviewModeController#showOverview} to start showing the
* GridTabSwitcher {@link TabListRecyclerView}, call this to populate it without making it
......
......@@ -122,6 +122,11 @@ public class GridTabSwitcherCoordinator
return mTabGridCoordinator.getResourceId();
}
@Override
public long getLastDirtyTimeForTesting() {
return mTabGridCoordinator.getLastDirtyTimeForTesting();
}
/**
* Reset the tab grid with the given {@link TabModel}. Can be null.
* @param tabList The current {@link TabList} to show the tabs for in the grid.
......
......@@ -208,4 +208,8 @@ public class TabListCoordinator implements Destroyable {
int getResourceId() {
return mRecyclerView.getResourceId();
}
long getLastDirtyTimeForTesting() {
return mRecyclerView.getLastDirtyTimeForTesting();
}
}
......@@ -11,6 +11,7 @@ import android.animation.ValueAnimator;
import android.annotation.SuppressLint;
import android.content.Context;
import android.graphics.Rect;
import android.os.SystemClock;
import android.support.annotation.Nullable;
import android.support.v7.widget.RecyclerView;
import android.util.AttributeSet;
......@@ -58,6 +59,7 @@ class TabListRecyclerView extends RecyclerView {
private ValueAnimator mFadeOutAnimator;
private VisibilityListener mListener;
private ViewResourceAdapter mDynamicView;
private long mLastDirtyTime;
private long mOriginalAddDuration;
/**
......@@ -119,12 +121,23 @@ class TabListRecyclerView extends RecyclerView {
return getId();
}
long getLastDirtyTimeForTesting() {
return mLastDirtyTime;
}
/**
* Create a DynamicResource for this RecyclerView.
* The view resource can be obtained by {@link #getResourceId} in compositor layer.
*/
void createDynamicView(DynamicResourceLoader loader) {
mDynamicView = new ViewResourceAdapter(this);
mDynamicView = new ViewResourceAdapter(this) {
@Override
public boolean isDirty() {
boolean dirty = super.isDirty();
if (dirty) mLastDirtyTime = SystemClock.elapsedRealtime();
return dirty;
}
};
loader.registerResource(getResourceId(), mDynamicView);
}
......
......@@ -50,7 +50,7 @@ public class CompositorAnimator extends Animator {
* See {@link ValueAnimator}.
**/
@VisibleForTesting
static float sDurationScale = 1;
public static float sDurationScale = 1;
/** The {@link CompositorAnimationHandler} running the animation. */
private final WeakReference<CompositorAnimationHandler> mHandler;
......
......@@ -15,6 +15,7 @@ import android.os.SystemClock;
import org.chromium.base.ContextUtils;
import org.chromium.base.Log;
import org.chromium.base.Supplier;
import org.chromium.base.VisibleForTesting;
import org.chromium.chrome.browser.ChromeFeatureList;
import org.chromium.chrome.browser.ChromeVersionInfo;
import org.chromium.chrome.browser.compositor.LayerTitleCache;
......@@ -47,7 +48,8 @@ public class GridTabSwitcherLayout
private static final String TAG = "GTSLayout";
// Duration of the transition animation
private static final long ZOOMING_DURATION = 300;
@VisibleForTesting
static final long ZOOMING_DURATION = 300;
// The transition animation from a tab to the tab switcher.
private AnimatorSet mTabToSwitcherAnimation;
......@@ -62,6 +64,12 @@ public class GridTabSwitcherLayout
private long mStartTime;
private int mStartFrame;
interface PerfListener {
void onAnimationDone(int frameRendered, long elapsedMs, int dirtySpan);
}
private PerfListener mPerfListenerForTesting;
public GridTabSwitcherLayout(Context context, LayoutUpdateHost updateHost,
LayoutRenderHost renderHost, GridTabSwitcher gridTabSwitcher) {
super(context, updateHost, renderHost);
......@@ -214,10 +222,7 @@ public class GridTabSwitcherLayout
// Step 2: fade in the real GTS RecyclerView.
mGridController.showOverview(true);
// TODO(crbug.com/964406): stop reporting on Canary before enabling in Finch.
if (ChromeVersionInfo.isLocalBuild() || ChromeVersionInfo.isCanaryBuild()) {
reportAnimationPerf();
}
reportAnimationPerf();
}
});
mStartFrame = mFrameCount;
......@@ -225,14 +230,34 @@ public class GridTabSwitcherLayout
mTabToSwitcherAnimation.start();
}
void setPerfListenerForTesting(PerfListener perfListener) {
mPerfListenerForTesting = perfListener;
}
@VisibleForTesting
GridTabSwitcher getGridTabSwitcherForTesting() {
return mGridTabSwitcher;
}
private void reportAnimationPerf() {
int frameRendered = mFrameCount - mStartFrame;
long elapsedMs = SystemClock.elapsedRealtime() - mStartTime;
String message = String.format(Locale.US, "fps = %.2f (%d / %dms)",
(1000.f * frameRendered / elapsedMs), frameRendered, elapsedMs);
long lastDirty = mGridTabSwitcher.getLastDirtyTimeForTesting();
int dirtySpan = (int) (lastDirty - mStartTime);
float fps = 1000.f * frameRendered / elapsedMs;
String message = String.format(Locale.US, "fps = %.2f (%d / %dms), dirtySpan = %d", fps,
frameRendered, elapsedMs, dirtySpan);
// TODO(crbug.com/964406): stop reporting on Canary before enabling in Finch.
if (ChromeVersionInfo.isLocalBuild() || ChromeVersionInfo.isCanaryBuild()) {
Toast.makeText(ContextUtils.getApplicationContext(), message, Toast.LENGTH_SHORT)
.show();
Log.i(TAG, message);
}
Toast.makeText(ContextUtils.getApplicationContext(), message, Toast.LENGTH_SHORT).show();
Log.i(TAG, message);
if (mPerfListenerForTesting != null) {
mPerfListenerForTesting.onAnimationDone(frameRendered, elapsedMs, dirtySpan);
}
}
@Override
......
// Copyright 2019 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
package org.chromium.chrome.browser.widget;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import static org.chromium.base.test.util.CallbackHelper.WAIT_TIMEOUT_SECONDS;
import static org.chromium.chrome.browser.UrlConstants.NTP_URL;
import static org.chromium.content_public.browser.test.util.CriteriaHelper.DEFAULT_MAX_TIME_TO_POLL;
import static org.chromium.content_public.browser.test.util.CriteriaHelper.DEFAULT_POLLING_INTERVAL;
import android.support.annotation.Nullable;
import android.support.test.InstrumentationRegistry;
import android.support.test.filters.LargeTest;
import android.support.test.filters.MediumTest;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TestRule;
import org.junit.runner.RunWith;
import org.chromium.base.Log;
import org.chromium.base.test.util.CommandLineFlags;
import org.chromium.chrome.browser.ChromeFeatureList;
import org.chromium.chrome.browser.ChromeSwitches;
import org.chromium.chrome.browser.compositor.animation.CompositorAnimator;
import org.chromium.chrome.browser.compositor.layouts.Layout;
import org.chromium.chrome.browser.tasks.tab_management.GridTabSwitcher;
import org.chromium.chrome.browser.util.FeatureUtilities;
import org.chromium.chrome.test.ChromeJUnit4ClassRunner;
import org.chromium.chrome.test.ChromeTabbedActivityTestRule;
import org.chromium.chrome.test.util.ChromeTabUtils;
import org.chromium.chrome.test.util.MenuUtils;
import org.chromium.chrome.test.util.browser.Features;
import org.chromium.content_public.browser.test.util.CriteriaHelper;
import org.chromium.content_public.browser.test.util.TestThreadUtils;
import org.chromium.net.test.EmbeddedTestServer;
import java.util.Arrays;
import java.util.LinkedList;
import java.util.List;
/** Tests for the {@link GridTabSwitcherLayout}, mainly for animation performance. */
@RunWith(ChromeJUnit4ClassRunner.class)
@CommandLineFlags.Add({ChromeSwitches.DISABLE_FIRST_RUN_EXPERIENCE})
@Features.EnableFeatures(ChromeFeatureList.TAB_TO_GTS_ANIMATION)
public class GridTabSwitcherLayoutPerfTest {
private static final String TAG = "GTSLayoutTest";
/** Flip this to {@code true} to run performance tests locally. */
private static final boolean PERF_RUN = false;
@Rule
public ChromeTabbedActivityTestRule mActivityTestRule = new ChromeTabbedActivityTestRule();
@Rule
public TestRule mProcessor = new Features.InstrumentationProcessor();
private EmbeddedTestServer mTestServer;
private GridTabSwitcherLayout mGtsLayout;
private String mUrl;
private int mRepeat;
private long mWaitingTime;
private int mTabNumCap;
@Before
public void setUp() throws InterruptedException {
FeatureUtilities.setGridTabSwitcherEnabledForTesting(true);
mTestServer = EmbeddedTestServer.createAndStartServer(InstrumentationRegistry.getContext());
mActivityTestRule.startMainActivityFromLauncher();
Layout layout = mActivityTestRule.getActivity().getLayoutManager().getOverviewLayout();
assertTrue(layout instanceof GridTabSwitcherLayout);
mGtsLayout = (GridTabSwitcherLayout) layout;
mUrl = mTestServer.getURL("/chrome/test/data/android/navigate/simple.html");
mRepeat = 2;
mWaitingTime = 0;
mTabNumCap = 3;
if (PERF_RUN) {
mRepeat = 30;
// Wait before the animation to get more stable results.
mWaitingTime = 1000;
mTabNumCap = 0;
}
}
@Test
@MediumTest
public void testTabToGridFromLiveTab() throws InterruptedException {
prepareTabs(1, NTP_URL);
reportTabToGridPerf(mUrl, "Tab-to-Grid from live tab");
}
@Test
@MediumTest
public void testTabToGridFromLiveTabWith10Tabs() throws InterruptedException {
prepareTabs(10, NTP_URL);
reportTabToGridPerf(mUrl, "Tab-to-Grid from live tab with 10 tabs");
}
@Test
@MediumTest
public void testTabToGridFromLiveTabWith10TabsWithoutThumbnail() throws InterruptedException {
// Note that most of the tabs won't have thumbnails.
prepareTabs(10, null);
reportTabToGridPerf(mUrl, "Tab-to-Grid from live tab with 10 tabs without thumbnails");
}
@Test
@LargeTest
public void testTabToGridFromLiveTabWith100Tabs() throws InterruptedException {
// Skip waiting for loading. Otherwise it would take too long.
// Note that most of the tabs won't have thumbnails.
prepareTabs(100, null);
reportTabToGridPerf(mUrl, "Tab-to-Grid from live tab with 100 tabs without thumbnails");
}
@Test
@MediumTest
public void testTabToGridFromNtp() throws InterruptedException {
prepareTabs(1, NTP_URL);
reportTabToGridPerf(NTP_URL, "Tab-to-Grid from NTP");
}
/**
* Make Chrome have {@code numTabs} or Tabs with {@code url} loaded.
* @param url The URL to load. Skip loading when null, but the thumbnail for the NTP might not
* be saved.
*/
private void prepareTabs(int numTabs, @Nullable String url) throws InterruptedException {
assertTrue(numTabs >= 1);
assertEquals(1, mActivityTestRule.getActivity().getTabModelSelector().getTotalTabCount());
// Only run the full size when doing local perf tests.
if (mTabNumCap > 0) numTabs = Math.min(numTabs, mTabNumCap);
if (url != null) mActivityTestRule.loadUrl(url);
for (int i = 0; i < numTabs - 1; i++) {
MenuUtils.invokeCustomMenuActionSync(InstrumentationRegistry.getInstrumentation(),
mActivityTestRule.getActivity(), org.chromium.chrome.R.id.new_tab_menu_id);
if (url != null) mActivityTestRule.loadUrl(url);
}
ChromeTabUtils.waitForTabPageLoaded(mActivityTestRule.getActivity().getActivityTab(), null,
null, WAIT_TIMEOUT_SECONDS * 10);
assertEquals(
numTabs, mActivityTestRule.getActivity().getTabModelSelector().getTotalTabCount());
}
private void reportTabToGridPerf(String fromUrl, String description)
throws InterruptedException {
List<Float> frameRates = new LinkedList<>();
List<Float> dirtySpans = new LinkedList<>();
GridTabSwitcherLayout.PerfListener collector = (frameRendered, elapsedMs, dirtySpan) -> {
assertTrue(elapsedMs
>= GridTabSwitcherLayout.ZOOMING_DURATION * CompositorAnimator.sDurationScale);
float fps = 1000.f * frameRendered / elapsedMs;
frameRates.add(fps);
dirtySpans.add((float) dirtySpan);
};
mActivityTestRule.loadUrl(fromUrl);
Thread.sleep(mWaitingTime);
GridTabSwitcher gts = mGtsLayout.getGridTabSwitcherForTesting();
for (int i = 0; i < mRepeat; i++) {
mGtsLayout.setPerfListenerForTesting(collector);
Thread.sleep(mWaitingTime);
TestThreadUtils.runOnUiThreadBlocking(
() -> mActivityTestRule.getActivity().getLayoutManager().showOverview(true));
final int expectedSize = i + 1;
CriteriaHelper.pollInstrumentationThread(() -> frameRates.size() == expectedSize,
"Have not got PerfListener callback", DEFAULT_MAX_TIME_TO_POLL * 10,
DEFAULT_POLLING_INTERVAL);
assertTrue(mActivityTestRule.getActivity().getLayoutManager().overviewVisible());
mGtsLayout.setPerfListenerForTesting(null);
// Make sure the fading animation is done.
Thread.sleep(1000);
TestThreadUtils.runOnUiThreadBlocking(
() -> gts.getGridController().hideOverview(false));
Thread.sleep(1000);
CriteriaHelper.pollInstrumentationThread(
() -> !mActivityTestRule.getActivity().getLayoutManager().overviewVisible(),
"Overview not hidden yet", DEFAULT_MAX_TIME_TO_POLL * 10,
DEFAULT_POLLING_INTERVAL);
}
assertEquals(mRepeat, frameRates.size());
Log.i(TAG, "%s: fps = %.2f, dirtySpan = %.0f", description, median(frameRates),
median(dirtySpans));
}
private float median(List<Float> list) {
float[] array = new float[list.size()];
for (int i = 0; i < array.length; i++) {
array[i] = list.get(i);
}
Arrays.sort(array);
return array[array.length / 2];
}
}
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment