blob: 5ce3c8396db2c3a27637b237ddd17efc01673b4c [file] [log] [blame]
Sergei Trofimov4e6afe92015-10-09 09:30:04 +01001# Copyright 2014-2015 ARM Limited
2#
3# Licensed under the Apache License, Version 2.0 (the "License");
4# you may not use this file except in compliance with the License.
5# You may obtain a copy of the License at
6#
7# http://www.apache.org/licenses/LICENSE-2.0
8#
9# Unless required by applicable law or agreed to in writing, software
10# distributed under the License is distributed on an "AS IS" BASIS,
11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12# See the License for the specific language governing permissions and
13# limitations under the License.
14#
Marissa Wall07048ee2017-08-31 16:26:35 -070015import json
Sergei Trofimov4e6afe92015-10-09 09:30:04 +010016from devlib.module import Module
17from devlib.exception import TargetError
18from devlib.utils.misc import memoized
19
20
21# a dict of governor name and a list of it tunables that can't be read
22WRITE_ONLY_TUNABLES = {
23 'interactive': ['boostpulse']
24}
25
26
27class CpufreqModule(Module):
28
29 name = 'cpufreq'
30
31 @staticmethod
32 def probe(target):
Patrick Bellasicbe80da2015-10-01 17:59:12 +010033
34 # x86 with Intel P-State driver
Patrick Bellasib19d76d2015-09-15 10:25:15 +010035 if target.abi == 'x86_64':
36 path = '/sys/devices/system/cpu/intel_pstate'
Patrick Bellasicbe80da2015-10-01 17:59:12 +010037 if target.file_exists(path):
38 return True
39
40 # Generic CPUFreq support (single policy)
41 path = '/sys/devices/system/cpu/cpufreq'
42 if target.file_exists(path):
43 return True
44
45 # Generic CPUFreq support (per CPU policy)
46 path = '/sys/devices/system/cpu/cpu0/cpufreq'
Sergei Trofimov4e6afe92015-10-09 09:30:04 +010047 return target.file_exists(path)
48
49 def __init__(self, target):
50 super(CpufreqModule, self).__init__(target)
51 self._governor_tunables = {}
52
53 @memoized
54 def list_governors(self, cpu):
55 """Returns a list of governors supported by the cpu."""
56 if isinstance(cpu, int):
57 cpu = 'cpu{}'.format(cpu)
58 sysfile = '/sys/devices/system/cpu/{}/cpufreq/scaling_available_governors'.format(cpu)
59 output = self.target.read_value(sysfile)
60 return output.strip().split()
61
62 def get_governor(self, cpu):
63 """Returns the governor currently set for the specified CPU."""
64 if isinstance(cpu, int):
65 cpu = 'cpu{}'.format(cpu)
66 sysfile = '/sys/devices/system/cpu/{}/cpufreq/scaling_governor'.format(cpu)
67 return self.target.read_value(sysfile)
68
69 def set_governor(self, cpu, governor, **kwargs):
70 """
71 Set the governor for the specified CPU.
72 See https://www.kernel.org/doc/Documentation/cpu-freq/governors.txt
73
74 :param cpu: The CPU for which the governor is to be set. This must be
75 the full name as it appears in sysfs, e.g. "cpu0".
76 :param governor: The name of the governor to be used. This must be
77 supported by the specific device.
78
79 Additional keyword arguments can be used to specify governor tunables for
80 governors that support them.
81
82 :note: On big.LITTLE all cores in a cluster must be using the same governor.
83 Setting the governor on any core in a cluster will also set it on all
84 other cores in that cluster.
85
86 :raises: TargetError if governor is not supported by the CPU, or if,
87 for some reason, the governor could not be set.
88
89 """
90 if isinstance(cpu, int):
91 cpu = 'cpu{}'.format(cpu)
92 supported = self.list_governors(cpu)
93 if governor not in supported:
94 raise TargetError('Governor {} not supported for cpu {}'.format(governor, cpu))
95 sysfile = '/sys/devices/system/cpu/{}/cpufreq/scaling_governor'.format(cpu)
96 self.target.write_value(sysfile, governor)
97 self.set_governor_tunables(cpu, governor, **kwargs)
98
99 def list_governor_tunables(self, cpu):
100 """Returns a list of tunables available for the governor on the specified CPU."""
101 if isinstance(cpu, int):
102 cpu = 'cpu{}'.format(cpu)
103 governor = self.get_governor(cpu)
104 if governor not in self._governor_tunables:
105 try:
106 tunables_path = '/sys/devices/system/cpu/{}/cpufreq/{}'.format(cpu, governor)
107 self._governor_tunables[governor] = self.target.list_directory(tunables_path)
108 except TargetError: # probably an older kernel
109 try:
110 tunables_path = '/sys/devices/system/cpu/cpufreq/{}'.format(governor)
111 self._governor_tunables[governor] = self.target.list_directory(tunables_path)
112 except TargetError: # governor does not support tunables
113 self._governor_tunables[governor] = []
114 return self._governor_tunables[governor]
115
116 def get_governor_tunables(self, cpu):
117 if isinstance(cpu, int):
118 cpu = 'cpu{}'.format(cpu)
119 governor = self.get_governor(cpu)
120 tunables = {}
121 for tunable in self.list_governor_tunables(cpu):
122 if tunable not in WRITE_ONLY_TUNABLES.get(governor, []):
123 try:
124 path = '/sys/devices/system/cpu/{}/cpufreq/{}/{}'.format(cpu, governor, tunable)
125 tunables[tunable] = self.target.read_value(path)
126 except TargetError: # May be an older kernel
127 path = '/sys/devices/system/cpu/cpufreq/{}/{}'.format(governor, tunable)
128 tunables[tunable] = self.target.read_value(path)
129 return tunables
130
131 def set_governor_tunables(self, cpu, governor=None, **kwargs):
132 """
133 Set tunables for the specified governor. Tunables should be specified as
134 keyword arguments. Which tunables and values are valid depends on the
135 governor.
136
Brendan Jackmana6fb5b52017-04-10 11:51:50 +0100137 :param cpu: The cpu for which the governor will be set. ``int`` or
Sergei Trofimov4e6afe92015-10-09 09:30:04 +0100138 full cpu name as it appears in sysfs, e.g. ``cpu0``.
139 :param governor: The name of the governor. Must be all lower case.
140
141 The rest should be keyword parameters mapping tunable name onto the value to
142 be set for it.
143
144 :raises: TargetError if governor specified is not a valid governor name, or if
145 a tunable specified is not valid for the governor, or if could not set
146 tunable.
147
148 """
149 if isinstance(cpu, int):
150 cpu = 'cpu{}'.format(cpu)
151 if governor is None:
152 governor = self.get_governor(cpu)
153 valid_tunables = self.list_governor_tunables(cpu)
154 for tunable, value in kwargs.iteritems():
155 if tunable in valid_tunables:
Brendan Jackman5c036ea2017-04-10 17:39:13 +0100156 path = '/sys/devices/system/cpu/{}/cpufreq/{}/{}'.format(cpu, governor, tunable)
Sergei Trofimov4e6afe92015-10-09 09:30:04 +0100157 try:
Sergei Trofimov4e6afe92015-10-09 09:30:04 +0100158 self.target.write_value(path, value)
Brendan Jackman5c036ea2017-04-10 17:39:13 +0100159 except TargetError:
160 if self.target.file_exists(path):
161 # File exists but we did something wrong
162 raise
163 # Expected file doesn't exist, try older sysfs layout.
Sergei Trofimov4e6afe92015-10-09 09:30:04 +0100164 path = '/sys/devices/system/cpu/cpufreq/{}/{}'.format(governor, tunable)
165 self.target.write_value(path, value)
166 else:
167 message = 'Unexpected tunable {} for governor {} on {}.\n'.format(tunable, governor, cpu)
168 message += 'Available tunables are: {}'.format(valid_tunables)
169 raise TargetError(message)
170
171 @memoized
172 def list_frequencies(self, cpu):
173 """Returns a list of frequencies supported by the cpu or an empty list
174 if not could be found."""
175 if isinstance(cpu, int):
176 cpu = 'cpu{}'.format(cpu)
177 try:
178 cmd = 'cat /sys/devices/system/cpu/{}/cpufreq/scaling_available_frequencies'.format(cpu)
179 output = self.target.execute(cmd)
180 available_frequencies = map(int, output.strip().split()) # pylint: disable=E1103
181 except TargetError:
182 # On some devices scaling_frequencies is not generated.
183 # http://adrynalyne-teachtofish.blogspot.co.uk/2011/11/how-to-enable-scalingavailablefrequenci.html
184 # Fall back to parsing stats/time_in_state
185 cmd = 'cat /sys/devices/system/cpu/{}/cpufreq/stats/time_in_state'.format(cpu)
186 out_iter = iter(self.target.execute(cmd).strip().split())
187 available_frequencies = map(int, reversed([f for f, _ in zip(out_iter, out_iter)]))
188 return available_frequencies
189
190 def get_min_frequency(self, cpu):
191 """
192 Returns the min frequency currently set for the specified CPU.
193
194 Warning, this method does not check if the cpu is online or not. It will
195 try to read the minimum frequency and the following exception will be
196 raised ::
197
198 :raises: TargetError if for some reason the frequency could not be read.
199
200 """
201 if isinstance(cpu, int):
202 cpu = 'cpu{}'.format(cpu)
203 sysfile = '/sys/devices/system/cpu/{}/cpufreq/scaling_min_freq'.format(cpu)
204 return self.target.read_int(sysfile)
205
206 def set_min_frequency(self, cpu, frequency, exact=True):
207 """
208 Set's the minimum value for CPU frequency. Actual frequency will
209 depend on the Governor used and may vary during execution. The value should be
210 either an int or a string representing an integer. The Value must also be
211 supported by the device. The available frequencies can be obtained by calling
212 get_frequencies() or examining
213
214 /sys/devices/system/cpu/cpuX/cpufreq/scaling_frequencies
215
216 on the device.
217
218 :raises: TargetError if the frequency is not supported by the CPU, or if, for
219 some reason, frequency could not be set.
220 :raises: ValueError if ``frequency`` is not an integer.
221
222 """
223 if isinstance(cpu, int):
224 cpu = 'cpu{}'.format(cpu)
225 available_frequencies = self.list_frequencies(cpu)
226 try:
227 value = int(frequency)
228 if exact and available_frequencies and value not in available_frequencies:
229 raise TargetError('Can\'t set {} frequency to {}\nmust be in {}'.format(cpu,
230 value,
231 available_frequencies))
232 sysfile = '/sys/devices/system/cpu/{}/cpufreq/scaling_min_freq'.format(cpu)
233 self.target.write_value(sysfile, value)
234 except ValueError:
235 raise ValueError('Frequency must be an integer; got: "{}"'.format(frequency))
236
237 def get_frequency(self, cpu):
238 """
239 Returns the current frequency currently set for the specified CPU.
240
241 Warning, this method does not check if the cpu is online or not. It will
242 try to read the current frequency and the following exception will be
243 raised ::
244
245 :raises: TargetError if for some reason the frequency could not be read.
246
247 """
248 if isinstance(cpu, int):
249 cpu = 'cpu{}'.format(cpu)
250 sysfile = '/sys/devices/system/cpu/{}/cpufreq/scaling_cur_freq'.format(cpu)
251 return self.target.read_int(sysfile)
252
253 def set_frequency(self, cpu, frequency, exact=True):
254 """
255 Set's the minimum value for CPU frequency. Actual frequency will
256 depend on the Governor used and may vary during execution. The value should be
257 either an int or a string representing an integer.
258
259 If ``exact`` flag is set (the default), the Value must also be supported by
260 the device. The available frequencies can be obtained by calling
261 get_frequencies() or examining
262
263 /sys/devices/system/cpu/cpuX/cpufreq/scaling_frequencies
264
265 on the device (if it exists).
266
267 :raises: TargetError if the frequency is not supported by the CPU, or if, for
268 some reason, frequency could not be set.
269 :raises: ValueError if ``frequency`` is not an integer.
270
271 """
272 if isinstance(cpu, int):
273 cpu = 'cpu{}'.format(cpu)
274 try:
275 value = int(frequency)
276 if exact:
277 available_frequencies = self.list_frequencies(cpu)
278 if available_frequencies and value not in available_frequencies:
279 raise TargetError('Can\'t set {} frequency to {}\nmust be in {}'.format(cpu,
280 value,
281 available_frequencies))
282 if self.get_governor(cpu) != 'userspace':
283 raise TargetError('Can\'t set {} frequency; governor must be "userspace"'.format(cpu))
284 sysfile = '/sys/devices/system/cpu/{}/cpufreq/scaling_setspeed'.format(cpu)
285 self.target.write_value(sysfile, value, verify=False)
286 except ValueError:
287 raise ValueError('Frequency must be an integer; got: "{}"'.format(frequency))
288
289 def get_max_frequency(self, cpu):
290 """
291 Returns the max frequency currently set for the specified CPU.
292
293 Warning, this method does not check if the cpu is online or not. It will
294 try to read the maximum frequency and the following exception will be
295 raised ::
296
297 :raises: TargetError if for some reason the frequency could not be read.
298 """
299 if isinstance(cpu, int):
300 cpu = 'cpu{}'.format(cpu)
301 sysfile = '/sys/devices/system/cpu/{}/cpufreq/scaling_max_freq'.format(cpu)
302 return self.target.read_int(sysfile)
303
304 def set_max_frequency(self, cpu, frequency, exact=True):
305 """
306 Set's the minimum value for CPU frequency. Actual frequency will
307 depend on the Governor used and may vary during execution. The value should be
308 either an int or a string representing an integer. The Value must also be
309 supported by the device. The available frequencies can be obtained by calling
310 get_frequencies() or examining
311
312 /sys/devices/system/cpu/cpuX/cpufreq/scaling_frequencies
313
314 on the device.
315
316 :raises: TargetError if the frequency is not supported by the CPU, or if, for
317 some reason, frequency could not be set.
318 :raises: ValueError if ``frequency`` is not an integer.
319
320 """
321 if isinstance(cpu, int):
322 cpu = 'cpu{}'.format(cpu)
323 available_frequencies = self.list_frequencies(cpu)
324 try:
325 value = int(frequency)
326 if exact and available_frequencies and value not in available_frequencies:
327 raise TargetError('Can\'t set {} frequency to {}\nmust be in {}'.format(cpu,
328 value,
329 available_frequencies))
330 sysfile = '/sys/devices/system/cpu/{}/cpufreq/scaling_max_freq'.format(cpu)
331 self.target.write_value(sysfile, value)
332 except ValueError:
333 raise ValueError('Frequency must be an integer; got: "{}"'.format(frequency))
Patrick Bellasi4dcb4972015-09-16 18:03:04 +0100334
335 def set_governor_for_cpus(self, cpus, governor, **kwargs):
336 """
337 Set the governor for the specified list of CPUs.
338 See https://www.kernel.org/doc/Documentation/cpu-freq/governors.txt
339
340 :param cpus: The list of CPU for which the governor is to be set.
341 """
Marc Bonnicib444ae62017-03-17 15:37:45 +0000342 for cpu in cpus:
343 self.set_governor(cpu, governor, **kwargs)
Patrick Bellasi4dcb4972015-09-16 18:03:04 +0100344
345 def set_frequency_for_cpus(self, cpus, freq, exact=False):
346 """
347 Set the frequency for the specified list of CPUs.
348 See https://www.kernel.org/doc/Documentation/cpu-freq/governors.txt
349
350 :param cpus: The list of CPU for which the frequency has to be set.
351 """
Marc Bonnicib444ae62017-03-17 15:37:45 +0000352 for cpu in cpus:
Patrick Bellasi4dcb4972015-09-16 18:03:04 +0100353 self.set_frequency(cpu, freq, exact)
354
Patrick Bellasicf761312015-11-27 16:38:30 +0000355 def set_all_frequencies(self, freq):
356 """
357 Set the specified (minimum) frequency for all the (online) CPUs
358 """
359 return self.target._execute_util(
360 'cpufreq_set_all_frequencies {}'.format(freq),
361 as_root=True)
362
Patrick Bellasi51b7f012015-11-27 16:40:58 +0000363 def get_all_frequencies(self):
364 """
365 Get the current frequency for all the (online) CPUs
366 """
367 output = self.target._execute_util(
368 'cpufreq_get_all_frequencies', as_root=True)
369 frequencies = {}
370 for x in output.splitlines():
371 kv = x.split(' ')
372 if kv[0] == '':
373 break
374 frequencies[kv[0]] = kv[1]
375 return frequencies
Patrick Bellasib598b382015-09-17 17:39:32 +0100376
377 def set_all_governors(self, governor):
Patrick Bellasicf761312015-11-27 16:38:30 +0000378 """
379 Set the specified governor for all the (online) CPUs
380 """
Brendan Jackmanaf4214c2016-10-10 12:27:34 +0100381 try:
382 return self.target._execute_util(
Patrick Bellasicf761312015-11-27 16:38:30 +0000383 'cpufreq_set_all_governors {}'.format(governor),
384 as_root=True)
Brendan Jackmanaf4214c2016-10-10 12:27:34 +0100385 except TargetError as e:
Brendan Jackman0cac92a2017-06-06 14:56:57 +0100386 if ("echo: I/O error" in str(e) or
387 "write error: Invalid argument" in str(e)):
388
Brendan Jackmanaf4214c2016-10-10 12:27:34 +0100389 cpus_unsupported = [c for c in self.target.list_online_cpus()
390 if governor not in self.list_governors(c)]
391 raise TargetError("Governor {} unsupported for CPUs {}".format(
392 governor, cpus_unsupported))
393 else:
394 raise
Patrick Bellasicf761312015-11-27 16:38:30 +0000395
Patrick Bellasi51b7f012015-11-27 16:40:58 +0000396 def get_all_governors(self):
397 """
398 Get the current governor for all the (online) CPUs
399 """
400 output = self.target._execute_util(
401 'cpufreq_get_all_governors', as_root=True)
402 governors = {}
403 for x in output.splitlines():
404 kv = x.split(' ')
405 if kv[0] == '':
406 break
407 governors[kv[0]] = kv[1]
408 return governors
Patrick Bellasib598b382015-09-17 17:39:32 +0100409
Patrick Bellasi701e6ad2015-11-16 17:40:46 +0000410 def trace_frequencies(self):
411 """
412 Report current frequencies on trace file
413 """
Patrick Bellasicf761312015-11-27 16:38:30 +0000414 return self.target._execute_util('cpufreq_trace_all_frequencies', as_root=True)
Patrick Bellasi701e6ad2015-11-16 17:40:46 +0000415
Sergei Trofimov36aa3af2017-07-26 13:59:55 +0100416 def get_affected_cpus(self, cpu):
417 """
Sergei Trofimove206e9b2017-07-26 14:20:58 +0100418 Get the online CPUs that share a frequency domain with the given CPU
Sergei Trofimov36aa3af2017-07-26 13:59:55 +0100419 """
420 if isinstance(cpu, int):
421 cpu = 'cpu{}'.format(cpu)
422
423 sysfile = '/sys/devices/system/cpu/{}/cpufreq/affected_cpus'.format(cpu)
424
425 return [int(c) for c in self.target.read_value(sysfile).split()]
426
Brendan Jackmanf1b4bf22016-10-10 14:05:09 +0100427 @memoized
Sergei Trofimov63e60402017-07-26 14:21:55 +0100428 def get_related_cpus(self, cpu):
Brendan Jackmanf1b4bf22016-10-10 14:05:09 +0100429 """
430 Get the CPUs that share a frequency domain with the given CPU
431 """
432 if isinstance(cpu, int):
433 cpu = 'cpu{}'.format(cpu)
434
Sergei Trofimov36aa3af2017-07-26 13:59:55 +0100435 sysfile = '/sys/devices/system/cpu/{}/cpufreq/related_cpus'.format(cpu)
Brendan Jackmanf1b4bf22016-10-10 14:05:09 +0100436
437 return [int(c) for c in self.target.read_value(sysfile).split()]
Brendan Jackmandc32fa92017-05-17 10:43:34 +0100438
439 def iter_domains(self):
440 """
441 Iterate over the frequency domains in the system
442 """
443 cpus = set(range(self.target.number_of_cpus))
444 while cpus:
445 cpu = iter(cpus).next()
Sergei Trofimov63e60402017-07-26 14:21:55 +0100446 domain = self.target.cpufreq.get_related_cpus(cpu)
Brendan Jackmandc32fa92017-05-17 10:43:34 +0100447 yield domain
448 cpus = cpus.difference(domain)
Marissa Wall07048ee2017-08-31 16:26:35 -0700449
450 def get_time_in_state(self, clusters):
451 """
452 Gets the time at each frequency on each cluster
453 :param clusters: A list of clusters on the device. Each cluster is a
454 list of cpus on that cluster.
455 """
456 time_in_state_by_cluster = {}
457
458 for i, cluster in enumerate(clusters):
459 frequencies = self.list_frequencies(cluster[0])
460 time_in_state = dict((str(freq), 0) for freq in frequencies)
461
462 for cpu in cluster:
463 stats = self.target.execute('cat '\
464 '/sys/devices/system/cpu/cpu{}/cpufreq/stats'
465 '/time_in_state'.format(cpu))
466 for entry in stats.split('\n'):
467 if len(entry) > 0:
468 freq, time = entry.split(' ')
469 time_in_state[freq] += int(time)
470
471 time_in_state_by_cluster[str(i)] = time_in_state
472
473 return time_in_state_by_cluster
474
475 def dump_time_in_state_delta(self, start, clusters, dump_file):
476 """
477 Dumps the time between the stop and start by cluster by frequency
478 :param start: The inital output from a call to cpufreq.get_time_in_state
479 :param clusters: A list of clusters on the device. Each cluster is a
480 list of cpus on that cluster.
481 :param dump_file: A file to dump the delta time_in_state_delta to.
482 """
483 stop = self.get_time_in_state(clusters)
484
485 time_in_state_delta = {}
486
487 for cl in start:
488 time_in_state_delta[cl] = {}
489
490 for freq in start[cl].keys():
491 time_in_start = start[cl][freq]
492 time_in_stop = stop[cl][freq]
493 time_in_state_delta[cl][freq] = time_in_stop - time_in_start
494
495 output = {'time_delta' : time_in_state_delta,
496 'clusters' : {str(i) : [str(c) for c in cl]
497 for i, cl in enumerate(clusters)}}
498
499 with open(dump_file, 'w') as dfile:
500 json.dump(output, dfile, indent=4, sort_keys=True)