| 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 96da78b3d5a359da553d096c8dc9821c5196aadc..fbd4e40c7aed06290a1b3e5f8c75e8494dce6956 100644
|
| --- a/build/android/pylib/surface_stats_collector.py
|
| +++ b/build/android/pylib/surface_stats_collector.py
|
| @@ -61,6 +61,11 @@ class SurfaceStatsCollector(object):
|
| def GetResults(self):
|
| return self._results
|
|
|
| + @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])
|
| +
|
| def _StorePerfResults(self):
|
| if self._use_legacy_method:
|
| surface_after = self._GetSurfaceStatsLegacy()
|
| @@ -70,45 +75,48 @@ class SurfaceStatsCollector(object):
|
| self._surface_before['page_flip_count'])
|
| else:
|
| assert self._collector_thread
|
| - (seconds, latencies) = self._GetDataFromThread()
|
| - if not seconds or not len(latencies):
|
| + (refresh_period, timestamps) = self._GetDataFromThread()
|
| + if not refresh_period or not len(timestamps) >= 3:
|
| logging.warning('Surface stat data is empty')
|
| return
|
| -
|
| - frame_count = len(latencies)
|
| - jitter_count = 0
|
| - last_latency = latencies[0]
|
| - for latency in latencies[1:]:
|
| - if latency > last_latency:
|
| - jitter_count = jitter_count + 1
|
| - last_latency = latency
|
| + frame_count = len(timestamps)
|
| + seconds = timestamps[-1] - timestamps[0]
|
| +
|
| + frame_lengths, normalized_frame_lengths = \
|
| + self._GetNormalizedDeltas(timestamps, refresh_period)
|
| + length_changes, normalized_changes = \
|
| + self._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)
|
|
|
| self._results.append(SurfaceStatsCollector.Result(
|
| - 'surface_latencies', latencies, ''))
|
| + 'refresh_period', refresh_period, 'seconds'))
|
| self._results.append(SurfaceStatsCollector.Result(
|
| - 'peak_jitter', [max(latencies)], ''))
|
| + 'jank_count', jank_count, 'janks'))
|
| self._results.append(SurfaceStatsCollector.Result(
|
| - 'jitter_percent', [jitter_count * 100.0 / frame_count], 'percent'))
|
| + 'max_frame_delay', round(max(normalized_frame_lengths)),
|
| + 'vsyncs'))
|
| + self._results.append(SurfaceStatsCollector.Result(
|
| + 'frame_lengths', normalized_frame_lengths, 'vsyncs'))
|
| self._results.append(SurfaceStatsCollector.Result(
|
| - 'avg_surface_fps', [int(round(frame_count / seconds))], 'fps'))
|
| + 'avg_surface_fps', int(round(frame_count / seconds)), 'fps'))
|
|
|
| def _CollectorThread(self):
|
| last_timestamp = 0
|
| - first_timestamp = 0
|
| - latencies = []
|
| + timestamps = []
|
| retries = 0
|
| - has_collected_data = False
|
|
|
| while not self._stop_event.is_set():
|
| self._get_data_event.wait(1)
|
| try:
|
| - (t, last_timestamp) = self._GetSurfaceFlingerLatencyData(last_timestamp,
|
| - latencies)
|
| - if (t, last_timestamp) == (None, None):
|
| + refresh_period, new_timestamps = self._GetSurfaceFlingerFrameData()
|
| + if refresh_period is None or timestamps is None:
|
| retries += 1
|
| if retries < 3:
|
| continue
|
| - if has_collected_data:
|
| + 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.
|
| @@ -117,17 +125,15 @@ class SurfaceStatsCollector(object):
|
| break
|
| raise Exception('Unable to get surface flinger latency data')
|
|
|
| - has_collected_data = True
|
| -
|
| - if not first_timestamp:
|
| - first_timestamp = t
|
| + 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(((last_timestamp - first_timestamp) / 1e9,
|
| - latencies))
|
| - latencies = []
|
| - first_timestamp = 0
|
| + 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.
|
| @@ -154,21 +160,15 @@ class SurfaceStatsCollector(object):
|
| 'dumpsys SurfaceFlinger --latency-clear SurfaceView')
|
| return not len(results)
|
|
|
| - def _GetSurfaceFlingerLatencyData(self, previous_timestamp, latencies):
|
| - """Returns collected SurfaceFlinger latency data.
|
| -
|
| - Args:
|
| - previous_timestamp: The timestamp returned from the previous call or 0.
|
| - Only data after this timestamp will be returned.
|
| - latencies: A list to receive latency data. The latencies are integers
|
| - each of which is the number of refresh periods of each frame.
|
| + def _GetSurfaceFlingerFrameData(self):
|
| + """Returns collected SurfaceFlinger frame timing data.
|
|
|
| Returns:
|
| A tuple containing:
|
| - - The timestamp of the beginning of the first frame (ns),
|
| - - The timestamp of the end of the last frame (ns).
|
| - The tuple may be (None, None) if there was no data collected (for example,
|
| - if the app was closed before the collector thread has finished).
|
| + - 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
|
| @@ -204,21 +204,18 @@ class SurfaceStatsCollector(object):
|
| if not len(results):
|
| return (None, None)
|
|
|
| - refresh_period = int(results[0])
|
| - last_timestamp = previous_timestamp
|
| - first_timestamp = 0
|
| + timestamps = []
|
| + nanoseconds_per_second = 1e9
|
| + refresh_period = long(results[0]) / nanoseconds_per_second
|
| +
|
| for line in results[1:]:
|
| fields = line.split()
|
| - if len(fields) == 3:
|
| - timestamp = long(fields[0])
|
| - last_timestamp = long(fields[2])
|
| - if (timestamp > previous_timestamp):
|
| - if not first_timestamp:
|
| - first_timestamp = timestamp
|
| - # This is integral equivalent of ceil((C-A) / refresh-period)
|
| - latency_ns = int(last_timestamp - timestamp)
|
| - latencies.append((latency_ns + refresh_period - 1) / refresh_period)
|
| - return (first_timestamp, last_timestamp)
|
| + if len(fields) != 3:
|
| + continue
|
| + timestamp = long(fields[1]) / nanoseconds_per_second
|
| + timestamps.append(timestamp)
|
| +
|
| + return (refresh_period, timestamps)
|
|
|
| def _GetSurfaceStatsLegacy(self):
|
| """Legacy method (before JellyBean), returns the current Surface index
|
|
|