Upstream version 7.35.144.0
[platform/framework/web/crosswalk.git] / src / tools / perf / metrics / rendering_stats_unittest.py
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
5 import random
6 import unittest
7
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
17
18
19 class MockTimer(object):
20   """A mock timer class which can generate random durations.
21
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.
25   """
26   def __init__(self):
27     self.milliseconds = 0
28
29   def Get(self):
30     return self.milliseconds
31
32   def Advance(self, low=0, high=1):
33     delta = random.uniform(low, high)
34     self.milliseconds += delta
35     return delta
36
37
38 class ReferenceRenderingStats(object):
39   """ Stores expected data for comparison with actual RenderingStats """
40   def __init__(self):
41     self.frame_timestamps = []
42     self.frame_times = []
43     self.paint_times = []
44     self.painted_pixel_counts = []
45     self.record_times = []
46     self.recorded_pixel_counts = []
47     self.rasterize_times = []
48     self.rasterized_pixel_counts = []
49
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([])
59
60 class ReferenceInputLatencyStats(object):
61   """ Stores expected data for comparison with actual input latency stats """
62   def __init__(self):
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 = []
69
70 def AddMainThreadRenderingStats(mock_timer, thread, first_frame,
71                                 ref_stats = None):
72   """ Adds a random main thread rendering stats event.
73
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.
77   """
78   # Create randonm data and timestap for main thread rendering stats.
79   data = { 'frame_count': 0,
80            'paint_time': 0.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()
85
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,
90       args={'data': data})
91
92   if not ref_stats:
93     return
94
95   # Add timestamp only if a frame was output
96   if data['frame_count'] == 1:
97     if not first_frame:
98       # Add frame_time if this is not the first frame in within the bounds of an
99       # action.
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)
103
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'])
108
109
110 def AddImplThreadRenderingStats(mock_timer, thread, first_frame,
111                                 ref_stats = None):
112   """ Adds a random impl thread rendering stats event.
113
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.
117   """
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()
123
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,
128       args={'data': data})
129
130   if not ref_stats:
131     return
132
133   # Add timestamp only if a frame was output
134   if data['frame_count'] == 1:
135     if not first_frame:
136       # Add frame_time if this is not the first frame in within the bounds of an
137       # action.
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)
141
142   ref_stats.rasterize_times[-1].append(data['rasterize_time'] * 1000.0)
143   ref_stats.rasterized_pixel_counts[-1].append(data['rasterized_pixel_count'])
144
145
146 def AddInputLatencyStats(mock_timer, input_type, start_thread, end_thread,
147                          ref_latency_stats = None):
148   """ Adds a random input latency stats event.
149
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.
154   """
155
156   mock_timer.Advance()
157   ui_comp_time = mock_timer.Get() * 1000.0
158   mock_timer.Advance()
159   begin_comp_time = mock_timer.Get() * 1000.0
160   mock_timer.Advance(10, 20)
161   end_comp_time = mock_timer.Get() * 1000.0
162
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} }
166
167   timestamp = mock_timer.Get()
168
169   async_slice = tracing_async_slice.AsyncSlice(
170       'benchmark', 'InputLatency', timestamp)
171
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
178
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)
183
184   if not ref_latency_stats:
185     return
186
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)
191
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)
196
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)
201
202 class RenderingStatsUnitTest(unittest.TestCase):
203   def testHasRenderingStats(self):
204     timeline = model.TimelineModel()
205     timer = MockTimer()
206
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))
212
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))
219
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))
226
227   def testFromTimeline(self):
228     timeline = model.TimelineModel()
229
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)
238
239     timer = MockTimer()
240     ref_stats = ReferenceRenderingStats()
241
242     # Create 10 main and impl rendering stats events for Action A.
243     timer.Advance()
244     renderer_main.BeginSlice('webkit.console', 'ActionA', timer.Get(), '')
245     ref_stats.AppendNewRange()
246     for i in xrange(0, 10):
247       first = (i == 0)
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())
253
254     # Create 5 main and impl rendering stats events not within any action.
255     for i in xrange(0, 5):
256       first = (i == 0)
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)
261
262     # Create 10 main and impl rendering stats events for Action B.
263     timer.Advance()
264     renderer_main.BeginSlice('webkit.console', 'ActionB', timer.Get(), '')
265     ref_stats.AppendNewRange()
266     for i in xrange(0, 10):
267       first = (i == 0)
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())
273
274     # Create 10 main and impl rendering stats events for Action A.
275     timer.Advance()
276     renderer_main.BeginSlice('webkit.console', 'ActionA', timer.Get(), '')
277     ref_stats.AppendNewRange()
278     for i in xrange(0, 10):
279       first = (i == 0)
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())
285
286     browser.FinalizeImport()
287     renderer.FinalizeImport()
288
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)
294
295     # Check if we are using the browser compositor's stats
296     self.assertEquals(stats.top_level_process, browser)
297
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)
310
311   def testScrollLatencyFromTimeline(self):
312     timeline = model.TimelineModel()
313
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)
319
320     timer = MockTimer()
321     ref_latency_stats = ReferenceInputLatencyStats()
322
323     # Create 10 input latency stats events for Action A.
324     timer.Advance()
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())
334
335     # Create 5 input latency stats events not within any action.
336     for _ in xrange(0, 5):
337       AddInputLatencyStats(timer, 'MouseWheel', browser_main,
338                            renderer_main, None)
339       AddInputLatencyStats(timer, 'GestureScrollUpdate', browser_main,
340                            renderer_main, None)
341       AddInputLatencyStats(timer, 'TouchMove', browser_main,
342                            renderer_main, None)
343
344     # Create 10 input latency stats events for Action B.
345     timer.Advance()
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())
355
356     # Create 10 input latency stats events for Action A.
357     timer.Advance()
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())
367
368     browser.FinalizeImport()
369     renderer.FinalizeImport()
370
371     mouse_wheel_scroll_events = []
372     touch_scroll_events = []
373     js_touch_scroll_events = []
374
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:
380         continue
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)
390
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)