普通文本  |  247行  |  10.21 KB

#
# 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")