xref: /aosp_15_r20/external/autotest/client/site_tests/power_CPUFreq/power_CPUFreq.py (revision 9c5db1993ded3edbeafc8092d69fe5de2ee02df7)
1# Lint as: python2, python3
2# Copyright (c) 2010 The Chromium OS Authors. All rights reserved.
3# Use of this source code is governed by a BSD-style license that can be
4# found in the LICENSE file.
5
6import glob
7import logging
8import os
9import time
10from autotest_lib.client.bin import test
11from autotest_lib.client.common_lib import error, utils
12
13SYSFS_CPUQUIET_ENABLE = '/sys/devices/system/cpu/cpuquiet/tegra_cpuquiet/enable'
14SYSFS_INTEL_PSTATE_PATH = '/sys/devices/system/cpu/intel_pstate'
15
16
17class power_CPUFreq(test.test):
18    version = 1
19
20    def initialize(self):
21        cpufreq_path = '/sys/devices/system/cpu/cpu*/cpufreq'
22
23        dirs = glob.glob(cpufreq_path)
24        if not dirs:
25            raise error.TestFail('cpufreq not supported')
26
27        self._cpufreq_dirs = dirs
28        self._cpus = [cpufreq(dirname) for dirname in dirs]
29        for cpu in self._cpus:
30            cpu.save_state()
31            cpu.disable_constraints()
32
33        # Store the setting if the system has CPUQuiet feature
34        if os.path.exists(SYSFS_CPUQUIET_ENABLE):
35            self.is_cpuquiet_enabled = utils.read_file(SYSFS_CPUQUIET_ENABLE)
36            utils.write_one_line(SYSFS_CPUQUIET_ENABLE, '0')
37
38    def run_once(self):
39        # TODO(crbug.com/485276) Revisit this exception once we've refactored
40        # test to account for intel_pstate cpufreq driver
41        if os.path.exists(SYSFS_INTEL_PSTATE_PATH):
42            raise error.TestNAError('Test does NOT support intel_pstate driver')
43
44        keyvals = {}
45        try:
46            # First attempt to set all frequencies on each core before going
47            # on to the next core.
48            self.test_cores_in_series()
49            # Record that it was the first test that passed.
50            keyvals['test_cores_in_series'] = 1
51        except error.TestFail as exception:
52            if str(exception) == 'Unable to set frequency':
53                # If test_cores_in_series fails, try to set each frequency for
54                # all cores before moving on to the next frequency.
55                logging.debug('trying to set freq in parallel')
56                self.test_cores_in_parallel()
57                # Record that it was the second test that passed.
58                keyvals['test_cores_in_parallel'] = 1
59            else:
60                raise exception
61
62        self.write_perf_keyval(keyvals)
63
64    def test_cores_in_series(self):
65        for cpu in self._cpus:
66            if 'userspace' not in cpu.get_available_governors():
67                raise error.TestError('userspace governor not supported')
68
69            available_frequencies = cpu.get_available_frequencies()
70            if len(available_frequencies) == 1:
71                raise error.TestFail('Not enough frequencies supported!')
72
73            # set cpufreq governor to userspace
74            cpu.set_governor('userspace')
75
76            # cycle through all available frequencies
77            for freq in available_frequencies:
78                cpu.set_frequency(freq)
79                if not self.compare_freqs(freq, cpu):
80                    raise error.TestFail('Unable to set frequency')
81
82    def test_cores_in_parallel(self):
83        cpus = self._cpus
84        cpu0 = self._cpus[0]
85
86        # Use the first CPU's frequencies for all CPUs.  Assume that they are
87        # the same.
88        available_frequencies = cpu0.get_available_frequencies()
89        if len(available_frequencies) == 1:
90            raise error.TestFail('Not enough frequencies supported!')
91
92        for cpu in cpus:
93            this_frequencies = cpu.get_available_frequencies()
94            if set(available_frequencies) != set(this_frequencies):
95                raise error.TestError(
96                    "Can't fallback to parallel test: %s / %s differ: %r / %r" %
97                    (cpu, cpu0, available_frequencies, this_frequencies))
98
99            if 'userspace' not in cpu.get_available_governors():
100                raise error.TestError('userspace governor not supported')
101
102            # set cpufreq governor to userspace
103            cpu.set_governor('userspace')
104
105        # cycle through all available frequencies
106        for freq in available_frequencies:
107            for cpu in cpus:
108                cpu.set_frequency(freq)
109            for cpu in cpus:
110                if not self.compare_freqs(freq, cpu):
111                    raise error.TestFail('Unable to set frequency')
112
113    def compare_freqs(self, set_freq, cpu):
114        # crbug.com/848309 : older kernels have race between setting
115        # governor and access to setting frequency so add a retry
116        try:
117            freq = cpu.get_current_frequency()
118        except IOError:
119            logging.warning('Frequency getting failed.  Retrying once.')
120            time.sleep(.1)
121            freq = cpu.get_current_frequency()
122
123        logging.debug('frequency set:%d vs actual:%d',
124                      set_freq, freq)
125
126        # setting freq to a particular frequency isn't reliable so just test
127        # that driver allows setting & getting.
128        if cpu.get_driver() == 'acpi-cpufreq':
129            return True
130
131        return set_freq == freq
132
133    def cleanup(self):
134        if self._cpus:
135            for cpu in self._cpus:
136                # restore cpufreq state
137                cpu.restore_state()
138
139        # Restore the original setting if system has CPUQuiet feature
140        if os.path.exists(SYSFS_CPUQUIET_ENABLE):
141            utils.open_write_close(SYSFS_CPUQUIET_ENABLE,
142                                   self.is_cpuquiet_enabled)
143
144
145class cpufreq(object):
146
147    def __init__(self, path):
148        self.__base_path = path
149        self.__save_files_list = [
150            'scaling_max_freq', 'scaling_min_freq', 'scaling_governor'
151        ]
152        self._freqs = None
153        # disable boost to limit how much freq can vary in userspace mode
154        if self.get_driver() == 'acpi-cpufreq':
155            self.disable_boost()
156
157    def __del__(self):
158        if self.get_driver() == 'acpi-cpufreq':
159            self.enable_boost()
160
161    def __str__(self):
162        return os.path.basename(os.path.dirname(self.__base_path))
163
164    def __write_file(self, file_name, data):
165        path = os.path.join(self.__base_path, file_name)
166        try:
167            utils.open_write_close(path, data)
168        except IOError as e:
169            logging.warning('write of %s failed: %s', path, str(e))
170
171    def __read_file(self, file_name):
172        path = os.path.join(self.__base_path, file_name)
173        f = open(path, 'r')
174        data = f.read()
175        f.close()
176        return data
177
178    def save_state(self):
179        logging.info('saving state:')
180        for fname in self.__save_files_list:
181            data = self.__read_file(fname)
182            setattr(self, fname, data)
183            logging.info(fname + ': ' + data)
184
185    def restore_state(self):
186        logging.info('restoring state:')
187        for fname in self.__save_files_list:
188            # Sometimes a newline gets appended to a data string and it throws
189            # an error when being written to a sysfs file.  Call strip() to
190            # eliminateextra whitespace characters so it can be written cleanly
191            # to the file.
192            data = getattr(self, fname).strip()
193            logging.info(fname + ': ' + data)
194            self.__write_file(fname, data)
195
196    def disable_constraints(self):
197        logging.info('disabling min/max constraints:')
198        self.__write_file('scaling_min_freq', str(self.get_min_frequency()))
199        self.__write_file('scaling_max_freq', str(self.get_max_frequency()))
200
201    def get_available_governors(self):
202        governors = self.__read_file('scaling_available_governors')
203        logging.info('available governors: %s', governors)
204        return governors.split()
205
206    def get_current_governor(self):
207        governor = self.__read_file('scaling_governor')
208        logging.info('current governor: %s', governor)
209        return governor.split()[0]
210
211    def get_driver(self):
212        driver = self.__read_file('scaling_driver')
213        logging.info('current driver: %s', driver)
214        return driver.split()[0]
215
216    def set_governor(self, governor):
217        logging.info('setting governor to %s', governor)
218        self.__write_file('scaling_governor', governor)
219
220    def get_available_frequencies(self):
221        if self._freqs:
222            return self._freqs
223        frequencies = self.__read_file('scaling_available_frequencies')
224        logging.info('available frequencies: %s', frequencies)
225        self._freqs = [int(i) for i in frequencies.split()]
226        return self._freqs
227
228    def get_current_frequency(self):
229        freq = int(self.__read_file('scaling_cur_freq'))
230        logging.info('current frequency: %s', freq)
231        return freq
232
233    def get_min_frequency(self):
234        freq = int(self.__read_file('cpuinfo_min_freq'))
235        logging.info('min frequency: %s', freq)
236        return freq
237
238    def get_max_frequency(self):
239        freq = int(self.__read_file('cpuinfo_max_freq'))
240        logging.info('max frequency: %s', freq)
241        return freq
242
243    def set_frequency(self, frequency):
244        logging.info('setting frequency to %d', frequency)
245        self.__write_file('scaling_setspeed', str(frequency))
246
247    def disable_boost(self):
248        """Disable boost.
249
250        Note, boost is NOT a per-cpu parameter,
251          /sys/device/system/cpu/cpufreq/boost
252
253        So code below would unnecessarily disable it per-cpu but that should not
254        cause any issues.
255        """
256        logging.debug('Disable boost')
257        self.__write_file('../../cpufreq/boost', '0')
258
259    def enable_boost(self):
260        """Enable boost.
261
262        Note, boost is NOT a per-cpu parameter,
263          /sys/device/system/cpu/cpufreq/boost
264
265        So code below would unnecessarily enable it per-cpu but that should not
266        cause any issues.
267        """
268        logging.debug('Enable boost')
269        self.__write_file('../../cpufreq/boost', '1')
270