blob: a0e2642300bb830f32d885b9c6e3976c5cec26e2 [file] [log] [blame]
# Copyright (c) 2013 The Chromium Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
import logging
import subprocess
import tempfile
from telemetry import decorators
from telemetry.core import bitmap
from telemetry.core import exceptions
from telemetry.core import platform
from telemetry.core import util
from telemetry.core.platform import proc_supporting_platform_backend
from telemetry.core.platform.power_monitor import android_ds2784_power_monitor
from telemetry.core.platform.power_monitor import android_dumpsys_power_monitor
from telemetry.core.platform.power_monitor import android_temperature_monitor
from telemetry.core.platform.power_monitor import monsoon_power_monitor
from telemetry.core.platform.power_monitor import power_monitor_controller
from telemetry.core.platform.profiler import android_prebuilt_profiler_helper
# Get build/android scripts into our path.
util.AddDirToPythonPath(util.GetChromiumSrcDir(), 'build', 'android')
from pylib import screenshot # pylint: disable=F0401
from pylib.perf import cache_control # pylint: disable=F0401
from pylib.perf import perf_control # pylint: disable=F0401
from pylib.perf import thermal_throttle # pylint: disable=F0401
try:
from pylib.perf import surface_stats_collector # pylint: disable=F0401
except Exception:
surface_stats_collector = None
_HOST_APPLICATIONS = [
'avconv',
'ipfw',
]
class AndroidPlatformBackend(
proc_supporting_platform_backend.ProcSupportingPlatformBackend):
def __init__(self, device, no_performance_mode):
super(AndroidPlatformBackend, self).__init__()
self._device = device
self._surface_stats_collector = None
self._perf_tests_setup = perf_control.PerfControl(self._device)
self._thermal_throttle = thermal_throttle.ThermalThrottle(self._device)
self._no_performance_mode = no_performance_mode
self._raw_display_frame_rate_measurements = []
self._can_access_protected_file_contents = \
self._device.old_interface.CanAccessProtectedFileContents()
power_controller = power_monitor_controller.PowerMonitorController([
monsoon_power_monitor.MonsoonPowerMonitor(),
android_ds2784_power_monitor.DS2784PowerMonitor(device),
android_dumpsys_power_monitor.DumpsysPowerMonitor(device),
])
self._powermonitor = android_temperature_monitor.AndroidTemperatureMonitor(
power_controller, device)
self._video_recorder = None
self._video_output = None
if self._no_performance_mode:
logging.warning('CPU governor will not be set!')
def IsRawDisplayFrameRateSupported(self):
return True
def StartRawDisplayFrameRateMeasurement(self):
assert not self._surface_stats_collector
# Clear any leftover data from previous timed out tests
self._raw_display_frame_rate_measurements = []
self._surface_stats_collector = \
surface_stats_collector.SurfaceStatsCollector(self._device)
self._surface_stats_collector.Start()
def StopRawDisplayFrameRateMeasurement(self):
if not self._surface_stats_collector:
return
self._surface_stats_collector.Stop()
for r in self._surface_stats_collector.GetResults():
self._raw_display_frame_rate_measurements.append(
platform.Platform.RawDisplayFrameRateMeasurement(
r.name, r.value, r.unit))
self._surface_stats_collector = None
def GetRawDisplayFrameRateMeasurements(self):
ret = self._raw_display_frame_rate_measurements
self._raw_display_frame_rate_measurements = []
return ret
def SetFullPerformanceModeEnabled(self, enabled):
if self._no_performance_mode:
return
if enabled:
self._perf_tests_setup.SetHighPerfMode()
else:
self._perf_tests_setup.SetDefaultPerfMode()
def CanMonitorThermalThrottling(self):
return True
def IsThermallyThrottled(self):
return self._thermal_throttle.IsThrottled()
def HasBeenThermallyThrottled(self):
return self._thermal_throttle.HasBeenThrottled()
def GetCpuStats(self, pid):
if not self._can_access_protected_file_contents:
logging.warning('CPU stats cannot be retrieved on non-rooted device.')
return {}
return super(AndroidPlatformBackend, self).GetCpuStats(pid)
def GetCpuTimestamp(self):
if not self._can_access_protected_file_contents:
logging.warning('CPU timestamp cannot be retrieved on non-rooted device.')
return {}
return super(AndroidPlatformBackend, self).GetCpuTimestamp()
def PurgeUnpinnedMemory(self):
"""Purges the unpinned ashmem memory for the whole system.
This can be used to make memory measurements more stable in particular.
"""
if not android_prebuilt_profiler_helper.InstallOnDevice(
self._device, 'purge_ashmem'):
raise Exception('Error installing purge_ashmem.')
(status, output) = self._device.old_interface.GetAndroidToolStatusAndOutput(
android_prebuilt_profiler_helper.GetDevicePath('purge_ashmem'),
log_result=True)
if status != 0:
raise Exception('Error while purging ashmem: ' + '\n'.join(output))
def GetMemoryStats(self, pid):
memory_usage = self._device.old_interface.GetMemoryUsageForPid(pid)
return {'ProportionalSetSize': memory_usage['Pss'] * 1024,
'SharedDirty': memory_usage['Shared_Dirty'] * 1024,
'PrivateDirty': memory_usage['Private_Dirty'] * 1024,
'VMPeak': memory_usage['VmHWM'] * 1024}
def GetIOStats(self, pid):
return {}
def GetChildPids(self, pid):
child_pids = []
ps = self._GetPsOutput(['pid', 'name'])
for curr_pid, curr_name in ps:
if int(curr_pid) == pid:
name = curr_name
for curr_pid, curr_name in ps:
if curr_name.startswith(name) and curr_name != name:
child_pids.append(int(curr_pid))
break
return child_pids
@decorators.Cache
def GetCommandLine(self, pid):
ps = self._GetPsOutput(['pid', 'name'], pid)
if not ps:
raise exceptions.ProcessGoneException()
return ps[0][1]
def GetOSName(self):
return 'android'
@decorators.Cache
def GetOSVersionName(self):
return self._device.old_interface.GetBuildId()[0]
def CanFlushIndividualFilesFromSystemCache(self):
return False
def FlushEntireSystemCache(self):
cache = cache_control.CacheControl(self._device)
cache.DropRamCaches()
def FlushSystemCacheForDirectory(self, directory, ignoring=None):
raise NotImplementedError()
def FlushDnsCache(self):
self._device.old_interface.RunShellCommandWithSU(
'ndc resolver flushdefaultif')
def LaunchApplication(
self, application, parameters=None, elevate_privilege=False):
if application in _HOST_APPLICATIONS:
platform.GetHostPlatform().LaunchApplication(
application, parameters, elevate_privilege=elevate_privilege)
return
if elevate_privilege:
raise NotImplementedError("elevate_privilege isn't supported on android.")
if not parameters:
parameters = ''
self._device.old_interface.RunShellCommand(
'am start ' + parameters + ' ' + application)
def IsApplicationRunning(self, application):
if application in _HOST_APPLICATIONS:
return platform.GetHostPlatform().IsApplicationRunning(application)
return len(self._device.old_interface.ExtractPid(application)) > 0
def CanLaunchApplication(self, application):
if application in _HOST_APPLICATIONS:
return platform.GetHostPlatform().CanLaunchApplication(application)
return True
def InstallApplication(self, application):
if application in _HOST_APPLICATIONS:
platform.GetHostPlatform().InstallApplication(application)
return
raise NotImplementedError(
'Please teach Telemetry how to install ' + application)
@decorators.Cache
def CanCaptureVideo(self):
return self.GetOSVersionName() >= 'K'
def StartVideoCapture(self, min_bitrate_mbps):
min_bitrate_mbps = max(min_bitrate_mbps, 0.1)
if min_bitrate_mbps > 100:
raise ValueError('Android video capture cannot capture at %dmbps. '
'Max capture rate is 100mbps.' % min_bitrate_mbps)
self._video_output = tempfile.mkstemp()[1]
if self.is_video_capture_running:
self._video_recorder.Stop()
self._video_recorder = screenshot.VideoRecorder(
self._device, self._video_output, megabits_per_second=min_bitrate_mbps)
self._video_recorder.Start()
util.WaitFor(self._video_recorder.IsStarted, 5)
@property
def is_video_capture_running(self):
return self._video_recorder is not None
def StopVideoCapture(self):
assert self.is_video_capture_running, 'Must start video capture first'
self._video_recorder.Stop()
self._video_output = self._video_recorder.Pull()
self._video_recorder = None
for frame in self._FramesFromMp4(self._video_output):
yield frame
def CanMonitorPower(self):
return self._powermonitor.CanMonitorPower()
def StartMonitoringPower(self, browser):
self._powermonitor.StartMonitoringPower(browser)
def StopMonitoringPower(self):
return self._powermonitor.StopMonitoringPower()
def _FramesFromMp4(self, mp4_file):
if not self.CanLaunchApplication('avconv'):
self.InstallApplication('avconv')
def GetDimensions(video):
proc = subprocess.Popen(['avconv', '-i', video], stderr=subprocess.PIPE)
dimensions = None
output = ''
for line in proc.stderr.readlines():
output += line
if 'Video:' in line:
dimensions = line.split(',')[2]
dimensions = map(int, dimensions.split()[0].split('x'))
break
proc.communicate()
assert dimensions, ('Failed to determine video dimensions. output=%s' %
output)
return dimensions
def GetFrameTimestampMs(stderr):
"""Returns the frame timestamp in integer milliseconds from the dump log.
The expected line format is:
' dts=1.715 pts=1.715\n'
We have to be careful to only read a single timestamp per call to avoid
deadlock because avconv interleaves its writes to stdout and stderr.
"""
while True:
line = ''
next_char = ''
while next_char != '\n':
next_char = stderr.read(1)
line += next_char
if 'pts=' in line:
return int(1000 * float(line.split('=')[-1]))
dimensions = GetDimensions(mp4_file)
frame_length = dimensions[0] * dimensions[1] * 3
frame_data = bytearray(frame_length)
# Use rawvideo so that we don't need any external library to parse frames.
proc = subprocess.Popen(['avconv', '-i', mp4_file, '-vcodec',
'rawvideo', '-pix_fmt', 'rgb24', '-dump',
'-loglevel', 'debug', '-f', 'rawvideo', '-'],
stderr=subprocess.PIPE, stdout=subprocess.PIPE)
while True:
num_read = proc.stdout.readinto(frame_data)
if not num_read:
raise StopIteration
assert num_read == len(frame_data), 'Unexpected frame size: %d' % num_read
yield (GetFrameTimestampMs(proc.stderr),
bitmap.Bitmap(3, dimensions[0], dimensions[1], frame_data))
def _GetFileContents(self, fname):
if not self._can_access_protected_file_contents:
logging.warning('%s cannot be retrieved on non-rooted device.' % fname)
return ''
return '\n'.join(
self._device.old_interface.GetProtectedFileContents(fname))
def _GetPsOutput(self, columns, pid=None):
assert columns == ['pid', 'name'] or columns == ['pid'], \
'Only know how to return pid and name. Requested: ' + columns
command = 'ps'
if pid:
command += ' -p %d' % pid
ps = self._device.old_interface.RunShellCommand(
command, log_result=False)[1:]
output = []
for line in ps:
data = line.split()
curr_pid = data[1]
curr_name = data[-1]
if columns == ['pid', 'name']:
output.append([curr_pid, curr_name])
else:
output.append([curr_pid])
return output