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 HasRenderingStats
13 from metrics.rendering_stats import RenderingStats
14 import telemetry.core.timeline.bounds as timeline_bounds
15 from telemetry.core.timeline import model
16 import telemetry.core.timeline.async_slice as tracing_async_slice
19 class MockTimer(object):
20 """A mock timer class which can generate random durations.
22 An instance of this class is used as a global timer to generate random
23 durations for stats and consistent timestamps for all mock trace events.
24 The unit of time is milliseconds.
30 return self.milliseconds
32 def Advance(self, low=0, high=1):
33 delta = random.uniform(low, high)
34 self.milliseconds += delta
38 class ReferenceRenderingStats(object):
39 """ Stores expected data for comparison with actual RenderingStats """
41 self.frame_timestamps = []
44 self.painted_pixel_counts = []
45 self.record_times = []
46 self.recorded_pixel_counts = []
47 self.rasterize_times = []
48 self.rasterized_pixel_counts = []
50 def AppendNewRange(self):
51 self.frame_timestamps.append([])
52 self.frame_times.append([])
53 self.paint_times.append([])
54 self.painted_pixel_counts.append([])
55 self.record_times.append([])
56 self.recorded_pixel_counts.append([])
57 self.rasterize_times.append([])
58 self.rasterized_pixel_counts.append([])
60 class ReferenceInputLatencyStats(object):
61 """ Stores expected data for comparison with actual input latency stats """
63 self.mouse_wheel_scroll_latency = []
64 self.touch_scroll_latency = []
65 self.js_touch_scroll_latency = []
66 self.mouse_wheel_scroll_events = []
67 self.touch_scroll_events = []
68 self.js_touch_scroll_events = []
70 def AddMainThreadRenderingStats(mock_timer, thread, first_frame,
72 """ Adds a random main thread rendering stats event.
74 thread: The timeline model thread to which the event will be added.
75 first_frame: Is this the first frame within the bounds of an action?
76 ref_stats: A ReferenceRenderingStats object to record expected values.
78 # Create randonm data and timestap for main thread rendering stats.
79 data = { 'frame_count': 0,
81 'painted_pixel_count': 0,
82 'record_time': mock_timer.Advance(2, 4) / 1000.0,
83 'recorded_pixel_count': 3000*3000 }
84 timestamp = mock_timer.Get()
86 # Add a slice with the event data to the given thread.
87 thread.PushCompleteSlice(
88 'benchmark', 'BenchmarkInstrumentation::MainThreadRenderingStats',
89 timestamp, duration=0.0, thread_timestamp=None, thread_duration=None,
95 # Add timestamp only if a frame was output
96 if data['frame_count'] == 1:
98 # Add frame_time if this is not the first frame in within the bounds of an
100 prev_timestamp = ref_stats.frame_timestamps[-1][-1]
101 ref_stats.frame_times[-1].append(round(timestamp - prev_timestamp, 2))
102 ref_stats.frame_timestamps[-1].append(timestamp)
104 ref_stats.paint_times[-1].append(data['paint_time'] * 1000.0)
105 ref_stats.painted_pixel_counts[-1].append(data['painted_pixel_count'])
106 ref_stats.record_times[-1].append(data['record_time'] * 1000.0)
107 ref_stats.recorded_pixel_counts[-1].append(data['recorded_pixel_count'])
110 def AddImplThreadRenderingStats(mock_timer, thread, first_frame,
112 """ Adds a random impl thread rendering stats event.
114 thread: The timeline model thread to which the event will be added.
115 first_frame: Is this the first frame within the bounds of an action?
116 ref_stats: A ReferenceRenderingStats object to record expected values.
118 # Create randonm data and timestap for impl thread rendering stats.
119 data = { 'frame_count': 1,
120 'rasterize_time': mock_timer.Advance(5, 10) / 1000.0,
121 'rasterized_pixel_count': 1280*720 }
122 timestamp = mock_timer.Get()
124 # Add a slice with the event data to the given thread.
125 thread.PushCompleteSlice(
126 'benchmark', 'BenchmarkInstrumentation::ImplThreadRenderingStats',
127 timestamp, duration=0.0, thread_timestamp=None, thread_duration=None,
133 # Add timestamp only if a frame was output
134 if data['frame_count'] == 1:
136 # Add frame_time if this is not the first frame in within the bounds of an
138 prev_timestamp = ref_stats.frame_timestamps[-1][-1]
139 ref_stats.frame_times[-1].append(round(timestamp - prev_timestamp, 2))
140 ref_stats.frame_timestamps[-1].append(timestamp)
142 ref_stats.rasterize_times[-1].append(data['rasterize_time'] * 1000.0)
143 ref_stats.rasterized_pixel_counts[-1].append(data['rasterized_pixel_count'])
146 def AddInputLatencyStats(mock_timer, input_type, start_thread, end_thread,
147 ref_latency_stats = None):
148 """ Adds a random input latency stats event.
150 input_type: The input type for which the latency slice is generated.
151 start_thread: The start thread on which the async slice is added.
152 end_thread: The end thread on which the async slice is ended.
153 ref_latency_stats: A ReferenceInputLatencyStats object for expected values.
157 ui_comp_time = mock_timer.Get() * 1000.0
159 begin_comp_time = mock_timer.Get() * 1000.0
160 mock_timer.Advance(10, 20)
161 end_comp_time = mock_timer.Get() * 1000.0
163 data = { UI_COMP_NAME: {'time': ui_comp_time},
164 BEGIN_COMP_NAME: {'time': begin_comp_time},
165 END_COMP_NAME: {'time': end_comp_time} }
167 timestamp = mock_timer.Get()
169 async_slice = tracing_async_slice.AsyncSlice(
170 'benchmark', 'InputLatency', timestamp)
172 async_sub_slice = tracing_async_slice.AsyncSlice(
173 'benchmark', 'InputLatency', timestamp)
174 async_sub_slice.args = {'data': data, 'step': input_type}
175 async_sub_slice.parent_slice = async_slice
176 async_sub_slice.start_thread = start_thread
177 async_sub_slice.end_thread = end_thread
179 async_slice.sub_slices.append(async_sub_slice)
180 async_slice.start_thread = start_thread
181 async_slice.end_thread = end_thread
182 start_thread.AddAsyncSlice(async_slice)
184 if not ref_latency_stats:
187 if input_type == 'MouseWheel':
188 ref_latency_stats.mouse_wheel_scroll_events.append(async_sub_slice)
189 ref_latency_stats.mouse_wheel_scroll_latency.append(
190 (data[END_COMP_NAME]['time'] - data[BEGIN_COMP_NAME]['time']) / 1000.0)
192 if input_type == 'GestureScrollUpdate':
193 ref_latency_stats.touch_scroll_events.append(async_sub_slice)
194 ref_latency_stats.touch_scroll_latency.append(
195 (data[END_COMP_NAME]['time'] - data[UI_COMP_NAME]['time']) / 1000.0)
197 if input_type == 'TouchMove':
198 ref_latency_stats.js_touch_scroll_events.append(async_sub_slice)
199 ref_latency_stats.js_touch_scroll_latency.append(
200 (data[END_COMP_NAME]['time'] - data[UI_COMP_NAME]['time']) / 1000.0)
202 class RenderingStatsUnitTest(unittest.TestCase):
203 def testHasRenderingStats(self):
204 timeline = model.TimelineModel()
207 # A process without rendering stats
208 process_without_stats = timeline.GetOrCreateProcess(pid = 1)
209 thread_without_stats = process_without_stats.GetOrCreateThread(tid = 11)
210 process_without_stats.FinalizeImport()
211 self.assertFalse(HasRenderingStats(thread_without_stats))
213 # A process with rendering stats, but no frames in them
214 process_without_frames = timeline.GetOrCreateProcess(pid = 2)
215 thread_without_frames = process_without_frames.GetOrCreateThread(tid = 21)
216 AddMainThreadRenderingStats(timer, thread_without_frames, True, None)
217 process_without_frames.FinalizeImport()
218 self.assertFalse(HasRenderingStats(thread_without_frames))
220 # A process with rendering stats and frames in them
221 process_with_frames = timeline.GetOrCreateProcess(pid = 3)
222 thread_with_frames = process_with_frames.GetOrCreateThread(tid = 31)
223 AddImplThreadRenderingStats(timer, thread_with_frames, True, None)
224 process_with_frames.FinalizeImport()
225 self.assertTrue(HasRenderingStats(thread_with_frames))
227 def testFromTimeline(self):
228 timeline = model.TimelineModel()
230 # Create a browser process and a renderer process, and a main thread and
231 # impl thread for each.
232 browser = timeline.GetOrCreateProcess(pid = 1)
233 browser_main = browser.GetOrCreateThread(tid = 11)
234 browser_compositor = browser.GetOrCreateThread(tid = 12)
235 renderer = timeline.GetOrCreateProcess(pid = 2)
236 renderer_main = renderer.GetOrCreateThread(tid = 21)
237 renderer_compositor = renderer.GetOrCreateThread(tid = 22)
240 ref_stats = ReferenceRenderingStats()
242 # Create 10 main and impl rendering stats events for Action A.
244 renderer_main.BeginSlice('webkit.console', 'ActionA', timer.Get(), '')
245 ref_stats.AppendNewRange()
246 for i in xrange(0, 10):
248 AddMainThreadRenderingStats(timer, renderer_main, first, None)
249 AddImplThreadRenderingStats(timer, renderer_compositor, first, None)
250 AddMainThreadRenderingStats(timer, browser_main, first, ref_stats)
251 AddImplThreadRenderingStats(timer, browser_compositor, first, ref_stats)
252 renderer_main.EndSlice(timer.Get())
254 # Create 5 main and impl rendering stats events not within any action.
255 for i in xrange(0, 5):
257 AddMainThreadRenderingStats(timer, renderer_main, first, None)
258 AddImplThreadRenderingStats(timer, renderer_compositor, first, None)
259 AddMainThreadRenderingStats(timer, browser_main, first, None)
260 AddImplThreadRenderingStats(timer, browser_compositor, first, None)
262 # Create 10 main and impl rendering stats events for Action B.
264 renderer_main.BeginSlice('webkit.console', 'ActionB', timer.Get(), '')
265 ref_stats.AppendNewRange()
266 for i in xrange(0, 10):
268 AddMainThreadRenderingStats(timer, renderer_main, first, None)
269 AddImplThreadRenderingStats(timer, renderer_compositor, first, None)
270 AddMainThreadRenderingStats(timer, browser_main, first, ref_stats)
271 AddImplThreadRenderingStats(timer, browser_compositor, first, ref_stats)
272 renderer_main.EndSlice(timer.Get())
274 # Create 10 main and impl rendering stats events for Action A.
276 renderer_main.BeginSlice('webkit.console', 'ActionA', timer.Get(), '')
277 ref_stats.AppendNewRange()
278 for i in xrange(0, 10):
280 AddMainThreadRenderingStats(timer, renderer_main, first, None)
281 AddImplThreadRenderingStats(timer, renderer_compositor, first, None)
282 AddMainThreadRenderingStats(timer, browser_main, first, ref_stats)
283 AddImplThreadRenderingStats(timer, browser_compositor, first, ref_stats)
284 renderer_main.EndSlice(timer.Get())
286 browser.FinalizeImport()
287 renderer.FinalizeImport()
289 timeline_markers = timeline.FindTimelineMarkers(
290 ['ActionA', 'ActionB', 'ActionA'])
291 timeline_ranges = [ timeline_bounds.Bounds.CreateFromEvent(marker)
292 for marker in timeline_markers ]
293 stats = RenderingStats(renderer, browser, timeline_ranges)
295 # Check if we are using the browser compositor's stats
296 self.assertEquals(stats.top_level_process, browser)
298 # Compare rendering stats to reference.
299 self.assertEquals(stats.frame_timestamps, ref_stats.frame_timestamps)
300 self.assertEquals(stats.frame_times, ref_stats.frame_times)
301 self.assertEquals(stats.rasterize_times, ref_stats.rasterize_times)
302 self.assertEquals(stats.rasterized_pixel_counts,
303 ref_stats.rasterized_pixel_counts)
304 self.assertEquals(stats.paint_times, ref_stats.paint_times)
305 self.assertEquals(stats.painted_pixel_counts,
306 ref_stats.painted_pixel_counts)
307 self.assertEquals(stats.record_times, ref_stats.record_times)
308 self.assertEquals(stats.recorded_pixel_counts,
309 ref_stats.recorded_pixel_counts)
311 def testScrollLatencyFromTimeline(self):
312 timeline = model.TimelineModel()
314 # Create a browser process and a renderer process.
315 browser = timeline.GetOrCreateProcess(pid = 1)
316 browser_main = browser.GetOrCreateThread(tid = 11)
317 renderer = timeline.GetOrCreateProcess(pid = 2)
318 renderer_main = renderer.GetOrCreateThread(tid = 21)
321 ref_latency_stats = ReferenceInputLatencyStats()
323 # Create 10 input latency stats events for Action A.
325 renderer_main.BeginSlice('webkit.console', 'ActionA', timer.Get(), '')
326 for _ in xrange(0, 10):
327 AddInputLatencyStats(timer, 'MouseWheel', browser_main,
328 renderer_main, ref_latency_stats)
329 AddInputLatencyStats(timer, 'GestureScrollUpdate', browser_main,
330 renderer_main, ref_latency_stats)
331 AddInputLatencyStats(timer, 'TouchMove', browser_main,
332 renderer_main, ref_latency_stats)
333 renderer_main.EndSlice(timer.Get())
335 # Create 5 input latency stats events not within any action.
336 for _ in xrange(0, 5):
337 AddInputLatencyStats(timer, 'MouseWheel', browser_main,
339 AddInputLatencyStats(timer, 'GestureScrollUpdate', browser_main,
341 AddInputLatencyStats(timer, 'TouchMove', browser_main,
344 # Create 10 input latency stats events for Action B.
346 renderer_main.BeginSlice('webkit.console', 'ActionB', timer.Get(), '')
347 for _ in xrange(0, 10):
348 AddInputLatencyStats(timer, 'MouseWheel', browser_main,
349 renderer_main, ref_latency_stats)
350 AddInputLatencyStats(timer, 'GestureScrollUpdate', browser_main,
351 renderer_main, ref_latency_stats)
352 AddInputLatencyStats(timer, 'TouchMove', browser_main,
353 renderer_main, ref_latency_stats)
354 renderer_main.EndSlice(timer.Get())
356 # Create 10 input latency stats events for Action A.
358 renderer_main.BeginSlice('webkit.console', 'ActionA', timer.Get(), '')
359 for _ in xrange(0, 10):
360 AddInputLatencyStats(timer, 'MouseWheel', browser_main,
361 renderer_main, ref_latency_stats)
362 AddInputLatencyStats(timer, 'GestureScrollUpdate', browser_main,
363 renderer_main, ref_latency_stats)
364 AddInputLatencyStats(timer, 'TouchMove', browser_main,
365 renderer_main, ref_latency_stats)
366 renderer_main.EndSlice(timer.Get())
368 browser.FinalizeImport()
369 renderer.FinalizeImport()
371 mouse_wheel_scroll_events = []
372 touch_scroll_events = []
373 js_touch_scroll_events = []
375 timeline_markers = timeline.FindTimelineMarkers(
376 ['ActionA', 'ActionB', 'ActionA'])
377 for timeline_range in [ timeline_bounds.Bounds.CreateFromEvent(marker)
378 for marker in timeline_markers ]:
379 if timeline_range.is_empty:
381 tmp_mouse_events = GetScrollInputLatencyEvents(
382 'MouseWheel', browser, timeline_range)
383 tmp_touch_scroll_events = GetScrollInputLatencyEvents(
384 'GestureScrollUpdate', browser, timeline_range)
385 tmp_js_touch_scroll_events = GetScrollInputLatencyEvents(
386 'TouchMove', browser, timeline_range)
387 mouse_wheel_scroll_events.extend(tmp_mouse_events)
388 touch_scroll_events.extend(tmp_touch_scroll_events)
389 js_touch_scroll_events.extend(tmp_js_touch_scroll_events)
391 self.assertEquals(mouse_wheel_scroll_events,
392 ref_latency_stats.mouse_wheel_scroll_events)
393 self.assertEquals(touch_scroll_events,
394 ref_latency_stats.touch_scroll_events)
395 self.assertEquals(js_touch_scroll_events,
396 ref_latency_stats.js_touch_scroll_events)
397 self.assertEquals(ComputeMouseWheelScrollLatency(mouse_wheel_scroll_events),
398 ref_latency_stats.mouse_wheel_scroll_latency)
399 self.assertEquals(ComputeTouchScrollLatency(touch_scroll_events),
400 ref_latency_stats.touch_scroll_latency)
401 self.assertEquals(ComputeTouchScrollLatency(js_touch_scroll_events),
402 ref_latency_stats.js_touch_scroll_latency)