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