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 devil.android import device_errors 9 10 11class PerfControl(object): 12 """Provides methods for setting the performance mode of a device.""" 13 _CPU_PATH = '/sys/devices/system/cpu' 14 _KERNEL_MAX = '/sys/devices/system/cpu/kernel_max' 15 16 def __init__(self, device): 17 self._device = device 18 # this will raise an AdbCommandFailedError if no CPU files are found 19 self._cpu_files = self._device.RunShellCommand( 20 'ls -d cpu[0-9]*', cwd=self._CPU_PATH, check_return=True, as_root=True) 21 assert self._cpu_files, 'Failed to detect CPUs.' 22 self._cpu_file_list = ' '.join(self._cpu_files) 23 logging.info('CPUs found: %s', self._cpu_file_list) 24 self._have_mpdecision = self._device.FileExists('/system/bin/mpdecision') 25 26 def SetHighPerfMode(self): 27 """Sets the highest stable performance mode for the device.""" 28 try: 29 self._device.EnableRoot() 30 except device_errors.CommandFailedError: 31 message = 'Need root for performance mode. Results may be NOISY!!' 32 logging.warning(message) 33 # Add an additional warning at exit, such that it's clear that any results 34 # may be different/noisy (due to the lack of intended performance mode). 35 atexit.register(logging.warning, message) 36 return 37 38 product_model = self._device.product_model 39 # TODO(epenner): Enable on all devices (http://crbug.com/383566) 40 if 'Nexus 4' == product_model: 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 elif 'Nexus 5' == product_model: 46 self._ForceAllCpusOnline(True) 47 if not self._AllCpusAreOnline(): 48 logging.warning('Failed to force CPUs online. Results may be NOISY!') 49 self._SetScalingGovernorInternal('performance') 50 self._SetScalingMaxFreq(1190400) 51 self._SetMaxGpuClock(200000000) 52 else: 53 self._SetScalingGovernorInternal('performance') 54 55 def SetPerfProfilingMode(self): 56 """Enables all cores for reliable perf profiling.""" 57 self._ForceAllCpusOnline(True) 58 self._SetScalingGovernorInternal('performance') 59 if not self._AllCpusAreOnline(): 60 if not self._device.HasRoot(): 61 raise RuntimeError('Need root to force CPUs online.') 62 raise RuntimeError('Failed to force CPUs online.') 63 64 def SetDefaultPerfMode(self): 65 """Sets the performance mode for the device to its default mode.""" 66 if not self._device.HasRoot(): 67 return 68 product_model = self._device.product_model 69 if 'Nexus 5' == product_model: 70 if self._AllCpusAreOnline(): 71 self._SetScalingMaxFreq(2265600) 72 self._SetMaxGpuClock(450000000) 73 74 governor_mode = { 75 'GT-I9300': 'pegasusq', 76 'Galaxy Nexus': 'interactive', 77 'Nexus 4': 'ondemand', 78 'Nexus 5': 'ondemand', 79 'Nexus 7': 'interactive', 80 'Nexus 10': 'interactive' 81 }.get(product_model, 'ondemand') 82 self._SetScalingGovernorInternal(governor_mode) 83 self._ForceAllCpusOnline(False) 84 85 def GetCpuInfo(self): 86 online = (output.rstrip() == '1' and status == 0 87 for (_, output, status) in self._ForEachCpu('cat "$CPU/online"')) 88 governor = (output.rstrip() if status == 0 else None 89 for (_, output, status) 90 in self._ForEachCpu('cat "$CPU/cpufreq/scaling_governor"')) 91 return zip(self._cpu_files, online, governor) 92 93 def _ForEachCpu(self, cmd): 94 script = '; '.join([ 95 'for CPU in %s' % self._cpu_file_list, 96 'do %s' % cmd, 97 'echo -n "%~%$?%~%"', 98 'done' 99 ]) 100 output = self._device.RunShellCommand( 101 script, cwd=self._CPU_PATH, check_return=True, as_root=True) 102 output = '\n'.join(output).split('%~%') 103 return zip(self._cpu_files, output[0::2], (int(c) for c in output[1::2])) 104 105 def _WriteEachCpuFile(self, path, value): 106 results = self._ForEachCpu( 107 'test -e "$CPU/{path}" && echo {value} > "$CPU/{path}"'.format( 108 path=path, value=value)) 109 cpus = ' '.join(cpu for (cpu, _, status) in results if status == 0) 110 if cpus: 111 logging.info('Successfully set %s to %r on: %s', path, value, cpus) 112 else: 113 logging.warning('Failed to set %s to %r on any cpus', path, value) 114 115 def _SetScalingGovernorInternal(self, value): 116 self._WriteEachCpuFile('cpufreq/scaling_governor', value) 117 118 def _SetScalingMaxFreq(self, value): 119 self._WriteEachCpuFile('cpufreq/scaling_max_freq', '%d' % value) 120 121 def _SetMaxGpuClock(self, value): 122 self._device.WriteFile('/sys/class/kgsl/kgsl-3d0/max_gpuclk', 123 str(value), 124 as_root=True) 125 126 def _AllCpusAreOnline(self): 127 results = self._ForEachCpu('cat "$CPU/online"') 128 # TODO(epenner): Investigate why file may be missing 129 # (http://crbug.com/397118) 130 return all(output.rstrip() == '1' and status == 0 131 for (cpu, output, status) in results 132 if cpu != 'cpu0') 133 134 def _ForceAllCpusOnline(self, force_online): 135 """Enable all CPUs on a device. 136 137 Some vendors (or only Qualcomm?) hot-plug their CPUs, which can add noise 138 to measurements: 139 - In perf, samples are only taken for the CPUs that are online when the 140 measurement is started. 141 - The scaling governor can't be set for an offline CPU and frequency scaling 142 on newly enabled CPUs adds noise to both perf and tracing measurements. 143 144 It appears Qualcomm is the only vendor that hot-plugs CPUs, and on Qualcomm 145 this is done by "mpdecision". 146 147 """ 148 if self._have_mpdecision: 149 script = 'stop mpdecision' if force_online else 'start mpdecision' 150 self._device.RunShellCommand(script, check_return=True, as_root=True) 151 152 if not self._have_mpdecision and not self._AllCpusAreOnline(): 153 logging.warning('Unexpected cpu hot plugging detected.') 154 155 if force_online: 156 self._ForEachCpu('echo 1 > "$CPU/online"') 157