1 # Copyright (c) 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.
9 from telemetry import decorators
10 from telemetry.core import bitmap
11 from telemetry.core import exceptions
12 from telemetry.core import platform
13 from telemetry.core import util
14 from telemetry.core.platform import proc_supporting_platform_backend
15 from telemetry.core.platform.power_monitor import android_ds2784_power_monitor
16 from telemetry.core.platform.power_monitor import monsoon_power_monitor
17 from telemetry.core.platform.power_monitor import power_monitor_controller
18 from telemetry.core.platform.profiler import android_prebuilt_profiler_helper
20 # Get build/android scripts into our path.
21 util.AddDirToPythonPath(util.GetChromiumSrcDir(), 'build', 'android')
22 from pylib import screenshot # pylint: disable=F0401
23 from pylib.perf import cache_control # pylint: disable=F0401
24 from pylib.perf import perf_control # pylint: disable=F0401
25 from pylib.perf import thermal_throttle # pylint: disable=F0401
28 from pylib.perf import surface_stats_collector # pylint: disable=F0401
30 surface_stats_collector = None
33 _HOST_APPLICATIONS = [
39 class AndroidPlatformBackend(
40 proc_supporting_platform_backend.ProcSupportingPlatformBackend):
41 def __init__(self, adb, no_performance_mode):
42 super(AndroidPlatformBackend, self).__init__()
44 self._surface_stats_collector = None
45 self._perf_tests_setup = perf_control.PerfControl(self._adb)
46 self._thermal_throttle = thermal_throttle.ThermalThrottle(self._adb)
47 self._no_performance_mode = no_performance_mode
48 self._raw_display_frame_rate_measurements = []
49 self._host_platform_backend = platform.CreatePlatformBackendForCurrentOS()
50 self._can_access_protected_file_contents = \
51 self._adb.CanAccessProtectedFileContents()
52 self._powermonitor = power_monitor_controller.PowerMonitorController([
53 monsoon_power_monitor.MonsoonPowerMonitor(),
54 android_ds2784_power_monitor.DS2784PowerMonitor(adb)
56 self._video_recorder = None
57 self._video_output = None
58 if self._no_performance_mode:
59 logging.warning('CPU governor will not be set!')
61 def IsRawDisplayFrameRateSupported(self):
64 def StartRawDisplayFrameRateMeasurement(self):
65 assert not self._surface_stats_collector
66 # Clear any leftover data from previous timed out tests
67 self._raw_display_frame_rate_measurements = []
68 self._surface_stats_collector = \
69 surface_stats_collector.SurfaceStatsCollector(self._adb)
70 self._surface_stats_collector.Start()
72 def StopRawDisplayFrameRateMeasurement(self):
73 self._surface_stats_collector.Stop()
74 for r in self._surface_stats_collector.GetResults():
75 self._raw_display_frame_rate_measurements.append(
76 platform.Platform.RawDisplayFrameRateMeasurement(
77 r.name, r.value, r.unit))
79 self._surface_stats_collector = None
81 def GetRawDisplayFrameRateMeasurements(self):
82 ret = self._raw_display_frame_rate_measurements
83 self._raw_display_frame_rate_measurements = []
86 def SetFullPerformanceModeEnabled(self, enabled):
87 if self._no_performance_mode:
90 self._perf_tests_setup.SetHighPerfMode()
92 self._perf_tests_setup.SetDefaultPerfMode()
94 def CanMonitorThermalThrottling(self):
97 def IsThermallyThrottled(self):
98 return self._thermal_throttle.IsThrottled()
100 def HasBeenThermallyThrottled(self):
101 return self._thermal_throttle.HasBeenThrottled()
103 def GetSystemCommitCharge(self):
104 for line in self._adb.RunShellCommand('dumpsys meminfo', log_result=False):
105 if line.startswith('Total PSS: '):
106 return int(line.split()[2]) * 1024
110 def GetSystemTotalPhysicalMemory(self):
111 for line in self._adb.RunShellCommand('dumpsys meminfo', log_result=False):
112 if line.startswith('Total RAM: '):
113 return int(line.split()[2]) * 1024
116 def GetCpuStats(self, pid):
117 if not self._can_access_protected_file_contents:
118 logging.warning('CPU stats cannot be retrieved on non-rooted device.')
120 return super(AndroidPlatformBackend, self).GetCpuStats(pid)
122 def GetCpuTimestamp(self):
123 if not self._can_access_protected_file_contents:
124 logging.warning('CPU timestamp cannot be retrieved on non-rooted device.')
126 return super(AndroidPlatformBackend, self).GetCpuTimestamp()
128 def PurgeUnpinnedMemory(self):
129 """Purges the unpinned ashmem memory for the whole system.
131 This can be used to make memory measurements more stable in particular.
133 android_prebuilt_profiler_helper.InstallOnDevice(self._adb, 'purge_ashmem')
134 if self._adb.RunShellCommand(
135 android_prebuilt_profiler_helper.GetDevicePath('purge_ashmem'),
138 raise Exception('Error while purging ashmem.')
140 def GetMemoryStats(self, pid):
141 memory_usage = self._adb.GetMemoryUsageForPid(pid)[0]
142 return {'ProportionalSetSize': memory_usage['Pss'] * 1024,
143 'SharedDirty': memory_usage['Shared_Dirty'] * 1024,
144 'PrivateDirty': memory_usage['Private_Dirty'] * 1024,
145 'VMPeak': memory_usage['VmHWM'] * 1024}
147 def GetIOStats(self, pid):
150 def GetChildPids(self, pid):
152 ps = self._GetPsOutput(['pid', 'name'])
153 for curr_pid, curr_name in ps:
154 if int(curr_pid) == pid:
156 for curr_pid, curr_name in ps:
157 if curr_name.startswith(name) and curr_name != name:
158 child_pids.append(int(curr_pid))
162 def GetCommandLine(self, pid):
163 ps = self._GetPsOutput(['pid', 'name'])
164 for curr_pid, curr_name in ps:
165 if int(curr_pid) == pid:
167 raise exceptions.ProcessGoneException()
173 def GetOSVersionName(self):
174 return self._adb.GetBuildId()[0]
176 def CanFlushIndividualFilesFromSystemCache(self):
179 def FlushEntireSystemCache(self):
180 cache = cache_control.CacheControl(self._adb)
181 cache.DropRamCaches()
183 def FlushSystemCacheForDirectory(self, directory, ignoring=None):
184 raise NotImplementedError()
186 def LaunchApplication(
187 self, application, parameters=None, elevate_privilege=False):
188 if application in _HOST_APPLICATIONS:
189 self._host_platform_backend.LaunchApplication(
190 application, parameters, elevate_privilege=elevate_privilege)
192 if elevate_privilege:
193 raise NotImplementedError("elevate_privilege isn't supported on android.")
196 self._adb.RunShellCommand('am start ' + parameters + ' ' + application)
198 def IsApplicationRunning(self, application):
199 if application in _HOST_APPLICATIONS:
200 return self._host_platform_backend.IsApplicationRunning(application)
201 return len(self._adb.ExtractPid(application)) > 0
203 def CanLaunchApplication(self, application):
204 if application in _HOST_APPLICATIONS:
205 return self._host_platform_backend.CanLaunchApplication(application)
208 def InstallApplication(self, application):
209 if application in _HOST_APPLICATIONS:
210 self._host_platform_backend.InstallApplication(application)
212 raise NotImplementedError(
213 'Please teach Telemetry how to install ' + application)
216 def CanCaptureVideo(self):
217 return self.GetOSVersionName() >= 'K'
219 def StartVideoCapture(self, min_bitrate_mbps):
220 min_bitrate_mbps = max(min_bitrate_mbps, 0.1)
221 if min_bitrate_mbps > 100:
222 raise ValueError('Android video capture cannot capture at %dmbps. '
223 'Max capture rate is 100mbps.' % min_bitrate_mbps)
224 self._video_output = tempfile.mkstemp()[1]
225 if self._video_recorder:
226 self._video_recorder.Stop()
227 self._video_recorder = screenshot.VideoRecorder(
228 self._adb, self._video_output, megabits_per_second=min_bitrate_mbps)
229 self._video_recorder.Start()
230 util.WaitFor(self._video_recorder.IsStarted, 5)
232 def StopVideoCapture(self):
233 assert self._video_recorder, 'Must start video capture first'
234 self._video_recorder.Stop()
235 self._video_output = self._video_recorder.Pull()
236 self._video_recorder = None
237 for frame in self._FramesFromMp4(self._video_output):
240 def CanMonitorPowerAsync(self):
241 return self._powermonitor.CanMonitorPowerAsync()
243 def StartMonitoringPowerAsync(self):
244 self._powermonitor.StartMonitoringPowerAsync()
246 def StopMonitoringPowerAsync(self):
247 return self._powermonitor.StopMonitoringPowerAsync()
249 def _FramesFromMp4(self, mp4_file):
250 if not self.CanLaunchApplication('avconv'):
251 self.InstallApplication('avconv')
253 def GetDimensions(video):
254 proc = subprocess.Popen(['avconv', '-i', video], stderr=subprocess.PIPE)
257 for line in proc.stderr.readlines():
260 dimensions = line.split(',')[2]
261 dimensions = map(int, dimensions.split()[0].split('x'))
264 assert dimensions, ('Failed to determine video dimensions. output=%s' %
268 def GetFrameTimestampMs(stderr):
269 """Returns the frame timestamp in integer milliseconds from the dump log.
271 The expected line format is:
272 ' dts=1.715 pts=1.715\n'
274 We have to be careful to only read a single timestamp per call to avoid
275 deadlock because avconv interleaves its writes to stdout and stderr.
280 while next_char != '\n':
281 next_char = stderr.read(1)
284 return int(1000 * float(line.split('=')[-1]))
286 dimensions = GetDimensions(mp4_file)
287 frame_length = dimensions[0] * dimensions[1] * 3
288 frame_data = bytearray(frame_length)
290 # Use rawvideo so that we don't need any external library to parse frames.
291 proc = subprocess.Popen(['avconv', '-i', mp4_file, '-vcodec',
292 'rawvideo', '-pix_fmt', 'rgb24', '-dump',
293 '-loglevel', 'debug', '-f', 'rawvideo', '-'],
294 stderr=subprocess.PIPE, stdout=subprocess.PIPE)
296 num_read = proc.stdout.readinto(frame_data)
299 assert num_read == len(frame_data), 'Unexpected frame size: %d' % num_read
300 yield (GetFrameTimestampMs(proc.stderr),
301 bitmap.Bitmap(3, dimensions[0], dimensions[1], frame_data))
303 def _GetFileContents(self, fname):
304 if not self._can_access_protected_file_contents:
305 logging.warning('%s cannot be retrieved on non-rooted device.' % fname)
308 self._adb.GetProtectedFileContents(fname, log_result=False))
310 def _GetPsOutput(self, columns, pid=None):
311 assert columns == ['pid', 'name'] or columns == ['pid'], \
312 'Only know how to return pid and name. Requested: ' + columns
315 command += ' -p %d' % pid
316 ps = self._adb.RunShellCommand(command, log_result=False)[1:]
322 if columns == ['pid', 'name']:
323 output.append([curr_pid, curr_name])
325 output.append([curr_pid])