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