#
# Copyright (C) 2016 The Android Open Source Project
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
import logging
import time
from vts.runners.host import asserts
from vts.runners.host import const
class CpuFrequencyScalingController(object):
"""CPU Frequency Scaling Controller.
The implementation is based on the special files in
/sys/devices/system/cpu/. CPU availability is shown in multiple files,
including online, present, and possible. This class assumes that a present
CPU may dynamically switch its online status. If a CPU is online, its
frequency scaling can be adjusted by reading/writing the files in
cpuX/cpufreq/ where X is the CPU number.
Attributes:
_dut: the target device DUT instance.
_shell: Shell mirror object for communication with a target.
_min_cpu_number: integer, the min CPU number.
_max_cpu_number; integer, the max CPU number.
_theoretical_max_frequency: a dict where its key is the CPU number and
its value is an integer containing the
theoretical max CPU frequency.
_perf_override: boolean, true if this module has switched the device from
its normal cpufreq governor to the performance
governor.
_saved_governors: list of strings, the saved cpufreq governor for each
CPU on the device.
"""
def __init__(self, dut):
self._dut = dut
self._init = False
def Init(self):
"""Creates a shell mirror object and reads the configuration values."""
if self._init:
return
self._shell = self._dut.shell
self._min_cpu_number, self._max_cpu_number = self._LoadMinAndMaxCpuNo()
self._theoretical_max_frequency = {}
self._perf_override = False
self._saved_governors = None
self._init = True
def _LoadMinAndMaxCpuNo(self):
"""Reads the min and max CPU numbers from sysfs.
Returns:
integer: min CPU number (inclusive)
integer: max CPU number (exclusive)
"""
results = self._shell.Execute("cat /sys/devices/system/cpu/present")
asserts.assertEqual(len(results[const.STDOUT]), 1)
stdout_lines = results[const.STDOUT][0].split("\n")
stdout_split = stdout_lines[0].split('-')
asserts.assertLess(len(stdout_split), 3)
low = stdout_split[0]
high = stdout_split[1] if len(stdout_split) == 2 else low
logging.debug("present cpus: %s : %s" % (low, high))
return int(low), int(high) + 1
def GetMinAndMaxCpuNo(self):
"""Returns the min and max CPU numbers.
Returns:
integer: min CPU number (inclusive)
integer: max CPU number (exclusive)
"""
return self._min_cpu_number, self._max_cpu_number
def _GetTheoreticalMaxFrequency(self, cpu_no):
"""Reads max value from cpufreq/scaling_available_frequencies.
If the read operation is successful, the return value is kept in
_theoretical_max_frequency as a cache.
Args:
cpu_no: integer, the CPU number.
Returns:
An integer which is the max frequency read from the file.
None if the file cannot be read.
"""
if cpu_no in self._theoretical_max_frequency:
return self._theoretical_max_frequency[cpu_no]
results = self._shell.Execute(
"cat /sys/devices/system/cpu/cpu%s/"
"cpufreq/scaling_available_frequencies" % cpu_no)
asserts.assertEqual(1, len(results[const.EXIT_CODE]))
if not results[const.EXIT_CODE][0]:
freq = [int(x) for x in results[const.STDOUT][0].split()]
self._theoretical_max_frequency[cpu_no] = max(freq)
return self._theoretical_max_frequency[cpu_no]
else:
logging.warn("cpufreq/scaling_available_frequencies for cpu %s"
" not set.", cpu_no)
return None
def ChangeCpuGovernor(self, modes):
"""Changes the CPU governor mode of all the CPUs on the device.
Args:
modes: list of expected CPU governor modes, e.g., 'performance'
or 'schedutil'. The length of the list must be equal to
the number of CPUs on the device.
Returns:
A list of the previous governor modes if successful, None otherwise.
"""
self.Init()
asserts.assertEqual(self._max_cpu_number - self._min_cpu_number,
len(modes))
# save current governor settings
target_cmd = []
prev_govs = []
for cpu_no in range(self._min_cpu_number, self._max_cpu_number):
target_cmd.append("cat /sys/devices/system/cpu/cpu%s/cpufreq/"
"scaling_governor" % cpu_no)
results = self._shell.Execute(target_cmd)
asserts.assertEqual(self._max_cpu_number - self._min_cpu_number,
len(results[const.STDOUT]))
if any(results[const.EXIT_CODE]):
logging.warn("Unable to save governors")
logging.warn("Stderr for saving scaling_governor: %s",
results[const.STDERR])
return
for cpu_no in range(self._min_cpu_number, self._max_cpu_number):
prev_govs.append(results[const.STDOUT][cpu_no].rstrip())
# set new governor
target_cmd = []
for cpu_no in range(self._min_cpu_number, self._max_cpu_number):
target_cmd.append(
"echo %s > /sys/devices/system/cpu/cpu%s/cpufreq/"
"scaling_governor" % (modes[cpu_no], cpu_no))
results = self._shell.Execute(target_cmd)
asserts.assertEqual(self._max_cpu_number - self._min_cpu_number,
len(results[const.STDOUT]))
if any(results[const.EXIT_CODE]):
logging.warn("Can't change CPU governor.")
logging.warn("Stderr for changing scaling_governor: %s",
results[const.STDERR])
return
return prev_govs
def DisableCpuScaling(self):
"""Disable CPU frequency scaling on the device."""
self.Init()
if self._perf_override:
logging.warn(
"DisableCpuScaling called while scaling already disabled.")
return
new_govs = []
for cpu_no in range(self._min_cpu_number, self._max_cpu_number):
new_govs.append("performance")
prev_govs = self.ChangeCpuGovernor(new_govs)
if prev_govs is not None:
self._saved_governors = prev_govs
self._perf_override = True
def EnableCpuScaling(self):
"""Enable CPU frequency scaling on the device."""
self.Init()
if not self._perf_override:
logging.warn(
"EnableCpuScaling called while scaling already enabled.")
return
if self._saved_governors is None:
logging.warn(
"EnableCpuScaling called and _saved_governors is None.")
return
self.ChangeCpuGovernor(self._saved_governors)
self._perf_override = False
def IsUnderThermalThrottling(self):
"""Checks whether a target device is under thermal throttling.
Returns:
True if the current CPU frequency is not the theoretical max,
False otherwise.
"""
self.Init()
for cpu_no in range(self._min_cpu_number, self._max_cpu_number):
results = self._shell.Execute([
"cat /sys/devices/system/cpu/cpu%s/cpufreq/scaling_max_freq" %
cpu_no,
"cat /sys/devices/system/cpu/cpu%s/cpufreq/scaling_cur_freq" %
cpu_no
])
asserts.assertEqual(2, len(results[const.STDOUT]))
if any(results[const.EXIT_CODE]):
logging.warn(
"Can't check the current and/or max CPU frequency.")
logging.warn("Stderr for scaling_max_freq: %s",
results[const.STDERR][0])
logging.warn("Stderr for scaling_cur_freq: %s",
results[const.STDERR][1])
return False
configurable_max_frequency = results[const.STDOUT][0].strip()
current_frequency = results[const.STDOUT][1].strip()
if configurable_max_frequency > current_frequency:
logging.error(
"CPU%s: Configurable max frequency %s > current frequency %s",
cpu_no, configurable_max_frequency, current_frequency)
return True
theoretical_max_frequency = self._GetTheoreticalMaxFrequency(
cpu_no)
if (theoretical_max_frequency is not None
and theoretical_max_frequency > int(current_frequency)):
logging.error(
"CPU%s, Theoretical max frequency %d > scaling current frequency %s",
cpu_no, theoretical_max_frequency, current_frequency)
return True
return False
def SkipIfThermalThrottling(self, retry_delay_secs=0):
"""Skips the current test case if a target device is under thermal throttling.
Args:
retry_delay_secs: integer, if not 0, retry after the specified seconds.
"""
throttling = self.IsUnderThermalThrottling()
if throttling and retry_delay_secs > 0:
logging.info("Waiting %s seconds for the target CPU to cool down.",
retry_delay_secs)
time.sleep(retry_delay_secs)
throttling = self.IsUnderThermalThrottling()
asserts.skipIf(throttling, "Thermal throttling")