Index: build/android/pylib/surface_stats_collector.py |
diff --git a/build/android/pylib/surface_stats_collector.py b/build/android/pylib/surface_stats_collector.py |
index e28df07f823b7a4933a3ec92f55291bbc755deab..d7b6a69a253a548cbcdd09ca902960ee0307a75c 100644 |
--- a/build/android/pylib/surface_stats_collector.py |
+++ b/build/android/pylib/surface_stats_collector.py |
@@ -2,297 +2,9 @@ |
# Use of this source code is governed by a BSD-style license that can be |
# found in the LICENSE file. |
-import Queue |
-import datetime |
-import logging |
-import re |
-import threading |
- |
-from pylib import perf_tests_helper |
- |
- |
-# Log marker containing SurfaceTexture timestamps. |
-_SURFACE_TEXTURE_TIMESTAMPS_MESSAGE = 'SurfaceTexture update timestamps' |
-_SURFACE_TEXTURE_TIMESTAMP_RE = '\d+' |
- |
- |
-class SurfaceStatsCollector(object): |
- """Collects surface stats for a SurfaceView from the output of SurfaceFlinger. |
- |
- Args: |
- adb: the adb connection to use. |
- """ |
- class Result(object): |
- def __init__(self, name, value, unit): |
- self.name = name |
- self.value = value |
- self.unit = unit |
+from pylib.perf import surface_stats_collector |
+# TODO(bulach): remove once all references to SurfaceStatsCollector are fixed. |
+class SurfaceStatsCollector(surface_stats_collector.SurfaceStatsCollector): |
def __init__(self, adb): |
- self._adb = adb |
- self._collector_thread = None |
- self._use_legacy_method = False |
- self._surface_before = None |
- self._get_data_event = None |
- self._data_queue = None |
- self._stop_event = None |
- self._results = [] |
- self._warn_about_empty_data = True |
- |
- def DisableWarningAboutEmptyData(self): |
- self._warn_about_empty_data = False |
- |
- def Start(self): |
- assert not self._collector_thread |
- |
- if self._ClearSurfaceFlingerLatencyData(): |
- self._get_data_event = threading.Event() |
- self._stop_event = threading.Event() |
- self._data_queue = Queue.Queue() |
- self._collector_thread = threading.Thread(target=self._CollectorThread) |
- self._collector_thread.start() |
- else: |
- self._use_legacy_method = True |
- self._surface_before = self._GetSurfaceStatsLegacy() |
- |
- def Stop(self): |
- self._StorePerfResults() |
- if self._collector_thread: |
- self._stop_event.set() |
- self._collector_thread.join() |
- self._collector_thread = None |
- |
- def SampleResults(self): |
- self._StorePerfResults() |
- results = self.GetResults() |
- self._results = [] |
- return results |
- |
- def GetResults(self): |
- return self._results or self._GetEmptyResults() |
- |
- def _GetEmptyResults(self): |
- return [ |
- SurfaceStatsCollector.Result('refresh_period', None, 'seconds'), |
- SurfaceStatsCollector.Result('jank_count', None, 'janks'), |
- SurfaceStatsCollector.Result('max_frame_delay', None, 'vsyncs'), |
- SurfaceStatsCollector.Result('frame_lengths', None, 'vsyncs'), |
- SurfaceStatsCollector.Result('avg_surface_fps', None, 'fps') |
- ] |
- |
- @staticmethod |
- def _GetNormalizedDeltas(data, refresh_period): |
- deltas = [t2 - t1 for t1, t2 in zip(data, data[1:])] |
- return (deltas, [delta / refresh_period for delta in deltas]) |
- |
- @staticmethod |
- def _CalculateResults(refresh_period, timestamps, result_suffix): |
- """Returns a list of SurfaceStatsCollector.Result.""" |
- frame_count = len(timestamps) |
- seconds = timestamps[-1] - timestamps[0] |
- |
- frame_lengths, normalized_frame_lengths = \ |
- SurfaceStatsCollector._GetNormalizedDeltas(timestamps, refresh_period) |
- length_changes, normalized_changes = \ |
- SurfaceStatsCollector._GetNormalizedDeltas( |
- frame_lengths, refresh_period) |
- jankiness = [max(0, round(change)) for change in normalized_changes] |
- pause_threshold = 20 |
- jank_count = sum(1 for change in jankiness |
- if change > 0 and change < pause_threshold) |
- return [ |
- SurfaceStatsCollector.Result( |
- 'avg_surface_fps' + result_suffix, |
- int(round(frame_count / seconds)), 'fps'), |
- SurfaceStatsCollector.Result( |
- 'jank_count' + result_suffix, jank_count, 'janks'), |
- SurfaceStatsCollector.Result( |
- 'max_frame_delay' + result_suffix, |
- round(max(normalized_frame_lengths)), |
- 'vsyncs'), |
- SurfaceStatsCollector.Result( |
- 'frame_lengths' + result_suffix, normalized_frame_lengths, |
- 'vsyncs'), |
- ] |
- |
- @staticmethod |
- def _CalculateBuckets(refresh_period, timestamps): |
- results = [] |
- for pct in [0.99, 0.5]: |
- sliced = timestamps[min(int(-pct * len(timestamps)), -3) : ] |
- results += SurfaceStatsCollector._CalculateResults( |
- refresh_period, sliced, '_' + str(int(pct * 100))) |
- return results |
- |
- def _StorePerfResults(self): |
- if self._use_legacy_method: |
- surface_after = self._GetSurfaceStatsLegacy() |
- td = surface_after['timestamp'] - self._surface_before['timestamp'] |
- seconds = td.seconds + td.microseconds / 1e6 |
- frame_count = (surface_after['page_flip_count'] - |
- self._surface_before['page_flip_count']) |
- self._results.append(SurfaceStatsCollector.Result( |
- 'avg_surface_fps', int(round(frame_count / seconds)), 'fps')) |
- return |
- |
- # Non-legacy method. |
- assert self._collector_thread |
- (refresh_period, timestamps) = self._GetDataFromThread() |
- if not refresh_period or not len(timestamps) >= 3: |
- if self._warn_about_empty_data: |
- logging.warning('Surface stat data is empty') |
- return |
- self._results.append(SurfaceStatsCollector.Result( |
- 'refresh_period', refresh_period, 'seconds')) |
- self._results += self._CalculateResults(refresh_period, timestamps, '') |
- self._results += self._CalculateBuckets(refresh_period, timestamps) |
- |
- def _CollectorThread(self): |
- last_timestamp = 0 |
- timestamps = [] |
- retries = 0 |
- |
- while not self._stop_event.is_set(): |
- self._get_data_event.wait(1) |
- try: |
- refresh_period, new_timestamps = self._GetSurfaceFlingerFrameData() |
- if refresh_period is None or timestamps is None: |
- retries += 1 |
- if retries < 3: |
- continue |
- if last_timestamp: |
- # Some data has already been collected, but either the app |
- # was closed or there's no new data. Signal the main thread and |
- # wait. |
- self._data_queue.put((None, None)) |
- self._stop_event.wait() |
- break |
- raise Exception('Unable to get surface flinger latency data') |
- |
- timestamps += [timestamp for timestamp in new_timestamps |
- if timestamp > last_timestamp] |
- if len(timestamps): |
- last_timestamp = timestamps[-1] |
- |
- if self._get_data_event.is_set(): |
- self._get_data_event.clear() |
- self._data_queue.put((refresh_period, timestamps)) |
- timestamps = [] |
- except Exception as e: |
- # On any error, before aborting, put the exception into _data_queue to |
- # prevent the main thread from waiting at _data_queue.get() infinitely. |
- self._data_queue.put(e) |
- raise |
- |
- def _GetDataFromThread(self): |
- self._get_data_event.set() |
- ret = self._data_queue.get() |
- if isinstance(ret, Exception): |
- raise ret |
- return ret |
- |
- def _ClearSurfaceFlingerLatencyData(self): |
- """Clears the SurfaceFlinger latency data. |
- |
- Returns: |
- True if SurfaceFlinger latency is supported by the device, otherwise |
- False. |
- """ |
- # The command returns nothing if it is supported, otherwise returns many |
- # lines of result just like 'dumpsys SurfaceFlinger'. |
- results = self._adb.RunShellCommand( |
- 'dumpsys SurfaceFlinger --latency-clear SurfaceView') |
- return not len(results) |
- |
- def _GetSurfaceFlingerFrameData(self): |
- """Returns collected SurfaceFlinger frame timing data. |
- |
- Returns: |
- A tuple containing: |
- - The display's nominal refresh period in seconds. |
- - A list of timestamps signifying frame presentation times in seconds. |
- The return value may be (None, None) if there was no data collected (for |
- example, if the app was closed before the collector thread has finished). |
- """ |
- # adb shell dumpsys SurfaceFlinger --latency <window name> |
- # prints some information about the last 128 frames displayed in |
- # that window. |
- # The data returned looks like this: |
- # 16954612 |
- # 7657467895508 7657482691352 7657493499756 |
- # 7657484466553 7657499645964 7657511077881 |
- # 7657500793457 7657516600576 7657527404785 |
- # (...) |
- # |
- # The first line is the refresh period (here 16.95 ms), it is followed |
- # by 128 lines w/ 3 timestamps in nanosecond each: |
- # A) when the app started to draw |
- # B) the vsync immediately preceding SF submitting the frame to the h/w |
- # C) timestamp immediately after SF submitted that frame to the h/w |
- # |
- # The difference between the 1st and 3rd timestamp is the frame-latency. |
- # An interesting data is when the frame latency crosses a refresh period |
- # boundary, this can be calculated this way: |
- # |
- # ceil((C - A) / refresh-period) |
- # |
- # (each time the number above changes, we have a "jank"). |
- # If this happens a lot during an animation, the animation appears |
- # janky, even if it runs at 60 fps in average. |
- # |
- # We use the special "SurfaceView" window name because the statistics for |
- # the activity's main window are not updated when the main web content is |
- # composited into a SurfaceView. |
- results = self._adb.RunShellCommand( |
- 'dumpsys SurfaceFlinger --latency SurfaceView', |
- log_result=logging.getLogger().isEnabledFor(logging.DEBUG)) |
- if not len(results): |
- return (None, None) |
- |
- timestamps = [] |
- nanoseconds_per_second = 1e9 |
- refresh_period = long(results[0]) / nanoseconds_per_second |
- |
- # If a fence associated with a frame is still pending when we query the |
- # latency data, SurfaceFlinger gives the frame a timestamp of INT64_MAX. |
- # Since we only care about completed frames, we will ignore any timestamps |
- # with this value. |
- pending_fence_timestamp = (1 << 63) - 1 |
- |
- for line in results[1:]: |
- fields = line.split() |
- if len(fields) != 3: |
- continue |
- timestamp = long(fields[1]) |
- if timestamp == pending_fence_timestamp: |
- continue |
- timestamp /= nanoseconds_per_second |
- timestamps.append(timestamp) |
- |
- return (refresh_period, timestamps) |
- |
- def _GetSurfaceStatsLegacy(self): |
- """Legacy method (before JellyBean), returns the current Surface index |
- and timestamp. |
- |
- Calculate FPS by measuring the difference of Surface index returned by |
- SurfaceFlinger in a period of time. |
- |
- Returns: |
- Dict of {page_flip_count (or 0 if there was an error), timestamp}. |
- """ |
- results = self._adb.RunShellCommand('service call SurfaceFlinger 1013') |
- assert len(results) == 1 |
- match = re.search('^Result: Parcel\((\w+)', results[0]) |
- cur_surface = 0 |
- if match: |
- try: |
- cur_surface = int(match.group(1), 16) |
- except Exception: |
- logging.error('Failed to parse current surface from ' + match.group(1)) |
- else: |
- logging.warning('Failed to call SurfaceFlinger surface ' + results[0]) |
- return { |
- 'page_flip_count': cur_surface, |
- 'timestamp': datetime.datetime.now(), |
- } |
+ super(SurfaceStatsCollector, self).__init__(adb) |