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 7import re 8 9from devil.android import device_errors 10 11logger = logging.getLogger(__name__) 12_atexit_messages = set() 13 14 15# Defines how to switch between the default performance configuration 16# ('default_mode') and the mode for use when benchmarking ('high_perf_mode'). 17# For devices not in the list the defaults are to set up the scaling governor to 18# 'performance' and reset it back to 'ondemand' when benchmarking is finished. 19# 20# The 'default_mode_governor' is mandatory to define, while 21# 'high_perf_mode_governor' is not taken into account. The latter is because the 22# governor 'performance' is currently used for all benchmarking on all devices. 23# 24# TODO(crbug.com/383566): Add definitions for all devices used in the perf 25# waterfall. 26_PERFORMANCE_MODE_DEFINITIONS = { 27 # Fire TV Edition - 4K 28 'AFTKMST12': { 29 'default_mode_governor': 'interactive', 30 }, 31 'GT-I9300': { 32 'default_mode_governor': 'pegasusq', 33 }, 34 'Galaxy Nexus': { 35 'default_mode_governor': 'interactive', 36 }, 37 'Nexus 7': { 38 'default_mode_governor': 'interactive', 39 }, 40 'Nexus 10': { 41 'default_mode_governor': 'interactive', 42 }, 43 'Nexus 4': { 44 'high_perf_mode': { 45 'bring_cpu_cores_online': True, 46 }, 47 'default_mode_governor': 'ondemand', 48 }, 49 'Nexus 5': { 50 # The list of possible GPU frequency values can be found in: 51 # /sys/class/kgsl/kgsl-3d0/gpu_available_frequencies. 52 # For CPU cores the possible frequency values are at: 53 # /sys/devices/system/cpu/cpu0/cpufreq/scaling_available_frequencies 54 'high_perf_mode': { 55 'bring_cpu_cores_online': True, 56 'cpu_max_freq': 1190400, 57 'gpu_max_freq': 200000000, 58 }, 59 'default_mode': { 60 'cpu_max_freq': 2265600, 61 'gpu_max_freq': 450000000, 62 }, 63 'default_mode_governor': 'ondemand', 64 }, 65 'Nexus 5X': { 66 'high_perf_mode': { 67 'bring_cpu_cores_online': True, 68 'cpu_max_freq': 1248000, 69 'gpu_max_freq': 300000000, 70 }, 71 'default_mode': { 72 'governor': 'ondemand', 73 # The SoC is ARM big.LITTLE. The cores 4..5 are big, the 0..3 are LITTLE. 74 'cpu_max_freq': {'0..3': 1440000, '4..5': 1824000}, 75 'gpu_max_freq': 600000000, 76 }, 77 'default_mode_governor': 'ondemand', 78 }, 79} 80 81 82def _NoisyWarning(message): 83 message += ' Results may be NOISY!!' 84 logger.warning(message) 85 # Add an additional warning at exit, such that it's clear that any results 86 # may be different/noisy (due to the lack of intended performance mode). 87 if message not in _atexit_messages: 88 _atexit_messages.add(message) 89 atexit.register(logger.warning, message) 90 91 92class PerfControl(object): 93 """Provides methods for setting the performance mode of a device.""" 94 95 _AVAILABLE_GOVERNORS_REL_PATH = 'cpufreq/scaling_available_governors' 96 _CPU_FILE_PATTERN = re.compile(r'^cpu\d+$') 97 _CPU_PATH = '/sys/devices/system/cpu' 98 _KERNEL_MAX = '/sys/devices/system/cpu/kernel_max' 99 100 def __init__(self, device): 101 self._device = device 102 self._cpu_files = [] 103 for file_name in self._device.ListDirectory(self._CPU_PATH, as_root=True): 104 if self._CPU_FILE_PATTERN.match(file_name): 105 self._cpu_files.append(file_name) 106 assert self._cpu_files, 'Failed to detect CPUs.' 107 self._cpu_file_list = ' '.join(self._cpu_files) 108 logger.info('CPUs found: %s', self._cpu_file_list) 109 110 self._have_mpdecision = self._device.FileExists('/system/bin/mpdecision') 111 112 raw = self._ReadEachCpuFile(self._AVAILABLE_GOVERNORS_REL_PATH) 113 self._available_governors = [ 114 (cpu, raw_governors.strip().split() if not exit_code else None) 115 for cpu, raw_governors, exit_code in raw] 116 117 def _SetMaxFrequenciesFromMode(self, mode): 118 """Set maximum frequencies for GPU and CPU cores. 119 120 Args: 121 mode: A dictionary mapping optional keys 'cpu_max_freq' and 'gpu_max_freq' 122 to integer values of frequency supported by the device. 123 """ 124 cpu_max_freq = mode.get('cpu_max_freq') 125 if cpu_max_freq: 126 if not isinstance(cpu_max_freq, dict): 127 self._SetScalingMaxFreqForCpus(cpu_max_freq, self._cpu_file_list) 128 else: 129 for key, max_frequency in cpu_max_freq.iteritems(): 130 # Convert 'X' to 'cpuX' and 'X..Y' to 'cpuX cpu<X+1> .. cpuY'. 131 if '..' in key: 132 range_min, range_max = key.split('..') 133 range_min, range_max = int(range_min), int(range_max) 134 else: 135 range_min = range_max = int(key) 136 cpu_files = ['cpu%d' % number 137 for number in xrange(range_min, range_max + 1)] 138 # Set the |max_frequency| on requested subset of the cores. 139 self._SetScalingMaxFreqForCpus(max_frequency, ' '.join(cpu_files)) 140 gpu_max_freq = mode.get('gpu_max_freq') 141 if gpu_max_freq: 142 self._SetMaxGpuClock(gpu_max_freq) 143 144 def SetHighPerfMode(self): 145 """Sets the highest stable performance mode for the device.""" 146 try: 147 self._device.EnableRoot() 148 except device_errors.CommandFailedError: 149 _NoisyWarning('Need root for performance mode.') 150 return 151 mode_definitions = _PERFORMANCE_MODE_DEFINITIONS.get( 152 self._device.product_model) 153 if not mode_definitions: 154 self.SetScalingGovernor('performance') 155 return 156 high_perf_mode = mode_definitions.get('high_perf_mode') 157 if not high_perf_mode: 158 self.SetScalingGovernor('performance') 159 return 160 if high_perf_mode.get('bring_cpu_cores_online', False): 161 self._ForceAllCpusOnline(True) 162 if not self._AllCpusAreOnline(): 163 _NoisyWarning('Failed to force CPUs online.') 164 # Scaling governor must be set _after_ bringing all CPU cores online, 165 # otherwise it would not affect the cores that are currently offline. 166 self.SetScalingGovernor('performance') 167 self._SetMaxFrequenciesFromMode(high_perf_mode) 168 169 def SetDefaultPerfMode(self): 170 """Sets the performance mode for the device to its default mode.""" 171 if not self._device.HasRoot(): 172 return 173 mode_definitions = _PERFORMANCE_MODE_DEFINITIONS.get( 174 self._device.product_model) 175 if not mode_definitions: 176 self.SetScalingGovernor('ondemand') 177 else: 178 default_mode_governor = mode_definitions.get('default_mode_governor') 179 assert default_mode_governor, ('Default mode governor must be provided ' 180 'for all perf mode definitions.') 181 self.SetScalingGovernor(default_mode_governor) 182 default_mode = mode_definitions.get('default_mode') 183 if default_mode: 184 self._SetMaxFrequenciesFromMode(default_mode) 185 self._ForceAllCpusOnline(False) 186 187 def SetPerfProfilingMode(self): 188 """Enables all cores for reliable perf profiling.""" 189 self._ForceAllCpusOnline(True) 190 self.SetScalingGovernor('performance') 191 if not self._AllCpusAreOnline(): 192 if not self._device.HasRoot(): 193 raise RuntimeError('Need root to force CPUs online.') 194 raise RuntimeError('Failed to force CPUs online.') 195 196 def GetCpuInfo(self): 197 online = (output.rstrip() == '1' and status == 0 198 for (_, output, status) in self._ForEachCpu('cat "$CPU/online"')) 199 governor = (output.rstrip() if status == 0 else None 200 for (_, output, status) 201 in self._ForEachCpu('cat "$CPU/cpufreq/scaling_governor"')) 202 return zip(self._cpu_files, online, governor) 203 204 def _ForEachCpu(self, cmd, cpu_list=None): 205 """Runs a command on the device for each of the CPUs. 206 207 Args: 208 cmd: A string with a shell command, may may use shell expansion: "$CPU" to 209 refer to the current CPU in the string form (e.g. "cpu0", "cpu1", 210 and so on). 211 cpu_list: A space-separated string of CPU core names, like in the example 212 above 213 Returns: 214 A list of tuples in the form (cpu_string, command_output, exit_code), one 215 tuple per each command invocation. As usual, all lines of the output 216 command are joined into one line with spaces. 217 """ 218 if cpu_list is None: 219 cpu_list = self._cpu_file_list 220 script = '; '.join([ 221 'for CPU in %s' % cpu_list, 222 'do %s' % cmd, 223 'echo -n "%~%$?%~%"', 224 'done' 225 ]) 226 output = self._device.RunShellCommand( 227 script, cwd=self._CPU_PATH, check_return=True, as_root=True, shell=True) 228 output = '\n'.join(output).split('%~%') 229 return zip(self._cpu_files, output[0::2], (int(c) for c in output[1::2])) 230 231 def _ConditionallyWriteCpuFiles(self, path, value, cpu_files, condition): 232 template = ( 233 '{condition} && test -e "$CPU/{path}" && echo {value} > "$CPU/{path}"') 234 results = self._ForEachCpu( 235 template.format(path=path, value=value, condition=condition), cpu_files) 236 cpus = ' '.join(cpu for (cpu, _, status) in results if status == 0) 237 if cpus: 238 logger.info('Successfully set %s to %r on: %s', path, value, cpus) 239 else: 240 logger.warning('Failed to set %s to %r on any cpus', path, value) 241 242 def _WriteCpuFiles(self, path, value, cpu_files): 243 self._ConditionallyWriteCpuFiles(path, value, cpu_files, condition='true') 244 245 def _ReadEachCpuFile(self, path): 246 return self._ForEachCpu( 247 'cat "$CPU/{path}"'.format(path=path)) 248 249 def SetScalingGovernor(self, value): 250 """Sets the scaling governor to the given value on all possible CPUs. 251 252 This does not attempt to set a governor to a value not reported as available 253 on the corresponding CPU. 254 255 Args: 256 value: [string] The new governor value. 257 """ 258 condition = 'test -e "{path}" && grep -q {value} {path}'.format( 259 path=('${CPU}/%s' % self._AVAILABLE_GOVERNORS_REL_PATH), 260 value=value) 261 self._ConditionallyWriteCpuFiles( 262 'cpufreq/scaling_governor', value, self._cpu_file_list, condition) 263 264 def GetScalingGovernor(self): 265 """Gets the currently set governor for each CPU. 266 267 Returns: 268 An iterable of 2-tuples, each containing the cpu and the current 269 governor. 270 """ 271 raw = self._ReadEachCpuFile('cpufreq/scaling_governor') 272 return [ 273 (cpu, raw_governor.strip() if not exit_code else None) 274 for cpu, raw_governor, exit_code in raw] 275 276 def ListAvailableGovernors(self): 277 """Returns the list of available governors for each CPU. 278 279 Returns: 280 An iterable of 2-tuples, each containing the cpu and a list of available 281 governors for that cpu. 282 """ 283 return self._available_governors 284 285 def _SetScalingMaxFreqForCpus(self, value, cpu_files): 286 self._WriteCpuFiles('cpufreq/scaling_max_freq', '%d' % value, cpu_files) 287 288 def _SetMaxGpuClock(self, value): 289 self._device.WriteFile('/sys/class/kgsl/kgsl-3d0/max_gpuclk', 290 str(value), 291 as_root=True) 292 293 def _AllCpusAreOnline(self): 294 results = self._ForEachCpu('cat "$CPU/online"') 295 # The file 'cpu0/online' is missing on some devices (example: Nexus 9). This 296 # is likely because on these devices it is impossible to bring the cpu0 297 # offline. Assuming the same for all devices until proven otherwise. 298 return all(output.rstrip() == '1' and status == 0 299 for (cpu, output, status) in results 300 if cpu != 'cpu0') 301 302 def _ForceAllCpusOnline(self, force_online): 303 """Enable all CPUs on a device. 304 305 Some vendors (or only Qualcomm?) hot-plug their CPUs, which can add noise 306 to measurements: 307 - In perf, samples are only taken for the CPUs that are online when the 308 measurement is started. 309 - The scaling governor can't be set for an offline CPU and frequency scaling 310 on newly enabled CPUs adds noise to both perf and tracing measurements. 311 312 It appears Qualcomm is the only vendor that hot-plugs CPUs, and on Qualcomm 313 this is done by "mpdecision". 314 315 """ 316 if self._have_mpdecision: 317 cmd = ['stop', 'mpdecision'] if force_online else ['start', 'mpdecision'] 318 self._device.RunShellCommand(cmd, check_return=True, as_root=True) 319 320 if not self._have_mpdecision and not self._AllCpusAreOnline(): 321 logger.warning('Unexpected cpu hot plugging detected.') 322 323 if force_online: 324 self._ForEachCpu('echo 1 > "$CPU/online"') 325