Upstream version 7.35.144.0
[platform/framework/web/crosswalk.git] / src / tools / perf / metrics / rendering_stats.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 from operator import attrgetter
6
7 # These are LatencyInfo component names indicating the various components
8 # that the input event has travelled through.
9 # This is when the input event first reaches chrome.
10 UI_COMP_NAME = 'INPUT_EVENT_LATENCY_UI_COMPONENT'
11 # This is when the input event was originally created by OS.
12 ORIGINAL_COMP_NAME = 'INPUT_EVENT_LATENCY_ORIGINAL_COMPONENT'
13 # This is when the input event was sent from browser to renderer.
14 BEGIN_COMP_NAME = 'INPUT_EVENT_LATENCY_BEGIN_RWH_COMPONENT'
15 # This is when the input event has reached swap buffer.
16 END_COMP_NAME = 'INPUT_EVENT_LATENCY_TERMINATED_FRAME_SWAP_COMPONENT'
17
18
19 def GetScrollInputLatencyEvents(scroll_type, browser_process, timeline_range):
20   """Get scroll events' LatencyInfo from the browser process's trace buffer
21      that are within the timeline_range.
22
23   Scroll events (MouseWheel, GestureScrollUpdate or JS scroll on TouchMove)
24   dump their LatencyInfo into trace buffer as async trace event with name
25   "InputLatency". The trace event has a memeber 'step' containing its event
26   type and a memeber 'data' containing its latency history.
27
28   """
29   scroll_events = []
30   if not browser_process:
31     return scroll_events
32   for event in browser_process.IterAllAsyncSlicesOfName("InputLatency"):
33     if event.start >= timeline_range.min and event.end <= timeline_range.max:
34       for ss in event.sub_slices:
35         if 'step' not in ss.args:
36           continue
37         if 'data' not in ss.args:
38           continue
39         if ss.args['step'] == scroll_type:
40           scroll_events.append(ss)
41   return scroll_events
42
43
44 def ComputeMouseWheelScrollLatency(mouse_wheel_events):
45   """ Compute the mouse wheel scroll latency.
46
47   Mouse wheel scroll latency is the time from when mouse wheel event is sent
48   from browser RWH to renderer (the timestamp of component
49   'INPUT_EVENT_LATENCY_BEGIN_RWH_COMPONENT') to when the scrolled page is
50   buffer swapped (the timestamp of component
51   'INPUT_EVENT_LATENCY_TERMINATED_FRAME_SWAP_COMPONENT')
52
53   """
54   mouse_wheel_latency = []
55   for event in mouse_wheel_events:
56     data = event.args['data']
57     if BEGIN_COMP_NAME in data and END_COMP_NAME in data:
58       latency = data[END_COMP_NAME]['time'] - data[BEGIN_COMP_NAME]['time']
59       mouse_wheel_latency.append(latency / 1000.0)
60   return mouse_wheel_latency
61
62
63 def ComputeTouchScrollLatency(touch_scroll_events):
64   """ Compute the touch scroll latency.
65
66   Touch scroll latency is the time from when the touch event is created to
67   when the scrolled page is buffer swapped.
68   Touch event on differnt platforms uses different LatencyInfo component to
69   record its creation timestamp. On Aura, the creation time is kept in
70   'INPUT_EVENT_LATENCY_UI_COMPONENT' . On Android, the creation time is kept
71   in 'INPUT_EVENT_LATENCY_ORIGINAL_COMPONENT'.
72
73   """
74   touch_scroll_latency = []
75   for event in touch_scroll_events:
76     data = event.args['data']
77     if END_COMP_NAME in data:
78       end_time = data[END_COMP_NAME]['time']
79       if UI_COMP_NAME in data and ORIGINAL_COMP_NAME in data:
80         raise ValueError, 'LatencyInfo has both UI and ORIGINAL component'
81       if UI_COMP_NAME in data:
82         latency = end_time - data[UI_COMP_NAME]['time']
83         touch_scroll_latency.append(latency / 1000.0)
84       elif ORIGINAL_COMP_NAME in data:
85         latency = end_time - data[ORIGINAL_COMP_NAME]['time']
86         touch_scroll_latency.append(latency / 1000.0)
87   return touch_scroll_latency
88
89
90 def HasRenderingStats(process):
91   """ Returns True if the process contains at least one
92       BenchmarkInstrumentation::*RenderingStats event with a frame.
93   """
94   if not process:
95     return False
96   for event in process.IterAllSlicesOfName(
97       'BenchmarkInstrumentation::MainThreadRenderingStats'):
98     if 'data' in event.args and event.args['data']['frame_count'] == 1:
99       return True
100   for event in process.IterAllSlicesOfName(
101       'BenchmarkInstrumentation::ImplThreadRenderingStats'):
102     if 'data' in event.args and event.args['data']['frame_count'] == 1:
103       return True
104   return False
105
106
107 class RenderingStats(object):
108   def __init__(self, renderer_process, browser_process, timeline_ranges):
109     """
110     Utility class for extracting rendering statistics from the timeline (or
111     other loggin facilities), and providing them in a common format to classes
112     that compute benchmark metrics from this data.
113
114     Stats are lists of lists of numbers. The outer list stores one list per
115     timeline range.
116
117     All *_time values are measured in milliseconds.
118     """
119     assert(len(timeline_ranges) > 0)
120     # Find the top level process with rendering stats (browser or renderer).
121     if HasRenderingStats(browser_process):
122       self.top_level_process = browser_process
123     else:
124       self.top_level_process  = renderer_process
125
126     self.frame_timestamps = []
127     self.frame_times = []
128     self.paint_times = []
129     self.painted_pixel_counts = []
130     self.record_times = []
131     self.recorded_pixel_counts = []
132     self.rasterize_times = []
133     self.rasterized_pixel_counts = []
134     # End-to-end latency for MouseWheel scroll - from when mouse wheel event is
135     # generated to when the scrolled page is buffer swapped.
136     self.mouse_wheel_scroll_latency = []
137     # End-to-end latency for GestureScrollUpdate scroll - from when the touch
138     # event is generated to the scrolled page is buffer swapped.
139     self.touch_scroll_latency = []
140     # End-to-end latency for JS touch handler scrolling - from when the touch
141     # event is generated to the scrolled page is buffer swapped.
142     self.js_touch_scroll_latency = []
143
144     for timeline_range in timeline_ranges:
145       self.frame_timestamps.append([])
146       self.frame_times.append([])
147       self.paint_times.append([])
148       self.painted_pixel_counts.append([])
149       self.record_times.append([])
150       self.recorded_pixel_counts.append([])
151       self.rasterize_times.append([])
152       self.rasterized_pixel_counts.append([])
153       self.mouse_wheel_scroll_latency.append([])
154       self.touch_scroll_latency.append([])
155       self.js_touch_scroll_latency.append([])
156
157       if timeline_range.is_empty:
158         continue
159       self.initMainThreadStatsFromTimeline(timeline_range)
160       self.initImplThreadStatsFromTimeline(timeline_range)
161       self.initScrollLatencyStatsFromTimeline(browser_process, timeline_range)
162
163   def initScrollLatencyStatsFromTimeline(self, browser_process, timeline_range):
164     mouse_wheel_events = GetScrollInputLatencyEvents(
165         "MouseWheel", browser_process, timeline_range)
166     self.mouse_wheel_scroll_latency = ComputeMouseWheelScrollLatency(
167         mouse_wheel_events)
168
169     touch_scroll_events = GetScrollInputLatencyEvents(
170         "GestureScrollUpdate", browser_process, timeline_range)
171     self.touch_scroll_latency = ComputeTouchScrollLatency(touch_scroll_events)
172
173     js_touch_scroll_events = GetScrollInputLatencyEvents(
174         "TouchMove", browser_process, timeline_range)
175     self.js_touch_scroll_latency = ComputeTouchScrollLatency(
176         js_touch_scroll_events)
177
178   def initMainThreadStatsFromTimeline(self, timeline_range):
179     event_name = 'BenchmarkInstrumentation::MainThreadRenderingStats'
180     events = []
181     for event in self.top_level_process.IterAllSlicesOfName(event_name):
182       if event.start >= timeline_range.min and event.end <= timeline_range.max:
183         if 'data' not in event.args:
184           continue
185         events.append(event)
186     events.sort(key=attrgetter('start'))
187
188     first_frame = True
189     for event in events:
190       frame_count = event.args['data']['frame_count']
191       if frame_count > 1:
192         raise ValueError, 'trace contains multi-frame render stats'
193       if frame_count == 1:
194         self.frame_timestamps[-1].append(
195             event.start)
196         if not first_frame:
197           self.frame_times[-1].append(round(self.frame_timestamps[-1][-1] -
198                                             self.frame_timestamps[-1][-2], 2))
199         first_frame = False
200       self.paint_times[-1].append(1000.0 *
201           event.args['data']['paint_time'])
202       self.painted_pixel_counts[-1].append(
203           event.args['data']['painted_pixel_count'])
204       self.record_times[-1].append(1000.0 *
205           event.args['data']['record_time'])
206       self.recorded_pixel_counts[-1].append(
207           event.args['data']['recorded_pixel_count'])
208
209   def initImplThreadStatsFromTimeline(self, timeline_range):
210     event_name = 'BenchmarkInstrumentation::ImplThreadRenderingStats'
211     events = []
212     for event in self.top_level_process.IterAllSlicesOfName(event_name):
213       if event.start >= timeline_range.min and event.end <= timeline_range.max:
214         if 'data' not in event.args:
215           continue
216         events.append(event)
217     events.sort(key=attrgetter('start'))
218
219     first_frame = True
220     for event in events:
221       frame_count = event.args['data']['frame_count']
222       if frame_count > 1:
223         raise ValueError, 'trace contains multi-frame render stats'
224       if frame_count == 1:
225         self.frame_timestamps[-1].append(
226             event.start)
227         if not first_frame:
228           self.frame_times[-1].append(round(self.frame_timestamps[-1][-1] -
229                                             self.frame_timestamps[-1][-2], 2))
230         first_frame = False
231       self.rasterize_times[-1].append(1000.0 *
232           event.args['data']['rasterize_time'])
233       self.rasterized_pixel_counts[-1].append(
234           event.args['data']['rasterized_pixel_count'])