android_platform_backend.py revision 03b57e008b61dfcb1fbad3aea950ae0e001748b0
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.platform import proc_supporting_platform_backend
14from telemetry.core.platform.power_monitor import android_ds2784_power_monitor
15from telemetry.core.platform.power_monitor import android_dumpsys_power_monitor
16from telemetry.core.platform.power_monitor import android_temperature_monitor
17from telemetry.core.platform.power_monitor import monsoon_power_monitor
18from telemetry.core.platform.power_monitor import power_monitor_controller
19from telemetry.core.platform.profiler import android_prebuilt_profiler_helper
20
21# Get build/android scripts into our path.
22util.AddDirToPythonPath(util.GetChromiumSrcDir(), 'build', 'android')
23from pylib import screenshot  # pylint: disable=F0401
24from pylib.perf import cache_control  # pylint: disable=F0401
25from pylib.perf import perf_control  # pylint: disable=F0401
26from pylib.perf import thermal_throttle  # pylint: disable=F0401
27
28try:
29  from pylib.perf import surface_stats_collector  # pylint: disable=F0401
30except Exception:
31  surface_stats_collector = None
32
33
34_HOST_APPLICATIONS = [
35    'avconv',
36    'ipfw',
37    'perfhost',
38    ]
39
40
41class AndroidPlatformBackend(
42    proc_supporting_platform_backend.ProcSupportingPlatformBackend):
43  def __init__(self, device, no_performance_mode):
44    super(AndroidPlatformBackend, self).__init__()
45    self._device = device
46    self._surface_stats_collector = None
47    self._perf_tests_setup = perf_control.PerfControl(self._device)
48    self._thermal_throttle = thermal_throttle.ThermalThrottle(self._device)
49    self._no_performance_mode = no_performance_mode
50    self._raw_display_frame_rate_measurements = []
51    self._can_access_protected_file_contents = \
52        self._device.old_interface.CanAccessProtectedFileContents()
53    power_controller = power_monitor_controller.PowerMonitorController([
54        monsoon_power_monitor.MonsoonPowerMonitor(device),
55        android_ds2784_power_monitor.DS2784PowerMonitor(device),
56        android_dumpsys_power_monitor.DumpsysPowerMonitor(device),
57    ])
58    self._power_monitor = android_temperature_monitor.AndroidTemperatureMonitor(
59        power_controller, device)
60    self._video_recorder = None
61    if self._no_performance_mode:
62      logging.warning('CPU governor will not be set!')
63
64  def IsRawDisplayFrameRateSupported(self):
65    return True
66
67  def StartRawDisplayFrameRateMeasurement(self):
68    assert not self._surface_stats_collector
69    # Clear any leftover data from previous timed out tests
70    self._raw_display_frame_rate_measurements = []
71    self._surface_stats_collector = \
72        surface_stats_collector.SurfaceStatsCollector(self._device)
73    self._surface_stats_collector.Start()
74
75  def StopRawDisplayFrameRateMeasurement(self):
76    if not self._surface_stats_collector:
77      return
78
79    self._surface_stats_collector.Stop()
80    for r in self._surface_stats_collector.GetResults():
81      self._raw_display_frame_rate_measurements.append(
82          platform.Platform.RawDisplayFrameRateMeasurement(
83              r.name, r.value, r.unit))
84
85    self._surface_stats_collector = None
86
87  def GetRawDisplayFrameRateMeasurements(self):
88    ret = self._raw_display_frame_rate_measurements
89    self._raw_display_frame_rate_measurements = []
90    return ret
91
92  def SetFullPerformanceModeEnabled(self, enabled):
93    if self._no_performance_mode:
94      return
95    if enabled:
96      self._perf_tests_setup.SetHighPerfMode()
97    else:
98      self._perf_tests_setup.SetDefaultPerfMode()
99
100  def CanMonitorThermalThrottling(self):
101    return True
102
103  def IsThermallyThrottled(self):
104    return self._thermal_throttle.IsThrottled()
105
106  def HasBeenThermallyThrottled(self):
107    return self._thermal_throttle.HasBeenThrottled()
108
109  def GetCpuStats(self, pid):
110    if not self._can_access_protected_file_contents:
111      logging.warning('CPU stats cannot be retrieved on non-rooted device.')
112      return {}
113    return super(AndroidPlatformBackend, self).GetCpuStats(pid)
114
115  def GetCpuTimestamp(self):
116    if not self._can_access_protected_file_contents:
117      logging.warning('CPU timestamp cannot be retrieved on non-rooted device.')
118      return {}
119    return super(AndroidPlatformBackend, self).GetCpuTimestamp()
120
121  def PurgeUnpinnedMemory(self):
122    """Purges the unpinned ashmem memory for the whole system.
123
124    This can be used to make memory measurements more stable. Requires root.
125    """
126    if not self._can_access_protected_file_contents:
127      logging.warning('Cannot run purge_ashmem. Requires a rooted device.')
128      return
129
130    if not android_prebuilt_profiler_helper.InstallOnDevice(
131        self._device, 'purge_ashmem'):
132      raise Exception('Error installing purge_ashmem.')
133    (status, output) = self._device.old_interface.GetAndroidToolStatusAndOutput(
134        android_prebuilt_profiler_helper.GetDevicePath('purge_ashmem'),
135        log_result=True)
136    if status != 0:
137      raise Exception('Error while purging ashmem: ' + '\n'.join(output))
138
139  def GetMemoryStats(self, pid):
140    memory_usage = self._device.GetMemoryUsageForPid(pid)
141    if not memory_usage:
142      return {}
143    return {'ProportionalSetSize': memory_usage['Pss'] * 1024,
144            'SharedDirty': memory_usage['Shared_Dirty'] * 1024,
145            'PrivateDirty': memory_usage['Private_Dirty'] * 1024,
146            'VMPeak': memory_usage['VmHWM'] * 1024}
147
148  def GetIOStats(self, pid):
149    return {}
150
151  def GetChildPids(self, pid):
152    child_pids = []
153    ps = self._GetPsOutput(['pid', 'name'])
154    for curr_pid, curr_name in ps:
155      if int(curr_pid) == pid:
156        name = curr_name
157        for curr_pid, curr_name in ps:
158          if curr_name.startswith(name) and curr_name != name:
159            child_pids.append(int(curr_pid))
160        break
161    return child_pids
162
163  @decorators.Cache
164  def GetCommandLine(self, pid):
165    ps = self._GetPsOutput(['pid', 'name'], pid)
166    if not ps:
167      raise exceptions.ProcessGoneException()
168    return ps[0][1]
169
170  def GetOSName(self):
171    return 'android'
172
173  @decorators.Cache
174  def GetOSVersionName(self):
175    return self._device.GetProp('ro.build.id')[0]
176
177  def CanFlushIndividualFilesFromSystemCache(self):
178    return False
179
180  def FlushEntireSystemCache(self):
181    cache = cache_control.CacheControl(self._device)
182    cache.DropRamCaches()
183
184  def FlushSystemCacheForDirectory(self, directory, ignoring=None):
185    raise NotImplementedError()
186
187  def FlushDnsCache(self):
188    self._device.RunShellCommand('ndc resolver flushdefaultif', as_root=True)
189
190  def LaunchApplication(
191      self, application, parameters=None, elevate_privilege=False):
192    if application in _HOST_APPLICATIONS:
193      platform.GetHostPlatform().LaunchApplication(
194          application, parameters, elevate_privilege=elevate_privilege)
195      return
196    if elevate_privilege:
197      raise NotImplementedError("elevate_privilege isn't supported on android.")
198    if not parameters:
199      parameters = ''
200    self._device.RunShellCommand('am start ' + parameters + ' ' + application)
201
202  def IsApplicationRunning(self, application):
203    if application in _HOST_APPLICATIONS:
204      return platform.GetHostPlatform().IsApplicationRunning(application)
205    return len(self._device.GetPids(application)) > 0
206
207  def CanLaunchApplication(self, application):
208    if application in _HOST_APPLICATIONS:
209      return platform.GetHostPlatform().CanLaunchApplication(application)
210    return True
211
212  def InstallApplication(self, application):
213    if application in _HOST_APPLICATIONS:
214      platform.GetHostPlatform().InstallApplication(application)
215      return
216    raise NotImplementedError(
217        'Please teach Telemetry how to install ' + application)
218
219  @decorators.Cache
220  def CanCaptureVideo(self):
221    return self.GetOSVersionName() >= 'K'
222
223  def StartVideoCapture(self, min_bitrate_mbps):
224    """Starts the video capture at specified bitrate."""
225    min_bitrate_mbps = max(min_bitrate_mbps, 0.1)
226    if min_bitrate_mbps > 100:
227      raise ValueError('Android video capture cannot capture at %dmbps. '
228                       'Max capture rate is 100mbps.' % min_bitrate_mbps)
229    if self.is_video_capture_running:
230      self._video_recorder.Stop()
231    self._video_recorder = screenshot.VideoRecorder(
232        self._device, megabits_per_second=min_bitrate_mbps)
233    self._video_recorder.Start()
234    util.WaitFor(self._video_recorder.IsStarted, 5)
235
236  @property
237  def is_video_capture_running(self):
238    return self._video_recorder is not None
239
240  def StopVideoCapture(self):
241    assert self.is_video_capture_running, 'Must start video capture first'
242    self._video_recorder.Stop()
243    video_file_obj = tempfile.NamedTemporaryFile()
244    self._video_recorder.Pull(video_file_obj.name)
245    self._video_recorder = None
246
247    return video.Video(video_file_obj)
248
249  def CanMonitorPower(self):
250    return self._power_monitor.CanMonitorPower()
251
252  def StartMonitoringPower(self, browser):
253    self._power_monitor.StartMonitoringPower(browser)
254
255  def StopMonitoringPower(self):
256    return self._power_monitor.StopMonitoringPower()
257
258  def _GetFileContents(self, fname):
259    if not self._can_access_protected_file_contents:
260      logging.warning('%s cannot be retrieved on non-rooted device.' % fname)
261      return ''
262    return '\n'.join(self._device.ReadFile(fname, as_root=True))
263
264  def _GetPsOutput(self, columns, pid=None):
265    assert columns == ['pid', 'name'] or columns == ['pid'], \
266        'Only know how to return pid and name. Requested: ' + columns
267    command = 'ps'
268    if pid:
269      command += ' -p %d' % pid
270    ps = self._device.RunShellCommand(command)[1:]
271    output = []
272    for line in ps:
273      data = line.split()
274      curr_pid = data[1]
275      curr_name = data[-1]
276      if columns == ['pid', 'name']:
277        output.append([curr_pid, curr_name])
278      else:
279        output.append([curr_pid])
280    return output
281