Commit 8d7dd7ad authored by ernstm@chromium.org's avatar ernstm@chromium.org

telemetry: Add RenderingStatsUnitTest.

SmoothnessMetricUnitTest mixes testing of TimelineModel,
SmoothnessMetric, and RenderingStats. This patch adds a dedicated unit test
for RenderingStats only. It creates a TimelineModel directly, without using
TraceEventTimelineImporter (which has its own unit test). RenderingStatsUnitTest
verifies that RenderingStats extracts the correct events from the model.

This patch also fixes a bug in TimelineModel.FindTimelineMarkers uncovered
by the new test.

SmoothnessMetricUnitTest itself will be refactored in a separate CL.

R=nduca@chromium.org, tonyg@chromium.org
BUG=314877

Review URL: https://codereview.chromium.org/59403008

git-svn-id: svn://svn.chromium.org/chrome/trunk/src@233512 0039d316-1c4b-4281-b951-d872f2087c98
parent e74c9bc2
...@@ -20,10 +20,7 @@ class RenderingStats(object): ...@@ -20,10 +20,7 @@ class RenderingStats(object):
""" """
assert(len(timeline_markers) > 0) assert(len(timeline_markers) > 0)
self.renderer_process = renderer_process self.renderer_process = renderer_process
self.start = timeline_markers[0].start
self.end = timeline_markers[-1].start + timeline_markers[-1].duration
self.frame_count = []
self.frame_timestamps = [] self.frame_timestamps = []
self.frame_times = [] self.frame_times = []
self.paint_time = [] self.paint_time = []
...@@ -62,7 +59,6 @@ class RenderingStats(object): ...@@ -62,7 +59,6 @@ class RenderingStats(object):
frame_count = event.args['data']['frame_count'] frame_count = event.args['data']['frame_count']
else: else:
frame_count = event.args['data']['screen_frame_count'] frame_count = event.args['data']['screen_frame_count']
self.frame_count.append(frame_count)
if frame_count > 1: if frame_count > 1:
raise ValueError, 'trace contains multi-frame render stats' raise ValueError, 'trace contains multi-frame render stats'
if frame_count == 1: if frame_count == 1:
...@@ -104,7 +100,6 @@ class RenderingStats(object): ...@@ -104,7 +100,6 @@ class RenderingStats(object):
frame_count = event.args['data']['frame_count'] frame_count = event.args['data']['frame_count']
else: else:
frame_count = event.args['data']['screen_frame_count'] frame_count = event.args['data']['screen_frame_count']
self.frame_count.append(frame_count)
if frame_count > 1: if frame_count > 1:
raise ValueError, 'trace contains multi-frame render stats' raise ValueError, 'trace contains multi-frame render stats'
if frame_count == 1: if frame_count == 1:
......
# Copyright 2013 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 random
import unittest
from metrics.rendering_stats import RenderingStats
from telemetry.core.timeline import model
class MockTimer(object):
"""A mock timer class which can generate random durations.
An instance of this class is used as a global timer to generate random
durations for stats and consistent timestamps for all mock trace events.
The unit of time is milliseconds.
"""
def __init__(self):
self.milliseconds = 0
def Get(self):
return self.milliseconds
def Advance(self, low=0, high=1):
delta = random.uniform(low, high)
self.milliseconds += delta
return delta
class ReferenceRenderingStats(object):
""" Stores expected data for comparison with actual RenderingStats """
def __init__(self):
self.frame_timestamps = []
self.frame_times = []
self.paint_time = []
self.painted_pixel_count = []
self.record_time = []
self.recorded_pixel_count = []
self.rasterize_time = []
self.rasterized_pixel_count = []
def AddMainThreadRenderingStats(mock_timer, thread, first_frame,
ref_stats = None):
""" Adds a random main thread rendering stats event.
thread: The timeline model thread to which the event will be added.
first_frame: Is this the first frame within the bounds of an action?
ref_stats: A ReferenceRenderingStats object to record expected values.
"""
# Create randonm data and timestap for main thread rendering stats.
data = { 'frame_count': 0,
'paint_time': 0.0,
'painted_pixel_count': 0,
'record_time': mock_timer.Advance(2, 4) / 1000.0,
'recorded_pixel_count': 3000*3000 }
timestamp = mock_timer.Get()
# Add a slice with the event data to the given thread.
thread.PushCompleteSlice(
'benchmark', 'BenchmarkInstrumentation::MainThreadRenderingStats',
timestamp, 0.0, {'data': data})
if not ref_stats:
return
# Add timestamp only if a frame was output
if data['frame_count'] == 1:
if not first_frame:
# Add frame_time if this is not the first frame in within the bounds of an
# action.
prev_timestamp = ref_stats.frame_timestamps[-1]
ref_stats.frame_times.append(round(timestamp - prev_timestamp, 2))
ref_stats.frame_timestamps.append(timestamp)
ref_stats.paint_time.append(data['paint_time'] * 1000.0)
ref_stats.painted_pixel_count.append(data['painted_pixel_count'])
ref_stats.record_time.append(data['record_time'] * 1000.0)
ref_stats.recorded_pixel_count.append(data['recorded_pixel_count'])
def AddImplThreadRenderingStats(mock_timer, thread, first_frame,
ref_stats = None):
""" Adds a random impl thread rendering stats event.
thread: The timeline model thread to which the event will be added.
first_frame: Is this the first frame within the bounds of an action?
ref_stats: A ReferenceRenderingStats object to record expected values.
"""
# Create randonm data and timestap for impl thread rendering stats.
data = { 'frame_count': 1,
'rasterize_time': mock_timer.Advance(5, 10) / 1000.0,
'rasterized_pixel_count': 1280*720 }
timestamp = mock_timer.Get()
# Add a slice with the event data to the given thread.
thread.PushCompleteSlice(
'benchmark', 'BenchmarkInstrumentation::ImplThreadRenderingStats',
timestamp, 0.0, {'data': data})
if not ref_stats:
return
# Add timestamp only if a frame was output
if data['frame_count'] == 1:
if not first_frame:
# Add frame_time if this is not the first frame in within the bounds of an
# action.
prev_timestamp = ref_stats.frame_timestamps[-1]
ref_stats.frame_times.append(round(timestamp - prev_timestamp, 2))
ref_stats.frame_timestamps.append(timestamp)
ref_stats.rasterize_time.append(data['rasterize_time'] * 1000.0)
ref_stats.rasterized_pixel_count.append(data['rasterized_pixel_count'])
class RenderingStatsUnitTest(unittest.TestCase):
def testFromTimeline(self):
timeline = model.TimelineModel()
# Create a browser process and a renderer process, and a main thread and
# impl thread for each.
browser = timeline.GetOrCreateProcess(pid = 1)
browser_main = browser.GetOrCreateThread(tid = 11)
browser_compositor = browser.GetOrCreateThread(tid = 12)
renderer = timeline.GetOrCreateProcess(pid = 2)
renderer_main = renderer.GetOrCreateThread(tid = 21)
renderer_compositor = renderer.GetOrCreateThread(tid = 22)
timer = MockTimer()
ref_stats = ReferenceRenderingStats()
# Create 10 main and impl rendering stats events for Action A.
timer.Advance()
renderer_main.BeginSlice('webkit.console', 'ActionA', timer.Get(), '')
for i in xrange(0, 10):
first = (i == 0)
AddMainThreadRenderingStats(timer, renderer_main, first, ref_stats)
AddImplThreadRenderingStats(timer, renderer_compositor, first, ref_stats)
AddMainThreadRenderingStats(timer, browser_main, first, None)
AddImplThreadRenderingStats(timer, browser_compositor, first, None)
renderer_main.EndSlice(timer.Get())
# Create 5 main and impl rendering stats events not within any action.
for i in xrange(0, 5):
first = (i == 0)
AddMainThreadRenderingStats(timer, renderer_main, first, None)
AddImplThreadRenderingStats(timer, renderer_compositor, first, None)
AddMainThreadRenderingStats(timer, browser_main, first, None)
AddImplThreadRenderingStats(timer, browser_compositor, first, None)
# Create 10 main and impl rendering stats events for Action B.
timer.Advance()
renderer_main.BeginSlice('webkit.console', 'ActionB', timer.Get(), '')
for i in xrange(0, 10):
first = (i == 0)
AddMainThreadRenderingStats(timer, renderer_main, first, ref_stats)
AddImplThreadRenderingStats(timer, renderer_compositor, first, ref_stats)
AddMainThreadRenderingStats(timer, browser_main, first, None)
AddImplThreadRenderingStats(timer, browser_compositor, first, None)
renderer_main.EndSlice(timer.Get())
# Create 10 main and impl rendering stats events for Action A.
timer.Advance()
renderer_main.BeginSlice('webkit.console', 'ActionA', timer.Get(), '')
for i in xrange(0, 10):
first = (i == 0)
AddMainThreadRenderingStats(timer, renderer_main, first, ref_stats)
AddImplThreadRenderingStats(timer, renderer_compositor, first, ref_stats)
AddMainThreadRenderingStats(timer, browser_main, first, None)
AddImplThreadRenderingStats(timer, browser_compositor, first, None)
renderer_main.EndSlice(timer.Get())
renderer_main.FinalizeImport()
renderer_compositor.FinalizeImport()
timeline_markers = timeline.FindTimelineMarkers(
['ActionA', 'ActionB', 'ActionA'])
stats = RenderingStats(renderer, timeline_markers)
# Compare rendering stats to reference.
self.assertEquals(stats.frame_timestamps, ref_stats.frame_timestamps)
self.assertEquals(stats.frame_times, ref_stats.frame_times)
self.assertEquals(stats.rasterize_time, ref_stats.rasterize_time)
self.assertEquals(stats.rasterized_pixel_count,
ref_stats.rasterized_pixel_count)
self.assertEquals(stats.paint_time, ref_stats.paint_time)
self.assertEquals(stats.painted_pixel_count, ref_stats.painted_pixel_count)
self.assertEquals(stats.record_time, ref_stats.record_time)
self.assertEquals(stats.recorded_pixel_count,
ref_stats.recorded_pixel_count)
...@@ -139,30 +139,33 @@ class TimelineModel(object): ...@@ -139,30 +139,33 @@ class TimelineModel(object):
self._processes[pid] = tracing_process.Process(self, pid) self._processes[pid] = tracing_process.Process(self, pid)
return self._processes[pid] return self._processes[pid]
def FindTimelineMarkers(self, timeline_marker_labels): def FindTimelineMarkers(self, timeline_marker_names):
"""Find the timeline events with the given names. """Find the timeline events with the given names.
If the number and order of events found does not match the labels, If the number and order of events found does not match the names,
raise an error. raise an error.
""" """
# Make sure labels are in a list and remove all None labels # Make sure names are in a list and remove all None names
if not isinstance(timeline_marker_labels, list): if not isinstance(timeline_marker_names, list):
timeline_marker_labels = [timeline_marker_labels] timeline_marker_names = [timeline_marker_names]
labels = [x for x in timeline_marker_labels if x is not None] names = [x for x in timeline_marker_names if x is not None]
# Gather all events that match the labels and sort them. # Gather all events that match the names and sort them.
events = [] events = []
for label in labels: name_set = set()
events.extend([s for s in self.GetAllEventsOfName(label) for name in names:
name_set.add(name)
for name in name_set:
events.extend([s for s in self.GetAllEventsOfName(name)
if s.parent_slice == None]) if s.parent_slice == None])
events.sort(key=attrgetter('start')) events.sort(key=attrgetter('start'))
# Check if the number and order of events matches the provided labels, # Check if the number and order of events matches the provided names,
# and that the events don't overlap. # and that the events don't overlap.
if len(events) != len(labels): if len(events) != len(names):
raise MarkerMismatchError() raise MarkerMismatchError()
for (i, event) in enumerate(events): for (i, event) in enumerate(events):
if event.name != labels[i]: if event.name != names[i]:
raise MarkerMismatchError() raise MarkerMismatchError()
for i in xrange(0, len(events)): for i in xrange(0, len(events)):
for j in xrange(i+1, len(events)): for j in xrange(i+1, len(events)):
......
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