Update To 11.40.268.0
[platform/framework/web/crosswalk.git] / src / tools / telemetry / telemetry / web_perf / metrics / rendering_stats_unittest.py
1 # Copyright 2014 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 import telemetry.timeline.async_slice as tracing_async_slice
9 import telemetry.timeline.bounds as timeline_bounds
10 from telemetry.perf_tests_helper import FlattenList
11 from telemetry.timeline import model
12 from telemetry.util.statistics import DivideIfPossibleOrZero
13 from telemetry.web_perf.metrics.rendering_stats import (
14     BEGIN_COMP_NAME,
15     BEGIN_SCROLL_UPDATE_COMP_NAME,
16     END_COMP_NAME,
17     FORWARD_SCROLL_UPDATE_COMP_NAME,
18     GESTURE_SCROLL_UPDATE_EVENT_NAME,
19     ORIGINAL_COMP_NAME,
20     SCROLL_UPDATE_EVENT_NAME,
21     UI_COMP_NAME)
22 from telemetry.web_perf.metrics.rendering_stats import (
23     ComputeInputEventLatencies)
24 from telemetry.web_perf.metrics.rendering_stats import GetInputLatencyEvents
25 from telemetry.web_perf.metrics.rendering_stats import HasRenderingStats
26 from telemetry.web_perf.metrics.rendering_stats import RenderingStats
27
28
29 class MockTimer(object):
30   """A mock timer class which can generate random durations.
31
32   An instance of this class is used as a global timer to generate random
33   durations for stats and consistent timestamps for all mock trace events.
34   The unit of time is milliseconds.
35   """
36   def __init__(self):
37     self.milliseconds = 0
38
39   def Get(self):
40     return self.milliseconds
41
42   def Advance(self, low=0, high=1):
43     delta = random.uniform(low, high)
44     self.milliseconds += delta
45     return delta
46
47
48 class ReferenceRenderingStats(object):
49   """ Stores expected data for comparison with actual RenderingStats """
50   def __init__(self):
51     self.frame_timestamps = []
52     self.frame_times = []
53     self.paint_times = []
54     self.painted_pixel_counts = []
55     self.record_times = []
56     self.recorded_pixel_counts = []
57     self.approximated_pixel_percentages = []
58
59   def AppendNewRange(self):
60     self.frame_timestamps.append([])
61     self.frame_times.append([])
62     self.paint_times.append([])
63     self.painted_pixel_counts.append([])
64     self.record_times.append([])
65     self.recorded_pixel_counts.append([])
66     self.approximated_pixel_percentages.append([])
67
68 class ReferenceInputLatencyStats(object):
69   """ Stores expected data for comparison with actual input latency stats """
70   def __init__(self):
71     self.input_event_latency = []
72     self.input_event = []
73
74 def AddMainThreadRenderingStats(mock_timer, thread, ref_stats = None):
75   """ Adds a random main thread rendering stats event.
76
77   thread: The timeline model thread to which the event will be added.
78   first_frame: Is this the first frame within the bounds of an action?
79   ref_stats: A ReferenceRenderingStats object to record expected values.
80   """
81   # Create randonm data and timestap for main thread rendering stats.
82   data = { 'paint_time': 0.0,
83            'painted_pixel_count': 0,
84            'record_time': mock_timer.Advance(2, 4) / 1000.0,
85            'recorded_pixel_count': 3000*3000 }
86   timestamp = mock_timer.Get()
87
88   # Add a slice with the event data to the given thread.
89   thread.PushCompleteSlice(
90       'benchmark', 'BenchmarkInstrumentation::MainThreadRenderingStats',
91       timestamp, duration=0.0, thread_timestamp=None, thread_duration=None,
92       args={'data': data})
93
94   if not ref_stats:
95     return
96
97   ref_stats.paint_times[-1].append(data['paint_time'] * 1000.0)
98   ref_stats.painted_pixel_counts[-1].append(data['painted_pixel_count'])
99   ref_stats.record_times[-1].append(data['record_time'] * 1000.0)
100   ref_stats.recorded_pixel_counts[-1].append(data['recorded_pixel_count'])
101
102
103 def AddDisplayRenderingStats(mock_timer, thread, first_frame,
104                              ref_stats = None):
105   """ Adds a random display rendering stats event.
106
107   thread: The timeline model thread to which the event will be added.
108   first_frame: Is this the first frame within the bounds of an action?
109   ref_stats: A ReferenceRenderingStats object to record expected values.
110   """
111   # Create randonm data and timestap for main thread rendering stats.
112   data = { 'frame_count': 1 }
113   timestamp = mock_timer.Get()
114
115   # Add a slice with the event data to the given thread.
116   thread.PushCompleteSlice(
117       'benchmark', 'BenchmarkInstrumentation::DisplayRenderingStats',
118       timestamp, duration=0.0, thread_timestamp=None, thread_duration=None,
119       args={'data': data})
120
121   if not ref_stats:
122     return
123
124   # Add timestamp only if a frame was output
125   if not first_frame:
126     # Add frame_time if this is not the first frame in within the bounds of an
127     # action.
128     prev_timestamp = ref_stats.frame_timestamps[-1][-1]
129     ref_stats.frame_times[-1].append(round(timestamp - prev_timestamp, 2))
130   ref_stats.frame_timestamps[-1].append(timestamp)
131
132
133 def AddImplThreadRenderingStats(mock_timer, thread, first_frame,
134                                 ref_stats = None):
135   """ Adds a random impl thread rendering stats event.
136
137   thread: The timeline model thread to which the event will be added.
138   first_frame: Is this the first frame within the bounds of an action?
139   ref_stats: A ReferenceRenderingStats object to record expected values.
140   """
141   # Create randonm data and timestap for impl thread rendering stats.
142   data = { 'frame_count': 1,
143            'visible_content_area': random.uniform(0, 100),
144            'approximated_visible_content_area': random.uniform(0, 5)}
145   timestamp = mock_timer.Get()
146
147   # Add a slice with the event data to the given thread.
148   thread.PushCompleteSlice(
149       'benchmark', 'BenchmarkInstrumentation::ImplThreadRenderingStats',
150       timestamp, duration=0.0, thread_timestamp=None, thread_duration=None,
151       args={'data': data})
152
153   if not ref_stats:
154     return
155
156   # Add timestamp only if a frame was output
157   if data['frame_count'] == 1:
158     if not first_frame:
159       # Add frame_time if this is not the first frame in within the bounds of an
160       # action.
161       prev_timestamp = ref_stats.frame_timestamps[-1][-1]
162       ref_stats.frame_times[-1].append(round(timestamp - prev_timestamp, 2))
163     ref_stats.frame_timestamps[-1].append(timestamp)
164
165   ref_stats.approximated_pixel_percentages[-1].append(
166       round(DivideIfPossibleOrZero(data['approximated_visible_content_area'],
167                                    data['visible_content_area']) * 100.0, 3))
168
169
170 def AddInputLatencyStats(mock_timer, start_thread, end_thread,
171                          ref_latency_stats = None):
172   """ Adds a random input latency stats event.
173
174   start_thread: The start thread on which the async slice is added.
175   end_thread: The end thread on which the async slice is ended.
176   ref_latency_stats: A ReferenceInputLatencyStats object for expected values.
177   """
178
179   mock_timer.Advance(2, 4)
180   original_comp_time = mock_timer.Get() * 1000.0
181   mock_timer.Advance(2, 4)
182   ui_comp_time = mock_timer.Get() * 1000.0
183   mock_timer.Advance(2, 4)
184   begin_comp_time = mock_timer.Get() * 1000.0
185   mock_timer.Advance(2, 4)
186   forward_comp_time = mock_timer.Get() * 1000.0
187   mock_timer.Advance(10, 20)
188   end_comp_time = mock_timer.Get() * 1000.0
189
190   data = { ORIGINAL_COMP_NAME: {'time': original_comp_time},
191            UI_COMP_NAME: {'time': ui_comp_time},
192            BEGIN_COMP_NAME: {'time': begin_comp_time},
193            END_COMP_NAME: {'time': end_comp_time} }
194
195   timestamp = mock_timer.Get()
196
197   async_slice = tracing_async_slice.AsyncSlice(
198       'benchmark', 'InputLatency', timestamp)
199
200   async_sub_slice = tracing_async_slice.AsyncSlice(
201       'benchmark', GESTURE_SCROLL_UPDATE_EVENT_NAME, timestamp)
202   async_sub_slice.args = {'data': data}
203   async_sub_slice.parent_slice = async_slice
204   async_sub_slice.start_thread = start_thread
205   async_sub_slice.end_thread = end_thread
206
207   async_slice.sub_slices.append(async_sub_slice)
208   async_slice.start_thread = start_thread
209   async_slice.end_thread = end_thread
210   start_thread.AddAsyncSlice(async_slice)
211
212   # Add scroll update latency info.
213   scroll_update_data = {
214       BEGIN_SCROLL_UPDATE_COMP_NAME: {'time': begin_comp_time},
215       FORWARD_SCROLL_UPDATE_COMP_NAME: {'time': forward_comp_time},
216       END_COMP_NAME: {'time': end_comp_time} }
217
218   scroll_async_slice = tracing_async_slice.AsyncSlice(
219       'benchmark', 'InputLatency', timestamp)
220
221   scroll_async_sub_slice = tracing_async_slice.AsyncSlice(
222       'benchmark', SCROLL_UPDATE_EVENT_NAME, timestamp)
223   scroll_async_sub_slice.args = {'data': scroll_update_data}
224   scroll_async_sub_slice.parent_slice = scroll_async_slice
225   scroll_async_sub_slice.start_thread = start_thread
226   scroll_async_sub_slice.end_thread = end_thread
227
228   scroll_async_slice.sub_slices.append(scroll_async_sub_slice)
229   scroll_async_slice.start_thread = start_thread
230   scroll_async_slice.end_thread = end_thread
231   start_thread.AddAsyncSlice(scroll_async_slice)
232
233   # Also add some dummy frame statistics so we can feed the resulting timeline
234   # to RenderingStats.
235   AddMainThreadRenderingStats(mock_timer, start_thread)
236   AddImplThreadRenderingStats(mock_timer, end_thread, False)
237
238   if not ref_latency_stats:
239     return
240
241   ref_latency_stats.input_event.append(async_sub_slice)
242   ref_latency_stats.input_event.append(scroll_async_sub_slice)
243   ref_latency_stats.input_event_latency.append((
244       GESTURE_SCROLL_UPDATE_EVENT_NAME,
245       (data[END_COMP_NAME]['time'] -
246        data[ORIGINAL_COMP_NAME]['time']) / 1000.0))
247   ref_latency_stats.input_event_latency.append((
248       SCROLL_UPDATE_EVENT_NAME,
249       (scroll_update_data[END_COMP_NAME]['time'] -
250        scroll_update_data[BEGIN_SCROLL_UPDATE_COMP_NAME]['time']) / 1000.0))
251
252
253 class RenderingStatsUnitTest(unittest.TestCase):
254   def testHasRenderingStats(self):
255     timeline = model.TimelineModel()
256     timer = MockTimer()
257
258     # A process without rendering stats
259     process_without_stats = timeline.GetOrCreateProcess(pid = 1)
260     thread_without_stats = process_without_stats.GetOrCreateThread(tid = 11)
261     process_without_stats.FinalizeImport()
262     self.assertFalse(HasRenderingStats(thread_without_stats))
263
264     # A process with rendering stats, but no frames in them
265     process_without_frames = timeline.GetOrCreateProcess(pid = 2)
266     thread_without_frames = process_without_frames.GetOrCreateThread(tid = 21)
267     AddMainThreadRenderingStats(timer, thread_without_frames, None)
268     process_without_frames.FinalizeImport()
269     self.assertFalse(HasRenderingStats(thread_without_frames))
270
271     # A process with rendering stats and frames in them
272     process_with_frames = timeline.GetOrCreateProcess(pid = 3)
273     thread_with_frames = process_with_frames.GetOrCreateThread(tid = 31)
274     AddImplThreadRenderingStats(timer, thread_with_frames, True, None)
275     process_with_frames.FinalizeImport()
276     self.assertTrue(HasRenderingStats(thread_with_frames))
277
278   def testBothDisplayAndImplStats(self):
279     timeline = model.TimelineModel()
280     timer = MockTimer()
281
282     ref_stats = ReferenceRenderingStats()
283     ref_stats.AppendNewRange()
284     renderer = timeline.GetOrCreateProcess(pid = 2)
285     browser = timeline.GetOrCreateProcess(pid = 3)
286     browser_main = browser.GetOrCreateThread(tid = 31)
287     browser_main.BeginSlice('webkit.console', 'ActionA', timer.Get(), '')
288
289     # Create main, impl, and display rendering stats.
290     for i in xrange(0, 10):
291       first = (i == 0)
292       AddMainThreadRenderingStats(timer, browser_main, ref_stats)
293       AddImplThreadRenderingStats(timer, browser_main, first, None)
294       timer.Advance(2, 4)
295
296     for i in xrange(0, 10):
297       first = (i == 0)
298       AddDisplayRenderingStats(timer, browser_main, first, ref_stats)
299       timer.Advance(5, 10)
300
301     browser_main.EndSlice(timer.Get())
302
303     browser.FinalizeImport()
304     renderer.FinalizeImport()
305     timeline_markers = timeline.FindTimelineMarkers(['ActionA'])
306     timeline_ranges = [ timeline_bounds.Bounds.CreateFromEvent(marker)
307                         for marker in timeline_markers ]
308     stats = RenderingStats(renderer, browser, timeline_ranges)
309
310     # Compare rendering stats to reference - Only display stats should count
311     self.assertEquals(stats.frame_timestamps, ref_stats.frame_timestamps)
312     self.assertEquals(stats.frame_times, ref_stats.frame_times)
313
314   def testRangeWithoutFrames(self):
315     timer = MockTimer()
316     timeline = model.TimelineModel()
317
318     # Create a renderer process, with a main thread and impl thread.
319     renderer = timeline.GetOrCreateProcess(pid = 2)
320     renderer_main = renderer.GetOrCreateThread(tid = 21)
321     renderer_compositor = renderer.GetOrCreateThread(tid = 22)
322
323     # Create 10 main and impl rendering stats events for Action A.
324     timer.Advance(2, 4)
325     renderer_main.BeginSlice('webkit.console', 'ActionA', timer.Get(), '')
326     for i in xrange(0, 10):
327       first = (i == 0)
328       AddMainThreadRenderingStats(timer, renderer_main, None)
329       AddImplThreadRenderingStats(timer, renderer_compositor, first, None)
330     timer.Advance(2, 4)
331     renderer_main.EndSlice(timer.Get())
332
333     # Create 5 main and impl rendering stats events not within any action.
334     for i in xrange(0, 5):
335       first = (i == 0)
336       AddMainThreadRenderingStats(timer, renderer_main, None)
337       AddImplThreadRenderingStats(timer, renderer_compositor, first, None)
338
339     # Create Action B without any frames. This should trigger
340     # NotEnoughFramesError when the RenderingStats object is created.
341     timer.Advance(2, 4)
342     renderer_main.BeginSlice('webkit.console', 'ActionB', timer.Get(), '')
343     timer.Advance(2, 4)
344     renderer_main.EndSlice(timer.Get())
345
346     renderer.FinalizeImport()
347
348     timeline_markers = timeline.FindTimelineMarkers(['ActionA', 'ActionB'])
349     timeline_ranges = [ timeline_bounds.Bounds.CreateFromEvent(marker)
350                         for marker in timeline_markers ]
351
352     stats = RenderingStats(renderer, None, timeline_ranges)
353     self.assertEquals(0, len(stats.frame_timestamps[1]))
354
355
356   def testFromTimeline(self):
357     timeline = model.TimelineModel()
358
359     # Create a browser process and a renderer process, and a main thread and
360     # impl thread for each.
361     browser = timeline.GetOrCreateProcess(pid = 1)
362     browser_main = browser.GetOrCreateThread(tid = 11)
363     browser_compositor = browser.GetOrCreateThread(tid = 12)
364     renderer = timeline.GetOrCreateProcess(pid = 2)
365     renderer_main = renderer.GetOrCreateThread(tid = 21)
366     renderer_compositor = renderer.GetOrCreateThread(tid = 22)
367
368     timer = MockTimer()
369     renderer_ref_stats = ReferenceRenderingStats()
370     browser_ref_stats = ReferenceRenderingStats()
371
372     # Create 10 main and impl rendering stats events for Action A.
373     timer.Advance(2, 4)
374     renderer_main.BeginSlice('webkit.console', 'ActionA', timer.Get(), '')
375     renderer_ref_stats.AppendNewRange()
376     browser_ref_stats.AppendNewRange()
377     for i in xrange(0, 10):
378       first = (i == 0)
379       AddMainThreadRenderingStats(
380           timer, renderer_main, renderer_ref_stats)
381       AddImplThreadRenderingStats(
382           timer, renderer_compositor, first, renderer_ref_stats)
383       AddMainThreadRenderingStats(
384           timer, browser_main, browser_ref_stats)
385       AddImplThreadRenderingStats(
386           timer, browser_compositor, first, browser_ref_stats)
387     timer.Advance(2, 4)
388     renderer_main.EndSlice(timer.Get())
389
390     # Create 5 main and impl rendering stats events not within any action.
391     for i in xrange(0, 5):
392       first = (i == 0)
393       AddMainThreadRenderingStats(timer, renderer_main, None)
394       AddImplThreadRenderingStats(timer, renderer_compositor, first, None)
395       AddMainThreadRenderingStats(timer, browser_main, None)
396       AddImplThreadRenderingStats(timer, browser_compositor, first, None)
397
398     # Create 10 main and impl rendering stats events for Action B.
399     timer.Advance(2, 4)
400     renderer_main.BeginSlice('webkit.console', 'ActionB', timer.Get(), '')
401     renderer_ref_stats.AppendNewRange()
402     browser_ref_stats.AppendNewRange()
403     for i in xrange(0, 10):
404       first = (i == 0)
405       AddMainThreadRenderingStats(
406           timer, renderer_main, renderer_ref_stats)
407       AddImplThreadRenderingStats(
408           timer, renderer_compositor, first, renderer_ref_stats)
409       AddMainThreadRenderingStats(
410           timer, browser_main, browser_ref_stats)
411       AddImplThreadRenderingStats(
412           timer, browser_compositor, first, browser_ref_stats)
413     timer.Advance(2, 4)
414     renderer_main.EndSlice(timer.Get())
415
416     # Create 10 main and impl rendering stats events for Action A.
417     timer.Advance(2, 4)
418     renderer_main.BeginSlice('webkit.console', 'ActionA', timer.Get(), '')
419     renderer_ref_stats.AppendNewRange()
420     browser_ref_stats.AppendNewRange()
421     for i in xrange(0, 10):
422       first = (i == 0)
423       AddMainThreadRenderingStats(
424           timer, renderer_main, renderer_ref_stats)
425       AddImplThreadRenderingStats(
426           timer, renderer_compositor, first, renderer_ref_stats)
427       AddMainThreadRenderingStats(
428           timer, browser_main, browser_ref_stats)
429       AddImplThreadRenderingStats(
430           timer, browser_compositor, first, browser_ref_stats)
431     timer.Advance(2, 4)
432     renderer_main.EndSlice(timer.Get())
433
434     browser.FinalizeImport()
435     renderer.FinalizeImport()
436
437     timeline_markers = timeline.FindTimelineMarkers(
438         ['ActionA', 'ActionB', 'ActionA'])
439     timeline_ranges = [ timeline_bounds.Bounds.CreateFromEvent(marker)
440                         for marker in timeline_markers ]
441     stats = RenderingStats(renderer, browser, timeline_ranges)
442
443     # Compare rendering stats to reference.
444     self.assertEquals(stats.frame_timestamps,
445                       browser_ref_stats.frame_timestamps)
446     self.assertEquals(stats.frame_times, browser_ref_stats.frame_times)
447     self.assertEquals(stats.approximated_pixel_percentages,
448                       renderer_ref_stats.approximated_pixel_percentages)
449     self.assertEquals(stats.paint_times, renderer_ref_stats.paint_times)
450     self.assertEquals(stats.painted_pixel_counts,
451                       renderer_ref_stats.painted_pixel_counts)
452     self.assertEquals(stats.record_times, renderer_ref_stats.record_times)
453     self.assertEquals(stats.recorded_pixel_counts,
454                       renderer_ref_stats.recorded_pixel_counts)
455
456   def testInputLatencyFromTimeline(self):
457     timeline = model.TimelineModel()
458
459     # Create a browser process and a renderer process.
460     browser = timeline.GetOrCreateProcess(pid = 1)
461     browser_main = browser.GetOrCreateThread(tid = 11)
462     renderer = timeline.GetOrCreateProcess(pid = 2)
463     renderer_main = renderer.GetOrCreateThread(tid = 21)
464
465     timer = MockTimer()
466     ref_latency = ReferenceInputLatencyStats()
467
468     # Create 10 input latency stats events for Action A.
469     timer.Advance(2, 4)
470     renderer_main.BeginSlice('webkit.console', 'ActionA', timer.Get(), '')
471     for _ in xrange(0, 10):
472       AddInputLatencyStats(timer, browser_main, renderer_main, ref_latency)
473     timer.Advance(2, 4)
474     renderer_main.EndSlice(timer.Get())
475
476     # Create 5 input latency stats events not within any action.
477     timer.Advance(2, 4)
478     for _ in xrange(0, 5):
479       AddInputLatencyStats(timer, browser_main, renderer_main, None)
480
481     # Create 10 input latency stats events for Action B.
482     timer.Advance(2, 4)
483     renderer_main.BeginSlice('webkit.console', 'ActionB', timer.Get(), '')
484     for _ in xrange(0, 10):
485       AddInputLatencyStats(timer, browser_main, renderer_main, ref_latency)
486     timer.Advance(2, 4)
487     renderer_main.EndSlice(timer.Get())
488
489     # Create 10 input latency stats events for Action A.
490     timer.Advance(2, 4)
491     renderer_main.BeginSlice('webkit.console', 'ActionA', timer.Get(), '')
492     for _ in xrange(0, 10):
493       AddInputLatencyStats(timer, browser_main, renderer_main, ref_latency)
494     timer.Advance(2, 4)
495     renderer_main.EndSlice(timer.Get())
496
497     browser.FinalizeImport()
498     renderer.FinalizeImport()
499
500     input_events = []
501
502     timeline_markers = timeline.FindTimelineMarkers(
503         ['ActionA', 'ActionB', 'ActionA'])
504     timeline_ranges = [timeline_bounds.Bounds.CreateFromEvent(marker)
505                        for marker in timeline_markers]
506     for timeline_range in timeline_ranges:
507       if timeline_range.is_empty:
508         continue
509       input_events.extend(GetInputLatencyEvents(browser, timeline_range))
510
511     self.assertEquals(input_events, ref_latency.input_event)
512     input_event_latency_result = ComputeInputEventLatencies(input_events)
513     self.assertEquals(input_event_latency_result,
514                       ref_latency.input_event_latency)
515
516     stats = RenderingStats(renderer, browser, timeline_ranges)
517     self.assertEquals(FlattenList(stats.input_event_latency), [
518         latency for name, latency in ref_latency.input_event_latency
519         if name != SCROLL_UPDATE_EVENT_NAME])
520     self.assertEquals(FlattenList(stats.scroll_update_latency), [
521         latency for name, latency in ref_latency.input_event_latency
522         if name == SCROLL_UPDATE_EVENT_NAME])
523     self.assertEquals(FlattenList(stats.gesture_scroll_update_latency), [
524         latency for name, latency in ref_latency.input_event_latency
525         if name == GESTURE_SCROLL_UPDATE_EVENT_NAME])