| 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 |