Index: build/android/pylib/perf/perf_control.py |
diff --git a/build/android/pylib/perf/perf_control.py b/build/android/pylib/perf/perf_control.py |
index 92aaa2433012499b4b78ab5682b557fc69eae604..7952656f9e334df00dfccbf2ecb976d1e8bd6d53 100644 |
--- a/build/android/pylib/perf/perf_control.py |
+++ b/build/android/pylib/perf/perf_control.py |
@@ -6,13 +6,24 @@ |
import logging |
from pylib import android_commands |
from pylib.device import device_utils |
+import time |
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') |
+ |
_KERNEL_MAX = '/sys/devices/system/cpu/kernel_max' |
+ _CPU_DIR_FMT = '/sys/devices/system/cpu/cpu%d/' |
+ _SCALING_GOVERNOR_FMT = _CPU_DIR_FMT + 'cpufreq/scaling_governor' |
+ _CPU_ONLINE_FMT = _CPU_DIR_FMT + 'online' |
+ _SCALING_FREQUENCY_STAT_NAMES = [ |
+ 'cpufreq/scaling_min_freq', |
+ 'cpufreq/scaling_max_freq', |
+ 'cpufreq/scaling_cur_freq', |
+ ] |
+ _MODELS_TO_FULLY_MANAGED_CPU_INDEXES = { |
+ 'Nexus 4': [0, 1, 2, 3], |
+ } |
def __init__(self, device): |
# TODO(jbudorick) Remove once telemetry gets switched over. |
@@ -28,33 +39,120 @@ class PerfControl(object): |
self._device.old_interface.GetFileContents( |
PerfControl._SCALING_GOVERNOR_FMT % 0, |
log_result=False)[0] |
+ self._LogCurrentCpuFrequencyInfo() |
+ |
+ # Bring CPUs online/offline on some device types to reduce variance in |
+ # performance measurements. Other device types are either hard to fully |
+ # manage properly or we did not decide what settings look 'typical' for |
+ # them. 'Galaxy Nexus' and 'Nexus 10' seem to use both cores all time |
+ # without hotplugging them with PerfControl, but we still may need to manage |
+ # their frequency later. TODO(pasko): manage Nexus 7 CPUs with PerfControl, |
+ # v1 and v2 differ significantly. |
+ # |
+ # *Not fully managing* CPUs means that only a subset of CPU properties |
+ # are updated (scaling governor only now) and only for those CPUs |
+ # represented by sysfs at the moment (/sys/devices/system/cpu/cpuX/...) |
+ self._fully_managed_cpus = ( |
+ self._MODELS_TO_FULLY_MANAGED_CPU_INDEXES.get( |
+ self._device.old_interface.GetProductModel(), [])) |
+ if self._fully_managed_cpus: |
+ self._cpus_on_originally = [] |
+ for cpu in xrange(self._kernel_max + 1): |
+ if '1' in self._GetFileContents( |
+ PerfControl._CPU_ONLINE_FMT % cpu): |
+ self._cpus_on_originally.append(cpu) |
+ |
+ def _BringCpuOffline(self, cpu_index): |
+ logging.info('Bringing CPU #%d offline.', cpu_index) |
+ self._device.old_interface.SetFileContents( |
+ PerfControl._CPU_ONLINE_FMT % cpu_index, '0') |
+ |
+ def _BringCpuOnline(self, cpu_index): |
+ "Brings a CPU core online, waits for it to show up in sysfs." |
+ |
+ logging.info('Bringing CPU #%d online.' %cpu_index) |
+ times_to_wait_left = 5 |
+ cpu_online_file = PerfControl._CPU_ONLINE_FMT % cpu_index |
+ while (True): |
+ self._device.old_interface.SetFileContents(cpu_online_file, '1') |
+ if '1' in self._GetFileContents(cpu_online_file): |
+ break |
+ if times_to_wait_left == 0: |
+ logging.warning('Gave up bringing CPU %d online', cpu_index) |
+ break |
+ times_to_wait_left -= 1 |
+ logging.warning('Could not bring CPU %d online, retrying..', |
+ cpu_index) |
+ time.sleep(0.1) |
+ |
+ def _GetFileContents(self, file_name): |
+ return '\n'.join(self._device.old_interface.GetFileContents(file_name)) |
+ |
+ def _LogCurrentCpuFrequencyInfo(self): |
+ for cpu in xrange(self._kernel_max + 1): |
+ for stat_name in PerfControl._SCALING_FREQUENCY_STAT_NAMES: |
+ stat_file_name = (PerfControl._CPU_DIR_FMT + stat_name) % cpu |
+ if self._device.old_interface.FileExistsOnDevice(stat_file_name): |
+ info = self._GetFileContents(stat_file_name) |
+ logging.info('CPU #%d frequency info: %s: %s', cpu, stat_name, info) |
def SetHighPerfMode(self): |
"""Sets the highest possible performance mode for the device.""" |
- self._SetScalingGovernorInternal('performance') |
+ |
+ if not self._fully_managed_cpus: |
+ for cpu in xrange(self._kernel_max + 1): |
+ self._SetScalingGovernorInternal(cpu, 'performance') |
+ else: |
+ # Stop the 'mpdecision' hotplug manager to get a constant number of CPUs |
+ # online when running tests. TODO(pasko): stop 'thermald'. |
+ self._device.old_interface.RunShellCommand('stop mpdecision') |
+ for cpu in xrange(self._kernel_max + 1): |
+ if cpu in self._fully_managed_cpus: |
+ self._BringCpuOnline(cpu) |
+ self._SetScalingGovernorInternal(cpu, 'performance') |
+ else: |
+ self._BringCpuOffline(cpu) |
def SetDefaultPerfMode(self): |
"""Sets the performance mode for the device to its default mode.""" |
+ self._LogCurrentCpuFrequencyInfo() |
product_model = self._device.old_interface.GetProductModel() |
governor_mode = { |
'GT-I9300': 'pegasusq', |
'Galaxy Nexus': 'interactive', |
'Nexus 4': 'ondemand', |
+ 'Nexus 5': 'ondemand', |
'Nexus 7': 'interactive', |
'Nexus 10': 'interactive' |
}.get(product_model, 'ondemand') |
- self._SetScalingGovernorInternal(governor_mode) |
+ if not self._fully_managed_cpus: |
+ # Set the default perf mode only if no CPUs were forced online. |
+ for cpu in xrange(self._kernel_max + 1): |
+ self._SetScalingGovernorInternal(cpu, governor_mode) |
+ # If 'mpdecision' is present on the system, it will manage CPU |
+ # online/offline state, otherwise it is a no-op. |
+ self._device.old_interface.RunShellCommand('start mpdecision') |
def RestoreOriginalPerfMode(self): |
"""Resets the original performance mode of the device.""" |
- self._SetScalingGovernorInternal(self._original_scaling_governor) |
- |
- def _SetScalingGovernorInternal(self, value): |
- for cpu in range(self._kernel_max + 1): |
- scaling_governor_file = PerfControl._SCALING_GOVERNOR_FMT % cpu |
- if self._device.old_interface.FileExistsOnDevice(scaling_governor_file): |
- logging.info('Writing scaling governor mode \'%s\' -> %s', |
- value, scaling_governor_file) |
- self._device.old_interface.SetProtectedFileContents( |
- scaling_governor_file, value) |
+ self._LogCurrentCpuFrequencyInfo() |
+ if not self._fully_managed_cpus: |
+ for cpu in xrange(self._kernel_max + 1): |
+ self._SetScalingGovernorInternal(cpu, self._original_scaling_governor) |
+ else: |
+ for cpu in xrange(self._kernel_max + 1): |
+ if cpu in self._cpus_on_originally: |
+ self._BringCpuOnline(cpu) |
+ self._SetScalingGovernorInternal(cpu, self._original_scaling_governor) |
+ else: |
+ self._SetScalingGovernorInternal(cpu, self._original_scaling_governor) |
+ self._BringCpuOffline(cpu) |
+ self._device.old_interface.RunShellCommand('start mpdecision') |
+ def _SetScalingGovernorInternal(self, cpu, value): |
+ scaling_governor_file = PerfControl._SCALING_GOVERNOR_FMT % cpu |
+ if (self._fully_managed_cpus or |
+ self._device.old_interface.FileExistsOnDevice(scaling_governor_file)): |
+ logging.info('Writing scaling governor mode "%s" -> %s', |
+ value, scaling_governor_file) |
+ self._device.old_interface.SetFileContents(scaling_governor_file, value) |