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 |
| 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(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 or GestureScrollUpdate) dump their LatencyInfo |
| 23 into trace buffer as async trace event with name "InputLatency". The trace |
| 24 event has a memeber 'step' containing its event type and a memeber 'data' |
| 25 containing its latency history. |
| 26 |
| 27 """ |
| 28 mouse_wheel_events = [] |
| 29 touch_scroll_events = [] |
| 30 if not browser_process: |
| 31 return (mouse_wheel_events, touch_scroll_events) |
| 32 for event in browser_process.IterAllAsyncSlicesOfName("InputLatency"): |
| 33 if event.start >= timeline_range.min and event.end <= timeline_range.max: |
| 34 for ss in event.sub_slices: |
| 35 if 'step' not in ss.args: |
| 36 continue |
| 37 if 'data' not in ss.args: |
| 38 continue |
| 39 if ss.args['step'] == 'MouseWheel': |
| 40 mouse_wheel_events.append(ss) |
| 41 elif ss.args['step'] == 'GestureScrollUpdate': |
| 42 touch_scroll_events.append(ss) |
| 43 return (mouse_wheel_events, touch_scroll_events) |
| 44 |
| 45 def ComputeMouseWheelScrollLatency(mouse_wheel_events): |
| 46 """ Compute the mouse wheel scroll latency. |
| 47 |
| 48 Mouse wheel scroll latency is the time from when mouse wheel event is sent |
| 49 from browser RWH to renderer (the timestamp of component |
| 50 'INPUT_EVENT_LATENCY_BEGIN_RWH_COMPONENT') to when the scrolled page is |
| 51 buffer swapped (the timestamp of component |
| 52 'INPUT_EVENT_LATENCY_TERMINATED_FRAME_SWAP_COMPONENT') |
| 53 |
| 54 """ |
| 55 mouse_wheel_latency = [] |
| 56 for event in mouse_wheel_events: |
| 57 data = event.args['data'] |
| 58 if BEGIN_COMP_NAME in data and END_COMP_NAME in data: |
| 59 latency = data[END_COMP_NAME]['time'] - data[BEGIN_COMP_NAME]['time'] |
| 60 mouse_wheel_latency.append(latency / 1000.0) |
| 61 return mouse_wheel_latency |
| 62 |
| 63 def ComputeTouchScrollLatency(touch_scroll_events): |
| 64 """ Compute the touch scroll latency. |
| 65 |
| 66 Touch scroll latency is the time from when the touch event is created to |
| 67 when the scrolled page is buffer swapped. |
| 68 Touch event on differnt platforms uses different LatencyInfo component to |
| 69 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 |
| 71 in 'INPUT_EVENT_LATENCY_ORIGINAL_COMPONENT'. |
| 72 |
| 73 """ |
| 74 touch_scroll_latency = [] |
| 75 for event in touch_scroll_events: |
| 76 data = event.args['data'] |
| 77 if END_COMP_NAME in data: |
| 78 end_time = data[END_COMP_NAME]['time'] |
| 79 if UI_COMP_NAME in data and ORIGINAL_COMP_NAME in data: |
| 80 raise ValueError, 'LatencyInfo has both UI and ORIGINAL component' |
| 81 if UI_COMP_NAME in data: |
| 82 latency = end_time - data[UI_COMP_NAME]['time'] |
| 83 touch_scroll_latency.append(latency / 1000.0) |
| 84 elif ORIGINAL_COMP_NAME in data: |
| 85 latency = end_time - data[ORIGINAL_COMP_NAME]['time'] |
| 86 touch_scroll_latency.append(latency / 1000.0) |
| 87 return touch_scroll_latency |
7 | 88 |
8 class RenderingStats(object): | 89 class RenderingStats(object): |
9 def __init__(self, renderer_process, timeline_ranges): | 90 def __init__(self, renderer_process, browser_process, timeline_ranges): |
10 """ | 91 """ |
11 Utility class for extracting rendering statistics from the timeline (or | 92 Utility class for extracting rendering statistics from the timeline (or |
12 other loggin facilities), and providing them in a common format to classes | 93 other loggin facilities), and providing them in a common format to classes |
13 that compute benchmark metrics from this data. | 94 that compute benchmark metrics from this data. |
14 | 95 |
15 Stats are lists of lists of numbers. The outer list stores one list per | 96 Stats are lists of lists of numbers. The outer list stores one list per |
16 timeline range. | 97 timeline range. |
17 | 98 |
18 All *_time values are measured in milliseconds. | 99 All *_time values are measured in milliseconds. |
19 """ | 100 """ |
20 assert(len(timeline_ranges) > 0) | 101 assert(len(timeline_ranges) > 0) |
21 self.renderer_process = renderer_process | 102 self.renderer_process = renderer_process |
22 | 103 |
23 self.frame_timestamps = [] | 104 self.frame_timestamps = [] |
24 self.frame_times = [] | 105 self.frame_times = [] |
25 self.paint_times = [] | 106 self.paint_times = [] |
26 self.painted_pixel_counts = [] | 107 self.painted_pixel_counts = [] |
27 self.record_times = [] | 108 self.record_times = [] |
28 self.recorded_pixel_counts = [] | 109 self.recorded_pixel_counts = [] |
29 self.rasterize_times = [] | 110 self.rasterize_times = [] |
30 self.rasterized_pixel_counts = [] | 111 self.rasterized_pixel_counts = [] |
| 112 # End-to-end latency for MouseWheel scroll - from when mouse wheel event is |
| 113 # generated to when the scrolled page is buffer swapped. |
| 114 self.mouse_wheel_scroll_latency = [] |
| 115 # End-to-end latency for touch scroll event - from when the touch event is |
| 116 # generated to the scrolled page is buffer swapped. |
| 117 self.touch_scroll_latency = [] |
31 | 118 |
32 for timeline_range in timeline_ranges: | 119 for timeline_range in timeline_ranges: |
33 self.frame_timestamps.append([]) | 120 self.frame_timestamps.append([]) |
34 self.frame_times.append([]) | 121 self.frame_times.append([]) |
35 self.paint_times.append([]) | 122 self.paint_times.append([]) |
36 self.painted_pixel_counts.append([]) | 123 self.painted_pixel_counts.append([]) |
37 self.record_times.append([]) | 124 self.record_times.append([]) |
38 self.recorded_pixel_counts.append([]) | 125 self.recorded_pixel_counts.append([]) |
39 self.rasterize_times.append([]) | 126 self.rasterize_times.append([]) |
40 self.rasterized_pixel_counts.append([]) | 127 self.rasterized_pixel_counts.append([]) |
| 128 self.mouse_wheel_scroll_latency.append([]) |
| 129 self.touch_scroll_latency.append([]) |
41 | 130 |
42 if timeline_range.is_empty: | 131 if timeline_range.is_empty: |
43 continue | 132 continue |
44 self.initMainThreadStatsFromTimeline(timeline_range) | 133 self.initMainThreadStatsFromTimeline(timeline_range) |
45 self.initImplThreadStatsFromTimeline(timeline_range) | 134 self.initImplThreadStatsFromTimeline(timeline_range) |
| 135 self.initScrollLatencyStatsFromTimeline(browser_process, timeline_range) |
| 136 |
| 137 def initScrollLatencyStatsFromTimeline(self, browser_process, timeline_range): |
| 138 mouse_wheel_events, touch_scroll_events = GetScrollInputLatencyEvents( |
| 139 browser_process, timeline_range) |
| 140 self.mouse_wheel_scroll_latency = ComputeMouseWheelScrollLatency( |
| 141 mouse_wheel_events) |
| 142 self.touch_scroll_latency = ComputeTouchScrollLatency(touch_scroll_events) |
46 | 143 |
47 def initMainThreadStatsFromTimeline(self, timeline_range): | 144 def initMainThreadStatsFromTimeline(self, timeline_range): |
48 event_name = 'BenchmarkInstrumentation::MainThreadRenderingStats' | 145 event_name = 'BenchmarkInstrumentation::MainThreadRenderingStats' |
49 events = [] | 146 events = [] |
50 for event in self.renderer_process.IterAllSlicesOfName(event_name): | 147 for event in self.renderer_process.IterAllSlicesOfName(event_name): |
51 if event.start >= timeline_range.min and event.end <= timeline_range.max: | 148 if event.start >= timeline_range.min and event.end <= timeline_range.max: |
52 if 'data' not in event.args: | 149 if 'data' not in event.args: |
53 continue | 150 continue |
54 events.append(event) | 151 events.append(event) |
55 events.sort(key=attrgetter('start')) | 152 events.sort(key=attrgetter('start')) |
(...skipping 38 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
94 self.frame_timestamps[-1].append( | 191 self.frame_timestamps[-1].append( |
95 event.start) | 192 event.start) |
96 if not first_frame: | 193 if not first_frame: |
97 self.frame_times[-1].append(round(self.frame_timestamps[-1][-1] - | 194 self.frame_times[-1].append(round(self.frame_timestamps[-1][-1] - |
98 self.frame_timestamps[-1][-2], 2)) | 195 self.frame_timestamps[-1][-2], 2)) |
99 first_frame = False | 196 first_frame = False |
100 self.rasterize_times[-1].append(1000.0 * | 197 self.rasterize_times[-1].append(1000.0 * |
101 event.args['data']['rasterize_time']) | 198 event.args['data']['rasterize_time']) |
102 self.rasterized_pixel_counts[-1].append( | 199 self.rasterized_pixel_counts[-1].append( |
103 event.args['data']['rasterized_pixel_count']) | 200 event.args['data']['rasterized_pixel_count']) |
OLD | NEW |