You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
247 lines
10 KiB
247 lines
10 KiB
#
|
|
# 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")
|