1# 2# Copyright (C) 2016 The Android Open Source Project 3# 4# Licensed under the Apache License, Version 2.0 (the "License"); 5# you may not use this file except in compliance with the License. 6# You may obtain a copy of the License at 7# 8# http://www.apache.org/licenses/LICENSE-2.0 9# 10# Unless required by applicable law or agreed to in writing, software 11# distributed under the License is distributed on an "AS IS" BASIS, 12# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13# See the License for the specific language governing permissions and 14# limitations under the License. 15# 16 17import logging 18import time 19 20from vts.runners.host import asserts 21from vts.runners.host import const 22 23 24class CpuFrequencyScalingController(object): 25 """CPU Frequency Scaling Controller. 26 27 The implementation is based on the special files in 28 /sys/devices/system/cpu/. CPU availability is shown in multiple files, 29 including online, present, and possible. This class assumes that a present 30 CPU may dynamically switch its online status. If a CPU is online, its 31 frequency scaling can be adjusted by reading/writing the files in 32 cpuX/cpufreq/ where X is the CPU number. 33 34 Attributes: 35 _dut: the target device DUT instance. 36 _shell: Shell mirror object for communication with a target. 37 _min_cpu_number: integer, the min CPU number. 38 _max_cpu_number; integer, the max CPU number. 39 _theoretical_max_frequency: a dict where its key is the CPU number and 40 its value is an integer containing the 41 theoretical max CPU frequency. 42 _perf_override: boolean, true if this module has switched the device from 43 its normal cpufreq governor to the performance 44 governor. 45 _saved_governors: list of strings, the saved cpufreq governor for each 46 CPU on the device. 47 """ 48 49 def __init__(self, dut): 50 self._dut = dut 51 self._init = False 52 53 def Init(self): 54 """Creates a shell mirror object and reads the configuration values.""" 55 if self._init: 56 return 57 self._shell = self._dut.shell 58 self._min_cpu_number, self._max_cpu_number = self._LoadMinAndMaxCpuNo() 59 self._theoretical_max_frequency = {} 60 self._perf_override = False 61 self._saved_governors = None 62 self._init = True 63 64 def _LoadMinAndMaxCpuNo(self): 65 """Reads the min and max CPU numbers from sysfs. 66 67 Returns: 68 integer: min CPU number (inclusive) 69 integer: max CPU number (exclusive) 70 """ 71 results = self._shell.Execute("cat /sys/devices/system/cpu/present") 72 asserts.assertEqual(len(results[const.STDOUT]), 1) 73 stdout_lines = results[const.STDOUT][0].split("\n") 74 stdout_split = stdout_lines[0].split('-') 75 asserts.assertLess(len(stdout_split), 3) 76 low = stdout_split[0] 77 high = stdout_split[1] if len(stdout_split) == 2 else low 78 logging.debug("present cpus: %s : %s" % (low, high)) 79 return int(low), int(high) + 1 80 81 def GetMinAndMaxCpuNo(self): 82 """Returns the min and max CPU numbers. 83 84 Returns: 85 integer: min CPU number (inclusive) 86 integer: max CPU number (exclusive) 87 """ 88 return self._min_cpu_number, self._max_cpu_number 89 90 def _GetTheoreticalMaxFrequency(self, cpu_no): 91 """Reads max value from cpufreq/scaling_available_frequencies. 92 93 If the read operation is successful, the return value is kept in 94 _theoretical_max_frequency as a cache. 95 96 Args: 97 cpu_no: integer, the CPU number. 98 99 Returns: 100 An integer which is the max frequency read from the file. 101 None if the file cannot be read. 102 """ 103 if cpu_no in self._theoretical_max_frequency: 104 return self._theoretical_max_frequency[cpu_no] 105 results = self._shell.Execute( 106 "cat /sys/devices/system/cpu/cpu%s/" 107 "cpufreq/scaling_available_frequencies" % cpu_no) 108 asserts.assertEqual(1, len(results[const.EXIT_CODE])) 109 if not results[const.EXIT_CODE][0]: 110 freq = [int(x) for x in results[const.STDOUT][0].split()] 111 self._theoretical_max_frequency[cpu_no] = max(freq) 112 return self._theoretical_max_frequency[cpu_no] 113 else: 114 logging.warn("cpufreq/scaling_available_frequencies for cpu %s" 115 " not set.", cpu_no) 116 return None 117 118 def ChangeCpuGovernor(self, modes): 119 """Changes the CPU governor mode of all the CPUs on the device. 120 121 Args: 122 modes: list of expected CPU governor modes, e.g., 'performance' 123 or 'schedutil'. The length of the list must be equal to 124 the number of CPUs on the device. 125 126 Returns: 127 A list of the previous governor modes if successful, None otherwise. 128 """ 129 self.Init() 130 asserts.assertEqual(self._max_cpu_number - self._min_cpu_number, 131 len(modes)) 132 # save current governor settings 133 target_cmd = [] 134 prev_govs = [] 135 for cpu_no in range(self._min_cpu_number, self._max_cpu_number): 136 target_cmd.append("cat /sys/devices/system/cpu/cpu%s/cpufreq/" 137 "scaling_governor" % cpu_no) 138 results = self._shell.Execute(target_cmd) 139 asserts.assertEqual(self._max_cpu_number - self._min_cpu_number, 140 len(results[const.STDOUT])) 141 if any(results[const.EXIT_CODE]): 142 logging.warn("Unable to save governors") 143 logging.warn("Stderr for saving scaling_governor: %s", 144 results[const.STDERR]) 145 return 146 for cpu_no in range(self._min_cpu_number, self._max_cpu_number): 147 prev_govs.append(results[const.STDOUT][cpu_no].rstrip()) 148 # set new governor 149 target_cmd = [] 150 for cpu_no in range(self._min_cpu_number, self._max_cpu_number): 151 target_cmd.append( 152 "echo %s > /sys/devices/system/cpu/cpu%s/cpufreq/" 153 "scaling_governor" % (modes[cpu_no], cpu_no)) 154 results = self._shell.Execute(target_cmd) 155 asserts.assertEqual(self._max_cpu_number - self._min_cpu_number, 156 len(results[const.STDOUT])) 157 if any(results[const.EXIT_CODE]): 158 logging.warn("Can't change CPU governor.") 159 logging.warn("Stderr for changing scaling_governor: %s", 160 results[const.STDERR]) 161 return 162 return prev_govs 163 164 def DisableCpuScaling(self): 165 """Disable CPU frequency scaling on the device.""" 166 self.Init() 167 if self._perf_override: 168 logging.warn( 169 "DisableCpuScaling called while scaling already disabled.") 170 return 171 new_govs = [] 172 for cpu_no in range(self._min_cpu_number, self._max_cpu_number): 173 new_govs.append("performance") 174 prev_govs = self.ChangeCpuGovernor(new_govs) 175 if prev_govs is not None: 176 self._saved_governors = prev_govs 177 self._perf_override = True 178 179 def EnableCpuScaling(self): 180 """Enable CPU frequency scaling on the device.""" 181 self.Init() 182 if not self._perf_override: 183 logging.warn( 184 "EnableCpuScaling called while scaling already enabled.") 185 return 186 if self._saved_governors is None: 187 logging.warn( 188 "EnableCpuScaling called and _saved_governors is None.") 189 return 190 self.ChangeCpuGovernor(self._saved_governors) 191 self._perf_override = False 192 193 def IsUnderThermalThrottling(self): 194 """Checks whether a target device is under thermal throttling. 195 196 Returns: 197 True if the current CPU frequency is not the theoretical max, 198 False otherwise. 199 """ 200 self.Init() 201 for cpu_no in range(self._min_cpu_number, self._max_cpu_number): 202 results = self._shell.Execute([ 203 "cat /sys/devices/system/cpu/cpu%s/cpufreq/scaling_max_freq" % 204 cpu_no, 205 "cat /sys/devices/system/cpu/cpu%s/cpufreq/scaling_cur_freq" % 206 cpu_no 207 ]) 208 asserts.assertEqual(2, len(results[const.STDOUT])) 209 if any(results[const.EXIT_CODE]): 210 logging.warn( 211 "Can't check the current and/or max CPU frequency.") 212 logging.warn("Stderr for scaling_max_freq: %s", 213 results[const.STDERR][0]) 214 logging.warn("Stderr for scaling_cur_freq: %s", 215 results[const.STDERR][1]) 216 return False 217 configurable_max_frequency = results[const.STDOUT][0].strip() 218 current_frequency = results[const.STDOUT][1].strip() 219 if configurable_max_frequency > current_frequency: 220 logging.error( 221 "CPU%s: Configurable max frequency %s > current frequency %s", 222 cpu_no, configurable_max_frequency, current_frequency) 223 return True 224 theoretical_max_frequency = self._GetTheoreticalMaxFrequency( 225 cpu_no) 226 if (theoretical_max_frequency is not None 227 and theoretical_max_frequency > int(current_frequency)): 228 logging.error( 229 "CPU%s, Theoretical max frequency %d > scaling current frequency %s", 230 cpu_no, theoretical_max_frequency, current_frequency) 231 return True 232 return False 233 234 def SkipIfThermalThrottling(self, retry_delay_secs=0): 235 """Skips the current test case if a target device is under thermal throttling. 236 237 Args: 238 retry_delay_secs: integer, if not 0, retry after the specified seconds. 239 """ 240 throttling = self.IsUnderThermalThrottling() 241 if throttling and retry_delay_secs > 0: 242 logging.info("Waiting %s seconds for the target CPU to cool down.", 243 retry_delay_secs) 244 time.sleep(retry_delay_secs) 245 throttling = self.IsUnderThermalThrottling() 246 asserts.skipIf(throttling, "Thermal throttling") 247