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._GetMinAndMaxCpuNo() 55 self._theoretical_max_frequency = {} 56 self._init = True 57 58 def _GetMinAndMaxCpuNo(self): 59 """Returns the min and max CPU numbers. 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 _GetTheoreticalMaxFrequency(self, cpu_no): 77 """Reads max value from cpufreq/scaling_available_frequencies. 78 79 If the read operation is successful, the return value is kept in 80 _theoretical_max_frequency as a cache. 81 82 Args: 83 cpu_no: integer, the CPU number. 84 85 Returns: 86 An integer which is the max frequency read from the file. 87 None if the file cannot be read. 88 """ 89 if cpu_no in self._theoretical_max_frequency: 90 return self._theoretical_max_frequency[cpu_no] 91 results = self._shell.Execute( 92 "cat /sys/devices/system/cpu/cpu%s/" 93 "cpufreq/scaling_available_frequencies" % cpu_no) 94 asserts.assertEqual(1, len(results[const.EXIT_CODE])) 95 if not results[const.EXIT_CODE][0]: 96 freq = [int(x) for x in results[const.STDOUT][0].split()] 97 self._theoretical_max_frequency[cpu_no] = max(freq) 98 return self._theoretical_max_frequency[cpu_no] 99 else: 100 logging.warn("cpufreq/scaling_available_frequencies for cpu %s" 101 " not set.", cpu_no) 102 return None 103 104 def ChangeCpuGovernor(self, mode): 105 """Changes the CPU governor mode of all the CPUs on the device. 106 107 Args: 108 mode: expected CPU governor mode, e.g., 'performance' or 'interactive'. 109 """ 110 self.Init() 111 for cpu_no in range(self._min_cpu_number, self._max_cpu_number): 112 results = self._shell.Execute( 113 "echo %s > /sys/devices/system/cpu/cpu%s/" 114 "cpufreq/scaling_governor" % (mode, cpu_no)) 115 asserts.assertEqual(1, len(results[const.EXIT_CODE])) 116 if results[const.EXIT_CODE][0]: 117 logging.warn("Can't change CPU governor.") 118 logging.warn("Stderr for scaling_governor: %s", 119 results[const.STDERR][0]) 120 121 def DisableCpuScaling(self): 122 """Disable CPU frequency scaling on the device.""" 123 self.ChangeCpuGovernor("performance") 124 125 def EnableCpuScaling(self): 126 """Enable CPU frequency scaling on the device.""" 127 self.ChangeCpuGovernor("interactive") 128 129 def IsUnderThermalThrottling(self): 130 """Checks whether a target device is under thermal throttling. 131 132 Returns: 133 True if the current CPU frequency is not the theoretical max, 134 False otherwise. 135 """ 136 self.Init() 137 for cpu_no in range(self._min_cpu_number, self._max_cpu_number): 138 results = self._shell.Execute( 139 ["cat /sys/devices/system/cpu/cpu%s/cpufreq/scaling_max_freq" % cpu_no, 140 "cat /sys/devices/system/cpu/cpu%s/cpufreq/scaling_cur_freq" % cpu_no]) 141 asserts.assertEqual(2, len(results[const.STDOUT])) 142 if any(results[const.EXIT_CODE]): 143 logging.warn("Can't check the current and/or max CPU frequency.") 144 logging.warn("Stderr for scaling_max_freq: %s", results[const.STDERR][0]) 145 logging.warn("Stderr for scaling_cur_freq: %s", results[const.STDERR][1]) 146 return False 147 configurable_max_frequency = results[const.STDOUT][0].strip() 148 current_frequency = results[const.STDOUT][1].strip() 149 if configurable_max_frequency != current_frequency: 150 logging.error( 151 "CPU%s: Configurable max frequency %s != current frequency %s", 152 cpu_no, configurable_max_frequency, current_frequency) 153 return True 154 theoretical_max_frequency = self._GetTheoreticalMaxFrequency(cpu_no) 155 if (theoretical_max_frequency is not None and 156 theoretical_max_frequency != int(current_frequency)): 157 logging.error( 158 "CPU%s, Theoretical max frequency %d != scaling current frequency %s", 159 cpu_no, theoretical_max_frequency, current_frequency) 160 return True 161 return False 162 163 def SkipIfThermalThrottling(self, retry_delay_secs=0): 164 """Skips the current test case if a target device is under thermal throttling. 165 166 Args: 167 retry_delay_secs: integer, if not 0, retry after the specified seconds. 168 """ 169 throttling = self.IsUnderThermalThrottling() 170 if throttling and retry_delay_secs > 0: 171 logging.info("Wait for %s seconds for the target to cool down.", 172 retry_delay_secs) 173 time.sleep(retry_delay_secs) 174 throttling = self.IsUnderThermalThrottling() 175 asserts.skipIf(throttling, "Thermal throttling") 176 177