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 = [ ...@@ -494,6 +494,7 @@ chrome_test_java_sources = [
"javatests/src/org/chromium/chrome/browser/webapps/WebappVisibilityTest.java", "javatests/src/org/chromium/chrome/browser/webapps/WebappVisibilityTest.java",
"javatests/src/org/chromium/chrome/browser/webauth/AuthenticatorTest.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/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/OverviewListLayoutTest.java",
"javatests/src/org/chromium/chrome/browser/widget/PromoDialogTest.java", "javatests/src/org/chromium/chrome/browser/widget/PromoDialogTest.java",
"javatests/src/org/chromium/chrome/browser/widget/RadioButtonLayoutTest.java", "javatests/src/org/chromium/chrome/browser/widget/RadioButtonLayoutTest.java",
......
...@@ -9,6 +9,7 @@ import android.support.annotation.NonNull; ...@@ -9,6 +9,7 @@ import android.support.annotation.NonNull;
import org.chromium.chrome.browser.compositor.layouts.OverviewModeBehavior; import org.chromium.chrome.browser.compositor.layouts.OverviewModeBehavior;
import org.chromium.chrome.browser.compositor.layouts.OverviewModeController; import org.chromium.chrome.browser.compositor.layouts.OverviewModeController;
import org.chromium.ui.resources.dynamics.ViewResourceAdapter;
/** /**
* Interface for the Grid Tab Switcher. * Interface for the Grid Tab Switcher.
...@@ -33,6 +34,12 @@ public interface GridTabSwitcher { ...@@ -33,6 +34,12 @@ public interface GridTabSwitcher {
*/ */
int getResourceId(); 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 * Before calling {@link OverviewModeController#showOverview} to start showing the
* GridTabSwitcher {@link TabListRecyclerView}, call this to populate it without making it * GridTabSwitcher {@link TabListRecyclerView}, call this to populate it without making it
......
...@@ -122,6 +122,11 @@ public class GridTabSwitcherCoordinator ...@@ -122,6 +122,11 @@ public class GridTabSwitcherCoordinator
return mTabGridCoordinator.getResourceId(); return mTabGridCoordinator.getResourceId();
} }
@Override
public long getLastDirtyTimeForTesting() {
return mTabGridCoordinator.getLastDirtyTimeForTesting();
}
/** /**
* Reset the tab grid with the given {@link TabModel}. Can be null. * 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. * @param tabList The current {@link TabList} to show the tabs for in the grid.
......
...@@ -208,4 +208,8 @@ public class TabListCoordinator implements Destroyable { ...@@ -208,4 +208,8 @@ public class TabListCoordinator implements Destroyable {
int getResourceId() { int getResourceId() {
return mRecyclerView.getResourceId(); return mRecyclerView.getResourceId();
} }
long getLastDirtyTimeForTesting() {
return mRecyclerView.getLastDirtyTimeForTesting();
}
} }
...@@ -11,6 +11,7 @@ import android.animation.ValueAnimator; ...@@ -11,6 +11,7 @@ import android.animation.ValueAnimator;
import android.annotation.SuppressLint; import android.annotation.SuppressLint;
import android.content.Context; import android.content.Context;
import android.graphics.Rect; import android.graphics.Rect;
import android.os.SystemClock;
import android.support.annotation.Nullable; import android.support.annotation.Nullable;
import android.support.v7.widget.RecyclerView; import android.support.v7.widget.RecyclerView;
import android.util.AttributeSet; import android.util.AttributeSet;
...@@ -58,6 +59,7 @@ class TabListRecyclerView extends RecyclerView { ...@@ -58,6 +59,7 @@ class TabListRecyclerView extends RecyclerView {
private ValueAnimator mFadeOutAnimator; private ValueAnimator mFadeOutAnimator;
private VisibilityListener mListener; private VisibilityListener mListener;
private ViewResourceAdapter mDynamicView; private ViewResourceAdapter mDynamicView;
private long mLastDirtyTime;
private long mOriginalAddDuration; private long mOriginalAddDuration;
/** /**
...@@ -119,12 +121,23 @@ class TabListRecyclerView extends RecyclerView { ...@@ -119,12 +121,23 @@ class TabListRecyclerView extends RecyclerView {
return getId(); return getId();
} }
long getLastDirtyTimeForTesting() {
return mLastDirtyTime;
}
/** /**
* Create a DynamicResource for this RecyclerView. * Create a DynamicResource for this RecyclerView.
* The view resource can be obtained by {@link #getResourceId} in compositor layer. * The view resource can be obtained by {@link #getResourceId} in compositor layer.
*/ */
void createDynamicView(DynamicResourceLoader loader) { 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); loader.registerResource(getResourceId(), mDynamicView);
} }
......
...@@ -50,7 +50,7 @@ public class CompositorAnimator extends Animator { ...@@ -50,7 +50,7 @@ public class CompositorAnimator extends Animator {
* See {@link ValueAnimator}. * See {@link ValueAnimator}.
**/ **/
@VisibleForTesting @VisibleForTesting
static float sDurationScale = 1; public static float sDurationScale = 1;
/** The {@link CompositorAnimationHandler} running the animation. */ /** The {@link CompositorAnimationHandler} running the animation. */
private final WeakReference<CompositorAnimationHandler> mHandler; private final WeakReference<CompositorAnimationHandler> mHandler;
......
...@@ -15,6 +15,7 @@ import android.os.SystemClock; ...@@ -15,6 +15,7 @@ import android.os.SystemClock;
import org.chromium.base.ContextUtils; import org.chromium.base.ContextUtils;
import org.chromium.base.Log; import org.chromium.base.Log;
import org.chromium.base.Supplier; import org.chromium.base.Supplier;
import org.chromium.base.VisibleForTesting;
import org.chromium.chrome.browser.ChromeFeatureList; import org.chromium.chrome.browser.ChromeFeatureList;
import org.chromium.chrome.browser.ChromeVersionInfo; import org.chromium.chrome.browser.ChromeVersionInfo;
import org.chromium.chrome.browser.compositor.LayerTitleCache; import org.chromium.chrome.browser.compositor.LayerTitleCache;
...@@ -47,7 +48,8 @@ public class GridTabSwitcherLayout ...@@ -47,7 +48,8 @@ public class GridTabSwitcherLayout
private static final String TAG = "GTSLayout"; private static final String TAG = "GTSLayout";
// Duration of the transition animation // 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. // The transition animation from a tab to the tab switcher.
private AnimatorSet mTabToSwitcherAnimation; private AnimatorSet mTabToSwitcherAnimation;
...@@ -62,6 +64,12 @@ public class GridTabSwitcherLayout ...@@ -62,6 +64,12 @@ public class GridTabSwitcherLayout
private long mStartTime; private long mStartTime;
private int mStartFrame; private int mStartFrame;
interface PerfListener {
void onAnimationDone(int frameRendered, long elapsedMs, int dirtySpan);
}
private PerfListener mPerfListenerForTesting;
public GridTabSwitcherLayout(Context context, LayoutUpdateHost updateHost, public GridTabSwitcherLayout(Context context, LayoutUpdateHost updateHost,
LayoutRenderHost renderHost, GridTabSwitcher gridTabSwitcher) { LayoutRenderHost renderHost, GridTabSwitcher gridTabSwitcher) {
super(context, updateHost, renderHost); super(context, updateHost, renderHost);
...@@ -214,10 +222,7 @@ public class GridTabSwitcherLayout ...@@ -214,10 +222,7 @@ public class GridTabSwitcherLayout
// Step 2: fade in the real GTS RecyclerView. // Step 2: fade in the real GTS RecyclerView.
mGridController.showOverview(true); mGridController.showOverview(true);
// TODO(crbug.com/964406): stop reporting on Canary before enabling in Finch. reportAnimationPerf();
if (ChromeVersionInfo.isLocalBuild() || ChromeVersionInfo.isCanaryBuild()) {
reportAnimationPerf();
}
} }
}); });
mStartFrame = mFrameCount; mStartFrame = mFrameCount;
...@@ -225,14 +230,34 @@ public class GridTabSwitcherLayout ...@@ -225,14 +230,34 @@ public class GridTabSwitcherLayout
mTabToSwitcherAnimation.start(); mTabToSwitcherAnimation.start();
} }
void setPerfListenerForTesting(PerfListener perfListener) {
mPerfListenerForTesting = perfListener;
}
@VisibleForTesting
GridTabSwitcher getGridTabSwitcherForTesting() {
return mGridTabSwitcher;
}
private void reportAnimationPerf() { private void reportAnimationPerf() {
int frameRendered = mFrameCount - mStartFrame; int frameRendered = mFrameCount - mStartFrame;
long elapsedMs = SystemClock.elapsedRealtime() - mStartTime; long elapsedMs = SystemClock.elapsedRealtime() - mStartTime;
String message = String.format(Locale.US, "fps = %.2f (%d / %dms)", long lastDirty = mGridTabSwitcher.getLastDirtyTimeForTesting();
(1000.f * frameRendered / elapsedMs), frameRendered, elapsedMs); 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(); if (mPerfListenerForTesting != null) {
Log.i(TAG, message); mPerfListenerForTesting.onAnimationDone(frameRendered, elapsedMs, dirtySpan);
}
} }
@Override @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