• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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