summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authornduca@chromium.org <nduca@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2013-01-12 00:53:27 +0000
committernduca@chromium.org <nduca@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2013-01-12 00:53:27 +0000
commitc6b768a124789c0ceb775a4811b146b3ecfe2a62 (patch)
tree8798788664b7536ed66b35d39f7b5c30bc53695d
parenta7002b5302c08588866d0298881d7c92cd4853d5 (diff)
downloadchromium_src-c6b768a124789c0ceb775a4811b146b3ecfe2a62.zip
chromium_src-c6b768a124789c0ceb775a4811b146b3ecfe2a62.tar.gz
chromium_src-c6b768a124789c0ceb775a4811b146b3ecfe2a62.tar.bz2
Split ScrollingInteraction into measurement, benchmark and true interaction components
We had code that did measurement wrapped up in the code to just scroll the page down. I moved this to smoothness_measurement.py and js. Scrolling benchmark now uses that R=hartmanng CC=marja NOTRY=True Review URL: https://chromiumcodereview.appspot.com/11865009 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@176483 0039d316-1c4b-4281-b951-d872f2087c98
-rwxr-xr-xtools/perf/perf_tools/skpicture_printer.py1
-rw-r--r--tools/perf/perf_tools/smoothness_benchmark.py10
-rw-r--r--tools/perf/perf_tools/smoothness_benchmark_unittest.py50
-rw-r--r--tools/perf/perf_tools/smoothness_measurement.js161
-rw-r--r--tools/perf/perf_tools/smoothness_measurement.py46
-rw-r--r--tools/telemetry/telemetry/browser_backend.py3
-rw-r--r--tools/telemetry/telemetry/click_element_interaction.py2
-rw-r--r--tools/telemetry/telemetry/click_element_interaction_unittest.py4
-rw-r--r--tools/telemetry/telemetry/compound_interaction.py8
-rw-r--r--tools/telemetry/telemetry/compound_interaction_unittest.py6
-rw-r--r--tools/telemetry/telemetry/cros_browser_backend.py2
-rw-r--r--tools/telemetry/telemetry/multi_page_benchmark_unittest.py2
-rw-r--r--tools/telemetry/telemetry/options_for_unittests.py5
-rw-r--r--tools/telemetry/telemetry/page_interaction.py10
-rw-r--r--tools/telemetry/telemetry/page_test.py18
-rwxr-xr-xtools/telemetry/telemetry/record_wpr.py3
-rw-r--r--tools/telemetry/telemetry/scrolling_interaction.js (renamed from tools/telemetry/telemetry/scroll.js)136
-rw-r--r--tools/telemetry/telemetry/scrolling_interaction.py36
-rw-r--r--tools/telemetry/telemetry/scrolling_interaction_unittest.py69
-rw-r--r--tools/telemetry/telemetry/wait_interaction.py2
-rw-r--r--tools/telemetry/telemetry/wait_interaction_unittest.py2
21 files changed, 365 insertions, 211 deletions
diff --git a/tools/perf/perf_tools/skpicture_printer.py b/tools/perf/perf_tools/skpicture_printer.py
index ba88bf5..d2780fb 100755
--- a/tools/perf/perf_tools/skpicture_printer.py
+++ b/tools/perf/perf_tools/skpicture_printer.py
@@ -1,4 +1,3 @@
-#!/usr/bin/env python
# Copyright (c) 2012 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.
diff --git a/tools/perf/perf_tools/smoothness_benchmark.py b/tools/perf/perf_tools/smoothness_benchmark.py
index 461b4a4..c0cbcd5 100644
--- a/tools/perf/perf_tools/smoothness_benchmark.py
+++ b/tools/perf/perf_tools/smoothness_benchmark.py
@@ -1,7 +1,7 @@
# Copyright (c) 2012 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.
-
+from perf_tools import smoothness_measurement
from telemetry import multi_page_benchmark
from telemetry import util
@@ -147,6 +147,7 @@ class SmoothnessBenchmark(multi_page_benchmark.MultiPageBenchmark):
super(SmoothnessBenchmark, self).__init__('smoothness')
self.force_enable_threaded_compositing = False
self.use_gpu_benchmarking_extension = True
+ self._measurement = None
def AddCommandLineOptions(self, parser):
parser.add_option('--report-all-results', dest='report_all_results',
@@ -162,9 +163,12 @@ class SmoothnessBenchmark(multi_page_benchmark.MultiPageBenchmark):
def CanRunForPage(self, page):
return hasattr(page, 'smoothness')
+ def WillRunInteraction(self, page, tab):
+ self._measurement = smoothness_measurement.SmoothnessMeasurement(tab)
+ self._measurement.bindToScrollInteraction()
+
def MeasurePage(self, page, tab, results):
- rendering_stats_deltas = tab.runtime.Evaluate(
- 'window.__renderingStatsDeltas')
+ rendering_stats_deltas = self._measurement.deltas
if not (rendering_stats_deltas['numFramesSentToScreen'] > 0):
raise DidNotScrollException()
diff --git a/tools/perf/perf_tools/smoothness_benchmark_unittest.py b/tools/perf/perf_tools/smoothness_benchmark_unittest.py
index 506eb82..60b0ded 100644
--- a/tools/perf/perf_tools/smoothness_benchmark_unittest.py
+++ b/tools/perf/perf_tools/smoothness_benchmark_unittest.py
@@ -5,13 +5,8 @@ from telemetry import multi_page_benchmark_unittest_base
from telemetry import page
from perf_tools import smoothness_benchmark
-from telemetry import browser_finder
-from telemetry import options_for_unittests
from telemetry.page_benchmark_results import PageBenchmarkResults
-import os
-import urlparse
-
class SmoothnessBenchmarkUnitTest(
multi_page_benchmark_unittest_base.MultiPageBenchmarkUnitTestBase):
@@ -79,51 +74,6 @@ class SmoothnessBenchmarkUnitTest(
1000/60.,
res.page_results[0]['mean_frame_time'].value, 2)
- def testBoundingClientRect(self):
- options = options_for_unittests.GetCopy()
- browser_to_create = browser_finder.FindBrowser(options)
- if not browser_to_create:
- raise Exception('No browser found, cannot continue test.')
-
- with browser_to_create.Create() as browser:
- tab = browser.tabs[0]
- ps = self.CreatePageSetFromFileInUnittestDataDir('blank.html')
- parsed_url = urlparse.urlparse(ps.pages[0].url)
- path = os.path.join(parsed_url.netloc, parsed_url.path)
- dirname, filename = os.path.split(path)
- dirname = os.path.join(ps.base_dir, dirname[1:])
- browser.SetHTTPServerDirectory(dirname)
- target_side_url = browser.http_server.UrlOf(filename)
- tab.page.Navigate(target_side_url)
-
- # Verify that the rect returned by getBoundingVisibleRect() in
- # scroll.js is completely contained within the viewport. Scroll
- # events dispatched by the benchmarks use the center of this rect
- # as their location, and this location needs to be within the
- # viewport bounds to correctly decide between main-thread and
- # impl-thread scrolling. If the scrollable area were not clipped
- # to the viewport bounds, then the instance used here (the scrollable
- # area being more than twice as tall as the viewport) would
- # result in a scroll location outside of the viewport bounds.
- tab.runtime.Execute("""document.body.style.height =
- (2 * window.innerHeight + 1) + 'px';""")
- scroll_js_path = os.path.join(os.path.dirname(__file__), '..', '..',
- 'telemetry', 'telemetry', 'scroll.js')
- scroll_js = open(scroll_js_path, 'r').read()
- tab.runtime.Evaluate(scroll_js)
-
- rect_bottom = int(tab.runtime.Evaluate("""
- __ScrollTest_GetBoundingVisibleRect(document.body).top +
- __ScrollTest_GetBoundingVisibleRect(document.body).height"""))
- rect_right = int(tab.runtime.Evaluate("""
- __ScrollTest_GetBoundingVisibleRect(document.body).left +
- __ScrollTest_GetBoundingVisibleRect(document.body).width"""))
- viewport_width = int(tab.runtime.Evaluate('window.innerWidth'))
- viewport_height = int(tab.runtime.Evaluate('window.innerHeight'))
-
- self.assertTrue(rect_bottom <= viewport_height)
- self.assertTrue(rect_right <= viewport_width)
-
def testDoesImplThreadScroll(self):
ps = self.CreatePageSetFromFileInUnittestDataDir('scrollable_page.html')
diff --git a/tools/perf/perf_tools/smoothness_measurement.js b/tools/perf/perf_tools/smoothness_measurement.js
new file mode 100644
index 0000000..22336fe
--- /dev/null
+++ b/tools/perf/perf_tools/smoothness_measurement.js
@@ -0,0 +1,161 @@
+// Copyright (c) 2012 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.
+
+'use strict';
+
+/**
+ * @fileoverview This file provides the RenderingStats object, used
+ * to characterize rendering smoothness.
+ */
+(function() {
+ var getTimeMs = (function() {
+ if (window.performance)
+ return (performance.now ||
+ performance.mozNow ||
+ performance.msNow ||
+ performance.oNow ||
+ performance.webkitNow).bind(window.performance);
+ else
+ return function() { return new Date().getTime(); };
+ })();
+
+ var requestAnimationFrame = (function() {
+ return window.requestAnimationFrame ||
+ window.webkitRequestAnimationFrame ||
+ window.mozRequestAnimationFrame ||
+ window.oRequestAnimationFrame ||
+ window.msRequestAnimationFrame ||
+ function(callback) {
+ window.setTimeout(callback, 1000 / 60);
+ };
+ })().bind(window);
+
+ /**
+ * Tracks rendering performance using the gpuBenchmarking.renderingStats API.
+ * @constructor
+ */
+ function GpuBenchmarkingRenderingStats() {
+ }
+
+ GpuBenchmarkingRenderingStats.prototype.start = function() {
+ this.startTime_ = getTimeMs();
+ this.initialStats_ = this.getRenderingStats_();
+ }
+
+ GpuBenchmarkingRenderingStats.prototype.stop = function() {
+ this.stopTime_ = getTimeMs();
+ this.finalStats_ = this.getRenderingStats_();
+ }
+
+ GpuBenchmarkingRenderingStats.prototype.getStartValues = function() {
+ if (!this.initialStats_)
+ throw new Error('Start not called.');
+
+ if (!this.finalStats_)
+ throw new Error('Stop was not called.');
+
+ return this.initialStats_;
+ }
+
+ GpuBenchmarkingRenderingStats.prototype.getEndValues = function() {
+ if (!this.initialStats_)
+ throw new Error('Start not called.');
+
+ if (!this.finalStats_)
+ throw new Error('Stop was not called.');
+
+ return this.finalStats_;
+ }
+
+ GpuBenchmarkingRenderingStats.prototype.getDeltas = function() {
+ if (!this.initialStats_)
+ throw new Error('Start not called.');
+
+ if (!this.finalStats_)
+ throw new Error('Stop was not called.');
+
+ var stats = {}
+ for (var key in this.finalStats_)
+ stats[key] = this.finalStats_[key] - this.initialStats_[key];
+ return stats;
+ };
+
+ GpuBenchmarkingRenderingStats.prototype.getRenderingStats_ = function() {
+ var stats = chrome.gpuBenchmarking.renderingStats();
+ stats.totalTimeInSeconds = getTimeMs() / 1000;
+ return stats;
+ };
+
+ /**
+ * Tracks rendering performance using requestAnimationFrame.
+ * @constructor
+ */
+ function RafRenderingStats() {
+ this.recording_ = false;
+ this.frameTimes_ = [];
+ }
+
+ RafRenderingStats.prototype.start = function() {
+ if (this.recording_)
+ throw new Error('Already started.');
+ this.recording_ = true;
+ requestAnimationFrame(this.recordFrameTime_.bind(this));
+ }
+
+ RafRenderingStats.prototype.stop = function() {
+ this.recording_ = false;
+ }
+
+ RafRenderingStats.prototype.getStartValues = function() {
+ var results = {};
+ results.numAnimationFrames = 0;
+ results.numFramesSentToScreen = 0;
+ results.droppedFrameCount = 0;
+ return results;
+ }
+
+ RafRenderingStats.prototype.getEndValues = function() {
+ var results = {};
+ results.numAnimationFrames = this.frameTimes_.length - 1;
+ results.numFramesSentToScreen = results.numAnimationFrames;
+ results.droppedFrameCount = this.getDroppedFrameCount_(this.frameTimes_);
+ return results;
+ }
+
+ RafRenderingStats.prototype.getDeltas = function() {
+ var endValues = this.getEndValues();
+ endValues.totalTimeInSeconds = (
+ this.frameTimes_[this.frameTimes_.length - 1] -
+ this.frameTimes_[0]) / 1000;
+ return endValues;
+ };
+
+ RafRenderingStats.prototype.recordFrameTime_ = function(timestamp) {
+ if (!this.recording_)
+ return;
+
+ this.frameTimes_.push(timestamp);
+ requestAnimationFrame(this.recordFrameTime_.bind(this));
+ };
+
+ RafRenderingStats.prototype.getDroppedFrameCount_ = function(frameTimes) {
+ var droppedFrameCount = 0;
+ for (var i = 1; i < frameTimes.length; i++) {
+ var frameTime = frameTimes[i] - frameTimes[i-1];
+ if (frameTime > 1000 / 55)
+ droppedFrameCount++;
+ }
+ return droppedFrameCount;
+ };
+
+ function RenderingStats() {
+ if (window.chrome && chrome.gpuBenchmarking &&
+ chrome.gpuBenchmarking.renderingStats) {
+ return new GpuBenchmarkingRenderingStats();
+ }
+ return new RafRenderingStats();
+ }
+
+ window.__RenderingStats = RenderingStats;
+})();
diff --git a/tools/perf/perf_tools/smoothness_measurement.py b/tools/perf/perf_tools/smoothness_measurement.py
new file mode 100644
index 0000000..774ac81
--- /dev/null
+++ b/tools/perf/perf_tools/smoothness_measurement.py
@@ -0,0 +1,46 @@
+# Copyright (c) 2012 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.
+import os
+
+class SmoothnessMeasurement(object):
+ def __init__(self, tab):
+ self._tab = tab
+ # Bring in the smoothness benchmark
+ with open(
+ os.path.join(os.path.dirname(__file__),
+ 'smoothness_measurement.js')) as f:
+ js = f.read()
+ tab.runtime.Execute(js)
+
+ def bindToScrollInteraction(self):
+ tab = self._tab
+ if tab.runtime.Evaluate('window.__scrollingInteraction === undefined'):
+ raise Exception('bindToScrollInteraction requires ' +
+ 'window.__scrollingInteraction to be set.')
+
+ # Make the scroll test start and stop measurement automatically.
+ tab.runtime.Execute("""
+ window.__renderingStats = new __RenderingStats();
+ window.__scrollingInteraction.beginMeasuringHook = function() {
+ window.__renderingStats.start();
+ };
+ window.__scrollingInteraction.endMeasuringHook = function() {
+ window.__renderingStats.stop();
+ };
+ """)
+
+ @property
+ def start_values(self):
+ return self._tab.runtime.Evaluate(
+ 'window.__renderingStats.getStartValues()')
+
+ @property
+ def end_values(self):
+ return self._tab.runtime.Evaluate(
+ 'window.__renderingStats.getEndValues()')
+
+ @property
+ def deltas(self):
+ return self._tab.runtime.Evaluate(
+ 'window.__renderingStats.getDeltas()')
diff --git a/tools/telemetry/telemetry/browser_backend.py b/tools/telemetry/telemetry/browser_backend.py
index 57c4f93..914be71 100644
--- a/tools/telemetry/telemetry/browser_backend.py
+++ b/tools/telemetry/telemetry/browser_backend.py
@@ -11,6 +11,7 @@ import sys
import weakref
from telemetry import browser_gone_exception
+from telemetry import options_for_unittests
from telemetry import tab
from telemetry import tracing_backend
from telemetry import user_agent
@@ -147,7 +148,7 @@ class BrowserBackend(object):
self._webkit_base_revision = 0
self._tracing_backend = None
- if options.dont_override_profile:
+ if options.dont_override_profile and not options_for_unittests.AreSet():
sys.stderr.write('Warning: Not overriding profile. This can cause '
'unexpected effects due to profile-specific settings, '
'such as about:flags settings, cookies, and '
diff --git a/tools/telemetry/telemetry/click_element_interaction.py b/tools/telemetry/telemetry/click_element_interaction.py
index 8c1b5ca..378d852 100644
--- a/tools/telemetry/telemetry/click_element_interaction.py
+++ b/tools/telemetry/telemetry/click_element_interaction.py
@@ -10,7 +10,7 @@ class ClickElementInteraction(page_interaction.PageInteraction):
def __init__(self, attributes=None):
super(ClickElementInteraction, self).__init__(attributes)
- def PerformInteraction(self, page, tab):
+ def RunInteraction(self, page, tab):
def DoClick():
assert hasattr(self, 'selector') or hasattr(self, 'text')
if hasattr(self, 'selector'):
diff --git a/tools/telemetry/telemetry/click_element_interaction_unittest.py b/tools/telemetry/telemetry/click_element_interaction_unittest.py
index ebfb240..3dbf80ad 100644
--- a/tools/telemetry/telemetry/click_element_interaction_unittest.py
+++ b/tools/telemetry/telemetry/click_element_interaction_unittest.py
@@ -19,7 +19,7 @@ class ClickElementInteractionTest(tab_test_case.TabTestCase):
data = {'selector': 'a[id="clickme"]', 'wait_for_navigation': True}
i = click_element_interaction.ClickElementInteraction(data)
- i.PerformInteraction({}, self._tab)
+ i.RunInteraction({}, self._tab)
self.assertEquals(self._tab.runtime.Evaluate('document.location.pathname;'),
'/blank.html')
@@ -36,7 +36,7 @@ class ClickElementInteractionTest(tab_test_case.TabTestCase):
data = {'text': 'Click me', 'wait_for_href_change': True}
i = click_element_interaction.ClickElementInteraction(data)
- i.PerformInteraction({}, self._tab)
+ i.RunInteraction({}, self._tab)
self.assertEquals(self._tab.runtime.Evaluate('document.location.pathname;'),
'/blank.html')
diff --git a/tools/telemetry/telemetry/compound_interaction.py b/tools/telemetry/telemetry/compound_interaction.py
index 5e93cf8..c3694ed 100644
--- a/tools/telemetry/telemetry/compound_interaction.py
+++ b/tools/telemetry/telemetry/compound_interaction.py
@@ -17,6 +17,10 @@ class CompoundInteraction(page_interaction.PageInteraction):
for interaction in self._interaction_list:
interaction.CustomizeBrowserOptions(options)
- def PerformInteraction(self, page, tab):
+ def WillRunInteraction(self, page, tab):
for interaction in self._interaction_list:
- interaction.PerformInteraction(page, tab)
+ interaction.WillRunInteraction(page, tab)
+
+ def RunInteraction(self, page, tab):
+ for interaction in self._interaction_list:
+ interaction.RunInteraction(page, tab)
diff --git a/tools/telemetry/telemetry/compound_interaction_unittest.py b/tools/telemetry/telemetry/compound_interaction_unittest.py
index 23ab392..23ec5f4 100644
--- a/tools/telemetry/telemetry/compound_interaction_unittest.py
+++ b/tools/telemetry/telemetry/compound_interaction_unittest.py
@@ -17,11 +17,11 @@ class CompoundInteractionTest(tab_test_case.TabTestCase):
def testCompoundInteraction(self):
class MockInteraction1(page_interaction.PageInteraction):
- def PerformInteraction(self, page, tab):
+ def RunInteraction(self, page, tab):
CompoundInteractionTest.interaction1_called = True
class MockInteraction2(page_interaction.PageInteraction):
- def PerformInteraction(self, page, tab):
+ def RunInteraction(self, page, tab):
CompoundInteractionTest.interaction2_called = True
all_page_interactions.RegisterClassForTest('mock1', MockInteraction1)
@@ -38,6 +38,6 @@ class CompoundInteractionTest(tab_test_case.TabTestCase):
}
]
})
- i.PerformInteraction({}, self._tab)
+ i.RunInteraction({}, self._tab)
self.assertTrue(CompoundInteractionTest.interaction1_called)
self.assertTrue(CompoundInteractionTest.interaction2_called)
diff --git a/tools/telemetry/telemetry/cros_browser_backend.py b/tools/telemetry/telemetry/cros_browser_backend.py
index 5fed131..2410e91 100644
--- a/tools/telemetry/telemetry/cros_browser_backend.py
+++ b/tools/telemetry/telemetry/cros_browser_backend.py
@@ -111,6 +111,8 @@ class CrOSBrowserBackend(browser_backend.BrowserBackend):
# Wait for the login screen to disappear. This can cause tab_url to be
# None or to not be 'chrome://oobe/login'.
def IsTabNoneOrOobeLogin():
+ if len(self.tabs) == 0:
+ return True
tab_url = self.tabs[0].url
return tab_url is None or tab_url != 'chrome://oobe/login'
diff --git a/tools/telemetry/telemetry/multi_page_benchmark_unittest.py b/tools/telemetry/telemetry/multi_page_benchmark_unittest.py
index 0737484..14e8d4f 100644
--- a/tools/telemetry/telemetry/multi_page_benchmark_unittest.py
+++ b/tools/telemetry/telemetry/multi_page_benchmark_unittest.py
@@ -115,7 +115,7 @@ class MultiPageBenchmarkUnitTest(
def testInteractions(self):
interaction_called = [False]
class MockInteraction(page_interaction.PageInteraction):
- def PerformInteraction(self, page, tab):
+ def RunInteraction(self, page, tab):
interaction_called[0] = True
from telemetry import all_page_interactions
all_page_interactions.RegisterClassForTest('mock', MockInteraction)
diff --git a/tools/telemetry/telemetry/options_for_unittests.py b/tools/telemetry/telemetry/options_for_unittests.py
index 088888c..766a641 100644
--- a/tools/telemetry/telemetry/options_for_unittests.py
+++ b/tools/telemetry/telemetry/options_for_unittests.py
@@ -24,5 +24,10 @@ def GetCopy():
return _options.Copy()
+def AreSet():
+ if _options:
+ return True
+ return False
+
def GetBrowserType():
return _browser_type
diff --git a/tools/telemetry/telemetry/page_interaction.py b/tools/telemetry/telemetry/page_interaction.py
index 57b2d7b..29f589c 100644
--- a/tools/telemetry/telemetry/page_interaction.py
+++ b/tools/telemetry/telemetry/page_interaction.py
@@ -16,10 +16,16 @@ class PageInteraction(object):
setattr(self, k, v)
def CustomizeBrowserOptions(self, options):
- """Override to add test-specific options to the BrowserOptions object"""
+ """Override to add interaction-specific options to the BrowserOptions
+ object"""
pass
- def PerformInteraction(self, page, tab):
+ def WillRunInteraction(self, page, tab):
+ """Override to do interaction-specific setup before
+ Test.WillRunInteraction is called"""
+ pass
+
+ def RunInteraction(self, page, tab):
raise NotImplementedError()
def CleanUp(self, page, tab):
diff --git a/tools/telemetry/telemetry/page_test.py b/tools/telemetry/telemetry/page_test.py
index eb4938f..b192345 100644
--- a/tools/telemetry/telemetry/page_test.py
+++ b/tools/telemetry/telemetry/page_test.py
@@ -1,6 +1,8 @@
# Copyright (c) 2012 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.
+import sys
+
class Failure(Exception):
"""Exception that can be thrown from MultiPageBenchmark to indicate an
undesired but designed-for problem."""
@@ -41,7 +43,7 @@ class PageTest(object):
"""Override to expose command-line options for this benchmark.
The provided parser is an optparse.OptionParser instance and accepts all
- normal results. The parsed options are available in MeasurePage as
+ normal results. The parsed options are available in Run as
self.options."""
pass
@@ -87,8 +89,9 @@ class PageTest(object):
interaction = self.GetInteraction(page)
if interaction:
tab.WaitForDocumentReadyStateToBeInteractiveOrBetter()
+ interaction.WillRunInteraction(page, tab)
self.WillRunInteraction(page, tab)
- interaction.PerformInteraction(page, tab)
+ interaction.RunInteraction(page, tab)
self.DidRunInteraction(page, tab)
try:
self._test_method(page, tab, results)
@@ -100,8 +103,15 @@ class PageTest(object):
return None
interaction_data = getattr(page, self._interaction_name_to_run)
from telemetry import all_page_interactions
- return all_page_interactions.FindClassWithName(
- interaction_data['action'])(interaction_data)
+ cls = all_page_interactions.FindClassWithName(interaction_data['action'])
+ if not cls:
+ sys.stderr.write('Could not find interaction named %s\n' %
+ interaction_data['action'])
+ sys.stderr.write('Check the pageset for a typo and check the error log' +
+ 'for possible python loading/compilation errors\n')
+ raise Exception('%s not found' % interaction_data['action'])
+ assert cls
+ return cls(interaction_data)
@property
def interaction_name_to_run(self):
diff --git a/tools/telemetry/telemetry/record_wpr.py b/tools/telemetry/telemetry/record_wpr.py
index fab420f..8580f7a 100755
--- a/tools/telemetry/telemetry/record_wpr.py
+++ b/tools/telemetry/telemetry/record_wpr.py
@@ -44,7 +44,8 @@ class RecordPage(page_test.PageTest):
if should_reload:
tab.page.Navigate(page.url)
tab.WaitForDocumentReadyStateToBeComplete()
- interaction.PerformInteraction(page, tab)
+ interaction.WillRunInteraction(page, tab)
+ interaction.RunInteraction(page, tab)
should_reload = True
def _InteractionsForPage(self, page):
diff --git a/tools/telemetry/telemetry/scroll.js b/tools/telemetry/telemetry/scrolling_interaction.js
index 7f11daa..67c2563 100644
--- a/tools/telemetry/telemetry/scroll.js
+++ b/tools/telemetry/telemetry/scrolling_interaction.js
@@ -2,17 +2,14 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
-// Inject this script on any page to measure framerate as the page is scrolled
-// from top to bottom.
-//
-// Usage:
-// 1. Define a callback that takes a RenderingStats object as a parameter.
-// 2. To start the test, call new __ScrollTest(callback).
-// 3a. When the test is complete, the callback will be called.
-// 3b. If no callback is specified, the results is sent to the console.
+// This file provides the ScrollingInteraction object, which scrolls a page
+// from top to bottom:
+// 1. var interaction = new __ScrollingInteraction(callback)
+// 2. interaction.start(element_to_scroll)
+'use strict';
(function() {
- MAX_SCROLL_LENGTH_PIXELS = 5000;
+ var MAX_SCROLL_LENGTH_PIXELS = 5000;
var getTimeMs = (function() {
if (window.performance)
@@ -87,88 +84,6 @@
requestAnimationFrame(callback);
};
- /**
- * Tracks rendering performance using the gpuBenchmarking.renderingStats API.
- * @constructor
- */
- function GpuBenchmarkingRenderingStats() {
- }
-
- GpuBenchmarkingRenderingStats.prototype.start = function() {
- this.initialStats_ = this.getRenderingStats_();
- }
- GpuBenchmarkingRenderingStats.prototype.stop = function() {
- this.finalStats_ = this.getRenderingStats_();
- }
-
- GpuBenchmarkingRenderingStats.prototype.getDeltas = function() {
- if (!this.initialStats_)
- throw new Error('Start not called.');
-
- if (!this.finalStats_)
- throw new Error('Stop was not called.');
-
- var stats = this.finalStats_;
- for (var key in stats)
- stats[key] -= this.initialStats_[key];
- return stats;
- };
-
- GpuBenchmarkingRenderingStats.prototype.getRenderingStats_ = function() {
- var stats = chrome.gpuBenchmarking.renderingStats();
- stats.totalTimeInSeconds = getTimeMs() / 1000;
- return stats;
- };
-
- /**
- * Tracks rendering performance using requestAnimationFrame.
- * @constructor
- */
- function RafRenderingStats() {
- this.recording_ = false;
- this.frameTimes_ = [];
- }
-
- RafRenderingStats.prototype.start = function() {
- if (this.recording_)
- throw new Error('Already started.');
- this.recording_ = true;
- requestAnimationFrame(this.recordFrameTime_.bind(this));
- }
-
- RafRenderingStats.prototype.stop = function() {
- this.recording_ = false;
- }
-
- RafRenderingStats.prototype.getDeltas = function() {
- var results = {};
- results.numAnimationFrames = this.frameTimes_.length - 1;
- results.numFramesSentToScreen = results.numAnimationFrames;
- results.droppedFrameCount = this.getDroppedFrameCount_(this.frameTimes_);
- results.totalTimeInSeconds = (
- this.frameTimes_[this.frameTimes_.length - 1] -
- this.frameTimes_[0]) / 1000;
- return results;
- };
-
- RafRenderingStats.prototype.recordFrameTime_ = function(timestamp) {
- if (!this.recording_)
- return;
-
- this.frameTimes_.push(timestamp);
- requestAnimationFrame(this.recordFrameTime_.bind(this));
- };
-
- RafRenderingStats.prototype.getDroppedFrameCount_ = function(frameTimes) {
- var droppedFrameCount = 0;
- for (var i = 1; i < frameTimes.length; i++) {
- var frameTime = frameTimes[i] - frameTimes[i-1];
- if (frameTime > 1000 / 55)
- droppedFrameCount++;
- }
- return droppedFrameCount;
- };
-
// This class scrolls a page from the top to the bottom once.
//
// The page is scrolled down by a set of scroll gestures. These gestures
@@ -176,13 +91,16 @@
//
// start -> startPass_ -> ...scrolling... -> onGestureComplete_ ->
// -> startPass_ -> .. scrolling... -> onGestureComplete_ -> callback_
- function ScrollTest(opt_callback) {
+ function ScrollingInteraction(opt_callback) {
var self = this;
+ this.beginMeasuringHook = function() {}
+ this.endMeasuringHook = function() {}
+
this.callback_ = opt_callback;
}
- ScrollTest.prototype.getRemainingScrollDistance_ = function() {
+ ScrollingInteraction.prototype.getRemainingScrollDistance_ = function() {
var clientHeight;
// clientHeight is "special" for the body element.
if (this.element_ == document.body)
@@ -193,7 +111,7 @@
return this.scrollHeight_ - this.element_.scrollTop - clientHeight;
}
- ScrollTest.prototype.start = function(opt_element) {
+ ScrollingInteraction.prototype.start = function(opt_element) {
// Assign this.element_ here instead of constructor, because the constructor
// ensures this method will be called after the document is loaded.
this.element_ = opt_element || document.body;
@@ -205,21 +123,21 @@
requestAnimationFrame(this.startPass_.bind(this));
};
- ScrollTest.prototype.startPass_ = function() {
+ ScrollingInteraction.prototype.startPass_ = function() {
this.element_.scrollTop = 0;
- if (window.chrome && chrome.gpuBenchmarking &&
- chrome.gpuBenchmarking.renderingStats)
- this.renderingStats_ = new GpuBenchmarkingRenderingStats();
- else
- this.renderingStats_ = new RafRenderingStats();
- this.renderingStats_.start();
+
+ this.beginMeasuringHook();
this.gesture_ = new SmoothScrollDownGesture(this.element_);
this.gesture_.start(this.getRemainingScrollDistance_(),
this.onGestureComplete_.bind(this));
};
- ScrollTest.prototype.onGestureComplete_ = function(timestamp) {
+ ScrollingInteraction.prototype.getResults = function() {
+ return this.renderingStats_;
+ }
+
+ ScrollingInteraction.prototype.onGestureComplete_ = function(timestamp) {
// If the scrollHeight went down, only scroll to the new scrollHeight.
// -1 to allow for rounding errors on scaled viewports (like mobile).
this.scrollHeight_ = Math.min(this.scrollHeight_,
@@ -231,19 +149,13 @@
return;
}
- this.endPass_();
+ this.endMeasuringHook();
// We're done.
if (this.callback_)
- this.callback_(this.renderingStats_.getDeltas());
- else
- console.log(this.renderingStats_.getDeltas());
- };
-
- ScrollTest.prototype.endPass_ = function() {
- this.renderingStats_.stop();
+ this.callback_();
};
- window.__ScrollTest = ScrollTest;
- window.__ScrollTest_GetBoundingVisibleRect = getBoundingVisibleRect;
+ window.__ScrollingInteraction = ScrollingInteraction;
+ window.__ScrollingInteraction_GetBoundingVisibleRect = getBoundingVisibleRect;
})();
diff --git a/tools/telemetry/telemetry/scrolling_interaction.py b/tools/telemetry/telemetry/scrolling_interaction.py
index a404fb3..7d6a775 100644
--- a/tools/telemetry/telemetry/scrolling_interaction.py
+++ b/tools/telemetry/telemetry/scrolling_interaction.py
@@ -10,34 +10,36 @@ class ScrollingInteraction(page_interaction.PageInteraction):
def __init__(self, attributes=None):
super(ScrollingInteraction, self).__init__(attributes)
- def PerformInteraction(self, page, tab):
- scroll_js_path = os.path.join(os.path.dirname(__file__), 'scroll.js')
- scroll_js = open(scroll_js_path, 'r').read()
+ def WillRunInteraction(self, page, tab):
+ with open(
+ os.path.join(os.path.dirname(__file__),
+ 'scrolling_interaction.js')) as f:
+ js = f.read()
+ tab.runtime.Execute(js)
- # Run scroll test.
- tab.runtime.Execute(scroll_js)
+ tab.runtime.Execute("""
+ window.__scrollingInteractionDone = false;
+ window.__scrollingInteraction = new __ScrollingInteraction(function() {
+ window.__scrollingInteractionDone = true;
+ });
+ """)
+ def RunInteraction(self, page, tab):
with tab.browser.platform.GetSurfaceCollector(''):
-
- start_scroll_js = """
- window.__renderingStatsDeltas = null;
- new __ScrollTest(function(rendering_stats_deltas) {
- window.__renderingStatsDeltas = rendering_stats_deltas;
- }).start(element);
- """
# scrollable_element_function is a function that passes the scrollable
# element on the page to a callback. For example:
# function (callback) {
# callback(document.getElementById('foo'));
# }
if hasattr(self, 'scrollable_element_function'):
- tab.runtime.Execute('(%s)(function(element) { %s });' %
- (self.scrollable_element_function, start_scroll_js))
+ tab.runtime.Execute("""
+ (%s)(function(element) {
+ window.__scrollingInteraction.start(element);
+ });""" % (self.scrollable_element_function))
else:
tab.runtime.Execute(
- '(function() { var element = document.body; %s})();' %
- start_scroll_js)
+ 'window.__scrollingInteraction.start(document.body);')
# Poll for scroll benchmark completion.
util.WaitFor(lambda: tab.runtime.Evaluate(
- 'window.__renderingStatsDeltas'), 60)
+ 'window.__scrollingInteractionDone'), 60)
diff --git a/tools/telemetry/telemetry/scrolling_interaction_unittest.py b/tools/telemetry/telemetry/scrolling_interaction_unittest.py
index c92157f..86f9016 100644
--- a/tools/telemetry/telemetry/scrolling_interaction_unittest.py
+++ b/tools/telemetry/telemetry/scrolling_interaction_unittest.py
@@ -3,20 +3,31 @@
# found in the LICENSE file.
import os
+from telemetry.page import Page
from telemetry import scrolling_interaction
from telemetry import tab_test_case
class ScrollingInteractionTest(tab_test_case.TabTestCase):
- def testScrollingInteraction(self):
+ def CreateAndNavigateToPageFromUnittestDataDir(
+ self, filename, page_attributes):
unittest_data_dir = os.path.join(os.path.dirname(__file__),
'..', 'unittest_data')
self._browser.SetHTTPServerDirectory(unittest_data_dir)
- self._tab.page.Navigate(
- self._browser.http_server.UrlOf('blank.html'))
+ page = Page(
+ self._browser.http_server.UrlOf(filename),
+ attributes=page_attributes)
+
+ self._tab.page.Navigate(page.url)
self._tab.WaitForDocumentReadyStateToBeComplete()
- self.assertEquals(self._tab.runtime.Evaluate('document.location.pathname;'),
- '/blank.html')
+ return page
+
+ def testScrollingInteraction(self):
+ page = self.CreateAndNavigateToPageFromUnittestDataDir(
+ "blank.html",
+ page_attributes={"smoothness": {
+ "action": "scrolling_interaction"
+ }})
# Make page bigger than window so it's scrollable.
self._tab.runtime.Execute("""document.body.style.height =
(2 * window.innerHeight + 1) + 'px';""")
@@ -24,13 +35,53 @@ class ScrollingInteractionTest(tab_test_case.TabTestCase):
self.assertEquals(self._tab.runtime.Evaluate('document.body.scrollTop'), 0)
i = scrolling_interaction.ScrollingInteraction()
- i.PerformInteraction(self._tab.page, self._tab)
+ i.WillRunInteraction(page, self._tab)
+
+ self._tab.runtime.Execute("""
+ window.__scrollingInteraction.beginMeasuringHook = function() {
+ window.__didBeginMeasuring = true;
+ };
+ window.__scrollingInteraction.endMeasuringHook = function() {
+ window.__didEndMeasuring = true;
+ };""")
+ i.RunInteraction(page, self._tab)
+
+ self.assertTrue(self._tab.runtime.Evaluate('window.__didBeginMeasuring'))
+ self.assertTrue(self._tab.runtime.Evaluate('window.__didEndMeasuring'))
self.assertEquals(self._tab.runtime.Evaluate(
'document.body.scrollTop + window.innerHeight'),
self._tab.runtime.Evaluate('document.body.scrollHeight'))
- rendering_stats_deltas = self._tab.runtime.Evaluate(
- 'window.__renderingStatsDeltas')
+ def testBoundingClientRect(self):
+ self.CreateAndNavigateToPageFromUnittestDataDir('blank.html', {})
+ with open(
+ os.path.join(os.path.dirname(__file__),
+ 'scrolling_interaction.js')) as f:
+ js = f.read()
+ self._tab.runtime.Execute(js)
+
+ # Verify that the rect returned by getBoundingVisibleRect() in
+ # scroll.js is completely contained within the viewport. Scroll
+ # events dispatched by the benchmarks use the center of this rect
+ # as their location, and this location needs to be within the
+ # viewport bounds to correctly decide between main-thread and
+ # impl-thread scrolling. If the scrollable area were not clipped
+ # to the viewport bounds, then the instance used here (the scrollable
+ # area being more than twice as tall as the viewport) would
+ # result in a scroll location outside of the viewport bounds.
+ self._tab.runtime.Execute("""document.body.style.height =
+ (2 * window.innerHeight + 1) + 'px';""")
+
+ rect_bottom = int(self._tab.runtime.Evaluate("""
+ __ScrollingInteraction_GetBoundingVisibleRect(document.body).top +
+ __ScrollingInteraction_GetBoundingVisibleRect(document.body).height"""))
+ rect_right = int(self._tab.runtime.Evaluate("""
+ __ScrollingInteraction_GetBoundingVisibleRect(document.body).left +
+ __ScrollingInteraction_GetBoundingVisibleRect(document.body).width"""))
+ viewport_width = int(self._tab.runtime.Evaluate('window.innerWidth'))
+ viewport_height = int(self._tab.runtime.Evaluate('window.innerHeight'))
+
+ self.assertTrue(rect_bottom <= viewport_height)
+ self.assertTrue(rect_right <= viewport_width)
- self.assertTrue(rendering_stats_deltas['numFramesSentToScreen'] > 0)
diff --git a/tools/telemetry/telemetry/wait_interaction.py b/tools/telemetry/telemetry/wait_interaction.py
index 0d0923b..6b38602 100644
--- a/tools/telemetry/telemetry/wait_interaction.py
+++ b/tools/telemetry/telemetry/wait_interaction.py
@@ -8,7 +8,7 @@ class WaitInteraction(page_interaction.PageInteraction):
def __init__(self, attributes=None):
super(WaitInteraction, self).__init__(attributes)
- def PerformInteraction(self, page, tab):
+ def RunInteraction(self, page, tab):
duration = 10
if hasattr(self, 'duration'):
duration = self.duration
diff --git a/tools/telemetry/telemetry/wait_interaction_unittest.py b/tools/telemetry/telemetry/wait_interaction_unittest.py
index ca831ce..742e2be 100644
--- a/tools/telemetry/telemetry/wait_interaction_unittest.py
+++ b/tools/telemetry/telemetry/wait_interaction_unittest.py
@@ -18,7 +18,7 @@ class WaitInteractionTest(tab_test_case.TabTestCase):
'/blank.html')
i = wait_interaction.WaitInteraction({ 'duration' : 1 })
- i.PerformInteraction(self._tab.page, self._tab)
+ i.RunInteraction(self._tab.page, self._tab)
rendering_stats_deltas = self._tab.runtime.Evaluate(
'window.__renderingStatsDeltas')