blob: b5bc61331707547734e5904b1ee3de957b358ca7 [file] [log] [blame]
# 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 time
from metrics import smoothness
from telemetry.page import page_measurement
class StatsCollector(object):
def __init__(self, timeline):
"""
Utility class for collecting rendering stats from timeline model.
timeline -- The timeline model
"""
self.timeline = timeline
self.total_best_rasterize_time = 0
self.total_best_record_time = 0
self.total_pixels_rasterized = 0
self.total_pixels_recorded = 0
self.trigger_event = self.FindTriggerEvent()
self.renderer_process = self.trigger_event.start_thread.parent
def FindTriggerEvent(self):
events = [s for
s in self.timeline.GetAllEventsOfName(
'measureNextFrame')
if s.parent_slice == None]
if len(events) != 1:
raise LookupError, 'no measureNextFrame event found'
return events[0]
def FindFrameNumber(self, trigger_time):
start_event = None
for event in self.renderer_process.IterAllSlicesOfName(
"LayerTreeHost::UpdateLayers"):
if event.start > trigger_time:
if start_event == None:
start_event = event
elif event.start < start_event.start:
start_event = event
if start_event is None:
raise LookupError, \
'no LayterTreeHost::UpdateLayers after measureNextFrame found'
return start_event.args["source_frame_number"]
def GatherRasterizeStats(self, frame_number):
for event in self.renderer_process.IterAllSlicesOfName(
"RasterWorkerPoolTaskImpl::RunRasterOnThread"):
if event.args["data"]["source_frame_number"] == frame_number:
for raster_loop_event in event.GetAllSubSlicesOfName("RasterLoop"):
best_rasterize_time = float("inf")
for raster_event in raster_loop_event.GetAllSubSlicesOfName(
"Picture::Raster"):
if "num_pixels_rasterized" in raster_event.args:
best_rasterize_time = min(best_rasterize_time,
raster_event.duration)
self.total_pixels_rasterized += \
raster_event.args["num_pixels_rasterized"]
if best_rasterize_time == float('inf'):
best_rasterize_time = 0
self.total_best_rasterize_time += best_rasterize_time
def GatherRecordStats(self, frame_number):
for event in self.renderer_process.IterAllSlicesOfName(
"PictureLayer::Update"):
if event.args["source_frame_number"] == frame_number:
for record_loop_event in event.GetAllSubSlicesOfName("RecordLoop"):
best_record_time = float('inf')
for record_event in record_loop_event.GetAllSubSlicesOfName(
"Picture::Record"):
best_record_time = min(best_record_time, record_event.duration)
self.total_pixels_recorded += (
record_event.args["data"]["width"] *
record_event.args["data"]["height"])
if best_record_time == float('inf'):
best_record_time = 0
self.total_best_record_time += best_record_time
def GatherRenderingStats(self):
trigger_time = self.trigger_event.start
frame_number = self.FindFrameNumber(trigger_time)
self.GatherRasterizeStats(frame_number)
self.GatherRecordStats(frame_number)
def DivideIfPossibleOrZero(numerator, denominator):
if denominator == 0:
return 0
return numerator / denominator
class RasterizeAndRecord(page_measurement.PageMeasurement):
def __init__(self):
super(RasterizeAndRecord, self).__init__('', True)
self._metrics = None
def AddCommandLineOptions(self, parser):
parser.add_option('--report-all-results', dest='report_all_results',
action='store_true',
help='Reports all data collected')
parser.add_option('--raster-record-repeat', dest='raster_record_repeat',
default=20,
help='Repetitions in raster and record loops.' +
'Higher values reduce variance, but can cause' +
'instability (timeouts, event buffer overflows, etc.).')
parser.add_option('--start-wait-time', dest='start_wait_time',
default=2,
help='Wait time before the benchmark is started ' +
'(must be long enought to load all content)')
parser.add_option('--stop-wait-time', dest='stop_wait_time',
default=5,
help='Wait time before measurement is taken ' +
'(must be long enough to render one frame)')
def CustomizeBrowserOptions(self, options):
smoothness.SmoothnessMetrics.CustomizeBrowserOptions(options)
# Run each raster task N times. This allows us to report the time for the
# best run, effectively excluding cache effects and time when the thread is
# de-scheduled.
options.AppendExtraBrowserArgs([
'--slow-down-raster-scale-factor=%d' % options.raster_record_repeat,
# Enable impl-side-painting. Current version of benchmark only works for
# this mode.
'--enable-impl-side-painting',
'--force-compositing-mode',
'--enable-threaded-compositing'
])
def MeasurePage(self, page, tab, results):
self._metrics = smoothness.SmoothnessMetrics(tab)
# Rasterize only what's visible.
tab.ExecuteJavaScript(
'chrome.gpuBenchmarking.setRasterizeOnlyVisibleContent();')
# Wait until the page has loaded and come to a somewhat steady state.
# Needs to be adjusted for every device (~2 seconds for workstation).
time.sleep(float(self.options.start_wait_time))
# Render one frame before we start gathering a trace. On some pages, the
# first frame requested has more variance in the number of pixels
# rasterized.
tab.ExecuteJavaScript("""
window.__rafFired = false;
window.webkitRequestAnimationFrame(function() {
chrome.gpuBenchmarking.setNeedsDisplayOnAllLayers();
window.__rafFired = true;
});
""")
tab.browser.StartTracing('webkit.console,benchmark', 60)
self._metrics.Start()
tab.ExecuteJavaScript("""
console.time("measureNextFrame");
window.__rafFired = false;
window.webkitRequestAnimationFrame(function() {
chrome.gpuBenchmarking.setNeedsDisplayOnAllLayers();
window.__rafFired = true;
});
""")
# Wait until the frame was drawn.
# Needs to be adjusted for every device and for different
# raster_record_repeat counts.
# TODO(ernstm): replace by call-back.
time.sleep(float(self.options.stop_wait_time))
tab.ExecuteJavaScript('console.timeEnd("measureNextFrame")')
self._metrics.Stop()
timeline = tab.browser.StopTracing().AsTimelineModel()
collector = StatsCollector(timeline)
collector.GatherRenderingStats()
rendering_stats = self._metrics.end_values
results.Add('best_rasterize_time', 'seconds',
collector.total_best_rasterize_time / 1.e3,
data_type='unimportant')
results.Add('best_record_time', 'seconds',
collector.total_best_record_time / 1.e3,
data_type='unimportant')
results.Add('total_pixels_rasterized', 'pixels',
collector.total_pixels_rasterized,
data_type='unimportant')
results.Add('total_pixels_recorded', 'pixels',
collector.total_pixels_recorded,
data_type='unimportant')
if self.options.report_all_results:
for k, v in rendering_stats.iteritems():
results.Add(k, '', v)