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
3 # found in the LICENSE file.
5 from operator import attrgetter
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'
19 def GetScrollInputLatencyEvents(scroll_type, browser_process, timeline_range):
20 """Get scroll events' LatencyInfo from the browser process's trace buffer
21 that are within the timeline_range.
23 Scroll events (MouseWheel, GestureScrollUpdate or JS scroll on TouchMove)
24 dump their LatencyInfo into trace buffer as async trace event with name
25 "InputLatency". The trace event has a memeber 'step' containing its event
26 type and a memeber 'data' containing its latency history.
30 if not browser_process:
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:
37 if 'data' not in ss.args:
39 if ss.args['step'] == scroll_type:
40 scroll_events.append(ss)
44 def ComputeMouseWheelScrollLatency(mouse_wheel_events):
45 """ Compute the mouse wheel scroll latency.
47 Mouse wheel scroll latency is the time from when mouse wheel event is sent
48 from browser RWH to renderer (the timestamp of component
49 'INPUT_EVENT_LATENCY_BEGIN_RWH_COMPONENT') to when the scrolled page is
50 buffer swapped (the timestamp of component
51 'INPUT_EVENT_LATENCY_TERMINATED_FRAME_SWAP_COMPONENT')
54 mouse_wheel_latency = []
55 for event in mouse_wheel_events:
56 data = event.args['data']
57 if BEGIN_COMP_NAME in data and END_COMP_NAME in data:
58 latency = data[END_COMP_NAME]['time'] - data[BEGIN_COMP_NAME]['time']
59 mouse_wheel_latency.append(latency / 1000.0)
60 return mouse_wheel_latency
63 def ComputeTouchScrollLatency(touch_scroll_events):
64 """ Compute the touch scroll latency.
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'.
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
90 def HasRenderingStats(process):
91 """ Returns True if the process contains at least one
92 BenchmarkInstrumentation::*RenderingStats event with a frame.
96 for event in process.IterAllSlicesOfName(
97 'BenchmarkInstrumentation::MainThreadRenderingStats'):
98 if 'data' in event.args and event.args['data']['frame_count'] == 1:
100 for event in process.IterAllSlicesOfName(
101 'BenchmarkInstrumentation::ImplThreadRenderingStats'):
102 if 'data' in event.args and event.args['data']['frame_count'] == 1:
107 class RenderingStats(object):
108 def __init__(self, renderer_process, browser_process, timeline_ranges):
110 Utility class for extracting rendering statistics from the timeline (or
111 other loggin facilities), and providing them in a common format to classes
112 that compute benchmark metrics from this data.
114 Stats are lists of lists of numbers. The outer list stores one list per
117 All *_time values are measured in milliseconds.
119 assert(len(timeline_ranges) > 0)
120 # Find the top level process with rendering stats (browser or renderer).
121 if HasRenderingStats(browser_process):
122 self.top_level_process = browser_process
124 self.top_level_process = renderer_process
126 self.frame_timestamps = []
127 self.frame_times = []
128 self.paint_times = []
129 self.painted_pixel_counts = []
130 self.record_times = []
131 self.recorded_pixel_counts = []
132 self.rasterize_times = []
133 self.rasterized_pixel_counts = []
134 # End-to-end latency for MouseWheel scroll - from when mouse wheel event is
135 # generated to when the scrolled page is buffer swapped.
136 self.mouse_wheel_scroll_latency = []
137 # End-to-end latency for GestureScrollUpdate scroll - from when the touch
138 # event is generated to the scrolled page is buffer swapped.
139 self.touch_scroll_latency = []
140 # End-to-end latency for JS touch handler scrolling - from when the touch
141 # event is generated to the scrolled page is buffer swapped.
142 self.js_touch_scroll_latency = []
144 for timeline_range in timeline_ranges:
145 self.frame_timestamps.append([])
146 self.frame_times.append([])
147 self.paint_times.append([])
148 self.painted_pixel_counts.append([])
149 self.record_times.append([])
150 self.recorded_pixel_counts.append([])
151 self.rasterize_times.append([])
152 self.rasterized_pixel_counts.append([])
153 self.mouse_wheel_scroll_latency.append([])
154 self.touch_scroll_latency.append([])
155 self.js_touch_scroll_latency.append([])
157 if timeline_range.is_empty:
159 self.initMainThreadStatsFromTimeline(timeline_range)
160 self.initImplThreadStatsFromTimeline(timeline_range)
161 self.initScrollLatencyStatsFromTimeline(browser_process, timeline_range)
163 def initScrollLatencyStatsFromTimeline(self, browser_process, timeline_range):
164 mouse_wheel_events = GetScrollInputLatencyEvents(
165 "MouseWheel", browser_process, timeline_range)
166 self.mouse_wheel_scroll_latency = ComputeMouseWheelScrollLatency(
169 touch_scroll_events = GetScrollInputLatencyEvents(
170 "GestureScrollUpdate", browser_process, timeline_range)
171 self.touch_scroll_latency = ComputeTouchScrollLatency(touch_scroll_events)
173 js_touch_scroll_events = GetScrollInputLatencyEvents(
174 "TouchMove", browser_process, timeline_range)
175 self.js_touch_scroll_latency = ComputeTouchScrollLatency(
176 js_touch_scroll_events)
178 def initMainThreadStatsFromTimeline(self, timeline_range):
179 event_name = 'BenchmarkInstrumentation::MainThreadRenderingStats'
181 for event in self.top_level_process.IterAllSlicesOfName(event_name):
182 if event.start >= timeline_range.min and event.end <= timeline_range.max:
183 if 'data' not in event.args:
186 events.sort(key=attrgetter('start'))
190 frame_count = event.args['data']['frame_count']
192 raise ValueError, 'trace contains multi-frame render stats'
194 self.frame_timestamps[-1].append(
197 self.frame_times[-1].append(round(self.frame_timestamps[-1][-1] -
198 self.frame_timestamps[-1][-2], 2))
200 self.paint_times[-1].append(1000.0 *
201 event.args['data']['paint_time'])
202 self.painted_pixel_counts[-1].append(
203 event.args['data']['painted_pixel_count'])
204 self.record_times[-1].append(1000.0 *
205 event.args['data']['record_time'])
206 self.recorded_pixel_counts[-1].append(
207 event.args['data']['recorded_pixel_count'])
209 def initImplThreadStatsFromTimeline(self, timeline_range):
210 event_name = 'BenchmarkInstrumentation::ImplThreadRenderingStats'
212 for event in self.top_level_process.IterAllSlicesOfName(event_name):
213 if event.start >= timeline_range.min and event.end <= timeline_range.max:
214 if 'data' not in event.args:
217 events.sort(key=attrgetter('start'))
221 frame_count = event.args['data']['frame_count']
223 raise ValueError, 'trace contains multi-frame render stats'
225 self.frame_timestamps[-1].append(
228 self.frame_times[-1].append(round(self.frame_timestamps[-1][-1] -
229 self.frame_timestamps[-1][-2], 2))
231 self.rasterize_times[-1].append(1000.0 *
232 event.args['data']['rasterize_time'])
233 self.rasterized_pixel_counts[-1].append(
234 event.args['data']['rasterized_pixel_count'])