Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(280)

Side by Side Diff: build/android/pylib/surface_stats_collector.py

Issue 11187036: Android: start upstreaming some of our perf tests. (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src
Patch Set: Created 8 years, 2 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
« no previous file with comments | « no previous file | tools/chrome_remote_control/chrome_remote_control/adb_commands.py » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
(Empty)
1 # Copyright (c) 2012 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.
4
5 import Queue
6 import datetime
7 import logging
8 import re
9 import threading
10
11 from pylib import perf_tests_helper
12
13
14 # Log marker containing SurfaceTexture timestamps.
15 _SURFACE_TEXTURE_TIMESTAMPS_MESSAGE = 'SurfaceTexture update timestamps'
16 _SURFACE_TEXTURE_TIMESTAMP_RE = '\d+'
17
18
19 class SurfaceStatsCollector(object):
20 """Collects surface stats for a window from the output of SurfaceFlinger.
21
22 Args:
23 adb: the adb coonection to use.
24 window_package: Package name of the window.
25 window_activity: Activity name of the window.
26 """
27 def __init__(self, adb, window_package, window_activity):
28 self._adb = adb
29 self._window_package = window_package
30 self._window_activity = window_activity
31 self._collector_thread = None
32 self._use_legacy_method = False
33 self._surface_before = None
34 self._get_data_event = None
35 self._data_queue = None
36 self._stop_event = None
37
38 def Start(self):
39 assert not self._collector_thread
40
41 if self._ClearSurfaceFlingerLatencyData():
42 self._get_data_event = threading.Event()
43 self._stop_event = threading.Event()
44 self._data_queue = Queue.Queue()
45 self._collector_thread = threading.Thread(target=self._CollectorThread)
46 self._collector_thread.start()
47 self._adb.StartRecordingLogcat(clear=False)
48 else:
49 self._use_legacy_method = True
50 self._surface_before = self._GetSurfaceStatsLegacy()
51
52 def Stop(self):
53 if self._collector_thread:
54 self._stop_event.set()
55 self._collector_thread.join()
56 self._collector_thread = None
57
58 def PrintPerfResults(self, trace_tag):
59 if self._use_legacy_method:
60 surface_after = self._GetSurfaceStatsLegacy()
61 td = surface_after['timestamp'] - self._surface_before['timestamp']
62 seconds = td.seconds + td.microseconds / 1e6
63 frame_count = (surface_after['page_flip_count'] -
64 self._surface_before['page_flip_count'])
65 else:
66 assert self._collector_thread
67 (seconds, latencies, app_timestamps) = self._GetDataFromThread()
68 if not seconds or not len(latencies):
69 logging.warning('Surface stat data is empty')
70 return
71
72 renderer_timestamps = self._CollectSurfaceTextureTimestamps()
Sami 2012/10/18 11:35:57 This bit depends on the browser sending us timesta
bulach 2012/10/18 12:33:56 thanks for the clarification sami! it simplifies t
73 if not renderer_timestamps:
74 logging.warning('No renderer timestamp data')
75 elif app_timestamps:
76 dropped_frames, repeated_frames, use_count = \
77 self._CalculateDroppedAndRepeatedFrames(app_timestamps,
78 renderer_timestamps)
79 perf_tests_helper.PrintPerfResult(
80 'dropped_frames', 'dropped_frames' + trace_tag,
81 [100.0 * dropped_frames / len(use_count)], 'percent')
82 perf_tests_helper.PrintPerfResult(
83 'repeated_frames', 'repeated_frames' + trace_tag,
84 [100.0 * repeated_frames / len(use_count)], 'percent')
85 perf_tests_helper.PrintPerfResult(
86 'frame_use_count', 'frame_use_count' + trace_tag, use_count, '')
87
88 frame_count = len(latencies)
89 jitter_count = 0
90 last_latency = latencies[0]
91 for latency in latencies[1:]:
92 if latency > last_latency:
93 jitter_count = jitter_count + 1
94 last_latency = latency
95
96 perf_tests_helper.PrintPerfResult(
97 'surface_latencies', 'surface_latencies' + trace_tag, latencies, '')
98 perf_tests_helper.PrintPerfResult(
99 'peak_jitter', 'peak_jitter' + trace_tag, [max(latencies)], '')
100 perf_tests_helper.PrintPerfResult(
101 'jitter_percent', 'jitter_percent' + trace_tag,
102 [jitter_count * 100.0 / frame_count], 'percent')
103
104 print 'SurfaceMonitorTime: %fsecs' % seconds
105 perf_tests_helper.PrintPerfResult(
106 'avg_surface_fps', 'avg_surface_fps' + trace_tag,
107 [int(round(frame_count / seconds))], 'fps')
108
109 def _CollectSurfaceTextureTimestamps(self):
110 """Extracts Surface Texture timestamps from logcat messages as generated by
111 --log-fps.
112
113 Returns:
114 A list of integer time stamps sorted in ascending order.
115 """
116 record = self._adb.StopRecordingLogcat()
117 timestamp_lines = self._adb.SearchLogcatRecord(record,
118 _SURFACE_TEXTURE_TIMESTAMPS_MESSAGE)
119 timestamps = []
120 for line in timestamp_lines:
121 for match in re.findall(_SURFACE_TEXTURE_TIMESTAMP_RE, line['message']):
122 timestamp = long(match)
123 # TODO(skyostil): Figure out why the first two timestamps are zero.
124 if timestamp > 0:
125 timestamps.append(timestamp)
126 timestamps.sort()
127 return timestamps
128
129 @staticmethod
130 def _CalculateDroppedAndRepeatedFrames(app_timestamps, renderer_timestamps):
131 """Calculate the number of dropped and repeated renderer frames. This is
132 done by calculating which renderer produced frame is used in each
133 application produced frame. To reduce false positives, only the
134 intersection of the timestamp ranges is examined.
135
136 Args:
137 app_timestamps: A sorted list of application frame timestamps.
138 renderer_timestamps: A sorted list of renderer frame timestamps.
139
140 Returns:
141 A tuple containing dropped and repeated frame counts and list of integer
142 use counts for every renderer frame in the examined range.
143 """
144 # Reduce the timestamps to the intersection of both sequences.
145 min_app, max_app = app_timestamps[0], app_timestamps[-1]
146 min_renderer, max_renderer = renderer_timestamps[0], renderer_timestamps[-1]
147 min_time = max(min_app, min_renderer)
148 max_time = min(max_app, max_renderer)
149 def FindFirstIndexOver(timestamps, min_time):
150 for i in range(len(timestamps)):
151 if timestamps[i] >= min_time:
152 return i
153 def FindLastIndexUnder(timestamps, max_time):
154 for i in range(len(timestamps) - 1, 0, -1):
155 if timestamps[i] <= max_time:
156 return i
157 def Intersect(timestamps, min_time, max_time):
158 min_index = FindFirstIndexOver(timestamps, min_time)
159 max_index = FindLastIndexUnder(timestamps, max_time)
160 if min_index is None or max_index is None:
161 return []
162 return timestamps[min_index:max_index + 1]
163 app_timestamps = Intersect(app_timestamps, min_time, max_time)
164 renderer_timestamps = Intersect(renderer_timestamps, min_time, max_time)
165
166 # Discard the last renderer frame because any app frame consuming it will be
167 # outside the intersected range.
168 renderer_timestamps = renderer_timestamps[:-1]
169
170 # If there was no common time interval, bail out.
171 if not app_timestamps or not renderer_timestamps:
172 return (0, 0, [0])
173
174 renderer_frame_use_count = dict((t, 0) for t in renderer_timestamps)
175 for app_time in app_timestamps:
176 # Find out which renderer frame was used to draw this app frame.
177 for renderer_time in reversed(renderer_timestamps):
178 if renderer_time < app_time:
179 renderer_frame_use_count[renderer_time] += 1
180 break
181
182 # Calculate statistics on dropped and repeated frames.
183 dropped_frames = 0
184 repeated_frames = 0
185 for renderer_time, use_count in renderer_frame_use_count.items():
186 if use_count == 0:
187 dropped_frames += 1
188 elif use_count > 1:
189 repeated_frames += use_count - 1
190 return (dropped_frames, repeated_frames, renderer_frame_use_count.values())
191
192 def _CollectorThread(self):
193 last_timestamp = 0
194 first_timestamp = 0
195 latencies = []
196 timestamps = []
197
198 while not self._stop_event.is_set():
199 self._get_data_event.wait(1)
200 try:
201 (t, last_timestamp) = self._GetSurfaceFlingerLatencyData(last_timestamp,
202 latencies,
203 timestamps)
204 if not first_timestamp:
205 first_timestamp = t
206
207 if self._get_data_event.is_set():
208 self._get_data_event.clear()
209 self._data_queue.put(((last_timestamp - first_timestamp) / 1e9,
210 latencies, timestamps))
211 latencies = []
212 timestamps = []
213 first_timestamp = 0
214 except Exception as e:
215 # On any error, before aborting, put the exception into _data_queue to
216 # prevent the main thread from waiting at _data_queue.get() infinitely.
217 self._data_queue.put(e)
218 raise
219
220 def _GetDataFromThread(self):
221 self._get_data_event.set()
222 ret = self._data_queue.get()
223 if isinstance(ret, Exception):
224 raise ret
225 return ret
226
227 def _ClearSurfaceFlingerLatencyData(self):
228 """Clears the SurfaceFlinger latency data.
229
230 Returns:
231 True if SurfaceFlinger latency is supported by the device, otherwise
232 False.
233 """
234 # The command returns nothing if it is supported, otherwise returns many
235 # lines of result just like 'dumpsys SurfaceFlinger'.
236 results = self._adb.RunShellCommand(
237 'dumpsys SurfaceFlinger --latency-clear %s/%s' %
238 (self._window_package, self._window_activity))
239 return not len(results)
240
241 def _GetSurfaceFlingerLatencyData(self, previous_timestamp, latencies,
242 timestamps):
243 """Returns collected SurfaceFlinger latency data.
244
245 Args:
246 previous_timestamp: The timestamp returned from the previous call or 0.
247 Only data after this timestamp will be returned.
248 latencies: A list to receive latency data. The latencies are integers
249 each of which is the number of refresh periods of each frame.
250 timestamps: A list to receive timestamp data. The timestamps indicate
251 when the application started drawing each frame.
252
253 Returns:
254 A tuple containing:
255 - The timestamp of the beginning of the first frame (ns),
256 - The timestamp of the end of the last frame (ns).
257
258 Raises:
259 Exception if failed to run the SurfaceFlinger command or SurfaceFlinger
260 returned invalid result.
261 """
262 # adb shell dumpsys SurfaceFlinger --latency <window name>
263 # prints some information about the last 128 frames displayed in
264 # that window.
265 # The data returned looks like this:
266 # 16954612
267 # 7657467895508 7657482691352 7657493499756
268 # 7657484466553 7657499645964 7657511077881
269 # 7657500793457 7657516600576 7657527404785
270 # (...)
271 #
272 # The first line is the refresh period (here 16.95 ms), it is followed
273 # by 128 lines w/ 3 timestamps in nanosecond each:
274 # A) when the app started to draw
275 # B) the vsync immediately preceding SF submitting the frame to the h/w
276 # C) timestamp immediately after SF submitted that frame to the h/w
277 #
278 # The difference between the 1st and 3rd timestamp is the frame-latency.
279 # An interesting data is when the frame latency crosses a refresh period
280 # boundary, this can be calculated this way:
281 #
282 # ceil((C - A) / refresh-period)
283 #
284 # (each time the number above changes, we have a "jank").
285 # If this happens a lot during an animation, the animation appears
286 # janky, even if it runs at 60 fps in average.
287 results = self._adb.RunShellCommand(
288 'dumpsys SurfaceFlinger --latency %s/%s' %
289 (self._window_package, self._window_activity), log_result=True)
290 assert len(results)
291
292 refresh_period = int(results[0])
293 last_timestamp = previous_timestamp
294 first_timestamp = 0
295 for line in results[1:]:
296 fields = line.split()
297 if len(fields) == 3:
298 timestamp = long(fields[0])
299 last_timestamp = long(fields[2])
300 if (timestamp > previous_timestamp):
301 if not first_timestamp:
302 first_timestamp = timestamp
303 # This is integral equivalent of ceil((C-A) / refresh-period)
304 latency_ns = int(last_timestamp - timestamp)
305 latencies.append((latency_ns + refresh_period - 1) / refresh_period)
306 timestamps.append(timestamp)
307 return (first_timestamp, last_timestamp)
308
309 def _GetSurfaceStatsLegacy(self):
310 """Legacy method (before JellyBean), returns the current Surface index
311 and timestamp.
312
313 Calculate FPS by measuring the difference of Surface index returned by
314 SurfaceFlinger in a period of time.
315
316 Returns:
317 Dict of {page_flip_count (or 0 if there was an error), timestamp}.
318 """
319 results = self._adb.RunShellCommand('service call SurfaceFlinger 1013')
320 assert len(results) == 1
321 match = re.search('^Result: Parcel\((\w+)', results[0])
322 cur_surface = 0
323 if match:
324 try:
325 cur_surface = int(match.group(1), 16)
326 except Exception:
327 logging.error('Failed to parse current surface from ' + match.group(1))
328 else:
329 logging.warning('Failed to call SurfaceFlinger surface ' + results[0])
330 return {
331 'page_flip_count': cur_surface,
332 'timestamp': datetime.datetime.now(),
333 }
OLDNEW
« no previous file with comments | « no previous file | tools/chrome_remote_control/chrome_remote_control/adb_commands.py » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698