OLD | NEW |
---|---|
(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): | |
tonyg
2012/10/19 00:47:19
Just a suggestion, feel free to ignore, but what d
bulach
2012/10/22 15:57:14
that's a pretty cool trick!! I wasn't aware of thi
| |
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 else: | |
48 self._use_legacy_method = True | |
49 self._surface_before = self._GetSurfaceStatsLegacy() | |
50 | |
51 def Stop(self): | |
52 if self._collector_thread: | |
53 self._stop_event.set() | |
54 self._collector_thread.join() | |
55 self._collector_thread = None | |
56 | |
57 def PrintPerfResults(self, trace_tag): | |
58 if self._use_legacy_method: | |
59 surface_after = self._GetSurfaceStatsLegacy() | |
60 td = surface_after['timestamp'] - self._surface_before['timestamp'] | |
61 seconds = td.seconds + td.microseconds / 1e6 | |
62 frame_count = (surface_after['page_flip_count'] - | |
63 self._surface_before['page_flip_count']) | |
64 else: | |
65 assert self._collector_thread | |
66 (seconds, latencies) = self._GetDataFromThread() | |
67 if not seconds or not len(latencies): | |
68 logging.warning('Surface stat data is empty') | |
69 return | |
70 | |
71 frame_count = len(latencies) | |
72 jitter_count = 0 | |
73 last_latency = latencies[0] | |
74 for latency in latencies[1:]: | |
75 if latency > last_latency: | |
76 jitter_count = jitter_count + 1 | |
77 last_latency = latency | |
78 | |
79 perf_tests_helper.PrintPerfResult( | |
80 'surface_latencies', 'surface_latencies' + trace_tag, latencies, '') | |
81 perf_tests_helper.PrintPerfResult( | |
82 'peak_jitter', 'peak_jitter' + trace_tag, [max(latencies)], '') | |
83 perf_tests_helper.PrintPerfResult( | |
84 'jitter_percent', 'jitter_percent' + trace_tag, | |
85 [jitter_count * 100.0 / frame_count], 'percent') | |
86 | |
87 print 'SurfaceMonitorTime: %fsecs' % seconds | |
88 perf_tests_helper.PrintPerfResult( | |
89 'avg_surface_fps', 'avg_surface_fps' + trace_tag, | |
90 [int(round(frame_count / seconds))], 'fps') | |
91 | |
92 def _CollectorThread(self): | |
93 last_timestamp = 0 | |
94 first_timestamp = 0 | |
95 latencies = [] | |
96 | |
97 while not self._stop_event.is_set(): | |
98 self._get_data_event.wait(1) | |
99 try: | |
100 (t, last_timestamp) = self._GetSurfaceFlingerLatencyData(last_timestamp, | |
101 latencies) | |
102 if not first_timestamp: | |
103 first_timestamp = t | |
104 | |
105 if self._get_data_event.is_set(): | |
106 self._get_data_event.clear() | |
107 self._data_queue.put(((last_timestamp - first_timestamp) / 1e9, | |
108 latencies)) | |
109 latencies = [] | |
110 first_timestamp = 0 | |
111 except Exception as e: | |
112 # On any error, before aborting, put the exception into _data_queue to | |
113 # prevent the main thread from waiting at _data_queue.get() infinitely. | |
114 self._data_queue.put(e) | |
115 raise | |
116 | |
117 def _GetDataFromThread(self): | |
118 self._get_data_event.set() | |
119 ret = self._data_queue.get() | |
120 if isinstance(ret, Exception): | |
121 raise ret | |
122 return ret | |
123 | |
124 def _ClearSurfaceFlingerLatencyData(self): | |
125 """Clears the SurfaceFlinger latency data. | |
126 | |
127 Returns: | |
128 True if SurfaceFlinger latency is supported by the device, otherwise | |
129 False. | |
130 """ | |
131 # The command returns nothing if it is supported, otherwise returns many | |
132 # lines of result just like 'dumpsys SurfaceFlinger'. | |
133 results = self._adb.RunShellCommand( | |
134 'dumpsys SurfaceFlinger --latency-clear %s/%s' % | |
135 (self._window_package, self._window_activity)) | |
136 return not len(results) | |
137 | |
138 def _GetSurfaceFlingerLatencyData(self, previous_timestamp, latencies): | |
139 """Returns collected SurfaceFlinger latency data. | |
140 | |
141 Args: | |
142 previous_timestamp: The timestamp returned from the previous call or 0. | |
143 Only data after this timestamp will be returned. | |
144 latencies: A list to receive latency data. The latencies are integers | |
145 each of which is the number of refresh periods of each frame. | |
146 | |
147 Returns: | |
148 A tuple containing: | |
149 - The timestamp of the beginning of the first frame (ns), | |
150 - The timestamp of the end of the last frame (ns). | |
151 | |
152 Raises: | |
153 Exception if failed to run the SurfaceFlinger command or SurfaceFlinger | |
154 returned invalid result. | |
155 """ | |
156 # adb shell dumpsys SurfaceFlinger --latency <window name> | |
157 # prints some information about the last 128 frames displayed in | |
158 # that window. | |
159 # The data returned looks like this: | |
160 # 16954612 | |
161 # 7657467895508 7657482691352 7657493499756 | |
162 # 7657484466553 7657499645964 7657511077881 | |
163 # 7657500793457 7657516600576 7657527404785 | |
164 # (...) | |
165 # | |
166 # The first line is the refresh period (here 16.95 ms), it is followed | |
167 # by 128 lines w/ 3 timestamps in nanosecond each: | |
168 # A) when the app started to draw | |
169 # B) the vsync immediately preceding SF submitting the frame to the h/w | |
170 # C) timestamp immediately after SF submitted that frame to the h/w | |
171 # | |
172 # The difference between the 1st and 3rd timestamp is the frame-latency. | |
173 # An interesting data is when the frame latency crosses a refresh period | |
174 # boundary, this can be calculated this way: | |
175 # | |
176 # ceil((C - A) / refresh-period) | |
177 # | |
178 # (each time the number above changes, we have a "jank"). | |
179 # If this happens a lot during an animation, the animation appears | |
180 # janky, even if it runs at 60 fps in average. | |
181 results = self._adb.RunShellCommand( | |
182 'dumpsys SurfaceFlinger --latency %s/%s' % | |
183 (self._window_package, self._window_activity), log_result=True) | |
184 assert len(results) | |
185 | |
186 refresh_period = int(results[0]) | |
187 last_timestamp = previous_timestamp | |
188 first_timestamp = 0 | |
189 for line in results[1:]: | |
190 fields = line.split() | |
191 if len(fields) == 3: | |
192 timestamp = long(fields[0]) | |
193 last_timestamp = long(fields[2]) | |
194 if (timestamp > previous_timestamp): | |
195 if not first_timestamp: | |
196 first_timestamp = timestamp | |
197 # This is integral equivalent of ceil((C-A) / refresh-period) | |
198 latency_ns = int(last_timestamp - timestamp) | |
199 latencies.append((latency_ns + refresh_period - 1) / refresh_period) | |
200 return (first_timestamp, last_timestamp) | |
201 | |
202 def _GetSurfaceStatsLegacy(self): | |
203 """Legacy method (before JellyBean), returns the current Surface index | |
204 and timestamp. | |
205 | |
206 Calculate FPS by measuring the difference of Surface index returned by | |
207 SurfaceFlinger in a period of time. | |
208 | |
209 Returns: | |
210 Dict of {page_flip_count (or 0 if there was an error), timestamp}. | |
211 """ | |
212 results = self._adb.RunShellCommand('service call SurfaceFlinger 1013') | |
213 assert len(results) == 1 | |
214 match = re.search('^Result: Parcel\((\w+)', results[0]) | |
215 cur_surface = 0 | |
216 if match: | |
217 try: | |
218 cur_surface = int(match.group(1), 16) | |
219 except Exception: | |
220 logging.error('Failed to parse current surface from ' + match.group(1)) | |
221 else: | |
222 logging.warning('Failed to call SurfaceFlinger surface ' + results[0]) | |
223 return { | |
224 'page_flip_count': cur_surface, | |
225 'timestamp': datetime.datetime.now(), | |
226 } | |
OLD | NEW |