Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(14)

Side by Side Diff: build/android/pylib/surface_stats_collector.py

Issue 12438022: telemetry: Update Android frame latency metrics (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src
Patch Set: Remove redundant lists. Created 7 years, 9 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
« no previous file with comments | « no previous file | no next file » | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
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
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
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 }
OLDNEW
« no previous file with comments | « no previous file | no next file » | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698