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 19 matching lines...) Expand all Loading... |
30 | 30 |
31 def __init__(self, adb): | 31 def __init__(self, adb): |
32 self._adb = adb | 32 self._adb = adb |
33 self._collector_thread = None | 33 self._collector_thread = None |
34 self._use_legacy_method = False | 34 self._use_legacy_method = False |
35 self._surface_before = None | 35 self._surface_before = None |
36 self._get_data_event = None | 36 self._get_data_event = None |
37 self._data_queue = None | 37 self._data_queue = None |
38 self._stop_event = None | 38 self._stop_event = None |
39 self._results = [] | 39 self._results = [] |
| 40 self._warn_about_empty_data = True |
| 41 |
| 42 def DisableWarningAboutEmptyData(self): |
| 43 self._warn_about_empty_data = False |
40 | 44 |
41 def Start(self): | 45 def Start(self): |
42 assert not self._collector_thread | 46 assert not self._collector_thread |
43 | 47 |
44 if self._ClearSurfaceFlingerLatencyData(): | 48 if self._ClearSurfaceFlingerLatencyData(): |
45 self._get_data_event = threading.Event() | 49 self._get_data_event = threading.Event() |
46 self._stop_event = threading.Event() | 50 self._stop_event = threading.Event() |
47 self._data_queue = Queue.Queue() | 51 self._data_queue = Queue.Queue() |
48 self._collector_thread = threading.Thread(target=self._CollectorThread) | 52 self._collector_thread = threading.Thread(target=self._CollectorThread) |
49 self._collector_thread.start() | 53 self._collector_thread.start() |
50 else: | 54 else: |
51 self._use_legacy_method = True | 55 self._use_legacy_method = True |
52 self._surface_before = self._GetSurfaceStatsLegacy() | 56 self._surface_before = self._GetSurfaceStatsLegacy() |
53 | 57 |
54 def Stop(self): | 58 def Stop(self): |
55 self._StorePerfResults() | 59 self._StorePerfResults() |
56 if self._collector_thread: | 60 if self._collector_thread: |
57 self._stop_event.set() | 61 self._stop_event.set() |
58 self._collector_thread.join() | 62 self._collector_thread.join() |
59 self._collector_thread = None | 63 self._collector_thread = None |
60 | 64 |
| 65 def SampleResults(self): |
| 66 self._StorePerfResults() |
| 67 results = self._results |
| 68 self._results = [] |
| 69 return results |
| 70 |
61 def GetResults(self): | 71 def GetResults(self): |
62 return self._results | 72 return self._results |
63 | 73 |
64 @staticmethod | 74 @staticmethod |
65 def _GetNormalizedDeltas(data, refresh_period): | 75 def _GetNormalizedDeltas(data, refresh_period): |
66 deltas = [t2 - t1 for t1, t2 in zip(data, data[1:])] | 76 deltas = [t2 - t1 for t1, t2 in zip(data, data[1:])] |
67 return (deltas, [delta / refresh_period for delta in deltas]) | 77 return (deltas, [delta / refresh_period for delta in deltas]) |
68 | 78 |
69 def _StorePerfResults(self): | 79 def _StorePerfResults(self): |
70 if self._use_legacy_method: | 80 if self._use_legacy_method: |
71 surface_after = self._GetSurfaceStatsLegacy() | 81 surface_after = self._GetSurfaceStatsLegacy() |
72 td = surface_after['timestamp'] - self._surface_before['timestamp'] | 82 td = surface_after['timestamp'] - self._surface_before['timestamp'] |
73 seconds = td.seconds + td.microseconds / 1e6 | 83 seconds = td.seconds + td.microseconds / 1e6 |
74 frame_count = (surface_after['page_flip_count'] - | 84 frame_count = (surface_after['page_flip_count'] - |
75 self._surface_before['page_flip_count']) | 85 self._surface_before['page_flip_count']) |
76 else: | 86 else: |
77 assert self._collector_thread | 87 assert self._collector_thread |
78 (refresh_period, timestamps) = self._GetDataFromThread() | 88 (refresh_period, timestamps) = self._GetDataFromThread() |
79 if not refresh_period or not len(timestamps) >= 3: | 89 if not refresh_period or not len(timestamps) >= 3: |
80 logging.warning('Surface stat data is empty') | 90 if self._warn_about_empty_data: |
| 91 logging.warning('Surface stat data is empty') |
81 return | 92 return |
82 frame_count = len(timestamps) | 93 frame_count = len(timestamps) |
83 seconds = timestamps[-1] - timestamps[0] | 94 seconds = timestamps[-1] - timestamps[0] |
84 | 95 |
85 frame_lengths, normalized_frame_lengths = \ | 96 frame_lengths, normalized_frame_lengths = \ |
86 self._GetNormalizedDeltas(timestamps, refresh_period) | 97 self._GetNormalizedDeltas(timestamps, refresh_period) |
87 length_changes, normalized_changes = \ | 98 length_changes, normalized_changes = \ |
88 self._GetNormalizedDeltas(frame_lengths, refresh_period) | 99 self._GetNormalizedDeltas(frame_lengths, refresh_period) |
89 jankiness = [max(0, round(change)) for change in normalized_changes] | 100 jankiness = [max(0, round(change)) for change in normalized_changes] |
90 pause_threshold = 20 | 101 pause_threshold = 20 |
(...skipping 110 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
201 # composited into a SurfaceView. | 212 # composited into a SurfaceView. |
202 results = self._adb.RunShellCommand( | 213 results = self._adb.RunShellCommand( |
203 'dumpsys SurfaceFlinger --latency SurfaceView', log_result=True) | 214 'dumpsys SurfaceFlinger --latency SurfaceView', log_result=True) |
204 if not len(results): | 215 if not len(results): |
205 return (None, None) | 216 return (None, None) |
206 | 217 |
207 timestamps = [] | 218 timestamps = [] |
208 nanoseconds_per_second = 1e9 | 219 nanoseconds_per_second = 1e9 |
209 refresh_period = long(results[0]) / nanoseconds_per_second | 220 refresh_period = long(results[0]) / nanoseconds_per_second |
210 | 221 |
| 222 # SurfaceFlinger sometimes gives an invalid timestamp for the very latest |
| 223 # frame if it is queried while the frame is still being presented. We ignore |
| 224 # these timestamps. |
| 225 bad_timestamp = (1 << 63) - 1 |
| 226 |
211 for line in results[1:]: | 227 for line in results[1:]: |
212 fields = line.split() | 228 fields = line.split() |
213 if len(fields) != 3: | 229 if len(fields) != 3: |
214 continue | 230 continue |
215 timestamp = long(fields[1]) / nanoseconds_per_second | 231 timestamp = long(fields[1]) |
| 232 if timestamp == bad_timestamp: |
| 233 continue |
| 234 timestamp /= nanoseconds_per_second |
216 timestamps.append(timestamp) | 235 timestamps.append(timestamp) |
217 | 236 |
218 return (refresh_period, timestamps) | 237 return (refresh_period, timestamps) |
219 | 238 |
220 def _GetSurfaceStatsLegacy(self): | 239 def _GetSurfaceStatsLegacy(self): |
221 """Legacy method (before JellyBean), returns the current Surface index | 240 """Legacy method (before JellyBean), returns the current Surface index |
222 and timestamp. | 241 and timestamp. |
223 | 242 |
224 Calculate FPS by measuring the difference of Surface index returned by | 243 Calculate FPS by measuring the difference of Surface index returned by |
225 SurfaceFlinger in a period of time. | 244 SurfaceFlinger in a period of time. |
226 | 245 |
227 Returns: | 246 Returns: |
228 Dict of {page_flip_count (or 0 if there was an error), timestamp}. | 247 Dict of {page_flip_count (or 0 if there was an error), timestamp}. |
229 """ | 248 """ |
230 results = self._adb.RunShellCommand('service call SurfaceFlinger 1013') | 249 results = self._adb.RunShellCommand('service call SurfaceFlinger 1013') |
231 assert len(results) == 1 | 250 assert len(results) == 1 |
232 match = re.search('^Result: Parcel\((\w+)', results[0]) | 251 match = re.search('^Result: Parcel\((\w+)', results[0]) |
233 cur_surface = 0 | 252 cur_surface = 0 |
234 if match: | 253 if match: |
235 try: | 254 try: |
236 cur_surface = int(match.group(1), 16) | 255 cur_surface = int(match.group(1), 16) |
237 except Exception: | 256 except Exception: |
238 logging.error('Failed to parse current surface from ' + match.group(1)) | 257 logging.error('Failed to parse current surface from ' + match.group(1)) |
239 else: | 258 else: |
240 logging.warning('Failed to call SurfaceFlinger surface ' + results[0]) | 259 logging.warning('Failed to call SurfaceFlinger surface ' + results[0]) |
241 return { | 260 return { |
242 'page_flip_count': cur_surface, | 261 'page_flip_count': cur_surface, |
243 'timestamp': datetime.datetime.now(), | 262 'timestamp': datetime.datetime.now(), |
244 } | 263 } |
OLD | NEW |