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.
8 from metrics.rendering_stats import UI_COMP_NAME, BEGIN_COMP_NAME, END_COMP_NAME
9 from metrics.rendering_stats import GetScrollInputLatencyEvents
10 from metrics.rendering_stats import ComputeMouseWheelScrollLatency
11 from metrics.rendering_stats import ComputeTouchScrollLatency
12 from metrics.rendering_stats import RenderingStats
13 import telemetry.core.timeline.bounds as timeline_bounds
14 from telemetry.core.timeline import model
15 import telemetry.core.timeline.async_slice as tracing_async_slice
18 class MockTimer(object):
19 """A mock timer class which can generate random durations.
21 An instance of this class is used as a global timer to generate random
22 durations for stats and consistent timestamps for all mock trace events.
23 The unit of time is milliseconds.
29 return self.milliseconds
31 def Advance(self, low=0, high=1):
32 delta = random.uniform(low, high)
33 self.milliseconds += delta
37 class ReferenceRenderingStats(object):
38 """ Stores expected data for comparison with actual RenderingStats """
40 self.frame_timestamps = []
43 self.painted_pixel_counts = []
44 self.record_times = []
45 self.recorded_pixel_counts = []
46 self.rasterize_times = []
47 self.rasterized_pixel_counts = []
49 def AppendNewRange(self):
50 self.frame_timestamps.append([])
51 self.frame_times.append([])
52 self.paint_times.append([])
53 self.painted_pixel_counts.append([])
54 self.record_times.append([])
55 self.recorded_pixel_counts.append([])
56 self.rasterize_times.append([])
57 self.rasterized_pixel_counts.append([])
59 class ReferenceInputLatencyStats(object):
60 """ Stores expected data for comparison with actual input latency stats """
62 self.mouse_wheel_scroll_latency = []
63 self.touch_scroll_latency = []
64 self.js_touch_scroll_latency = []
65 self.mouse_wheel_scroll_events = []
66 self.touch_scroll_events = []
67 self.js_touch_scroll_events = []
69 def AddMainThreadRenderingStats(mock_timer, thread, first_frame,
71 """ Adds a random main thread rendering stats event.
73 thread: The timeline model thread to which the event will be added.
74 first_frame: Is this the first frame within the bounds of an action?
75 ref_stats: A ReferenceRenderingStats object to record expected values.
77 # Create randonm data and timestap for main thread rendering stats.
78 data = { 'frame_count': 0,
80 'painted_pixel_count': 0,
81 'record_time': mock_timer.Advance(2, 4) / 1000.0,
82 'recorded_pixel_count': 3000*3000 }
83 timestamp = mock_timer.Get()
85 # Add a slice with the event data to the given thread.
86 thread.PushCompleteSlice(
87 'benchmark', 'BenchmarkInstrumentation::MainThreadRenderingStats',
88 timestamp, duration=0.0, thread_timestamp=None, thread_duration=None,
94 # Add timestamp only if a frame was output
95 if data['frame_count'] == 1:
97 # Add frame_time if this is not the first frame in within the bounds of an
99 prev_timestamp = ref_stats.frame_timestamps[-1][-1]
100 ref_stats.frame_times[-1].append(round(timestamp - prev_timestamp, 2))
101 ref_stats.frame_timestamps[-1].append(timestamp)
103 ref_stats.paint_times[-1].append(data['paint_time'] * 1000.0)
104 ref_stats.painted_pixel_counts[-1].append(data['painted_pixel_count'])
105 ref_stats.record_times[-1].append(data['record_time'] * 1000.0)
106 ref_stats.recorded_pixel_counts[-1].append(data['recorded_pixel_count'])
109 def AddImplThreadRenderingStats(mock_timer, thread, first_frame,
111 """ Adds a random impl thread rendering stats event.
113 thread: The timeline model thread to which the event will be added.
114 first_frame: Is this the first frame within the bounds of an action?
115 ref_stats: A ReferenceRenderingStats object to record expected values.
117 # Create randonm data and timestap for impl thread rendering stats.
118 data = { 'frame_count': 1,
119 'rasterize_time': mock_timer.Advance(5, 10) / 1000.0,
120 'rasterized_pixel_count': 1280*720 }
121 timestamp = mock_timer.Get()
123 # Add a slice with the event data to the given thread.
124 thread.PushCompleteSlice(
125 'benchmark', 'BenchmarkInstrumentation::ImplThreadRenderingStats',
126 timestamp, duration=0.0, thread_timestamp=None, thread_duration=None,
132 # Add timestamp only if a frame was output
133 if data['frame_count'] == 1:
135 # Add frame_time if this is not the first frame in within the bounds of an
137 prev_timestamp = ref_stats.frame_timestamps[-1][-1]
138 ref_stats.frame_times[-1].append(round(timestamp - prev_timestamp, 2))
139 ref_stats.frame_timestamps[-1].append(timestamp)
141 ref_stats.rasterize_times[-1].append(data['rasterize_time'] * 1000.0)
142 ref_stats.rasterized_pixel_counts[-1].append(data['rasterized_pixel_count'])
145 def AddInputLatencyStats(mock_timer, input_type, start_thread, end_thread,
146 ref_latency_stats = None):
147 """ Adds a random input latency stats event.
149 input_type: The input type for which the latency slice is generated.
150 start_thread: The start thread on which the async slice is added.
151 end_thread: The end thread on which the async slice is ended.
152 ref_latency_stats: A ReferenceInputLatencyStats object for expected values.
156 ui_comp_time = mock_timer.Get() * 1000.0
158 begin_comp_time = mock_timer.Get() * 1000.0
159 mock_timer.Advance(10, 20)
160 end_comp_time = mock_timer.Get() * 1000.0
162 data = { UI_COMP_NAME: {'time': ui_comp_time},
163 BEGIN_COMP_NAME: {'time': begin_comp_time},
164 END_COMP_NAME: {'time': end_comp_time} }
166 timestamp = mock_timer.Get()
168 async_slice = tracing_async_slice.AsyncSlice(
169 'benchmark', 'InputLatency', timestamp)
171 async_sub_slice = tracing_async_slice.AsyncSlice(
172 'benchmark', 'InputLatency', timestamp)
173 async_sub_slice.args = {'data': data, 'step': input_type}
174 async_sub_slice.parent_slice = async_slice
175 async_sub_slice.start_thread = start_thread
176 async_sub_slice.end_thread = end_thread
178 async_slice.sub_slices.append(async_sub_slice)
179 async_slice.start_thread = start_thread
180 async_slice.end_thread = end_thread
181 start_thread.AddAsyncSlice(async_slice)
183 if not ref_latency_stats:
186 if input_type == 'MouseWheel':
187 ref_latency_stats.mouse_wheel_scroll_events.append(async_sub_slice)
188 ref_latency_stats.mouse_wheel_scroll_latency.append(
189 (data[END_COMP_NAME]['time'] - data[BEGIN_COMP_NAME]['time']) / 1000.0)
191 if input_type == 'GestureScrollUpdate':
192 ref_latency_stats.touch_scroll_events.append(async_sub_slice)
193 ref_latency_stats.touch_scroll_latency.append(
194 (data[END_COMP_NAME]['time'] - data[UI_COMP_NAME]['time']) / 1000.0)
196 if input_type == 'TouchMove':
197 ref_latency_stats.js_touch_scroll_events.append(async_sub_slice)
198 ref_latency_stats.js_touch_scroll_latency.append(
199 (data[END_COMP_NAME]['time'] - data[UI_COMP_NAME]['time']) / 1000.0)
201 class RenderingStatsUnitTest(unittest.TestCase):
202 def testFromTimeline(self):
203 timeline = model.TimelineModel()
205 # Create a browser process and a renderer process, and a main thread and
206 # impl thread for each.
207 browser = timeline.GetOrCreateProcess(pid = 1)
208 browser_main = browser.GetOrCreateThread(tid = 11)
209 browser_compositor = browser.GetOrCreateThread(tid = 12)
210 renderer = timeline.GetOrCreateProcess(pid = 2)
211 renderer_main = renderer.GetOrCreateThread(tid = 21)
212 renderer_compositor = renderer.GetOrCreateThread(tid = 22)
215 ref_stats = ReferenceRenderingStats()
217 # Create 10 main and impl rendering stats events for Action A.
219 renderer_main.BeginSlice('webkit.console', 'ActionA', timer.Get(), '')
220 ref_stats.AppendNewRange()
221 for i in xrange(0, 10):
223 AddMainThreadRenderingStats(timer, renderer_main, first, ref_stats)
224 AddImplThreadRenderingStats(timer, renderer_compositor, first, ref_stats)
225 AddMainThreadRenderingStats(timer, browser_main, first, None)
226 AddImplThreadRenderingStats(timer, browser_compositor, first, None)
227 renderer_main.EndSlice(timer.Get())
229 # Create 5 main and impl rendering stats events not within any action.
230 for i in xrange(0, 5):
232 AddMainThreadRenderingStats(timer, renderer_main, first, None)
233 AddImplThreadRenderingStats(timer, renderer_compositor, first, None)
234 AddMainThreadRenderingStats(timer, browser_main, first, None)
235 AddImplThreadRenderingStats(timer, browser_compositor, first, None)
237 # Create 10 main and impl rendering stats events for Action B.
239 renderer_main.BeginSlice('webkit.console', 'ActionB', timer.Get(), '')
240 ref_stats.AppendNewRange()
241 for i in xrange(0, 10):
243 AddMainThreadRenderingStats(timer, renderer_main, first, ref_stats)
244 AddImplThreadRenderingStats(timer, renderer_compositor, first, ref_stats)
245 AddMainThreadRenderingStats(timer, browser_main, first, None)
246 AddImplThreadRenderingStats(timer, browser_compositor, first, None)
247 renderer_main.EndSlice(timer.Get())
249 # Create 10 main and impl rendering stats events for Action A.
251 renderer_main.BeginSlice('webkit.console', 'ActionA', timer.Get(), '')
252 ref_stats.AppendNewRange()
253 for i in xrange(0, 10):
255 AddMainThreadRenderingStats(timer, renderer_main, first, ref_stats)
256 AddImplThreadRenderingStats(timer, renderer_compositor, first, ref_stats)
257 AddMainThreadRenderingStats(timer, browser_main, first, None)
258 AddImplThreadRenderingStats(timer, browser_compositor, first, None)
259 renderer_main.EndSlice(timer.Get())
261 renderer_main.FinalizeImport()
262 renderer_compositor.FinalizeImport()
264 timeline_markers = timeline.FindTimelineMarkers(
265 ['ActionA', 'ActionB', 'ActionA'])
266 timeline_ranges = [ timeline_bounds.Bounds.CreateFromEvent(marker)
267 for marker in timeline_markers ]
268 stats = RenderingStats(renderer, browser, timeline_ranges)
270 # Compare rendering stats to reference.
271 self.assertEquals(stats.frame_timestamps, ref_stats.frame_timestamps)
272 self.assertEquals(stats.frame_times, ref_stats.frame_times)
273 self.assertEquals(stats.rasterize_times, ref_stats.rasterize_times)
274 self.assertEquals(stats.rasterized_pixel_counts,
275 ref_stats.rasterized_pixel_counts)
276 self.assertEquals(stats.paint_times, ref_stats.paint_times)
277 self.assertEquals(stats.painted_pixel_counts,
278 ref_stats.painted_pixel_counts)
279 self.assertEquals(stats.record_times, ref_stats.record_times)
280 self.assertEquals(stats.recorded_pixel_counts,
281 ref_stats.recorded_pixel_counts)
283 def testScrollLatencyFromTimeline(self):
284 timeline = model.TimelineModel()
286 # Create a browser process and a renderer process.
287 browser = timeline.GetOrCreateProcess(pid = 1)
288 browser_main = browser.GetOrCreateThread(tid = 11)
289 renderer = timeline.GetOrCreateProcess(pid = 2)
290 renderer_main = renderer.GetOrCreateThread(tid = 21)
293 ref_latency_stats = ReferenceInputLatencyStats()
295 # Create 10 input latency stats events for Action A.
297 renderer_main.BeginSlice('webkit.console', 'ActionA', timer.Get(), '')
298 for _ in xrange(0, 10):
299 AddInputLatencyStats(timer, 'MouseWheel', browser_main,
300 renderer_main, ref_latency_stats)
301 AddInputLatencyStats(timer, 'GestureScrollUpdate', browser_main,
302 renderer_main, ref_latency_stats)
303 AddInputLatencyStats(timer, 'TouchMove', browser_main,
304 renderer_main, ref_latency_stats)
305 renderer_main.EndSlice(timer.Get())
307 # Create 5 input latency stats events not within any action.
308 for _ in xrange(0, 5):
309 AddInputLatencyStats(timer, 'MouseWheel', browser_main,
311 AddInputLatencyStats(timer, 'GestureScrollUpdate', browser_main,
313 AddInputLatencyStats(timer, 'TouchMove', browser_main,
316 # Create 10 input latency stats events for Action B.
318 renderer_main.BeginSlice('webkit.console', 'ActionB', timer.Get(), '')
319 for _ in xrange(0, 10):
320 AddInputLatencyStats(timer, 'MouseWheel', browser_main,
321 renderer_main, ref_latency_stats)
322 AddInputLatencyStats(timer, 'GestureScrollUpdate', browser_main,
323 renderer_main, ref_latency_stats)
324 AddInputLatencyStats(timer, 'TouchMove', browser_main,
325 renderer_main, ref_latency_stats)
326 renderer_main.EndSlice(timer.Get())
328 # Create 10 input latency stats events for Action A.
330 renderer_main.BeginSlice('webkit.console', 'ActionA', timer.Get(), '')
331 for _ in xrange(0, 10):
332 AddInputLatencyStats(timer, 'MouseWheel', browser_main,
333 renderer_main, ref_latency_stats)
334 AddInputLatencyStats(timer, 'GestureScrollUpdate', browser_main,
335 renderer_main, ref_latency_stats)
336 AddInputLatencyStats(timer, 'TouchMove', browser_main,
337 renderer_main, ref_latency_stats)
338 renderer_main.EndSlice(timer.Get())
340 browser_main.FinalizeImport()
341 renderer_main.FinalizeImport()
343 mouse_wheel_scroll_events = []
344 touch_scroll_events = []
345 js_touch_scroll_events = []
347 timeline_markers = timeline.FindTimelineMarkers(
348 ['ActionA', 'ActionB', 'ActionA'])
349 for timeline_range in [ timeline_bounds.Bounds.CreateFromEvent(marker)
350 for marker in timeline_markers ]:
351 if timeline_range.is_empty:
353 tmp_mouse_events = GetScrollInputLatencyEvents(
354 'MouseWheel', browser, timeline_range)
355 tmp_touch_scroll_events = GetScrollInputLatencyEvents(
356 'GestureScrollUpdate', browser, timeline_range)
357 tmp_js_touch_scroll_events = GetScrollInputLatencyEvents(
358 'TouchMove', browser, timeline_range)
359 mouse_wheel_scroll_events.extend(tmp_mouse_events)
360 touch_scroll_events.extend(tmp_touch_scroll_events)
361 js_touch_scroll_events.extend(tmp_js_touch_scroll_events)
363 self.assertEquals(mouse_wheel_scroll_events,
364 ref_latency_stats.mouse_wheel_scroll_events)
365 self.assertEquals(touch_scroll_events,
366 ref_latency_stats.touch_scroll_events)
367 self.assertEquals(js_touch_scroll_events,
368 ref_latency_stats.js_touch_scroll_events)
369 self.assertEquals(ComputeMouseWheelScrollLatency(mouse_wheel_scroll_events),
370 ref_latency_stats.mouse_wheel_scroll_latency)
371 self.assertEquals(ComputeTouchScrollLatency(touch_scroll_events),
372 ref_latency_stats.touch_scroll_latency)
373 self.assertEquals(ComputeTouchScrollLatency(js_touch_scroll_events),
374 ref_latency_stats.js_touch_scroll_latency)