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