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