Commit e55737f1 authored by dtu@chromium.org's avatar dtu@chromium.org

Refactor/rewrite scroll.js.

The file has been rewritten to conform to Google's JavaScript style, moving all of the __private_variables into a class.

Results are now reported as a list of renderingStats objects. If gpuBenchmarking is unavailable, it falls back to measuring frame times using RAF and populating some of the renderingStats fields.

The way the test is called has changed. Previously, you would call __scroll_test(), wait for __scrolling_complete, then read __frame_times. Now you call new ScrollTest(callback), which takes a callback as a parameter, and calls that with the results list when the test is finished. This simplifies the way it is called in perf.py.

gpu_benchmarking_extension.cc no longer checks if the fields are nonzero. This was a problem when the droppedFrameCount really was 0, but it wouldn't populate that field as it should.


BUG=137789, 141477
TEST=This is a test.

Review URL: https://chromiumcodereview.appspot.com/10836202

git-svn-id: svn://svn.chromium.org/chrome/trunk/src@152041 0039d316-1c4b-4281-b951-d872f2087c98
parent ecb42c65
...@@ -3,119 +3,178 @@ ...@@ -3,119 +3,178 @@
// found in the LICENSE file. // found in the LICENSE file.
// Inject this script on any page to measure framerate as the page is scrolled // Inject this script on any page to measure framerate as the page is scrolled
// twice from top to bottom. // from top to bottom.
// //
// USAGE: // Usage:
// 1. To start the scrolling, invoke __scroll_test(). // 1. Define a callback that takes the results array as a parameter.
// 2. Wait for __scrolling_complete to be true // 2. To start the test, call new __ScrollTest(callback).
// 3. Read __frame_times array. // 3a. When the test is complete, the callback will be called.
// 3b. If no callback is specified, the results are sent to the console.
(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);
// Function that sets a callback called when the next frame is rendered. function GpuBenchmarkingScrollStats() {
var __set_frame_callback; this.initialStats_ = this.getRenderingStats_();
}
// Function that scrolls the page. GpuBenchmarkingScrollStats.prototype.getResult = function() {
var __scroll_by; var stats = this.getRenderingStats_();
for (var key in stats)
stats[key] -= this.initialStats_[key];
return stats;
};
// Element that can be scrolled. Must provide scrollTop property. GpuBenchmarkingScrollStats.prototype.getRenderingStats_ = function() {
var __scrollable_element; var stats = chrome.gpuBenchmarking.renderingStats();
stats.totalTimeInSeconds = getTimeMs() / 1000;
return stats;
};
// Amount of scrolling at each frame. function RafScrollStats(timestamp) {
var __scroll_delta = 100; this.frameTimes_ = [timestamp];
this.recording_ = true;
requestAnimationFrame(this.processStep_.bind(this));
}
// Number of scrolls to perform. RafScrollStats.prototype.getResult = function() {
var __num_scrolls = 2; this.recording_ = false;
// Current scroll position. // Fill in the result object.
var __ypos; var result = {};
result.numAnimationFrames = this.frameTimes_.length - 1;
result.droppedFrameCount = this.getDroppedFrameCount_(this.frameTimes_);
result.totalTimeInSeconds = (this.frameTimes_[this.frameTimes_.length - 1] -
this.frameTimes_[0]) / 1000;
return result;
};
// Time of previous scroll callback execution. RafScrollStats.prototype.processStep_ = function(timestamp) {
var __start_time = 0; if (!this.recording_)
return;
// True when all scrolling has completed. this.frameTimes_.push(timestamp);
var __scrolling_complete = false; requestAnimationFrame(this.processStep_.bind(this));
};
// Array of frame times for each scroll in __num_scrolls. RafScrollStats.prototype.getDroppedFrameCount_ = function(frameTimes) {
var __frame_times = [[]]; 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;
};
// Set this to true when scrolling in Gmail. // In this class, a "step" is when the page is being scrolled.
var __is_gmail_test = false; // i.e. startScroll -> startStep -> scroll -> processStep ->
// -> startStep -> scroll -> processStep -> endScroll
function ScrollTest(callback, opt_isGmailTest) {
var self = this;
this.TOTAL_ITERATIONS_ = 2;
this.SCROLL_DELTA_ = 100;
// Initializes the platform-independent frame callback scheduler function. this.callback_ = callback;
function __init_set_frame_callback() { this.isGmailTest_ = opt_isGmailTest;
__set_frame_callback = window.requestAnimationFrame || this.iteration_ = 0;
window.mozRequestAnimationFrame ||
window.msRequestAnimationFrame ||
window.oRequestAnimationFrame ||
window.webkitRequestAnimationFrame;
}
this.results_ = []
// Initializes the most realistic scrolling method. if (this.isGmailTest_) {
function __init_scroll_by() { gmonkey.load('2.0', function(api) {
if (__is_gmail_test) { self.start_(api.getScrollableElement());
__scroll_by = function(x, y) { });
__scrollable_element.scrollByLines(1);
};
} else if (window.chrome && window.chrome.benchmarking &&
window.chrome.benchmarking.smoothScrollBy) {
__scroll_by = window.chrome.benchmarking.smoothScrollBy;
} else { } else {
__scroll_by = window.scrollBy; if (document.readyState == 'complete')
this.start_();
else
window.addEventListener('load', function() { self.start_(); });
} }
}
// Scrolls page down and reschedules itself until it hits the bottom.
// Collects stats along the way.
function __do_scroll(now_time) {
__scroll_by(0, __scroll_delta);
__set_frame_callback(function(now_time) {
if (__start_time) {
if (__scrollable_element.scrollTop > __ypos) {
// Scroll in progress, push a frame.
__frame_times[__frame_times.length-1].push(now_time - __start_time);
} else {
// Scroll complete, either scroll again or finish.
if (__frame_times.length < __num_scrolls) {
__scrollable_element.scrollTop = 0;
__frame_times.push([]);
} else {
console.log('frame_times', '' + __frame_times);
__scrolling_complete = true;
return;
} }
}
}
__ypos = __scrollable_element.scrollTop;
__start_time = now_time;
__do_scroll();
});
}
ScrollTest.prototype.scroll_ = function(x, y) {
if (this.isGmailTest_)
this.element_.scrollByLines(1);
else
window.scrollBy(x, y);
};
function __start_scroll(scrollable_element) { ScrollTest.prototype.start_ = function(opt_element) {
__scrollable_element = scrollable_element; // Assign this.element_ here instead of constructor, because the constructor
__init_scroll_by(); // ensures this method will be called after the document is loaded.
__set_frame_callback(__do_scroll); this.element_ = opt_element || document.body;
} requestAnimationFrame(this.startScroll_.bind(this));
};
ScrollTest.prototype.startScroll_ = function(timestamp) {
this.element_.scrollTop = 0;
if (window.chrome && chrome.gpuBenchmarking)
this.scrollStats_ = new GpuBenchmarkingScrollStats();
else
this.scrollStats_ = new RafScrollStats(timestamp);
this.startStep_();
};
// Performs the scroll test. ScrollTest.prototype.startStep_ = function() {
function __scroll_test() { this.scroll_(0, this.SCROLL_DELTA_);
__init_set_frame_callback(); requestAnimationFrame(this.processStep_.bind(this));
if (__is_gmail_test) { };
gmonkey.load("2.0", function(api) {
__start_scroll(api.getScrollableElement()); ScrollTest.prototype.endScroll_ = function() {
}); this.results_.push(this.scrollStats_.getResult());
} else { this.iteration_++;
if (window.performance.timing.loadEventStart) { };
__start_scroll(document.body); // Page already loaded.
} else { ScrollTest.prototype.processStep_ = function(timestamp) {
window.addEventListener('load', function() { // clientHeight is "special" for the body element.
__start_scroll(document.body); // Page hasn't loaded yet, schedule. if (this.element_ == document.body)
}); var clientHeight = window.innerHeight;
else
var clientHeight = this.element_.clientHeight;
var isScrollComplete =
this.element_.scrollTop + clientHeight >= this.element_.scrollHeight;
if (!isScrollComplete) {
this.startStep_();
return;
} }
this.endScroll_();
var isTestComplete = this.iteration_ >= this.TOTAL_ITERATIONS_;
if (!isTestComplete) {
requestAnimationFrame(this.startScroll_.bind(this));
return;
} }
}
// Send results.
if (this.callback_)
this.callback_(this.results_);
else
console.log(this.results_);
};
window.__ScrollTest = ScrollTest;
})();
...@@ -748,8 +748,8 @@ class BenchmarkPerfTest(BasePerfTest): ...@@ -748,8 +748,8 @@ class BenchmarkPerfTest(BasePerfTest):
if iteration: if iteration:
for key, val in result_dict.items(): for key, val in result_dict.items():
timings.setdefault(key, []).append(val) timings.setdefault(key, []).append(val)
logging.info('Iteration %d of %d:\n%s', iteration, self._num_iterations, logging.info('Iteration %d of %d:\n%s', iteration,
self.pformat(result_dict)) self._num_iterations, self.pformat(result_dict))
for key, val in timings.items(): for key, val in timings.items():
if key == 'final_score': if key == 'final_score':
...@@ -1628,40 +1628,32 @@ class FileUploadDownloadTest(BasePerfTest): ...@@ -1628,40 +1628,32 @@ class FileUploadDownloadTest(BasePerfTest):
'upload_file') 'upload_file')
class FrameTimes(object): class ScrollResults(object):
"""Container for a list of frame times.""" """Container for ScrollTest results."""
def __init__(self, frame_times):
self._frame_times = frame_times
def GetFps(self): def __init__(self, first_paint_seconds, results_list):
if not self._frame_times: assert len(results_list) == 2, 'Expecting initial and repeat results.'
return 0 self._first_paint_time = 1000.0 * first_paint_seconds
avg = sum(self._frame_times) / len(self._frame_times) self._results_list = results_list
if not avg:
return 0
return int(1000.0 / avg)
def GetMeanFrameTime(self): def GetFirstPaintTime(self):
return Mean(self._frame_times) return self._first_paint_time
def GetPercentBelow60Fps(self): def GetFrameCount(self, index):
if not self._frame_times: results = self._results_list[index]
return 0 return results.get('numFramesSentToScreen', results['numAnimationFrames'])
threshold = math.ceil(1000 / 60.)
num_frames_below_60 = len([t for t in self._frame_times if t > threshold])
num_frames = len(self._frame_times)
return (100. * num_frames_below_60) / num_frames
def GetFps(self, index):
return (self.GetFrameCount(index) /
self._results_list[index]['totalTimeInSeconds'])
class ScrollResults(object): def GetMeanFrameTime(self, index):
"""Container for ScrollTest results.""" return (self._results_list[index]['totalTimeInSeconds'] /
self.GetFrameCount(index))
def __init__(self, first_paint_seconds, frame_times_lists): def GetPercentBelow60Fps(self, index):
assert len(frame_times_lists) == 2, 'Expecting initial and repeat times' return (float(self._results_list[index]['droppedFrameCount']) /
self.first_paint_time = 1000.0 * first_paint_seconds self.GetFrameCount(index))
self.initial_frame_times = FrameTimes(frame_times_lists[0])
self.repeat_frame_times = FrameTimes(frame_times_lists[1])
class BaseScrollTest(BasePerfTest): class BaseScrollTest(BasePerfTest):
...@@ -1674,57 +1666,50 @@ class BaseScrollTest(BasePerfTest): ...@@ -1674,57 +1666,50 @@ class BaseScrollTest(BasePerfTest):
with open(scroll_file) as f: with open(scroll_file) as f:
self._scroll_text = f.read() self._scroll_text = f.read()
def RunSingleInvocation(self, url, setup_js=''): def ExtraChromeFlags(self):
"""Ensures Chrome is launched with custom flags.
Returns:
A list of extra flags to pass to Chrome when it is launched.
"""
# Extra flag used by scroll performance tests.
return (super(BaseScrollTest, self).ExtraChromeFlags() +
['--enable-gpu-benchmarking'])
def RunSingleInvocation(self, url, is_gmail_test=False):
"""Runs a single invocation of the scroll test. """Runs a single invocation of the scroll test.
Args: Args:
url: The string url for the webpage on which to run the scroll test. url: The string url for the webpage on which to run the scroll test.
setup_js: String representing additional Javascript setup code to execute is_gmail_test: True iff the test is a GMail test.
in the webpage immediately before running the scroll test.
Returns: Returns:
Instance of ScrollResults. Instance of ScrollResults.
""" """
self.assertTrue(self.AppendTab(pyauto.GURL(url)), self.assertTrue(self.AppendTab(pyauto.GURL(url)),
msg='Failed to append tab for webpage.') msg='Failed to append tab for webpage.')
js = """ timeout = pyauto.PyUITest.ActionTimeoutChanger(self, 300 * 1000) # ms
%s test_js = """%s;
%s new __ScrollTest(function(results) {
__scroll_test(); var stringify = JSON.stringify || JSON.encode;
window.domAutomationController.send('done'); window.domAutomationController.send(stringify(results));
""" % (self._scroll_text, setup_js) }, %s);
self.ExecuteJavascript(js, tab_index=1) """ % (self._scroll_text, 'true' if is_gmail_test else 'false')
results = simplejson.loads(self.ExecuteJavascript(test_js, tab_index=1))
first_paint_js = ('window.domAutomationController.send('
'(chrome.loadTimes().firstPaintTime - '
'chrome.loadTimes().requestTime).toString());')
first_paint_time = float(self.ExecuteJavascript(first_paint_js,
tab_index=1))
# Poll the webpage until the test is complete.
def IsTestComplete():
done_js = """
if (__scrolling_complete)
window.domAutomationController.send('complete');
else
window.domAutomationController.send('incomplete');
"""
return self.ExecuteJavascript(done_js, tab_index=1) == 'complete'
self.assertTrue(
self.WaitUntil(IsTestComplete, timeout=300, expect_retval=True,
retry_sleep=1),
msg='Timed out when waiting for scrolling tests to complete.')
# Get the scroll test results from the webpage.
results_js = """
var __stringify = JSON.stringify || JSON.encode;
window.domAutomationController.send(__stringify({
'first_paint_time': chrome.loadTimes().firstPaintTime -
chrome.loadTimes().requestTime,
'frame_times': __frame_times,
}));
"""
results = eval(self.ExecuteJavascript(results_js, tab_index=1))
self.CloseTab(tab_index=1) self.CloseTab(tab_index=1)
return ScrollResults(results['first_paint_time'], results['frame_times'])
def RunScrollTest(self, url, description, graph_name, setup_js=''): return ScrollResults(first_paint_time, results)
def RunScrollTest(self, url, description, graph_name, is_gmail_test=False):
"""Runs a scroll performance test on the specified webpage. """Runs a scroll performance test on the specified webpage.
Args: Args:
...@@ -1732,21 +1717,20 @@ class BaseScrollTest(BasePerfTest): ...@@ -1732,21 +1717,20 @@ class BaseScrollTest(BasePerfTest):
description: A string description for the particular test being run. description: A string description for the particular test being run.
graph_name: A string name for the performance graph associated with this graph_name: A string name for the performance graph associated with this
test. Only used on Chrome desktop. test. Only used on Chrome desktop.
setup_js: String representing additional Javascript setup code to execute is_gmail_test: True iff the test is a GMail test.
in the webpage immediately before running the scroll test.
""" """
results = [] results = []
for iteration in range(self._num_iterations + 1): for iteration in range(self._num_iterations + 1):
result = self.RunSingleInvocation(url, setup_js) result = self.RunSingleInvocation(url, is_gmail_test)
# Ignore the first iteration. # Ignore the first iteration.
if iteration: if iteration:
fps = result.repeat_frame_times.GetFps() fps = result.GetFps(1)
assert fps, '%s did not scroll' % url assert fps, '%s did not scroll' % url
logging.info('Iteration %d of %d: %f fps', iteration, logging.info('Iteration %d of %d: %f fps', iteration,
self._num_iterations, fps) self._num_iterations, fps)
results.append(result) results.append(result)
self._PrintSummaryResults( self._PrintSummaryResults(
description, [r.repeat_frame_times.GetFps() for r in results], description, [r.GetFps(1) for r in results],
'FPS', graph_name) 'FPS', graph_name)
...@@ -1776,21 +1760,21 @@ class PopularSitesScrollTest(BaseScrollTest): ...@@ -1776,21 +1760,21 @@ class PopularSitesScrollTest(BaseScrollTest):
def _PrintScrollResults(self, results): def _PrintScrollResults(self, results):
self._PrintSummaryResults( self._PrintSummaryResults(
'initial', [r.initial_frame_times.GetMeanFrameTime() for r in results], 'initial', [r.GetMeanFrameTime(0) for r in results],
'ms', 'FrameTimes') 'ms', 'FrameTimes')
self._PrintSummaryResults( self._PrintSummaryResults(
'repeat', [r.repeat_frame_times.GetMeanFrameTime() for r in results], 'repeat', [r.GetMeanFrameTime(1) for r in results],
'ms', 'FrameTimes') 'ms', 'FrameTimes')
self._PrintSummaryResults( self._PrintSummaryResults(
'initial', 'initial',
[r.initial_frame_times.GetPercentBelow60Fps() for r in results], [r.GetPercentBelow60Fps(0) for r in results],
'percent', 'PercentBelow60FPS') 'percent', 'PercentBelow60FPS')
self._PrintSummaryResults( self._PrintSummaryResults(
'repeat', 'repeat',
[r.repeat_frame_times.GetPercentBelow60Fps() for r in results], [r.GetPercentBelow60Fps(1) for r in results],
'percent', 'PercentBelow60FPS') 'percent', 'PercentBelow60FPS')
self._PrintSummaryResults( self._PrintSummaryResults(
'first_paint_time', [r.first_paint_time for r in results], 'first_paint_time', [r.GetFirstPaintTime() for r in results],
'ms', 'FirstPaintTime') 'ms', 'FirstPaintTime')
def test2012Q3(self): def test2012Q3(self):
...@@ -1803,9 +1787,9 @@ class PopularSitesScrollTest(BaseScrollTest): ...@@ -1803,9 +1787,9 @@ class PopularSitesScrollTest(BaseScrollTest):
for iteration in range(self._num_iterations): for iteration in range(self._num_iterations):
for url in urls: for url in urls:
result = self.RunSingleInvocation(url) result = self.RunSingleInvocation(url)
fps = result.initial_frame_times.GetFps() fps = result.GetFps(0)
assert fps, '%s did not scroll' % url assert fps, '%s did not scroll' % url
logging.info('Iteration %d of %d: %f fps', iteration, logging.info('Iteration %d of %d: %f fps', iteration + 1,
self._num_iterations, fps) self._num_iterations, fps)
results.append(result) results.append(result)
self._PrintScrollResults(results) self._PrintScrollResults(results)
...@@ -1844,8 +1828,8 @@ class ScrollTest(BaseScrollTest): ...@@ -1844,8 +1828,8 @@ class ScrollTest(BaseScrollTest):
def testGmailScroll(self): def testGmailScroll(self):
"""Runs the scroll test using the live Gmail site.""" """Runs the scroll test using the live Gmail site."""
self._LoginToGoogleAccount(account_key='test_google_account_gmail') self._LoginToGoogleAccount(account_key='test_google_account_gmail')
self.RunScrollTest('http://www.gmail.com', 'ScrollGmail', 'scroll_fps', self.RunScrollTest('http://www.gmail.com', 'ScrollGmail',
setup_js='__is_gmail_test = true;') 'scroll_fps', True)
class FlashTest(BasePerfTest): class FlashTest(BasePerfTest):
......
...@@ -181,26 +181,16 @@ class GpuBenchmarkingWrapper : public v8::Extension { ...@@ -181,26 +181,16 @@ class GpuBenchmarkingWrapper : public v8::Extension {
render_view_impl->GetRenderingStats(stats); render_view_impl->GetRenderingStats(stats);
v8::Handle<v8::Object> stats_object = v8::Object::New(); v8::Handle<v8::Object> stats_object = v8::Object::New();
if (stats.numAnimationFrames)
stats_object->Set(v8::String::New("numAnimationFrames"), stats_object->Set(v8::String::New("numAnimationFrames"),
v8::Integer::New(stats.numAnimationFrames), v8::Integer::New(stats.numAnimationFrames));
v8::ReadOnly);
if (stats.numFramesSentToScreen)
stats_object->Set(v8::String::New("numFramesSentToScreen"), stats_object->Set(v8::String::New("numFramesSentToScreen"),
v8::Integer::New(stats.numFramesSentToScreen), v8::Integer::New(stats.numFramesSentToScreen));
v8::ReadOnly);
if (stats.droppedFrameCount)
stats_object->Set(v8::String::New("droppedFrameCount"), stats_object->Set(v8::String::New("droppedFrameCount"),
v8::Integer::New(stats.droppedFrameCount), v8::Integer::New(stats.droppedFrameCount));
v8::ReadOnly);
if (stats.totalPaintTimeInSeconds)
stats_object->Set(v8::String::New("totalPaintTimeInSeconds"), stats_object->Set(v8::String::New("totalPaintTimeInSeconds"),
v8::Number::New(stats.totalPaintTimeInSeconds), v8::Number::New(stats.totalPaintTimeInSeconds));
v8::ReadOnly);
if (stats.totalRasterizeTimeInSeconds)
stats_object->Set(v8::String::New("totalRasterizeTimeInSeconds"), stats_object->Set(v8::String::New("totalRasterizeTimeInSeconds"),
v8::Number::New(stats.totalRasterizeTimeInSeconds), v8::Number::New(stats.totalRasterizeTimeInSeconds));
v8::ReadOnly);
return stats_object; return stats_object;
} }
......
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