Home | History | Annotate | Download | only in perf
      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 atexit
      6 import logging
      7 import re
      8 
      9 from devil.android import device_errors
     10 
     11 logger = logging.getLogger(__name__)
     12 
     13 
     14 class PerfControl(object):
     15   """Provides methods for setting the performance mode of a device."""
     16 
     17   _AVAILABLE_GOVERNORS_REL_PATH = 'cpufreq/scaling_available_governors'
     18   _CPU_FILE_PATTERN = re.compile(r'^cpu\d+$')
     19   _CPU_PATH = '/sys/devices/system/cpu'
     20   _KERNEL_MAX = '/sys/devices/system/cpu/kernel_max'
     21 
     22   def __init__(self, device):
     23     self._device = device
     24     self._cpu_files = [
     25         filename
     26         for filename in self._device.ListDirectory(self._CPU_PATH, as_root=True)
     27         if self._CPU_FILE_PATTERN.match(filename)]
     28     assert self._cpu_files, 'Failed to detect CPUs.'
     29     self._cpu_file_list = ' '.join(self._cpu_files)
     30     logger.info('CPUs found: %s', self._cpu_file_list)
     31 
     32     self._have_mpdecision = self._device.FileExists('/system/bin/mpdecision')
     33 
     34     raw = self._ReadEachCpuFile(self._AVAILABLE_GOVERNORS_REL_PATH)
     35     self._available_governors = [
     36         (cpu, raw_governors.strip().split() if not exit_code else None)
     37         for cpu, raw_governors, exit_code in raw]
     38 
     39   def SetHighPerfMode(self):
     40     """Sets the highest stable performance mode for the device."""
     41     try:
     42       self._device.EnableRoot()
     43     except device_errors.CommandFailedError:
     44       message = 'Need root for performance mode. Results may be NOISY!!'
     45       logger.warning(message)
     46       # Add an additional warning at exit, such that it's clear that any results
     47       # may be different/noisy (due to the lack of intended performance mode).
     48       atexit.register(logger.warning, message)
     49       return
     50 
     51     product_model = self._device.product_model
     52     # TODO(epenner): Enable on all devices (http://crbug.com/383566)
     53     if 'Nexus 4' == product_model:
     54       self._ForceAllCpusOnline(True)
     55       if not self._AllCpusAreOnline():
     56         logger.warning('Failed to force CPUs online. Results may be NOISY!')
     57       self.SetScalingGovernor('performance')
     58     elif 'Nexus 5' == product_model:
     59       self._ForceAllCpusOnline(True)
     60       if not self._AllCpusAreOnline():
     61         logger.warning('Failed to force CPUs online. Results may be NOISY!')
     62       self.SetScalingGovernor('performance')
     63       self._SetScalingMaxFreq(1190400)
     64       self._SetMaxGpuClock(200000000)
     65     else:
     66       self.SetScalingGovernor('performance')
     67 
     68   def SetPerfProfilingMode(self):
     69     """Enables all cores for reliable perf profiling."""
     70     self._ForceAllCpusOnline(True)
     71     self.SetScalingGovernor('performance')
     72     if not self._AllCpusAreOnline():
     73       if not self._device.HasRoot():
     74         raise RuntimeError('Need root to force CPUs online.')
     75       raise RuntimeError('Failed to force CPUs online.')
     76 
     77   def SetDefaultPerfMode(self):
     78     """Sets the performance mode for the device to its default mode."""
     79     if not self._device.HasRoot():
     80       return
     81     product_model = self._device.product_model
     82     if 'Nexus 5' == product_model:
     83       if self._AllCpusAreOnline():
     84         self._SetScalingMaxFreq(2265600)
     85         self._SetMaxGpuClock(450000000)
     86 
     87     governor_mode = {
     88         'GT-I9300': 'pegasusq',
     89         'Galaxy Nexus': 'interactive',
     90         'Nexus 4': 'ondemand',
     91         'Nexus 5': 'ondemand',
     92         'Nexus 7': 'interactive',
     93         'Nexus 10': 'interactive'
     94     }.get(product_model, 'ondemand')
     95     self.SetScalingGovernor(governor_mode)
     96     self._ForceAllCpusOnline(False)
     97 
     98   def GetCpuInfo(self):
     99     online = (output.rstrip() == '1' and status == 0
    100               for (_, output, status) in self._ForEachCpu('cat "$CPU/online"'))
    101     governor = (output.rstrip() if status == 0 else None
    102                 for (_, output, status)
    103                 in self._ForEachCpu('cat "$CPU/cpufreq/scaling_governor"'))
    104     return zip(self._cpu_files, online, governor)
    105 
    106   def _ForEachCpu(self, cmd):
    107     script = '; '.join([
    108         'for CPU in %s' % self._cpu_file_list,
    109         'do %s' % cmd,
    110         'echo -n "%~%$?%~%"',
    111         'done'
    112     ])
    113     output = self._device.RunShellCommand(
    114         script, cwd=self._CPU_PATH, check_return=True, as_root=True, shell=True)
    115     output = '\n'.join(output).split('%~%')
    116     return zip(self._cpu_files, output[0::2], (int(c) for c in output[1::2]))
    117 
    118   def _WriteEachCpuFile(self, path, value):
    119     self._ConditionallyWriteEachCpuFile(path, value, condition='true')
    120 
    121   def _ConditionallyWriteEachCpuFile(self, path, value, condition):
    122     template = (
    123         '{condition} && test -e "$CPU/{path}" && echo {value} > "$CPU/{path}"')
    124     results = self._ForEachCpu(
    125         template.format(path=path, value=value, condition=condition))
    126     cpus = ' '.join(cpu for (cpu, _, status) in results if status == 0)
    127     if cpus:
    128       logger.info('Successfully set %s to %r on: %s', path, value, cpus)
    129     else:
    130       logger.warning('Failed to set %s to %r on any cpus', path, value)
    131 
    132   def _ReadEachCpuFile(self, path):
    133     return self._ForEachCpu(
    134         'cat "$CPU/{path}"'.format(path=path))
    135 
    136   def SetScalingGovernor(self, value):
    137     """Sets the scaling governor to the given value on all possible CPUs.
    138 
    139     This does not attempt to set a governor to a value not reported as available
    140     on the corresponding CPU.
    141 
    142     Args:
    143       value: [string] The new governor value.
    144     """
    145     condition = 'test -e "{path}" && grep -q {value} {path}'.format(
    146         path=('${CPU}/%s' % self._AVAILABLE_GOVERNORS_REL_PATH),
    147         value=value)
    148     self._ConditionallyWriteEachCpuFile(
    149         'cpufreq/scaling_governor', value, condition)
    150 
    151   def GetScalingGovernor(self):
    152     """Gets the currently set governor for each CPU.
    153 
    154     Returns:
    155       An iterable of 2-tuples, each containing the cpu and the current
    156       governor.
    157     """
    158     raw = self._ReadEachCpuFile('cpufreq/scaling_governor')
    159     return [
    160         (cpu, raw_governor.strip() if not exit_code else None)
    161         for cpu, raw_governor, exit_code in raw]
    162 
    163   def ListAvailableGovernors(self):
    164     """Returns the list of available governors for each CPU.
    165 
    166     Returns:
    167       An iterable of 2-tuples, each containing the cpu and a list of available
    168       governors for that cpu.
    169     """
    170     return self._available_governors
    171 
    172   def _SetScalingMaxFreq(self, value):
    173     self._WriteEachCpuFile('cpufreq/scaling_max_freq', '%d' % value)
    174 
    175   def _SetMaxGpuClock(self, value):
    176     self._device.WriteFile('/sys/class/kgsl/kgsl-3d0/max_gpuclk',
    177                            str(value),
    178                            as_root=True)
    179 
    180   def _AllCpusAreOnline(self):
    181     results = self._ForEachCpu('cat "$CPU/online"')
    182     # TODO(epenner): Investigate why file may be missing
    183     # (http://crbug.com/397118)
    184     return all(output.rstrip() == '1' and status == 0
    185                for (cpu, output, status) in results
    186                if cpu != 'cpu0')
    187 
    188   def _ForceAllCpusOnline(self, force_online):
    189     """Enable all CPUs on a device.
    190 
    191     Some vendors (or only Qualcomm?) hot-plug their CPUs, which can add noise
    192     to measurements:
    193     - In perf, samples are only taken for the CPUs that are online when the
    194       measurement is started.
    195     - The scaling governor can't be set for an offline CPU and frequency scaling
    196       on newly enabled CPUs adds noise to both perf and tracing measurements.
    197 
    198     It appears Qualcomm is the only vendor that hot-plugs CPUs, and on Qualcomm
    199     this is done by "mpdecision".
    200 
    201     """
    202     if self._have_mpdecision:
    203       cmd = ['stop', 'mpdecision'] if force_online else ['start', 'mpdecision']
    204       self._device.RunShellCommand(cmd, check_return=True, as_root=True)
    205 
    206     if not self._have_mpdecision and not self._AllCpusAreOnline():
    207       logger.warning('Unexpected cpu hot plugging detected.')
    208 
    209     if force_online:
    210       self._ForEachCpu('echo 1 > "$CPU/online"')
    211