Upstream version 5.34.104.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 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
16
17
18 class MockTimer(object):
19   """A mock timer class which can generate random durations.
20
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.
24   """
25   def __init__(self):
26     self.milliseconds = 0
27
28   def Get(self):
29     return self.milliseconds
30
31   def Advance(self, low=0, high=1):
32     delta = random.uniform(low, high)
33     self.milliseconds += delta
34     return delta
35
36
37 class ReferenceRenderingStats(object):
38   """ Stores expected data for comparison with actual RenderingStats """
39   def __init__(self):
40     self.frame_timestamps = []
41     self.frame_times = []
42     self.paint_times = []
43     self.painted_pixel_counts = []
44     self.record_times = []
45     self.recorded_pixel_counts = []
46     self.rasterize_times = []
47     self.rasterized_pixel_counts = []
48
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([])
58
59 class ReferenceInputLatencyStats(object):
60   """ Stores expected data for comparison with actual input latency stats """
61   def __init__(self):
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 = []
68
69 def AddMainThreadRenderingStats(mock_timer, thread, first_frame,
70                                 ref_stats = None):
71   """ Adds a random main thread rendering stats event.
72
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.
76   """
77   # Create randonm data and timestap for main thread rendering stats.
78   data = { 'frame_count': 0,
79            'paint_time': 0.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()
84
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,
89       args={'data': data})
90
91   if not ref_stats:
92     return
93
94   # Add timestamp only if a frame was output
95   if data['frame_count'] == 1:
96     if not first_frame:
97       # Add frame_time if this is not the first frame in within the bounds of an
98       # action.
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)
102
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'])
107
108
109 def AddImplThreadRenderingStats(mock_timer, thread, first_frame,
110                                 ref_stats = None):
111   """ Adds a random impl thread rendering stats event.
112
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.
116   """
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()
122
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,
127       args={'data': data})
128
129   if not ref_stats:
130     return
131
132   # Add timestamp only if a frame was output
133   if data['frame_count'] == 1:
134     if not first_frame:
135       # Add frame_time if this is not the first frame in within the bounds of an
136       # action.
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)
140
141   ref_stats.rasterize_times[-1].append(data['rasterize_time'] * 1000.0)
142   ref_stats.rasterized_pixel_counts[-1].append(data['rasterized_pixel_count'])
143
144
145 def AddInputLatencyStats(mock_timer, input_type, start_thread, end_thread,
146                          ref_latency_stats = None):
147   """ Adds a random input latency stats event.
148
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.
153   """
154
155   mock_timer.Advance()
156   ui_comp_time = mock_timer.Get() * 1000.0
157   mock_timer.Advance()
158   begin_comp_time = mock_timer.Get() * 1000.0
159   mock_timer.Advance(10, 20)
160   end_comp_time = mock_timer.Get() * 1000.0
161
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} }
165
166   timestamp = mock_timer.Get()
167
168   async_slice = tracing_async_slice.AsyncSlice(
169       'benchmark', 'InputLatency', timestamp)
170
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
177
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)
182
183   if not ref_latency_stats:
184     return
185
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)
190
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)
195
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)
200
201 class RenderingStatsUnitTest(unittest.TestCase):
202   def testFromTimeline(self):
203     timeline = model.TimelineModel()
204
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)
213
214     timer = MockTimer()
215     ref_stats = ReferenceRenderingStats()
216
217     # Create 10 main and impl rendering stats events for Action A.
218     timer.Advance()
219     renderer_main.BeginSlice('webkit.console', 'ActionA', timer.Get(), '')
220     ref_stats.AppendNewRange()
221     for i in xrange(0, 10):
222       first = (i == 0)
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())
228
229     # Create 5 main and impl rendering stats events not within any action.
230     for i in xrange(0, 5):
231       first = (i == 0)
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)
236
237     # Create 10 main and impl rendering stats events for Action B.
238     timer.Advance()
239     renderer_main.BeginSlice('webkit.console', 'ActionB', timer.Get(), '')
240     ref_stats.AppendNewRange()
241     for i in xrange(0, 10):
242       first = (i == 0)
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())
248
249     # Create 10 main and impl rendering stats events for Action A.
250     timer.Advance()
251     renderer_main.BeginSlice('webkit.console', 'ActionA', timer.Get(), '')
252     ref_stats.AppendNewRange()
253     for i in xrange(0, 10):
254       first = (i == 0)
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())
260
261     renderer_main.FinalizeImport()
262     renderer_compositor.FinalizeImport()
263
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)
269
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)
282
283   def testScrollLatencyFromTimeline(self):
284     timeline = model.TimelineModel()
285
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)
291
292     timer = MockTimer()
293     ref_latency_stats = ReferenceInputLatencyStats()
294
295     # Create 10 input latency stats events for Action A.
296     timer.Advance()
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())
306
307     # Create 5 input latency stats events not within any action.
308     for _ in xrange(0, 5):
309       AddInputLatencyStats(timer, 'MouseWheel', browser_main,
310                            renderer_main, None)
311       AddInputLatencyStats(timer, 'GestureScrollUpdate', browser_main,
312                            renderer_main, None)
313       AddInputLatencyStats(timer, 'TouchMove', browser_main,
314                            renderer_main, None)
315
316     # Create 10 input latency stats events for Action B.
317     timer.Advance()
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())
327
328     # Create 10 input latency stats events for Action A.
329     timer.Advance()
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())
339
340     browser_main.FinalizeImport()
341     renderer_main.FinalizeImport()
342
343     mouse_wheel_scroll_events = []
344     touch_scroll_events = []
345     js_touch_scroll_events = []
346
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:
352         continue
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)
362
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)