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 """ 43 44 def __init__(self, dut): 45 self._dut = dut 46 self._init = False 47 48 def Init(self): 49 """Creates a shell mirror object and reads the configuration values.""" 50 if self._init: 51 return 52 self._dut.shell.InvokeTerminal("cpu_frequency_scaling") 53 self._shell = self._dut.shell.cpu_frequency_scaling 54 self._min_cpu_number, self._max_cpu_number = self._LoadMinAndMaxCpuNo() 55 self._theoretical_max_frequency = {} 56 self._init = True 57 58 def _LoadMinAndMaxCpuNo(self): 59 """Reads the min and max CPU numbers from sysfs. 60 61 Returns: 62 integer: min CPU number (inclusive) 63 integer: max CPU number (exclusive) 64 """ 65 results = self._shell.Execute( 66 "cat /sys/devices/system/cpu/present") 67 asserts.assertEqual(len(results[const.STDOUT]), 1) 68 stdout_lines = results[const.STDOUT][0].split("\n") 69 stdout_split = stdout_lines[0].split('-') 70 asserts.assertLess(len(stdout_split), 3) 71 low = stdout_split[0] 72 high = stdout_split[1] if len(stdout_split) == 2 else low 73 logging.info("present cpus: %s : %s" % (low, high)) 74 return int(low), int(high) + 1 75 76 def GetMinAndMaxCpuNo(self): 77 """Returns the min and max CPU numbers. 78 79 Returns: 80 integer: min CPU number (inclusive) 81 integer: max CPU number (exclusive) 82 """ 83 return self._min_cpu_number, self._max_cpu_number; 84 85 def _GetTheoreticalMaxFrequency(self, cpu_no): 86 """Reads max value from cpufreq/scaling_available_frequencies. 87 88 If the read operation is successful, the return value is kept in 89 _theoretical_max_frequency as a cache. 90 91 Args: 92 cpu_no: integer, the CPU number. 93 94 Returns: 95 An integer which is the max frequency read from the file. 96 None if the file cannot be read. 97 """ 98 if cpu_no in self._theoretical_max_frequency: 99 return self._theoretical_max_frequency[cpu_no] 100 results = self._shell.Execute( 101 "cat /sys/devices/system/cpu/cpu%s/" 102 "cpufreq/scaling_available_frequencies" % cpu_no) 103 asserts.assertEqual(1, len(results[const.EXIT_CODE])) 104 if not results[const.EXIT_CODE][0]: 105 freq = [int(x) for x in results[const.STDOUT][0].split()] 106 self._theoretical_max_frequency[cpu_no] = max(freq) 107 return self._theoretical_max_frequency[cpu_no] 108 else: 109 logging.warn("cpufreq/scaling_available_frequencies for cpu %s" 110 " not set.", cpu_no) 111 return None 112 113 def ChangeCpuGovernor(self, mode): 114 """Changes the CPU governor mode of all the CPUs on the device. 115 116 Args: 117 mode: expected CPU governor mode, e.g., 'performance' or 'interactive'. 118 """ 119 self.Init() 120 for cpu_no in range(self._min_cpu_number, self._max_cpu_number): 121 results = self._shell.Execute( 122 "echo %s > /sys/devices/system/cpu/cpu%s/" 123 "cpufreq/scaling_governor" % (mode, cpu_no)) 124 asserts.assertEqual(1, len(results[const.EXIT_CODE])) 125 if results[const.EXIT_CODE][0]: 126 logging.warn("Can't change CPU governor.") 127 logging.warn("Stderr for scaling_governor: %s", 128 results[const.STDERR][0]) 129 130 def DisableCpuScaling(self): 131 """Disable CPU frequency scaling on the device.""" 132 self.ChangeCpuGovernor("performance") 133 134 def EnableCpuScaling(self): 135 """Enable CPU frequency scaling on the device.""" 136 self.ChangeCpuGovernor("interactive") 137 138 def IsUnderThermalThrottling(self): 139 """Checks whether a target device is under thermal throttling. 140 141 Returns: 142 True if the current CPU frequency is not the theoretical max, 143 False otherwise. 144 """ 145 self.Init() 146 for cpu_no in range(self._min_cpu_number, self._max_cpu_number): 147 results = self._shell.Execute( 148 ["cat /sys/devices/system/cpu/cpu%s/cpufreq/scaling_max_freq" % cpu_no, 149 "cat /sys/devices/system/cpu/cpu%s/cpufreq/scaling_cur_freq" % cpu_no]) 150 asserts.assertEqual(2, len(results[const.STDOUT])) 151 if any(results[const.EXIT_CODE]): 152 logging.warn("Can't check the current and/or max CPU frequency.") 153 logging.warn("Stderr for scaling_max_freq: %s", results[const.STDERR][0]) 154 logging.warn("Stderr for scaling_cur_freq: %s", results[const.STDERR][1]) 155 return False 156 configurable_max_frequency = results[const.STDOUT][0].strip() 157 current_frequency = results[const.STDOUT][1].strip() 158 if configurable_max_frequency != current_frequency: 159 logging.error( 160 "CPU%s: Configurable max frequency %s != current frequency %s", 161 cpu_no, configurable_max_frequency, current_frequency) 162 return True 163 theoretical_max_frequency = self._GetTheoreticalMaxFrequency(cpu_no) 164 if (theoretical_max_frequency is not None and 165 theoretical_max_frequency != int(current_frequency)): 166 logging.error( 167 "CPU%s, Theoretical max frequency %d != scaling current frequency %s", 168 cpu_no, theoretical_max_frequency, current_frequency) 169 return True 170 return False 171 172 def SkipIfThermalThrottling(self, retry_delay_secs=0): 173 """Skips the current test case if a target device is under thermal throttling. 174 175 Args: 176 retry_delay_secs: integer, if not 0, retry after the specified seconds. 177 """ 178 throttling = self.IsUnderThermalThrottling() 179 if throttling and retry_delay_secs > 0: 180 logging.info("Wait for %s seconds for the target to cool down.", 181 retry_delay_secs) 182 time.sleep(retry_delay_secs) 183 throttling = self.IsUnderThermalThrottling() 184 asserts.skipIf(throttling, "Thermal throttling") 185 186