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

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

Issue 809753002: Revert of telemetry: Clean up SurfaceFlinger statistics (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: Created 6 years 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
« no previous file with comments | « no previous file | build/android/surface_stats.py » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
1 # Copyright 2013 The Chromium Authors. All rights reserved. 1 # Copyright 2013 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 from pylib import android_commands 10 from pylib import android_commands
11 from pylib.device import device_utils 11 from pylib.device import device_utils
12 12
13 13
14 # Log marker containing SurfaceTexture timestamps. 14 # Log marker containing SurfaceTexture timestamps.
15 _SURFACE_TEXTURE_TIMESTAMPS_MESSAGE = 'SurfaceTexture update timestamps' 15 _SURFACE_TEXTURE_TIMESTAMPS_MESSAGE = 'SurfaceTexture update timestamps'
16 _SURFACE_TEXTURE_TIMESTAMP_RE = r'\d+' 16 _SURFACE_TEXTURE_TIMESTAMP_RE = r'\d+'
17 17
18 _MIN_NORMALIZED_FRAME_LENGTH = 0.5
19
18 20
19 class SurfaceStatsCollector(object): 21 class SurfaceStatsCollector(object):
20 """Collects surface stats for a SurfaceView from the output of SurfaceFlinger. 22 """Collects surface stats for a SurfaceView from the output of SurfaceFlinger.
21 23
22 Args: 24 Args:
23 device: A DeviceUtils instance. 25 device: A DeviceUtils instance.
24 """ 26 """
27 class Result(object):
28 def __init__(self, name, value, unit):
29 self.name = name
30 self.value = value
31 self.unit = unit
25 32
26 def __init__(self, device): 33 def __init__(self, device):
27 # TODO(jbudorick) Remove once telemetry gets switched over. 34 # TODO(jbudorick) Remove once telemetry gets switched over.
28 if isinstance(device, android_commands.AndroidCommands): 35 if isinstance(device, android_commands.AndroidCommands):
29 device = device_utils.DeviceUtils(device) 36 device = device_utils.DeviceUtils(device)
30 self._device = device 37 self._device = device
31 self._collector_thread = None 38 self._collector_thread = None
39 self._use_legacy_method = False
32 self._surface_before = None 40 self._surface_before = None
33 self._get_data_event = None 41 self._get_data_event = None
34 self._data_queue = None 42 self._data_queue = None
35 self._stop_event = None 43 self._stop_event = None
44 self._results = []
36 self._warn_about_empty_data = True 45 self._warn_about_empty_data = True
37 46
38 def DisableWarningAboutEmptyData(self): 47 def DisableWarningAboutEmptyData(self):
39 self._warn_about_empty_data = False 48 self._warn_about_empty_data = False
40 49
41 def Start(self): 50 def Start(self):
42 assert not self._collector_thread 51 assert not self._collector_thread
43 52
44 if self._ClearSurfaceFlingerLatencyData(): 53 if self._ClearSurfaceFlingerLatencyData():
45 self._get_data_event = threading.Event() 54 self._get_data_event = threading.Event()
46 self._stop_event = threading.Event() 55 self._stop_event = threading.Event()
47 self._data_queue = Queue.Queue() 56 self._data_queue = Queue.Queue()
48 self._collector_thread = threading.Thread(target=self._CollectorThread) 57 self._collector_thread = threading.Thread(target=self._CollectorThread)
49 self._collector_thread.start() 58 self._collector_thread.start()
50 else: 59 else:
51 raise Exception('SurfaceFlinger not supported on this device.') 60 self._use_legacy_method = True
61 self._surface_before = self._GetSurfaceStatsLegacy()
52 62
53 def Stop(self): 63 def Stop(self):
54 assert self._collector_thread 64 self._StorePerfResults()
55 (refresh_period, timestamps) = self._GetDataFromThread()
56 if self._collector_thread: 65 if self._collector_thread:
57 self._stop_event.set() 66 self._stop_event.set()
58 self._collector_thread.join() 67 self._collector_thread.join()
59 self._collector_thread = None 68 self._collector_thread = None
60 return (refresh_period, timestamps) 69
70 def SampleResults(self):
71 self._StorePerfResults()
72 results = self.GetResults()
73 self._results = []
74 return results
75
76 def GetResults(self):
77 return self._results or self._GetEmptyResults()
78
79 @staticmethod
80 def _GetEmptyResults():
81 return [
82 SurfaceStatsCollector.Result('refresh_period', None, 'seconds'),
83 SurfaceStatsCollector.Result('jank_count', None, 'janks'),
84 SurfaceStatsCollector.Result('max_frame_delay', None, 'vsyncs'),
85 SurfaceStatsCollector.Result('frame_lengths', None, 'vsyncs'),
86 SurfaceStatsCollector.Result('avg_surface_fps', None, 'fps')
87 ]
88
89 @staticmethod
90 def _GetNormalizedDeltas(data, refresh_period, min_normalized_delta=None):
91 deltas = [t2 - t1 for t1, t2 in zip(data, data[1:])]
92 if min_normalized_delta != None:
93 deltas = [d for d in deltas
94 if d / refresh_period >= min_normalized_delta]
95 return (deltas, [delta / refresh_period for delta in deltas])
96
97 @staticmethod
98 def _CalculateResults(refresh_period, timestamps, result_suffix):
99 """Returns a list of SurfaceStatsCollector.Result."""
100 frame_count = len(timestamps)
101 seconds = timestamps[-1] - timestamps[0]
102
103 frame_lengths, normalized_frame_lengths = \
104 SurfaceStatsCollector._GetNormalizedDeltas(
105 timestamps, refresh_period, _MIN_NORMALIZED_FRAME_LENGTH)
106 if len(frame_lengths) < frame_count - 1:
107 logging.warning('Skipping frame lengths that are too short.')
108 frame_count = len(frame_lengths) + 1
109 if len(frame_lengths) == 0:
110 raise Exception('No valid frames lengths found.')
111 _, normalized_changes = \
112 SurfaceStatsCollector._GetNormalizedDeltas(
113 frame_lengths, refresh_period)
114 jankiness = [max(0, round(change)) for change in normalized_changes]
115 pause_threshold = 20
116 jank_count = sum(1 for change in jankiness
117 if change > 0 and change < pause_threshold)
118 return [
119 SurfaceStatsCollector.Result(
120 'avg_surface_fps' + result_suffix,
121 int(round((frame_count - 1) / seconds)), 'fps'),
122 SurfaceStatsCollector.Result(
123 'jank_count' + result_suffix, jank_count, 'janks'),
124 SurfaceStatsCollector.Result(
125 'max_frame_delay' + result_suffix,
126 round(max(normalized_frame_lengths)),
127 'vsyncs'),
128 SurfaceStatsCollector.Result(
129 'frame_lengths' + result_suffix, normalized_frame_lengths,
130 'vsyncs'),
131 ]
132
133 @staticmethod
134 def _CalculateBuckets(refresh_period, timestamps):
135 results = []
136 for pct in [0.99, 0.5]:
137 sliced = timestamps[min(int(-pct * len(timestamps)), -3) : ]
138 results += SurfaceStatsCollector._CalculateResults(
139 refresh_period, sliced, '_' + str(int(pct * 100)))
140 return results
141
142 def _StorePerfResults(self):
143 if self._use_legacy_method:
144 surface_after = self._GetSurfaceStatsLegacy()
145 td = surface_after['timestamp'] - self._surface_before['timestamp']
146 seconds = td.seconds + td.microseconds / 1e6
147 frame_count = (surface_after['page_flip_count'] -
148 self._surface_before['page_flip_count'])
149 self._results.append(SurfaceStatsCollector.Result(
150 'avg_surface_fps', int(round(frame_count / seconds)), 'fps'))
151 return
152
153 # Non-legacy method.
154 assert self._collector_thread
155 (refresh_period, timestamps) = self._GetDataFromThread()
156 if not refresh_period or not len(timestamps) >= 3:
157 if self._warn_about_empty_data:
158 logging.warning('Surface stat data is empty')
159 return
160 self._results.append(SurfaceStatsCollector.Result(
161 'refresh_period', refresh_period, 'seconds'))
162 self._results += self._CalculateResults(refresh_period, timestamps, '')
163 self._results += self._CalculateBuckets(refresh_period, timestamps)
61 164
62 def _CollectorThread(self): 165 def _CollectorThread(self):
63 last_timestamp = 0 166 last_timestamp = 0
64 timestamps = [] 167 timestamps = []
65 retries = 0 168 retries = 0
66 169
67 while not self._stop_event.is_set(): 170 while not self._stop_event.is_set():
68 self._get_data_event.wait(1) 171 self._get_data_event.wait(1)
69 try: 172 try:
70 refresh_period, new_timestamps = self._GetSurfaceFlingerFrameData() 173 refresh_period, new_timestamps = self._GetSurfaceFlingerFrameData()
(...skipping 38 matching lines...) Expand 10 before | Expand all | Expand 10 after
109 Returns: 212 Returns:
110 True if SurfaceFlinger latency is supported by the device, otherwise 213 True if SurfaceFlinger latency is supported by the device, otherwise
111 False. 214 False.
112 """ 215 """
113 # The command returns nothing if it is supported, otherwise returns many 216 # The command returns nothing if it is supported, otherwise returns many
114 # lines of result just like 'dumpsys SurfaceFlinger'. 217 # lines of result just like 'dumpsys SurfaceFlinger'.
115 results = self._device.RunShellCommand( 218 results = self._device.RunShellCommand(
116 'dumpsys SurfaceFlinger --latency-clear SurfaceView') 219 'dumpsys SurfaceFlinger --latency-clear SurfaceView')
117 return not len(results) 220 return not len(results)
118 221
119 def GetSurfaceFlingerPid(self):
120 results = self._device.RunShellCommand('ps | grep surfaceflinger')
121 if not results:
122 raise Exception('Unable to get surface flinger process id')
123 pid = results[0].split()[1]
124 return pid
125
126 def _GetSurfaceFlingerFrameData(self): 222 def _GetSurfaceFlingerFrameData(self):
127 """Returns collected SurfaceFlinger frame timing data. 223 """Returns collected SurfaceFlinger frame timing data.
128 224
129 Returns: 225 Returns:
130 A tuple containing: 226 A tuple containing:
131 - The display's nominal refresh period in milliseconds. 227 - The display's nominal refresh period in seconds.
132 - A list of timestamps signifying frame presentation times in 228 - A list of timestamps signifying frame presentation times in seconds.
133 milliseconds.
134 The return value may be (None, None) if there was no data collected (for 229 The return value may be (None, None) if there was no data collected (for
135 example, if the app was closed before the collector thread has finished). 230 example, if the app was closed before the collector thread has finished).
136 """ 231 """
137 # adb shell dumpsys SurfaceFlinger --latency <window name> 232 # adb shell dumpsys SurfaceFlinger --latency <window name>
138 # prints some information about the last 128 frames displayed in 233 # prints some information about the last 128 frames displayed in
139 # that window. 234 # that window.
140 # The data returned looks like this: 235 # The data returned looks like this:
141 # 16954612 236 # 16954612
142 # 7657467895508 7657482691352 7657493499756 237 # 7657467895508 7657482691352 7657493499756
143 # 7657484466553 7657499645964 7657511077881 238 # 7657484466553 7657499645964 7657511077881
(...skipping 18 matching lines...) Expand all
162 # 257 #
163 # We use the special "SurfaceView" window name because the statistics for 258 # We use the special "SurfaceView" window name because the statistics for
164 # the activity's main window are not updated when the main web content is 259 # the activity's main window are not updated when the main web content is
165 # composited into a SurfaceView. 260 # composited into a SurfaceView.
166 results = self._device.RunShellCommand( 261 results = self._device.RunShellCommand(
167 'dumpsys SurfaceFlinger --latency SurfaceView') 262 'dumpsys SurfaceFlinger --latency SurfaceView')
168 if not len(results): 263 if not len(results):
169 return (None, None) 264 return (None, None)
170 265
171 timestamps = [] 266 timestamps = []
172 nanoseconds_per_millisecond = 1e6 267 nanoseconds_per_second = 1e9
173 refresh_period = long(results[0]) / nanoseconds_per_millisecond 268 refresh_period = long(results[0]) / nanoseconds_per_second
174 269
175 # If a fence associated with a frame is still pending when we query the 270 # If a fence associated with a frame is still pending when we query the
176 # latency data, SurfaceFlinger gives the frame a timestamp of INT64_MAX. 271 # latency data, SurfaceFlinger gives the frame a timestamp of INT64_MAX.
177 # Since we only care about completed frames, we will ignore any timestamps 272 # Since we only care about completed frames, we will ignore any timestamps
178 # with this value. 273 # with this value.
179 pending_fence_timestamp = (1 << 63) - 1 274 pending_fence_timestamp = (1 << 63) - 1
180 275
181 for line in results[1:]: 276 for line in results[1:]:
182 fields = line.split() 277 fields = line.split()
183 if len(fields) != 3: 278 if len(fields) != 3:
184 continue 279 continue
185 timestamp = long(fields[1]) 280 timestamp = long(fields[1])
186 if timestamp == pending_fence_timestamp: 281 if timestamp == pending_fence_timestamp:
187 continue 282 continue
188 timestamp /= nanoseconds_per_millisecond 283 timestamp /= nanoseconds_per_second
189 timestamps.append(timestamp) 284 timestamps.append(timestamp)
190 285
191 return (refresh_period, timestamps) 286 return (refresh_period, timestamps)
287
288 def _GetSurfaceStatsLegacy(self):
289 """Legacy method (before JellyBean), returns the current Surface index
290 and timestamp.
291
292 Calculate FPS by measuring the difference of Surface index returned by
293 SurfaceFlinger in a period of time.
294
295 Returns:
296 Dict of {page_flip_count (or 0 if there was an error), timestamp}.
297 """
298 results = self._device.RunShellCommand('service call SurfaceFlinger 1013')
299 assert len(results) == 1
300 match = re.search(r'^Result: Parcel\((\w+)', results[0])
301 cur_surface = 0
302 if match:
303 try:
304 cur_surface = int(match.group(1), 16)
305 except Exception:
306 logging.error('Failed to parse current surface from ' + match.group(1))
307 else:
308 logging.warning('Failed to call SurfaceFlinger surface ' + results[0])
309 return {
310 'page_flip_count': cur_surface,
311 'timestamp': datetime.datetime.now(),
312 }
OLDNEW
« no previous file with comments | « no previous file | build/android/surface_stats.py » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698