OLD | NEW |
| (Empty) |
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 | |
3 # found in the LICENSE file. | |
4 | |
5 import Queue | |
6 import datetime | |
7 import logging | |
8 import re | |
9 import threading | |
10 from pylib import android_commands | |
11 from pylib.device import device_utils | |
12 | |
13 | |
14 # Log marker containing SurfaceTexture timestamps. | |
15 _SURFACE_TEXTURE_TIMESTAMPS_MESSAGE = 'SurfaceTexture update timestamps' | |
16 _SURFACE_TEXTURE_TIMESTAMP_RE = r'\d+' | |
17 | |
18 | |
19 class SurfaceStatsCollector(object): | |
20 """Collects surface stats for a SurfaceView from the output of SurfaceFlinger. | |
21 | |
22 Args: | |
23 device: A DeviceUtils instance. | |
24 """ | |
25 | |
26 def __init__(self, device): | |
27 # TODO(jbudorick) Remove once telemetry gets switched over. | |
28 if isinstance(device, android_commands.AndroidCommands): | |
29 device = device_utils.DeviceUtils(device) | |
30 self._device = device | |
31 self._collector_thread = None | |
32 self._surface_before = None | |
33 self._get_data_event = None | |
34 self._data_queue = None | |
35 self._stop_event = None | |
36 self._warn_about_empty_data = True | |
37 | |
38 def DisableWarningAboutEmptyData(self): | |
39 self._warn_about_empty_data = False | |
40 | |
41 def Start(self): | |
42 assert not self._collector_thread | |
43 | |
44 if self._ClearSurfaceFlingerLatencyData(): | |
45 self._get_data_event = threading.Event() | |
46 self._stop_event = threading.Event() | |
47 self._data_queue = Queue.Queue() | |
48 self._collector_thread = threading.Thread(target=self._CollectorThread) | |
49 self._collector_thread.start() | |
50 else: | |
51 raise Exception('SurfaceFlinger not supported on this device.') | |
52 | |
53 def Stop(self): | |
54 assert self._collector_thread | |
55 (refresh_period, timestamps) = self._GetDataFromThread() | |
56 if self._collector_thread: | |
57 self._stop_event.set() | |
58 self._collector_thread.join() | |
59 self._collector_thread = None | |
60 return (refresh_period, timestamps) | |
61 | |
62 def _CollectorThread(self): | |
63 last_timestamp = 0 | |
64 timestamps = [] | |
65 retries = 0 | |
66 | |
67 while not self._stop_event.is_set(): | |
68 self._get_data_event.wait(1) | |
69 try: | |
70 refresh_period, new_timestamps = self._GetSurfaceFlingerFrameData() | |
71 if refresh_period is None or timestamps is None: | |
72 retries += 1 | |
73 if retries < 3: | |
74 continue | |
75 if last_timestamp: | |
76 # Some data has already been collected, but either the app | |
77 # was closed or there's no new data. Signal the main thread and | |
78 # wait. | |
79 self._data_queue.put((None, None)) | |
80 self._stop_event.wait() | |
81 break | |
82 raise Exception('Unable to get surface flinger latency data') | |
83 | |
84 timestamps += [timestamp for timestamp in new_timestamps | |
85 if timestamp > last_timestamp] | |
86 if len(timestamps): | |
87 last_timestamp = timestamps[-1] | |
88 | |
89 if self._get_data_event.is_set(): | |
90 self._get_data_event.clear() | |
91 self._data_queue.put((refresh_period, timestamps)) | |
92 timestamps = [] | |
93 except Exception as e: | |
94 # On any error, before aborting, put the exception into _data_queue to | |
95 # prevent the main thread from waiting at _data_queue.get() infinitely. | |
96 self._data_queue.put(e) | |
97 raise | |
98 | |
99 def _GetDataFromThread(self): | |
100 self._get_data_event.set() | |
101 ret = self._data_queue.get() | |
102 if isinstance(ret, Exception): | |
103 raise ret | |
104 return ret | |
105 | |
106 def _ClearSurfaceFlingerLatencyData(self): | |
107 """Clears the SurfaceFlinger latency data. | |
108 | |
109 Returns: | |
110 True if SurfaceFlinger latency is supported by the device, otherwise | |
111 False. | |
112 """ | |
113 # The command returns nothing if it is supported, otherwise returns many | |
114 # lines of result just like 'dumpsys SurfaceFlinger'. | |
115 results = self._device.RunShellCommand( | |
116 'dumpsys SurfaceFlinger --latency-clear SurfaceView') | |
117 return not len(results) | |
118 | |
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): | |
127 """Returns collected SurfaceFlinger frame timing data. | |
128 | |
129 Returns: | |
130 A tuple containing: | |
131 - The display's nominal refresh period in milliseconds. | |
132 - A list of timestamps signifying frame presentation times in | |
133 milliseconds. | |
134 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). | |
136 """ | |
137 # adb shell dumpsys SurfaceFlinger --latency <window name> | |
138 # prints some information about the last 128 frames displayed in | |
139 # that window. | |
140 # The data returned looks like this: | |
141 # 16954612 | |
142 # 7657467895508 7657482691352 7657493499756 | |
143 # 7657484466553 7657499645964 7657511077881 | |
144 # 7657500793457 7657516600576 7657527404785 | |
145 # (...) | |
146 # | |
147 # The first line is the refresh period (here 16.95 ms), it is followed | |
148 # by 128 lines w/ 3 timestamps in nanosecond each: | |
149 # A) when the app started to draw | |
150 # B) the vsync immediately preceding SF submitting the frame to the h/w | |
151 # C) timestamp immediately after SF submitted that frame to the h/w | |
152 # | |
153 # The difference between the 1st and 3rd timestamp is the frame-latency. | |
154 # An interesting data is when the frame latency crosses a refresh period | |
155 # boundary, this can be calculated this way: | |
156 # | |
157 # ceil((C - A) / refresh-period) | |
158 # | |
159 # (each time the number above changes, we have a "jank"). | |
160 # If this happens a lot during an animation, the animation appears | |
161 # janky, even if it runs at 60 fps in average. | |
162 # | |
163 # 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 | |
165 # composited into a SurfaceView. | |
166 results = self._device.RunShellCommand( | |
167 'dumpsys SurfaceFlinger --latency SurfaceView') | |
168 if not len(results): | |
169 return (None, None) | |
170 | |
171 timestamps = [] | |
172 nanoseconds_per_millisecond = 1e6 | |
173 refresh_period = long(results[0]) / nanoseconds_per_millisecond | |
174 | |
175 # 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. | |
177 # Since we only care about completed frames, we will ignore any timestamps | |
178 # with this value. | |
179 pending_fence_timestamp = (1 << 63) - 1 | |
180 | |
181 for line in results[1:]: | |
182 fields = line.split() | |
183 if len(fields) != 3: | |
184 continue | |
185 timestamp = long(fields[1]) | |
186 if timestamp == pending_fence_timestamp: | |
187 continue | |
188 timestamp /= nanoseconds_per_millisecond | |
189 timestamps.append(timestamp) | |
190 | |
191 return (refresh_period, timestamps) | |
OLD | NEW |