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