diff options
author | nduca@chromium.org <nduca@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2013-01-12 00:53:27 +0000 |
---|---|---|
committer | nduca@chromium.org <nduca@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2013-01-12 00:53:27 +0000 |
commit | c6b768a124789c0ceb775a4811b146b3ecfe2a62 (patch) | |
tree | 8798788664b7536ed66b35d39f7b5c30bc53695d | |
parent | a7002b5302c08588866d0298881d7c92cd4853d5 (diff) | |
download | chromium_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
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') |