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 @@
// found in the LICENSE file.
// Inject this script on any page to measure framerate as the page is scrolled
// twice from top to bottom.
// from top to bottom.
//
// USAGE:
// 1. To start the scrolling, invoke __scroll_test().
// 2. Wait for __scrolling_complete to be true
// 3. Read __frame_times array.
// Function that sets a callback called when the next frame is rendered.
var __set_frame_callback;
// Function that scrolls the page.
var __scroll_by;
// Usage:
// 1. Define a callback that takes the results array 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 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 GpuBenchmarkingScrollStats() {
this.initialStats_ = this.getRenderingStats_();
}
// Element that can be scrolled. Must provide scrollTop property.
var __scrollable_element;
GpuBenchmarkingScrollStats.prototype.getResult = function() {
var stats = this.getRenderingStats_();
for (var key in stats)
stats[key] -= this.initialStats_[key];
return stats;
};
GpuBenchmarkingScrollStats.prototype.getRenderingStats_ = function() {
var stats = chrome.gpuBenchmarking.renderingStats();
stats.totalTimeInSeconds = getTimeMs() / 1000;
return stats;
};
function RafScrollStats(timestamp) {
this.frameTimes_ = [timestamp];
this.recording_ = true;
requestAnimationFrame(this.processStep_.bind(this));
}
// Amount of scrolling at each frame.
var __scroll_delta = 100;
RafScrollStats.prototype.getResult = function() {
this.recording_ = false;
// Fill in the result object.
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;
};
RafScrollStats.prototype.processStep_ = function(timestamp) {
if (!this.recording_)
return;
this.frameTimes_.push(timestamp);
requestAnimationFrame(this.processStep_.bind(this));
};
RafScrollStats.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;
};
// Number of scrolls to perform.
var __num_scrolls = 2;
// In this class, a "step" is when the page is being scrolled.
// i.e. startScroll -> startStep -> scroll -> processStep ->
// -> startStep -> scroll -> processStep -> endScroll
function ScrollTest(callback, opt_isGmailTest) {
var self = this;
// Current scroll position.
var __ypos;
this.TOTAL_ITERATIONS_ = 2;
this.SCROLL_DELTA_ = 100;
// Time of previous scroll callback execution.
var __start_time = 0;
this.callback_ = callback;
this.isGmailTest_ = opt_isGmailTest;
this.iteration_ = 0;
// True when all scrolling has completed.
var __scrolling_complete = false;
this.results_ = []
// Array of frame times for each scroll in __num_scrolls.
var __frame_times = [[]];
if (this.isGmailTest_) {
gmonkey.load('2.0', function(api) {
self.start_(api.getScrollableElement());
});
} else {
if (document.readyState == 'complete')
this.start_();
else
window.addEventListener('load', function() { self.start_(); });
}
}
// Set this to true when scrolling in Gmail.
var __is_gmail_test = false;
ScrollTest.prototype.scroll_ = function(x, y) {
if (this.isGmailTest_)
this.element_.scrollByLines(1);
else
window.scrollBy(x, y);
};
ScrollTest.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;
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_();
};
ScrollTest.prototype.startStep_ = function() {
this.scroll_(0, this.SCROLL_DELTA_);
requestAnimationFrame(this.processStep_.bind(this));
};
ScrollTest.prototype.endScroll_ = function() {
this.results_.push(this.scrollStats_.getResult());
this.iteration_++;
};
ScrollTest.prototype.processStep_ = function(timestamp) {
// clientHeight is "special" for the body element.
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_();
// Initializes the platform-independent frame callback scheduler function.
function __init_set_frame_callback() {
__set_frame_callback = window.requestAnimationFrame ||
window.mozRequestAnimationFrame ||
window.msRequestAnimationFrame ||
window.oRequestAnimationFrame ||
window.webkitRequestAnimationFrame;
}
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_);
};
// Initializes the most realistic scrolling method.
function __init_scroll_by() {
if (__is_gmail_test) {
__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 {
__scroll_by = window.scrollBy;
}
}
// 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();
});
}
function __start_scroll(scrollable_element) {
__scrollable_element = scrollable_element;
__init_scroll_by();
__set_frame_callback(__do_scroll);
}
// Performs the scroll test.
function __scroll_test() {
__init_set_frame_callback();
if (__is_gmail_test) {
gmonkey.load("2.0", function(api) {
__start_scroll(api.getScrollableElement());
});
} else {
if (window.performance.timing.loadEventStart) {
__start_scroll(document.body); // Page already loaded.
} else {
window.addEventListener('load', function() {
__start_scroll(document.body); // Page hasn't loaded yet, schedule.
});
}
}
}
window.__ScrollTest = ScrollTest;
})();
......@@ -748,8 +748,8 @@ class BenchmarkPerfTest(BasePerfTest):
if iteration:
for key, val in result_dict.items():
timings.setdefault(key, []).append(val)
logging.info('Iteration %d of %d:\n%s', iteration, self._num_iterations,
self.pformat(result_dict))
logging.info('Iteration %d of %d:\n%s', iteration,
self._num_iterations, self.pformat(result_dict))
for key, val in timings.items():
if key == 'final_score':
......@@ -1628,40 +1628,32 @@ class FileUploadDownloadTest(BasePerfTest):
'upload_file')
class FrameTimes(object):
"""Container for a list of frame times."""
def __init__(self, frame_times):
self._frame_times = frame_times
class ScrollResults(object):
"""Container for ScrollTest results."""
def GetFps(self):
if not self._frame_times:
return 0
avg = sum(self._frame_times) / len(self._frame_times)
if not avg:
return 0
return int(1000.0 / avg)
def __init__(self, first_paint_seconds, results_list):
assert len(results_list) == 2, 'Expecting initial and repeat results.'
self._first_paint_time = 1000.0 * first_paint_seconds
self._results_list = results_list
def GetMeanFrameTime(self):
return Mean(self._frame_times)
def GetFirstPaintTime(self):
return self._first_paint_time
def GetPercentBelow60Fps(self):
if not self._frame_times:
return 0
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 GetFrameCount(self, index):
results = self._results_list[index]
return results.get('numFramesSentToScreen', results['numAnimationFrames'])
def GetFps(self, index):
return (self.GetFrameCount(index) /
self._results_list[index]['totalTimeInSeconds'])
class ScrollResults(object):
"""Container for ScrollTest results."""
def GetMeanFrameTime(self, index):
return (self._results_list[index]['totalTimeInSeconds'] /
self.GetFrameCount(index))
def __init__(self, first_paint_seconds, frame_times_lists):
assert len(frame_times_lists) == 2, 'Expecting initial and repeat times'
self.first_paint_time = 1000.0 * first_paint_seconds
self.initial_frame_times = FrameTimes(frame_times_lists[0])
self.repeat_frame_times = FrameTimes(frame_times_lists[1])
def GetPercentBelow60Fps(self, index):
return (float(self._results_list[index]['droppedFrameCount']) /
self.GetFrameCount(index))
class BaseScrollTest(BasePerfTest):
......@@ -1674,57 +1666,50 @@ class BaseScrollTest(BasePerfTest):
with open(scroll_file) as f:
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.
Args:
url: The string url for the webpage on which to run the scroll test.
setup_js: String representing additional Javascript setup code to execute
in the webpage immediately before running the scroll test.
is_gmail_test: True iff the test is a GMail test.
Returns:
Instance of ScrollResults.
"""
self.assertTrue(self.AppendTab(pyauto.GURL(url)),
msg='Failed to append tab for webpage.')
js = """
%s
%s
__scroll_test();
window.domAutomationController.send('done');
""" % (self._scroll_text, setup_js)
self.ExecuteJavascript(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'
timeout = pyauto.PyUITest.ActionTimeoutChanger(self, 300 * 1000) # ms
test_js = """%s;
new __ScrollTest(function(results) {
var stringify = JSON.stringify || JSON.encode;
window.domAutomationController.send(stringify(results));
}, %s);
""" % (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))
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)
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.
Args:
......@@ -1732,21 +1717,20 @@ class BaseScrollTest(BasePerfTest):
description: A string description for the particular test being run.
graph_name: A string name for the performance graph associated with this
test. Only used on Chrome desktop.
setup_js: String representing additional Javascript setup code to execute
in the webpage immediately before running the scroll test.
is_gmail_test: True iff the test is a GMail test.
"""
results = []
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.
if iteration:
fps = result.repeat_frame_times.GetFps()
fps = result.GetFps(1)
assert fps, '%s did not scroll' % url
logging.info('Iteration %d of %d: %f fps', iteration,
self._num_iterations, fps)
results.append(result)
self._PrintSummaryResults(
description, [r.repeat_frame_times.GetFps() for r in results],
description, [r.GetFps(1) for r in results],
'FPS', graph_name)
......@@ -1776,21 +1760,21 @@ class PopularSitesScrollTest(BaseScrollTest):
def _PrintScrollResults(self, results):
self._PrintSummaryResults(
'initial', [r.initial_frame_times.GetMeanFrameTime() for r in results],
'initial', [r.GetMeanFrameTime(0) for r in results],
'ms', 'FrameTimes')
self._PrintSummaryResults(
'repeat', [r.repeat_frame_times.GetMeanFrameTime() for r in results],
'repeat', [r.GetMeanFrameTime(1) for r in results],
'ms', 'FrameTimes')
self._PrintSummaryResults(
'initial',
[r.initial_frame_times.GetPercentBelow60Fps() for r in results],
[r.GetPercentBelow60Fps(0) for r in results],
'percent', 'PercentBelow60FPS')
self._PrintSummaryResults(
'repeat',
[r.repeat_frame_times.GetPercentBelow60Fps() for r in results],
[r.GetPercentBelow60Fps(1) for r in results],
'percent', 'PercentBelow60FPS')
self._PrintSummaryResults(
'first_paint_time', [r.first_paint_time for r in results],
'first_paint_time', [r.GetFirstPaintTime() for r in results],
'ms', 'FirstPaintTime')
def test2012Q3(self):
......@@ -1803,9 +1787,9 @@ class PopularSitesScrollTest(BaseScrollTest):
for iteration in range(self._num_iterations):
for url in urls:
result = self.RunSingleInvocation(url)
fps = result.initial_frame_times.GetFps()
fps = result.GetFps(0)
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)
results.append(result)
self._PrintScrollResults(results)
......@@ -1844,8 +1828,8 @@ class ScrollTest(BaseScrollTest):
def testGmailScroll(self):
"""Runs the scroll test using the live Gmail site."""
self._LoginToGoogleAccount(account_key='test_google_account_gmail')
self.RunScrollTest('http://www.gmail.com', 'ScrollGmail', 'scroll_fps',
setup_js='__is_gmail_test = true;')
self.RunScrollTest('http://www.gmail.com', 'ScrollGmail',
'scroll_fps', True)
class FlashTest(BasePerfTest):
......
......@@ -181,26 +181,16 @@ class GpuBenchmarkingWrapper : public v8::Extension {
render_view_impl->GetRenderingStats(stats);
v8::Handle<v8::Object> stats_object = v8::Object::New();
if (stats.numAnimationFrames)
stats_object->Set(v8::String::New("numAnimationFrames"),
v8::Integer::New(stats.numAnimationFrames),
v8::ReadOnly);
if (stats.numFramesSentToScreen)
stats_object->Set(v8::String::New("numFramesSentToScreen"),
v8::Integer::New(stats.numFramesSentToScreen),
v8::ReadOnly);
if (stats.droppedFrameCount)
stats_object->Set(v8::String::New("droppedFrameCount"),
v8::Integer::New(stats.droppedFrameCount),
v8::ReadOnly);
if (stats.totalPaintTimeInSeconds)
stats_object->Set(v8::String::New("totalPaintTimeInSeconds"),
v8::Number::New(stats.totalPaintTimeInSeconds),
v8::ReadOnly);
if (stats.totalRasterizeTimeInSeconds)
stats_object->Set(v8::String::New("totalRasterizeTimeInSeconds"),
v8::Number::New(stats.totalRasterizeTimeInSeconds),
v8::ReadOnly);
stats_object->Set(v8::String::New("numAnimationFrames"),
v8::Integer::New(stats.numAnimationFrames));
stats_object->Set(v8::String::New("numFramesSentToScreen"),
v8::Integer::New(stats.numFramesSentToScreen));
stats_object->Set(v8::String::New("droppedFrameCount"),
v8::Integer::New(stats.droppedFrameCount));
stats_object->Set(v8::String::New("totalPaintTimeInSeconds"),
v8::Number::New(stats.totalPaintTimeInSeconds));
stats_object->Set(v8::String::New("totalRasterizeTimeInSeconds"),
v8::Number::New(stats.totalRasterizeTimeInSeconds));
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