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