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 atexit 6import logging 7 8from pylib import android_commands 9from pylib.device import device_utils 10 11class PerfControl(object): 12 """Provides methods for setting the performance mode of a device.""" 13 _SCALING_GOVERNOR_FMT = ( 14 '/sys/devices/system/cpu/cpu%d/cpufreq/scaling_governor') 15 _CPU_ONLINE_FMT = '/sys/devices/system/cpu/cpu%d/online' 16 _KERNEL_MAX = '/sys/devices/system/cpu/kernel_max' 17 18 def __init__(self, device): 19 # TODO(jbudorick) Remove once telemetry gets switched over. 20 if isinstance(device, android_commands.AndroidCommands): 21 device = device_utils.DeviceUtils(device) 22 self._device = device 23 cpu_files = self._device.RunShellCommand( 24 'ls -d /sys/devices/system/cpu/cpu[0-9]*') 25 self._num_cpu_cores = len(cpu_files) 26 assert self._num_cpu_cores > 0, 'Failed to detect CPUs.' 27 logging.info('Number of CPUs: %d', self._num_cpu_cores) 28 self._have_mpdecision = self._device.FileExists('/system/bin/mpdecision') 29 30 def SetHighPerfMode(self): 31 """Sets the highest possible performance mode for the device.""" 32 if not self._device.old_interface.IsRootEnabled(): 33 message = 'Need root for performance mode. Results may be NOISY!!' 34 logging.warning(message) 35 # Add an additional warning at exit, such that it's clear that any results 36 # may be different/noisy (due to the lack of intended performance mode). 37 atexit.register(logging.warning, message) 38 return 39 # TODO(epenner): Enable on all devices (http://crbug.com/383566) 40 if 'Nexus 4' == self._device.old_interface.GetProductModel(): 41 self._ForceAllCpusOnline(True) 42 if not self._AllCpusAreOnline(): 43 logging.warning('Failed to force CPUs online. Results may be NOISY!') 44 self._SetScalingGovernorInternal('performance') 45 46 def SetPerfProfilingMode(self): 47 """Enables all cores for reliable perf profiling.""" 48 self._ForceAllCpusOnline(True) 49 self._SetScalingGovernorInternal('performance') 50 if not self._AllCpusAreOnline(): 51 if not self._device.old_interface.IsRootEnabled(): 52 raise RuntimeError('Need root to force CPUs online.') 53 raise RuntimeError('Failed to force CPUs online.') 54 55 def SetDefaultPerfMode(self): 56 """Sets the performance mode for the device to its default mode.""" 57 if not self._device.old_interface.IsRootEnabled(): 58 return 59 product_model = self._device.GetProp('ro.product.model') 60 governor_mode = { 61 'GT-I9300': 'pegasusq', 62 'Galaxy Nexus': 'interactive', 63 'Nexus 4': 'ondemand', 64 'Nexus 7': 'interactive', 65 'Nexus 10': 'interactive' 66 }.get(product_model, 'ondemand') 67 self._SetScalingGovernorInternal(governor_mode) 68 self._ForceAllCpusOnline(False) 69 70 def _SetScalingGovernorInternal(self, value): 71 cpu_cores = ' '.join([str(x) for x in range(self._num_cpu_cores)]) 72 script = ('for CPU in %s; do\n' 73 ' FILE="/sys/devices/system/cpu/cpu$CPU/cpufreq/scaling_governor"\n' 74 ' test -e $FILE && echo %s > $FILE\n' 75 'done\n') % (cpu_cores, value) 76 logging.info('Setting scaling governor mode: %s', value) 77 self._device.RunShellCommand(script, as_root=True) 78 79 def _AllCpusAreOnline(self): 80 for cpu in range(1, self._num_cpu_cores): 81 online_path = PerfControl._CPU_ONLINE_FMT % cpu 82 # TODO(epenner): Investigate why file may be missing 83 # (http://crbug.com/397118) 84 if not self._device.FileExists(online_path) or \ 85 self._device.ReadFile(online_path)[0] == '0': 86 return False 87 return True 88 89 def _ForceAllCpusOnline(self, force_online): 90 """Enable all CPUs on a device. 91 92 Some vendors (or only Qualcomm?) hot-plug their CPUs, which can add noise 93 to measurements: 94 - In perf, samples are only taken for the CPUs that are online when the 95 measurement is started. 96 - The scaling governor can't be set for an offline CPU and frequency scaling 97 on newly enabled CPUs adds noise to both perf and tracing measurements. 98 99 It appears Qualcomm is the only vendor that hot-plugs CPUs, and on Qualcomm 100 this is done by "mpdecision". 101 102 """ 103 if self._have_mpdecision: 104 script = 'stop mpdecision' if force_online else 'start mpdecision' 105 self._device.RunShellCommand(script, as_root=True) 106 107 if not self._have_mpdecision and not self._AllCpusAreOnline(): 108 logging.warning('Unexpected cpu hot plugging detected.') 109 110 if not force_online: 111 return 112 113 for cpu in range(self._num_cpu_cores): 114 online_path = PerfControl._CPU_ONLINE_FMT % cpu 115 self._device.WriteFile(online_path, '1', as_root=True) 116