• 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._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