1# Copyright 2013 The Chromium Authors. All rights reserved.
2# Use of this source code is governed by a BSD-style license that can be
3# found in the LICENSE file.
4
5import random
6import unittest
7
8from metrics.rendering_stats import RenderingStats
9from telemetry.core.timeline import model
10
11
12class MockTimer(object):
13  """A mock timer class which can generate random durations.
14
15  An instance of this class is used as a global timer to generate random
16  durations for stats and consistent timestamps for all mock trace events.
17  The unit of time is milliseconds.
18  """
19  def __init__(self):
20    self.milliseconds = 0
21
22  def Get(self):
23    return self.milliseconds
24
25  def Advance(self, low=0, high=1):
26    delta = random.uniform(low, high)
27    self.milliseconds += delta
28    return delta
29
30
31class ReferenceRenderingStats(object):
32  """ Stores expected data for comparison with actual RenderingStats """
33  def __init__(self):
34    self.frame_timestamps = []
35    self.frame_times = []
36    self.paint_time = []
37    self.painted_pixel_count = []
38    self.record_time = []
39    self.recorded_pixel_count = []
40    self.rasterize_time = []
41    self.rasterized_pixel_count = []
42
43
44def AddMainThreadRenderingStats(mock_timer, thread, first_frame,
45                                ref_stats = None):
46  """ Adds a random main thread rendering stats event.
47
48  thread: The timeline model thread to which the event will be added.
49  first_frame: Is this the first frame within the bounds of an action?
50  ref_stats: A ReferenceRenderingStats object to record expected values.
51  """
52  # Create randonm data and timestap for main thread rendering stats.
53  data = { 'frame_count': 0,
54           'paint_time': 0.0,
55           'painted_pixel_count': 0,
56           'record_time': mock_timer.Advance(2, 4) / 1000.0,
57           'recorded_pixel_count': 3000*3000 }
58  timestamp = mock_timer.Get()
59
60  # Add a slice with the event data to the given thread.
61  thread.PushCompleteSlice(
62      'benchmark', 'BenchmarkInstrumentation::MainThreadRenderingStats',
63      timestamp, duration=0.0, thread_timestamp=None, thread_duration=None,
64      args={'data': data})
65
66  if not ref_stats:
67    return
68
69  # Add timestamp only if a frame was output
70  if data['frame_count'] == 1:
71    if not first_frame:
72      # Add frame_time if this is not the first frame in within the bounds of an
73      # action.
74      prev_timestamp = ref_stats.frame_timestamps[-1]
75      ref_stats.frame_times.append(round(timestamp - prev_timestamp, 2))
76    ref_stats.frame_timestamps.append(timestamp)
77
78  ref_stats.paint_time.append(data['paint_time'] * 1000.0)
79  ref_stats.painted_pixel_count.append(data['painted_pixel_count'])
80  ref_stats.record_time.append(data['record_time'] * 1000.0)
81  ref_stats.recorded_pixel_count.append(data['recorded_pixel_count'])
82
83
84def AddImplThreadRenderingStats(mock_timer, thread, first_frame,
85                                ref_stats = None):
86  """ Adds a random impl thread rendering stats event.
87
88  thread: The timeline model thread to which the event will be added.
89  first_frame: Is this the first frame within the bounds of an action?
90  ref_stats: A ReferenceRenderingStats object to record expected values.
91  """
92  # Create randonm data and timestap for impl thread rendering stats.
93  data = { 'frame_count': 1,
94           'rasterize_time': mock_timer.Advance(5, 10) / 1000.0,
95           'rasterized_pixel_count': 1280*720 }
96  timestamp = mock_timer.Get()
97
98  # Add a slice with the event data to the given thread.
99  thread.PushCompleteSlice(
100      'benchmark', 'BenchmarkInstrumentation::ImplThreadRenderingStats',
101      timestamp, duration=0.0, thread_timestamp=None, thread_duration=None,
102      args={'data': data})
103
104  if not ref_stats:
105    return
106
107  # Add timestamp only if a frame was output
108  if data['frame_count'] == 1:
109    if not first_frame:
110      # Add frame_time if this is not the first frame in within the bounds of an
111      # action.
112      prev_timestamp = ref_stats.frame_timestamps[-1]
113      ref_stats.frame_times.append(round(timestamp - prev_timestamp, 2))
114    ref_stats.frame_timestamps.append(timestamp)
115
116  ref_stats.rasterize_time.append(data['rasterize_time'] * 1000.0)
117  ref_stats.rasterized_pixel_count.append(data['rasterized_pixel_count'])
118
119
120class RenderingStatsUnitTest(unittest.TestCase):
121  def testFromTimeline(self):
122    timeline = model.TimelineModel()
123
124    # Create a browser process and a renderer process, and a main thread and
125    # impl thread for each.
126    browser = timeline.GetOrCreateProcess(pid = 1)
127    browser_main = browser.GetOrCreateThread(tid = 11)
128    browser_compositor = browser.GetOrCreateThread(tid = 12)
129    renderer = timeline.GetOrCreateProcess(pid = 2)
130    renderer_main = renderer.GetOrCreateThread(tid = 21)
131    renderer_compositor = renderer.GetOrCreateThread(tid = 22)
132
133    timer = MockTimer()
134    ref_stats = ReferenceRenderingStats()
135
136    # Create 10 main and impl rendering stats events for Action A.
137    timer.Advance()
138    renderer_main.BeginSlice('webkit.console', 'ActionA', timer.Get(), '')
139    for i in xrange(0, 10):
140      first = (i == 0)
141      AddMainThreadRenderingStats(timer, renderer_main, first, ref_stats)
142      AddImplThreadRenderingStats(timer, renderer_compositor, first, ref_stats)
143      AddMainThreadRenderingStats(timer, browser_main, first, None)
144      AddImplThreadRenderingStats(timer, browser_compositor, first, None)
145    renderer_main.EndSlice(timer.Get())
146
147    # Create 5 main and impl rendering stats events not within any action.
148    for i in xrange(0, 5):
149      first = (i == 0)
150      AddMainThreadRenderingStats(timer, renderer_main, first, None)
151      AddImplThreadRenderingStats(timer, renderer_compositor, first, None)
152      AddMainThreadRenderingStats(timer, browser_main, first, None)
153      AddImplThreadRenderingStats(timer, browser_compositor, first, None)
154
155    # Create 10 main and impl rendering stats events for Action B.
156    timer.Advance()
157    renderer_main.BeginSlice('webkit.console', 'ActionB', timer.Get(), '')
158    for i in xrange(0, 10):
159      first = (i == 0)
160      AddMainThreadRenderingStats(timer, renderer_main, first, ref_stats)
161      AddImplThreadRenderingStats(timer, renderer_compositor, first, ref_stats)
162      AddMainThreadRenderingStats(timer, browser_main, first, None)
163      AddImplThreadRenderingStats(timer, browser_compositor, first, None)
164    renderer_main.EndSlice(timer.Get())
165
166    # Create 10 main and impl rendering stats events for Action A.
167    timer.Advance()
168    renderer_main.BeginSlice('webkit.console', 'ActionA', timer.Get(), '')
169    for i in xrange(0, 10):
170      first = (i == 0)
171      AddMainThreadRenderingStats(timer, renderer_main, first, ref_stats)
172      AddImplThreadRenderingStats(timer, renderer_compositor, first, ref_stats)
173      AddMainThreadRenderingStats(timer, browser_main, first, None)
174      AddImplThreadRenderingStats(timer, browser_compositor, first, None)
175    renderer_main.EndSlice(timer.Get())
176
177    renderer_main.FinalizeImport()
178    renderer_compositor.FinalizeImport()
179
180    timeline_markers = timeline.FindTimelineMarkers(
181        ['ActionA', 'ActionB', 'ActionA'])
182    stats = RenderingStats(renderer, timeline_markers)
183
184    # Compare rendering stats to reference.
185    self.assertEquals(stats.frame_timestamps, ref_stats.frame_timestamps)
186    self.assertEquals(stats.frame_times, ref_stats.frame_times)
187    self.assertEquals(stats.rasterize_time, ref_stats.rasterize_time)
188    self.assertEquals(stats.rasterized_pixel_count,
189                      ref_stats.rasterized_pixel_count)
190    self.assertEquals(stats.paint_time, ref_stats.paint_time)
191    self.assertEquals(stats.painted_pixel_count, ref_stats.painted_pixel_count)
192    self.assertEquals(stats.record_time, ref_stats.record_time)
193    self.assertEquals(stats.recorded_pixel_count,
194                      ref_stats.recorded_pixel_count)
195