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'
18 def GetScrollInputLatencyEvents(scroll_type, browser_process, timeline_range):
19 """Get scroll events' LatencyInfo from the browser process's trace buffer
20 that are within the timeline_range.
22 Scroll events (MouseWheel, GestureScrollUpdate or JS scroll on TouchMove)
23 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 type and a memeber 'data' containing its latency history.
29 if not browser_process:
31 for event in browser_process.IterAllAsyncSlicesOfName("InputLatency"):
32 if event.start >= timeline_range.min and event.end <= timeline_range.max:
33 for ss in event.sub_slices:
34 if 'step' not in ss.args:
36 if 'data' not in ss.args:
38 if ss.args['step'] == scroll_type:
39 scroll_events.append(ss)
42 def ComputeMouseWheelScrollLatency(mouse_wheel_events):
43 """ Compute the mouse wheel scroll latency.
45 Mouse wheel scroll latency is the time from when mouse wheel event is sent
46 from browser RWH to renderer (the timestamp of component
47 'INPUT_EVENT_LATENCY_BEGIN_RWH_COMPONENT') to when the scrolled page is
48 buffer swapped (the timestamp of component
49 'INPUT_EVENT_LATENCY_TERMINATED_FRAME_SWAP_COMPONENT')
52 mouse_wheel_latency = []
53 for event in mouse_wheel_events:
54 data = event.args['data']
55 if BEGIN_COMP_NAME in data and END_COMP_NAME in data:
56 latency = data[END_COMP_NAME]['time'] - data[BEGIN_COMP_NAME]['time']
57 mouse_wheel_latency.append(latency / 1000.0)
58 return mouse_wheel_latency
60 def ComputeTouchScrollLatency(touch_scroll_events):
61 """ Compute the touch scroll latency.
63 Touch scroll latency is the time from when the touch event is created to
64 when the scrolled page is buffer swapped.
65 Touch event on differnt platforms uses different LatencyInfo component to
66 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
68 in 'INPUT_EVENT_LATENCY_ORIGINAL_COMPONENT'.
71 touch_scroll_latency = []
72 for event in touch_scroll_events:
73 data = event.args['data']
74 if END_COMP_NAME in data:
75 end_time = data[END_COMP_NAME]['time']
76 if UI_COMP_NAME in data and ORIGINAL_COMP_NAME in data:
77 raise ValueError, 'LatencyInfo has both UI and ORIGINAL component'
78 if UI_COMP_NAME in data:
79 latency = end_time - data[UI_COMP_NAME]['time']
80 touch_scroll_latency.append(latency / 1000.0)
81 elif ORIGINAL_COMP_NAME in data:
82 latency = end_time - data[ORIGINAL_COMP_NAME]['time']
83 touch_scroll_latency.append(latency / 1000.0)
84 return touch_scroll_latency
86 class RenderingStats(object):
87 def __init__(self, renderer_process, browser_process, timeline_ranges):
89 Utility class for extracting rendering statistics from the timeline (or
90 other loggin facilities), and providing them in a common format to classes
91 that compute benchmark metrics from this data.
93 Stats are lists of lists of numbers. The outer list stores one list per
96 All *_time values are measured in milliseconds.
98 assert(len(timeline_ranges) > 0)
99 self.renderer_process = renderer_process
101 self.frame_timestamps = []
102 self.frame_times = []
103 self.paint_times = []
104 self.painted_pixel_counts = []
105 self.record_times = []
106 self.recorded_pixel_counts = []
107 self.rasterize_times = []
108 self.rasterized_pixel_counts = []
109 # End-to-end latency for MouseWheel scroll - from when mouse wheel event is
110 # generated to when the scrolled page is buffer swapped.
111 self.mouse_wheel_scroll_latency = []
112 # End-to-end latency for GestureScrollUpdate scroll - from when the touch
113 # event is generated to the scrolled page is buffer swapped.
114 self.touch_scroll_latency = []
115 # End-to-end latency for JS touch handler scrolling - from when the touch
116 # event is generated to the scrolled page is buffer swapped.
117 self.js_touch_scroll_latency = []
119 for timeline_range in timeline_ranges:
120 self.frame_timestamps.append([])
121 self.frame_times.append([])
122 self.paint_times.append([])
123 self.painted_pixel_counts.append([])
124 self.record_times.append([])
125 self.recorded_pixel_counts.append([])
126 self.rasterize_times.append([])
127 self.rasterized_pixel_counts.append([])
128 self.mouse_wheel_scroll_latency.append([])
129 self.touch_scroll_latency.append([])
130 self.js_touch_scroll_latency.append([])
132 if timeline_range.is_empty:
134 self.initMainThreadStatsFromTimeline(timeline_range)
135 self.initImplThreadStatsFromTimeline(timeline_range)
136 self.initScrollLatencyStatsFromTimeline(browser_process, timeline_range)
138 def initScrollLatencyStatsFromTimeline(self, browser_process, timeline_range):
139 mouse_wheel_events = GetScrollInputLatencyEvents(
140 "MouseWheel", browser_process, timeline_range)
141 self.mouse_wheel_scroll_latency = ComputeMouseWheelScrollLatency(
144 touch_scroll_events = GetScrollInputLatencyEvents(
145 "GestureScrollUpdate", browser_process, timeline_range)
146 self.touch_scroll_latency = ComputeTouchScrollLatency(touch_scroll_events)
148 js_touch_scroll_events = GetScrollInputLatencyEvents(
149 "TouchMove", browser_process, timeline_range)
150 self.js_touch_scroll_latency = ComputeTouchScrollLatency(
151 js_touch_scroll_events)
153 def initMainThreadStatsFromTimeline(self, timeline_range):
154 event_name = 'BenchmarkInstrumentation::MainThreadRenderingStats'
156 for event in self.renderer_process.IterAllSlicesOfName(event_name):
157 if event.start >= timeline_range.min and event.end <= timeline_range.max:
158 if 'data' not in event.args:
161 events.sort(key=attrgetter('start'))
165 frame_count = event.args['data']['frame_count']
167 raise ValueError, 'trace contains multi-frame render stats'
169 self.frame_timestamps[-1].append(
172 self.frame_times[-1].append(round(self.frame_timestamps[-1] -
173 self.frame_timestamps[-2], 2))
175 self.paint_times[-1].append(1000.0 *
176 event.args['data']['paint_time'])
177 self.painted_pixel_counts[-1].append(
178 event.args['data']['painted_pixel_count'])
179 self.record_times[-1].append(1000.0 *
180 event.args['data']['record_time'])
181 self.recorded_pixel_counts[-1].append(
182 event.args['data']['recorded_pixel_count'])
184 def initImplThreadStatsFromTimeline(self, timeline_range):
185 event_name = 'BenchmarkInstrumentation::ImplThreadRenderingStats'
187 for event in self.renderer_process.IterAllSlicesOfName(event_name):
188 if event.start >= timeline_range.min and event.end <= timeline_range.max:
189 if 'data' not in event.args:
192 events.sort(key=attrgetter('start'))
196 frame_count = event.args['data']['frame_count']
198 raise ValueError, 'trace contains multi-frame render stats'
200 self.frame_timestamps[-1].append(
203 self.frame_times[-1].append(round(self.frame_timestamps[-1][-1] -
204 self.frame_timestamps[-1][-2], 2))
206 self.rasterize_times[-1].append(1000.0 *
207 event.args['data']['rasterize_time'])
208 self.rasterized_pixel_counts[-1].append(
209 event.args['data']['rasterized_pixel_count'])