| OLD | NEW |
| 1 # Copyright 2013 The Chromium Authors. All rights reserved. | 1 # Copyright 2015 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 # pylint: disable=unused-wildcard-import |
| 6 import threading | 6 # pylint: disable=wildcard-import |
| 7 | 7 |
| 8 | 8 from devil.android.perf.surface_stats_collector import * |
| 9 # Log marker containing SurfaceTexture timestamps. | |
| 10 _SURFACE_TEXTURE_TIMESTAMPS_MESSAGE = 'SurfaceTexture update timestamps' | |
| 11 _SURFACE_TEXTURE_TIMESTAMP_RE = r'\d+' | |
| 12 | |
| 13 | |
| 14 class SurfaceStatsCollector(object): | |
| 15 """Collects surface stats for a SurfaceView from the output of SurfaceFlinger. | |
| 16 | |
| 17 Args: | |
| 18 device: A DeviceUtils instance. | |
| 19 """ | |
| 20 | |
| 21 def __init__(self, device): | |
| 22 self._device = device | |
| 23 self._collector_thread = None | |
| 24 self._surface_before = None | |
| 25 self._get_data_event = None | |
| 26 self._data_queue = None | |
| 27 self._stop_event = None | |
| 28 self._warn_about_empty_data = True | |
| 29 | |
| 30 def DisableWarningAboutEmptyData(self): | |
| 31 self._warn_about_empty_data = False | |
| 32 | |
| 33 def Start(self): | |
| 34 assert not self._collector_thread | |
| 35 | |
| 36 if self._ClearSurfaceFlingerLatencyData(): | |
| 37 self._get_data_event = threading.Event() | |
| 38 self._stop_event = threading.Event() | |
| 39 self._data_queue = Queue.Queue() | |
| 40 self._collector_thread = threading.Thread(target=self._CollectorThread) | |
| 41 self._collector_thread.start() | |
| 42 else: | |
| 43 raise Exception('SurfaceFlinger not supported on this device.') | |
| 44 | |
| 45 def Stop(self): | |
| 46 assert self._collector_thread | |
| 47 (refresh_period, timestamps) = self._GetDataFromThread() | |
| 48 if self._collector_thread: | |
| 49 self._stop_event.set() | |
| 50 self._collector_thread.join() | |
| 51 self._collector_thread = None | |
| 52 return (refresh_period, timestamps) | |
| 53 | |
| 54 def _CollectorThread(self): | |
| 55 last_timestamp = 0 | |
| 56 timestamps = [] | |
| 57 retries = 0 | |
| 58 | |
| 59 while not self._stop_event.is_set(): | |
| 60 self._get_data_event.wait(1) | |
| 61 try: | |
| 62 refresh_period, new_timestamps = self._GetSurfaceFlingerFrameData() | |
| 63 if refresh_period is None or timestamps is None: | |
| 64 retries += 1 | |
| 65 if retries < 3: | |
| 66 continue | |
| 67 if last_timestamp: | |
| 68 # Some data has already been collected, but either the app | |
| 69 # was closed or there's no new data. Signal the main thread and | |
| 70 # wait. | |
| 71 self._data_queue.put((None, None)) | |
| 72 self._stop_event.wait() | |
| 73 break | |
| 74 raise Exception('Unable to get surface flinger latency data') | |
| 75 | |
| 76 timestamps += [timestamp for timestamp in new_timestamps | |
| 77 if timestamp > last_timestamp] | |
| 78 if len(timestamps): | |
| 79 last_timestamp = timestamps[-1] | |
| 80 | |
| 81 if self._get_data_event.is_set(): | |
| 82 self._get_data_event.clear() | |
| 83 self._data_queue.put((refresh_period, timestamps)) | |
| 84 timestamps = [] | |
| 85 except Exception as e: | |
| 86 # On any error, before aborting, put the exception into _data_queue to | |
| 87 # prevent the main thread from waiting at _data_queue.get() infinitely. | |
| 88 self._data_queue.put(e) | |
| 89 raise | |
| 90 | |
| 91 def _GetDataFromThread(self): | |
| 92 self._get_data_event.set() | |
| 93 ret = self._data_queue.get() | |
| 94 if isinstance(ret, Exception): | |
| 95 raise ret | |
| 96 return ret | |
| 97 | |
| 98 def _ClearSurfaceFlingerLatencyData(self): | |
| 99 """Clears the SurfaceFlinger latency data. | |
| 100 | |
| 101 Returns: | |
| 102 True if SurfaceFlinger latency is supported by the device, otherwise | |
| 103 False. | |
| 104 """ | |
| 105 # The command returns nothing if it is supported, otherwise returns many | |
| 106 # lines of result just like 'dumpsys SurfaceFlinger'. | |
| 107 results = self._device.RunShellCommand( | |
| 108 'dumpsys SurfaceFlinger --latency-clear SurfaceView') | |
| 109 return not len(results) | |
| 110 | |
| 111 def GetSurfaceFlingerPid(self): | |
| 112 results = self._device.RunShellCommand('ps | grep surfaceflinger') | |
| 113 if not results: | |
| 114 raise Exception('Unable to get surface flinger process id') | |
| 115 pid = results[0].split()[1] | |
| 116 return pid | |
| 117 | |
| 118 def _GetSurfaceFlingerFrameData(self): | |
| 119 """Returns collected SurfaceFlinger frame timing data. | |
| 120 | |
| 121 Returns: | |
| 122 A tuple containing: | |
| 123 - The display's nominal refresh period in milliseconds. | |
| 124 - A list of timestamps signifying frame presentation times in | |
| 125 milliseconds. | |
| 126 The return value may be (None, None) if there was no data collected (for | |
| 127 example, if the app was closed before the collector thread has finished). | |
| 128 """ | |
| 129 # adb shell dumpsys SurfaceFlinger --latency <window name> | |
| 130 # prints some information about the last 128 frames displayed in | |
| 131 # that window. | |
| 132 # The data returned looks like this: | |
| 133 # 16954612 | |
| 134 # 7657467895508 7657482691352 7657493499756 | |
| 135 # 7657484466553 7657499645964 7657511077881 | |
| 136 # 7657500793457 7657516600576 7657527404785 | |
| 137 # (...) | |
| 138 # | |
| 139 # The first line is the refresh period (here 16.95 ms), it is followed | |
| 140 # by 128 lines w/ 3 timestamps in nanosecond each: | |
| 141 # A) when the app started to draw | |
| 142 # B) the vsync immediately preceding SF submitting the frame to the h/w | |
| 143 # C) timestamp immediately after SF submitted that frame to the h/w | |
| 144 # | |
| 145 # The difference between the 1st and 3rd timestamp is the frame-latency. | |
| 146 # An interesting data is when the frame latency crosses a refresh period | |
| 147 # boundary, this can be calculated this way: | |
| 148 # | |
| 149 # ceil((C - A) / refresh-period) | |
| 150 # | |
| 151 # (each time the number above changes, we have a "jank"). | |
| 152 # If this happens a lot during an animation, the animation appears | |
| 153 # janky, even if it runs at 60 fps in average. | |
| 154 # | |
| 155 # We use the special "SurfaceView" window name because the statistics for | |
| 156 # the activity's main window are not updated when the main web content is | |
| 157 # composited into a SurfaceView. | |
| 158 results = self._device.RunShellCommand( | |
| 159 'dumpsys SurfaceFlinger --latency SurfaceView') | |
| 160 if not len(results): | |
| 161 return (None, None) | |
| 162 | |
| 163 timestamps = [] | |
| 164 nanoseconds_per_millisecond = 1e6 | |
| 165 refresh_period = long(results[0]) / nanoseconds_per_millisecond | |
| 166 | |
| 167 # If a fence associated with a frame is still pending when we query the | |
| 168 # latency data, SurfaceFlinger gives the frame a timestamp of INT64_MAX. | |
| 169 # Since we only care about completed frames, we will ignore any timestamps | |
| 170 # with this value. | |
| 171 pending_fence_timestamp = (1 << 63) - 1 | |
| 172 | |
| 173 for line in results[1:]: | |
| 174 fields = line.split() | |
| 175 if len(fields) != 3: | |
| 176 continue | |
| 177 timestamp = long(fields[1]) | |
| 178 if timestamp == pending_fence_timestamp: | |
| 179 continue | |
| 180 timestamp /= nanoseconds_per_millisecond | |
| 181 timestamps.append(timestamp) | |
| 182 | |
| 183 return (refresh_period, timestamps) | |
| OLD | NEW |