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