OLD | NEW |
---|---|
1 # Copyright (c) 2012 The Chromium Authors. All rights reserved. | 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 | 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 import Queue | 5 import Queue |
6 import datetime | 6 import datetime |
7 import logging | 7 import logging |
8 import re | 8 import re |
9 import threading | 9 import threading |
10 | 10 |
(...skipping 52 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
63 | 63 |
64 def _StorePerfResults(self): | 64 def _StorePerfResults(self): |
65 if self._use_legacy_method: | 65 if self._use_legacy_method: |
66 surface_after = self._GetSurfaceStatsLegacy() | 66 surface_after = self._GetSurfaceStatsLegacy() |
67 td = surface_after['timestamp'] - self._surface_before['timestamp'] | 67 td = surface_after['timestamp'] - self._surface_before['timestamp'] |
68 seconds = td.seconds + td.microseconds / 1e6 | 68 seconds = td.seconds + td.microseconds / 1e6 |
69 frame_count = (surface_after['page_flip_count'] - | 69 frame_count = (surface_after['page_flip_count'] - |
70 self._surface_before['page_flip_count']) | 70 self._surface_before['page_flip_count']) |
71 else: | 71 else: |
72 assert self._collector_thread | 72 assert self._collector_thread |
73 (seconds, latencies) = self._GetDataFromThread() | 73 (refresh_period, timestamps) = self._GetDataFromThread() |
74 if not seconds or not len(latencies): | 74 if not refresh_period or not len(timestamps) >= 3: |
75 logging.warning('Surface stat data is empty') | 75 logging.warning('Surface stat data is empty') |
76 return | 76 return |
77 frame_count = len(timestamps) | |
78 seconds = timestamps[-1] - timestamps[0] | |
77 | 79 |
78 frame_count = len(latencies) | 80 frame_lengths = [t2 - t1 for t1, t2 in zip(timestamps, timestamps[1:])] |
79 jitter_count = 0 | 81 normalized_frame_lengths = [length / refresh_period for length in |
80 last_latency = latencies[0] | 82 frame_lengths] |
81 for latency in latencies[1:]: | 83 |
82 if latency > last_latency: | 84 length_changes = [d2 - d1 for d1, d2 in zip(frame_lengths, |
83 jitter_count = jitter_count + 1 | 85 frame_lengths[1:])] |
84 last_latency = latency | 86 normalized_changes = [change / refresh_period |
87 for change in length_changes] | |
bulach
2013/03/18 18:45:03
not sure if it'd be any more readable, but it seem
Sami
2013/03/18 19:03:15
That's much more readable, thanks!
| |
88 jankiness = [max(0, round(change)) for change in normalized_changes] | |
89 pause_threshold = 20 | |
90 jank_count = sum(1 for change in jankiness | |
91 if change > 0 and change < pause_threshold) | |
85 | 92 |
86 self._results.append(SurfaceStatsCollector.Result( | 93 self._results.append(SurfaceStatsCollector.Result( |
87 'surface_latencies', latencies, '')) | 94 'refresh_period', [refresh_period], 'seconds')) |
88 self._results.append(SurfaceStatsCollector.Result( | 95 self._results.append(SurfaceStatsCollector.Result( |
89 'peak_jitter', [max(latencies)], '')) | 96 'jank_count', [jank_count], 'janks')) |
90 self._results.append(SurfaceStatsCollector.Result( | 97 self._results.append(SurfaceStatsCollector.Result( |
91 'jitter_percent', [jitter_count * 100.0 / frame_count], 'percent')) | 98 'max_frame_delay', [round(max(normalized_frame_lengths))], |
99 'vsyncs')) | |
100 self._results.append(SurfaceStatsCollector.Result( | |
101 'frame_lengths', normalized_frame_lengths, 'vsyncs')) | |
92 self._results.append(SurfaceStatsCollector.Result( | 102 self._results.append(SurfaceStatsCollector.Result( |
93 'avg_surface_fps', [int(round(frame_count / seconds))], 'fps')) | 103 'avg_surface_fps', [int(round(frame_count / seconds))], 'fps')) |
94 | 104 |
95 def _CollectorThread(self): | 105 def _CollectorThread(self): |
96 last_timestamp = 0 | 106 last_timestamp = 0 |
97 first_timestamp = 0 | 107 timestamps = [] |
98 latencies = [] | |
99 retries = 0 | 108 retries = 0 |
100 has_collected_data = False | |
101 | 109 |
102 while not self._stop_event.is_set(): | 110 while not self._stop_event.is_set(): |
103 self._get_data_event.wait(1) | 111 self._get_data_event.wait(1) |
104 try: | 112 try: |
105 (t, last_timestamp) = self._GetSurfaceFlingerLatencyData(last_timestamp, | 113 refresh_period, new_timestamps = self._GetSurfaceFlingerFrameData() |
106 latencies) | 114 if refresh_period is None or timestamps is None: |
107 if (t, last_timestamp) == (None, None): | |
108 retries += 1 | 115 retries += 1 |
109 if retries < 3: | 116 if retries < 3: |
110 continue | 117 continue |
111 if has_collected_data: | 118 if last_timestamp: |
112 # Some data has already been collected, but either the app | 119 # Some data has already been collected, but either the app |
113 # was closed or there's no new data. Signal the main thread and | 120 # was closed or there's no new data. Signal the main thread and |
114 # wait. | 121 # wait. |
115 self._data_queue.put((None, None)) | 122 self._data_queue.put((None, None)) |
116 self._stop_event.wait() | 123 self._stop_event.wait() |
117 break | 124 break |
118 raise Exception('Unable to get surface flinger latency data') | 125 raise Exception('Unable to get surface flinger latency data') |
119 | 126 |
120 has_collected_data = True | 127 timestamps += [timestamp for timestamp in new_timestamps |
121 | 128 if timestamp > last_timestamp] |
122 if not first_timestamp: | 129 if len(timestamps): |
123 first_timestamp = t | 130 last_timestamp = timestamps[-1] |
124 | 131 |
125 if self._get_data_event.is_set(): | 132 if self._get_data_event.is_set(): |
126 self._get_data_event.clear() | 133 self._get_data_event.clear() |
127 self._data_queue.put(((last_timestamp - first_timestamp) / 1e9, | 134 self._data_queue.put((refresh_period, timestamps)) |
128 latencies)) | 135 timestamps = [] |
129 latencies = [] | |
130 first_timestamp = 0 | |
131 except Exception as e: | 136 except Exception as e: |
132 # On any error, before aborting, put the exception into _data_queue to | 137 # On any error, before aborting, put the exception into _data_queue to |
133 # prevent the main thread from waiting at _data_queue.get() infinitely. | 138 # prevent the main thread from waiting at _data_queue.get() infinitely. |
134 self._data_queue.put(e) | 139 self._data_queue.put(e) |
135 raise | 140 raise |
136 | 141 |
137 def _GetDataFromThread(self): | 142 def _GetDataFromThread(self): |
138 self._get_data_event.set() | 143 self._get_data_event.set() |
139 ret = self._data_queue.get() | 144 ret = self._data_queue.get() |
140 if isinstance(ret, Exception): | 145 if isinstance(ret, Exception): |
141 raise ret | 146 raise ret |
142 return ret | 147 return ret |
143 | 148 |
144 def _ClearSurfaceFlingerLatencyData(self): | 149 def _ClearSurfaceFlingerLatencyData(self): |
145 """Clears the SurfaceFlinger latency data. | 150 """Clears the SurfaceFlinger latency data. |
146 | 151 |
147 Returns: | 152 Returns: |
148 True if SurfaceFlinger latency is supported by the device, otherwise | 153 True if SurfaceFlinger latency is supported by the device, otherwise |
149 False. | 154 False. |
150 """ | 155 """ |
151 # The command returns nothing if it is supported, otherwise returns many | 156 # The command returns nothing if it is supported, otherwise returns many |
152 # lines of result just like 'dumpsys SurfaceFlinger'. | 157 # lines of result just like 'dumpsys SurfaceFlinger'. |
153 results = self._adb.RunShellCommand( | 158 results = self._adb.RunShellCommand( |
154 'dumpsys SurfaceFlinger --latency-clear SurfaceView') | 159 'dumpsys SurfaceFlinger --latency-clear SurfaceView') |
155 return not len(results) | 160 return not len(results) |
156 | 161 |
157 def _GetSurfaceFlingerLatencyData(self, previous_timestamp, latencies): | 162 def _GetSurfaceFlingerFrameData(self): |
158 """Returns collected SurfaceFlinger latency data. | 163 """Returns collected SurfaceFlinger frame timing data. |
159 | |
160 Args: | |
161 previous_timestamp: The timestamp returned from the previous call or 0. | |
162 Only data after this timestamp will be returned. | |
163 latencies: A list to receive latency data. The latencies are integers | |
164 each of which is the number of refresh periods of each frame. | |
165 | 164 |
166 Returns: | 165 Returns: |
167 A tuple containing: | 166 A tuple containing: |
168 - The timestamp of the beginning of the first frame (ns), | 167 - The display's nominal refresh period in seconds. |
169 - The timestamp of the end of the last frame (ns). | 168 - A list of timestamps signifying frame presentation times in seconds. |
170 The tuple may be (None, None) if there was no data collected (for example, | 169 The return value may be (None, None) if there was no data collected (for |
171 if the app was closed before the collector thread has finished). | 170 example, if the app was closed before the collector thread has finished). |
172 """ | 171 """ |
173 # adb shell dumpsys SurfaceFlinger --latency <window name> | 172 # adb shell dumpsys SurfaceFlinger --latency <window name> |
174 # prints some information about the last 128 frames displayed in | 173 # prints some information about the last 128 frames displayed in |
175 # that window. | 174 # that window. |
176 # The data returned looks like this: | 175 # The data returned looks like this: |
177 # 16954612 | 176 # 16954612 |
178 # 7657467895508 7657482691352 7657493499756 | 177 # 7657467895508 7657482691352 7657493499756 |
179 # 7657484466553 7657499645964 7657511077881 | 178 # 7657484466553 7657499645964 7657511077881 |
180 # 7657500793457 7657516600576 7657527404785 | 179 # 7657500793457 7657516600576 7657527404785 |
181 # (...) | 180 # (...) |
(...skipping 15 matching lines...) Expand all Loading... | |
197 # janky, even if it runs at 60 fps in average. | 196 # janky, even if it runs at 60 fps in average. |
198 # | 197 # |
199 # We use the special "SurfaceView" window name because the statistics for | 198 # We use the special "SurfaceView" window name because the statistics for |
200 # the activity's main window are not updated when the main web content is | 199 # the activity's main window are not updated when the main web content is |
201 # composited into a SurfaceView. | 200 # composited into a SurfaceView. |
202 results = self._adb.RunShellCommand( | 201 results = self._adb.RunShellCommand( |
203 'dumpsys SurfaceFlinger --latency SurfaceView', log_result=True) | 202 'dumpsys SurfaceFlinger --latency SurfaceView', log_result=True) |
204 if not len(results): | 203 if not len(results): |
205 return (None, None) | 204 return (None, None) |
206 | 205 |
207 refresh_period = int(results[0]) | 206 timestamps = [] |
208 last_timestamp = previous_timestamp | 207 nanoseconds_per_second = 1e9 |
209 first_timestamp = 0 | 208 refresh_period = long(results[0]) / nanoseconds_per_second |
209 | |
210 for line in results[1:]: | 210 for line in results[1:]: |
211 fields = line.split() | 211 fields = line.split() |
212 if len(fields) == 3: | 212 if len(fields) != 3: |
213 timestamp = long(fields[0]) | 213 continue |
214 last_timestamp = long(fields[2]) | 214 timestamp = long(fields[1]) / nanoseconds_per_second |
215 if (timestamp > previous_timestamp): | 215 timestamps.append(timestamp) |
216 if not first_timestamp: | 216 |
217 first_timestamp = timestamp | 217 return (refresh_period, timestamps) |
218 # This is integral equivalent of ceil((C-A) / refresh-period) | |
219 latency_ns = int(last_timestamp - timestamp) | |
220 latencies.append((latency_ns + refresh_period - 1) / refresh_period) | |
221 return (first_timestamp, last_timestamp) | |
222 | 218 |
223 def _GetSurfaceStatsLegacy(self): | 219 def _GetSurfaceStatsLegacy(self): |
224 """Legacy method (before JellyBean), returns the current Surface index | 220 """Legacy method (before JellyBean), returns the current Surface index |
225 and timestamp. | 221 and timestamp. |
226 | 222 |
227 Calculate FPS by measuring the difference of Surface index returned by | 223 Calculate FPS by measuring the difference of Surface index returned by |
228 SurfaceFlinger in a period of time. | 224 SurfaceFlinger in a period of time. |
229 | 225 |
230 Returns: | 226 Returns: |
231 Dict of {page_flip_count (or 0 if there was an error), timestamp}. | 227 Dict of {page_flip_count (or 0 if there was an error), timestamp}. |
232 """ | 228 """ |
233 results = self._adb.RunShellCommand('service call SurfaceFlinger 1013') | 229 results = self._adb.RunShellCommand('service call SurfaceFlinger 1013') |
234 assert len(results) == 1 | 230 assert len(results) == 1 |
235 match = re.search('^Result: Parcel\((\w+)', results[0]) | 231 match = re.search('^Result: Parcel\((\w+)', results[0]) |
236 cur_surface = 0 | 232 cur_surface = 0 |
237 if match: | 233 if match: |
238 try: | 234 try: |
239 cur_surface = int(match.group(1), 16) | 235 cur_surface = int(match.group(1), 16) |
240 except Exception: | 236 except Exception: |
241 logging.error('Failed to parse current surface from ' + match.group(1)) | 237 logging.error('Failed to parse current surface from ' + match.group(1)) |
242 else: | 238 else: |
243 logging.warning('Failed to call SurfaceFlinger surface ' + results[0]) | 239 logging.warning('Failed to call SurfaceFlinger surface ' + results[0]) |
244 return { | 240 return { |
245 'page_flip_count': cur_surface, | 241 'page_flip_count': cur_surface, |
246 'timestamp': datetime.datetime.now(), | 242 'timestamp': datetime.datetime.now(), |
247 } | 243 } |
OLD | NEW |