# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
-
+import atexit
import logging
+from pylib import android_commands
+from pylib.device import device_utils
class PerfControl(object):
"""Provides methods for setting the performance mode of a device."""
_SCALING_GOVERNOR_FMT = (
'/sys/devices/system/cpu/cpu%d/cpufreq/scaling_governor')
+ _CPU_ONLINE_FMT = '/sys/devices/system/cpu/cpu%d/online'
_KERNEL_MAX = '/sys/devices/system/cpu/kernel_max'
- def __init__(self, adb):
- self._adb = adb
- kernel_max = self._adb.GetFileContents(PerfControl._KERNEL_MAX,
- log_result=False)
- assert kernel_max, 'Unable to find %s' % PerfControl._KERNEL_MAX
- self._kernel_max = int(kernel_max[0])
- logging.info('Maximum CPU index: %d', self._kernel_max)
- self._original_scaling_governor = self._adb.GetFileContents(
- PerfControl._SCALING_GOVERNOR_FMT % 0,
- log_result=False)[0]
+ def __init__(self, device):
+ # TODO(jbudorick) Remove once telemetry gets switched over.
+ if isinstance(device, android_commands.AndroidCommands):
+ device = device_utils.DeviceUtils(device)
+ self._device = device
+ cpu_files = self._device.RunShellCommand(
+ 'ls -d /sys/devices/system/cpu/cpu[0-9]*')
+ self._num_cpu_cores = len(cpu_files)
+ assert self._num_cpu_cores > 0, 'Failed to detect CPUs.'
+ logging.info('Number of CPUs: %d', self._num_cpu_cores)
+ self._have_mpdecision = self._device.FileExists('/system/bin/mpdecision')
def SetHighPerfMode(self):
"""Sets the highest possible performance mode for the device."""
+ if not self._device.old_interface.IsRootEnabled():
+ message = 'Need root for performance mode. Results may be NOISY!!'
+ logging.warning(message)
+ # Add an additional warning at exit, such that it's clear that any results
+ # may be different/noisy (due to the lack of intended performance mode).
+ atexit.register(logging.warning, message)
+ return
+ # TODO(epenner): Enable on all devices (http://crbug.com/383566)
+ if 'Nexus 4' == self._device.old_interface.GetProductModel():
+ self._ForceAllCpusOnline(True)
+ if not self._AllCpusAreOnline():
+ logging.warning('Failed to force CPUs online. Results may be NOISY!')
+ self._SetScalingGovernorInternal('performance')
+
+ def SetPerfProfilingMode(self):
+ """Enables all cores for reliable perf profiling."""
+ self._ForceAllCpusOnline(True)
self._SetScalingGovernorInternal('performance')
+ if not self._AllCpusAreOnline():
+ if not self._device.old_interface.IsRootEnabled():
+ raise RuntimeError('Need root to force CPUs online.')
+ raise RuntimeError('Failed to force CPUs online.')
def SetDefaultPerfMode(self):
"""Sets the performance mode for the device to its default mode."""
- product_model = self._adb.GetProductModel()
+ if not self._device.old_interface.IsRootEnabled():
+ return
+ product_model = self._device.GetProp('ro.product.model')
governor_mode = {
'GT-I9300': 'pegasusq',
'Galaxy Nexus': 'interactive',
'Nexus 10': 'interactive'
}.get(product_model, 'ondemand')
self._SetScalingGovernorInternal(governor_mode)
-
- def RestoreOriginalPerfMode(self):
- """Resets the original performance mode of the device."""
- self._SetScalingGovernorInternal(self._original_scaling_governor)
+ self._ForceAllCpusOnline(False)
def _SetScalingGovernorInternal(self, value):
- for cpu in range(self._kernel_max + 1):
+ for cpu in range(self._num_cpu_cores):
scaling_governor_file = PerfControl._SCALING_GOVERNOR_FMT % cpu
- if self._adb.FileExistsOnDevice(scaling_governor_file):
+ if self._device.FileExists(scaling_governor_file):
logging.info('Writing scaling governor mode \'%s\' -> %s',
value, scaling_governor_file)
- self._adb.SetProtectedFileContents(scaling_governor_file, value)
+ self._device.WriteFile(scaling_governor_file, value, as_root=True)
+
+ def _AllCpusAreOnline(self):
+ for cpu in range(self._num_cpu_cores):
+ online_path = PerfControl._CPU_ONLINE_FMT % cpu
+ # TODO(epenner): Investigate why file may be missing
+ # (http://crbug.com/397118)
+ if not self._device.FileExists(online_path) or \
+ self._device.ReadFile(online_path)[0] == '0':
+ return False
+ return True
+
+ def _ForceAllCpusOnline(self, force_online):
+ """Enable all CPUs on a device.
+
+ Some vendors (or only Qualcomm?) hot-plug their CPUs, which can add noise
+ to measurements:
+ - In perf, samples are only taken for the CPUs that are online when the
+ measurement is started.
+ - The scaling governor can't be set for an offline CPU and frequency scaling
+ on newly enabled CPUs adds noise to both perf and tracing measurements.
+
+ It appears Qualcomm is the only vendor that hot-plugs CPUs, and on Qualcomm
+ this is done by "mpdecision".
+
+ """
+ if self._have_mpdecision:
+ script = 'stop mpdecision' if force_online else 'start mpdecision'
+ self._device.RunShellCommand(script, as_root=True)
+
+ if not self._have_mpdecision and not self._AllCpusAreOnline():
+ logging.warning('Unexpected cpu hot plugging detected.')
+
+ if not force_online:
+ return
+
+ for cpu in range(self._num_cpu_cores):
+ online_path = PerfControl._CPU_ONLINE_FMT % cpu
+ self._device.WriteFile(online_path, '1', as_root=True)