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 5import logging 6import tempfile 7 8from telemetry import decorators 9from telemetry.core import exceptions 10from telemetry.core import platform 11from telemetry.core import util 12from telemetry.core import video 13from telemetry.core.backends import adb_commands 14from telemetry.core.platform import android_device 15from telemetry.core.platform import linux_based_platform_backend 16from telemetry.core.platform.power_monitor import android_ds2784_power_monitor 17from telemetry.core.platform.power_monitor import android_dumpsys_power_monitor 18from telemetry.core.platform.power_monitor import android_temperature_monitor 19from telemetry.core.platform.power_monitor import monsoon_power_monitor 20from telemetry.core.platform.power_monitor import power_monitor_controller 21from telemetry.core.platform.profiler import android_prebuilt_profiler_helper 22 23# Get build/android scripts into our path. 24util.AddDirToPythonPath(util.GetChromiumSrcDir(), 'build', 'android') 25from pylib import screenshot # pylint: disable=F0401 26from pylib.perf import cache_control # pylint: disable=F0401 27from pylib.perf import perf_control # pylint: disable=F0401 28from pylib.perf import thermal_throttle # pylint: disable=F0401 29 30try: 31 from pylib.perf import surface_stats_collector # pylint: disable=F0401 32except Exception: 33 surface_stats_collector = None 34 35 36class AndroidPlatformBackend( 37 linux_based_platform_backend.LinuxBasedPlatformBackend): 38 def __init__(self, device): 39 assert device, ( 40 'AndroidPlatformBackend can only be initialized from remote device') 41 super(AndroidPlatformBackend, self).__init__(device) 42 self._adb = adb_commands.AdbCommands(device=device.device_id) 43 installed_prebuilt_tools = adb_commands.SetupPrebuiltTools(self._adb) 44 if not installed_prebuilt_tools: 45 logging.error( 46 '%s detected, however prebuilt android tools could not ' 47 'be used. To run on Android you must build them first:\n' 48 ' $ ninja -C out/Release android_tools' % device.name) 49 raise exceptions.PlatformError() 50 # Trying to root the device, if possible. 51 if not self._adb.IsRootEnabled(): 52 # Ignore result. 53 self._adb.EnableAdbRoot() 54 self._device = self._adb.device() 55 self._enable_performance_mode = device.enable_performance_mode 56 self._surface_stats_collector = None 57 self._perf_tests_setup = perf_control.PerfControl(self._device) 58 self._thermal_throttle = thermal_throttle.ThermalThrottle(self._device) 59 self._raw_display_frame_rate_measurements = [] 60 self._can_access_protected_file_contents = \ 61 self._device.old_interface.CanAccessProtectedFileContents() 62 power_controller = power_monitor_controller.PowerMonitorController([ 63 monsoon_power_monitor.MonsoonPowerMonitor(self._device, self), 64 android_ds2784_power_monitor.DS2784PowerMonitor(self._device, self), 65 android_dumpsys_power_monitor.DumpsysPowerMonitor(self._device, self), 66 ]) 67 self._power_monitor = android_temperature_monitor.AndroidTemperatureMonitor( 68 power_controller, self._device) 69 self._video_recorder = None 70 self._installed_applications = None 71 if self._enable_performance_mode: 72 logging.warning('CPU governor will not be set!') 73 74 @classmethod 75 def SupportsDevice(cls, device): 76 return isinstance(device, android_device.AndroidDevice) 77 78 @property 79 def adb(self): 80 return self._adb 81 82 def IsRawDisplayFrameRateSupported(self): 83 return True 84 85 def StartRawDisplayFrameRateMeasurement(self): 86 assert not self._surface_stats_collector 87 # Clear any leftover data from previous timed out tests 88 self._raw_display_frame_rate_measurements = [] 89 self._surface_stats_collector = \ 90 surface_stats_collector.SurfaceStatsCollector(self._device) 91 self._surface_stats_collector.Start() 92 93 def StopRawDisplayFrameRateMeasurement(self): 94 if not self._surface_stats_collector: 95 return 96 97 self._surface_stats_collector.Stop() 98 for r in self._surface_stats_collector.GetResults(): 99 self._raw_display_frame_rate_measurements.append( 100 platform.Platform.RawDisplayFrameRateMeasurement( 101 r.name, r.value, r.unit)) 102 103 self._surface_stats_collector = None 104 105 def GetRawDisplayFrameRateMeasurements(self): 106 ret = self._raw_display_frame_rate_measurements 107 self._raw_display_frame_rate_measurements = [] 108 return ret 109 110 def SetFullPerformanceModeEnabled(self, enabled): 111 if not self._enable_performance_mode: 112 return 113 if enabled: 114 self._perf_tests_setup.SetHighPerfMode() 115 else: 116 self._perf_tests_setup.SetDefaultPerfMode() 117 118 def CanMonitorThermalThrottling(self): 119 return True 120 121 def IsThermallyThrottled(self): 122 return self._thermal_throttle.IsThrottled() 123 124 def HasBeenThermallyThrottled(self): 125 return self._thermal_throttle.HasBeenThrottled() 126 127 def GetCpuStats(self, pid): 128 if not self._can_access_protected_file_contents: 129 logging.warning('CPU stats cannot be retrieved on non-rooted device.') 130 return {} 131 return super(AndroidPlatformBackend, self).GetCpuStats(pid) 132 133 def GetCpuTimestamp(self): 134 if not self._can_access_protected_file_contents: 135 logging.warning('CPU timestamp cannot be retrieved on non-rooted device.') 136 return {} 137 return super(AndroidPlatformBackend, self).GetCpuTimestamp() 138 139 def PurgeUnpinnedMemory(self): 140 """Purges the unpinned ashmem memory for the whole system. 141 142 This can be used to make memory measurements more stable. Requires root. 143 """ 144 if not self._can_access_protected_file_contents: 145 logging.warning('Cannot run purge_ashmem. Requires a rooted device.') 146 return 147 148 if not android_prebuilt_profiler_helper.InstallOnDevice( 149 self._device, 'purge_ashmem'): 150 raise Exception('Error installing purge_ashmem.') 151 (status, output) = self._device.old_interface.GetAndroidToolStatusAndOutput( 152 android_prebuilt_profiler_helper.GetDevicePath('purge_ashmem'), 153 log_result=True) 154 if status != 0: 155 raise Exception('Error while purging ashmem: ' + '\n'.join(output)) 156 157 def GetMemoryStats(self, pid): 158 memory_usage = self._device.GetMemoryUsageForPid(pid) 159 if not memory_usage: 160 return {} 161 return {'ProportionalSetSize': memory_usage['Pss'] * 1024, 162 'SharedDirty': memory_usage['Shared_Dirty'] * 1024, 163 'PrivateDirty': memory_usage['Private_Dirty'] * 1024, 164 'VMPeak': memory_usage['VmHWM'] * 1024} 165 166 def GetIOStats(self, pid): 167 return {} 168 169 def GetChildPids(self, pid): 170 child_pids = [] 171 ps = self.GetPsOutput(['pid', 'name']) 172 for curr_pid, curr_name in ps: 173 if int(curr_pid) == pid: 174 name = curr_name 175 for curr_pid, curr_name in ps: 176 if curr_name.startswith(name) and curr_name != name: 177 child_pids.append(int(curr_pid)) 178 break 179 return child_pids 180 181 @decorators.Cache 182 def GetCommandLine(self, pid): 183 ps = self.GetPsOutput(['pid', 'name'], pid) 184 if not ps: 185 raise exceptions.ProcessGoneException() 186 return ps[0][1] 187 188 def GetOSName(self): 189 return 'android' 190 191 @decorators.Cache 192 def GetOSVersionName(self): 193 return self._device.GetProp('ro.build.id')[0] 194 195 def CanFlushIndividualFilesFromSystemCache(self): 196 return False 197 198 def FlushEntireSystemCache(self): 199 cache = cache_control.CacheControl(self._device) 200 cache.DropRamCaches() 201 202 def FlushSystemCacheForDirectory(self, directory, ignoring=None): 203 raise NotImplementedError() 204 205 def FlushDnsCache(self): 206 self._device.RunShellCommand('ndc resolver flushdefaultif', as_root=True) 207 208 def StopApplication(self, application): 209 """Stop the given |application|. 210 Args: 211 application: The full package name string of the application to launch. 212 """ 213 self._adb.device().ForceStop(application) 214 215 def LaunchApplication( 216 self, application, parameters=None, elevate_privilege=False): 217 """Launches the given |application| with a list of |parameters| on the OS. 218 219 Args: 220 application: The full package name string of the application to launch. 221 parameters: A list of parameters to be passed to the ActivityManager. 222 elevate_privilege: Currently unimplemented on Android. 223 """ 224 if elevate_privilege: 225 raise NotImplementedError("elevate_privilege isn't supported on android.") 226 if not parameters: 227 parameters = '' 228 result_lines = self._device.RunShellCommand('am start %s %s' % 229 (parameters, application)) 230 for line in result_lines: 231 if line.startswith('Error: '): 232 raise ValueError('Failed to start "%s" with error\n %s' % 233 (application, line)) 234 235 def IsApplicationRunning(self, application): 236 return len(self._device.GetPids(application)) > 0 237 238 def CanLaunchApplication(self, application): 239 if not self._installed_applications: 240 self._installed_applications = self._device.RunShellCommand( 241 'pm list packages') 242 return 'package:' + application in self._installed_applications 243 244 def InstallApplication(self, application): 245 self._installed_applications = None 246 self._device.Install(application) 247 248 @decorators.Cache 249 def CanCaptureVideo(self): 250 return self.GetOSVersionName() >= 'K' 251 252 def StartVideoCapture(self, min_bitrate_mbps): 253 """Starts the video capture at specified bitrate.""" 254 min_bitrate_mbps = max(min_bitrate_mbps, 0.1) 255 if min_bitrate_mbps > 100: 256 raise ValueError('Android video capture cannot capture at %dmbps. ' 257 'Max capture rate is 100mbps.' % min_bitrate_mbps) 258 if self.is_video_capture_running: 259 self._video_recorder.Stop() 260 self._video_recorder = screenshot.VideoRecorder( 261 self._device, megabits_per_second=min_bitrate_mbps) 262 self._video_recorder.Start() 263 util.WaitFor(self._video_recorder.IsStarted, 5) 264 265 @property 266 def is_video_capture_running(self): 267 return self._video_recorder is not None 268 269 def StopVideoCapture(self): 270 assert self.is_video_capture_running, 'Must start video capture first' 271 self._video_recorder.Stop() 272 video_file_obj = tempfile.NamedTemporaryFile() 273 self._video_recorder.Pull(video_file_obj.name) 274 self._video_recorder = None 275 276 return video.Video(video_file_obj) 277 278 def CanMonitorPower(self): 279 return self._power_monitor.CanMonitorPower() 280 281 def StartMonitoringPower(self, browser): 282 self._power_monitor.StartMonitoringPower(browser) 283 284 def StopMonitoringPower(self): 285 return self._power_monitor.StopMonitoringPower() 286 287 def GetFileContents(self, fname): 288 if not self._can_access_protected_file_contents: 289 logging.warning('%s cannot be retrieved on non-rooted device.' % fname) 290 return '' 291 return '\n'.join(self._device.ReadFile(fname, as_root=True)) 292 293 def GetPsOutput(self, columns, pid=None): 294 assert columns == ['pid', 'name'] or columns == ['pid'], \ 295 'Only know how to return pid and name. Requested: ' + columns 296 command = 'ps' 297 if pid: 298 command += ' -p %d' % pid 299 ps = self._device.RunShellCommand(command)[1:] 300 output = [] 301 for line in ps: 302 data = line.split() 303 curr_pid = data[1] 304 curr_name = data[-1] 305 if columns == ['pid', 'name']: 306 output.append([curr_pid, curr_name]) 307 else: 308 output.append([curr_pid]) 309 return output 310 311 def RunCommand(self, command): 312 return '\n'.join(self._device.RunShellCommand(command)) 313 314 @staticmethod 315 def ParseCStateSample(sample): 316 sample_stats = {} 317 for cpu in sample: 318 values = sample[cpu].splitlines() 319 # Each state has three values after excluding the time value. 320 num_states = (len(values) - 1) / 3 321 names = values[:num_states] 322 times = values[num_states:2 * num_states] 323 cstates = {'C0': int(values[-1]) * 10 ** 6} 324 for i, state in enumerate(names): 325 if state == 'C0': 326 # The Exynos cpuidle driver for the Nexus 10 uses the name 'C0' for 327 # its WFI state. 328 # TODO(tmandel): We should verify that no other Android device 329 # actually reports time in C0 causing this to report active time as 330 # idle time. 331 state = 'WFI' 332 cstates[state] = int(times[i]) 333 cstates['C0'] -= int(times[i]) 334 sample_stats[cpu] = cstates 335 return sample_stats 336