Sergei Trofimov | 4e6afe9 | 2015-10-09 09:30:04 +0100 | [diff] [blame] | 1 | # 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 Wall | 07048ee | 2017-08-31 16:26:35 -0700 | [diff] [blame] | 15 | import json |
Sergei Trofimov | 4e6afe9 | 2015-10-09 09:30:04 +0100 | [diff] [blame] | 16 | from devlib.module import Module |
| 17 | from devlib.exception import TargetError |
| 18 | from devlib.utils.misc import memoized |
| 19 | |
| 20 | |
| 21 | # a dict of governor name and a list of it tunables that can't be read |
| 22 | WRITE_ONLY_TUNABLES = { |
| 23 | 'interactive': ['boostpulse'] |
| 24 | } |
| 25 | |
| 26 | |
| 27 | class CpufreqModule(Module): |
| 28 | |
| 29 | name = 'cpufreq' |
| 30 | |
| 31 | @staticmethod |
| 32 | def probe(target): |
Patrick Bellasi | cbe80da | 2015-10-01 17:59:12 +0100 | [diff] [blame] | 33 | |
| 34 | # x86 with Intel P-State driver |
Patrick Bellasi | b19d76d | 2015-09-15 10:25:15 +0100 | [diff] [blame] | 35 | if target.abi == 'x86_64': |
| 36 | path = '/sys/devices/system/cpu/intel_pstate' |
Patrick Bellasi | cbe80da | 2015-10-01 17:59:12 +0100 | [diff] [blame] | 37 | 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 Trofimov | 4e6afe9 | 2015-10-09 09:30:04 +0100 | [diff] [blame] | 47 | 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 Jackman | a6fb5b5 | 2017-04-10 11:51:50 +0100 | [diff] [blame] | 137 | :param cpu: The cpu for which the governor will be set. ``int`` or |
Sergei Trofimov | 4e6afe9 | 2015-10-09 09:30:04 +0100 | [diff] [blame] | 138 | 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 Jackman | 5c036ea | 2017-04-10 17:39:13 +0100 | [diff] [blame] | 156 | path = '/sys/devices/system/cpu/{}/cpufreq/{}/{}'.format(cpu, governor, tunable) |
Sergei Trofimov | 4e6afe9 | 2015-10-09 09:30:04 +0100 | [diff] [blame] | 157 | try: |
Sergei Trofimov | 4e6afe9 | 2015-10-09 09:30:04 +0100 | [diff] [blame] | 158 | self.target.write_value(path, value) |
Brendan Jackman | 5c036ea | 2017-04-10 17:39:13 +0100 | [diff] [blame] | 159 | 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 Trofimov | 4e6afe9 | 2015-10-09 09:30:04 +0100 | [diff] [blame] | 164 | 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 Bellasi | 4dcb497 | 2015-09-16 18:03:04 +0100 | [diff] [blame] | 334 | |
| 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 Bonnici | b444ae6 | 2017-03-17 15:37:45 +0000 | [diff] [blame] | 342 | for cpu in cpus: |
| 343 | self.set_governor(cpu, governor, **kwargs) |
Patrick Bellasi | 4dcb497 | 2015-09-16 18:03:04 +0100 | [diff] [blame] | 344 | |
| 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 Bonnici | b444ae6 | 2017-03-17 15:37:45 +0000 | [diff] [blame] | 352 | for cpu in cpus: |
Patrick Bellasi | 4dcb497 | 2015-09-16 18:03:04 +0100 | [diff] [blame] | 353 | self.set_frequency(cpu, freq, exact) |
| 354 | |
Patrick Bellasi | cf76131 | 2015-11-27 16:38:30 +0000 | [diff] [blame] | 355 | 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 Bellasi | 51b7f01 | 2015-11-27 16:40:58 +0000 | [diff] [blame] | 363 | 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 Bellasi | b598b38 | 2015-09-17 17:39:32 +0100 | [diff] [blame] | 376 | |
| 377 | def set_all_governors(self, governor): |
Patrick Bellasi | cf76131 | 2015-11-27 16:38:30 +0000 | [diff] [blame] | 378 | """ |
| 379 | Set the specified governor for all the (online) CPUs |
| 380 | """ |
Brendan Jackman | af4214c | 2016-10-10 12:27:34 +0100 | [diff] [blame] | 381 | try: |
| 382 | return self.target._execute_util( |
Patrick Bellasi | cf76131 | 2015-11-27 16:38:30 +0000 | [diff] [blame] | 383 | 'cpufreq_set_all_governors {}'.format(governor), |
| 384 | as_root=True) |
Brendan Jackman | af4214c | 2016-10-10 12:27:34 +0100 | [diff] [blame] | 385 | except TargetError as e: |
Brendan Jackman | 0cac92a | 2017-06-06 14:56:57 +0100 | [diff] [blame] | 386 | if ("echo: I/O error" in str(e) or |
| 387 | "write error: Invalid argument" in str(e)): |
| 388 | |
Brendan Jackman | af4214c | 2016-10-10 12:27:34 +0100 | [diff] [blame] | 389 | 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 Bellasi | cf76131 | 2015-11-27 16:38:30 +0000 | [diff] [blame] | 395 | |
Patrick Bellasi | 51b7f01 | 2015-11-27 16:40:58 +0000 | [diff] [blame] | 396 | 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 Bellasi | b598b38 | 2015-09-17 17:39:32 +0100 | [diff] [blame] | 409 | |
Patrick Bellasi | 701e6ad | 2015-11-16 17:40:46 +0000 | [diff] [blame] | 410 | def trace_frequencies(self): |
| 411 | """ |
| 412 | Report current frequencies on trace file |
| 413 | """ |
Patrick Bellasi | cf76131 | 2015-11-27 16:38:30 +0000 | [diff] [blame] | 414 | return self.target._execute_util('cpufreq_trace_all_frequencies', as_root=True) |
Patrick Bellasi | 701e6ad | 2015-11-16 17:40:46 +0000 | [diff] [blame] | 415 | |
Sergei Trofimov | 36aa3af | 2017-07-26 13:59:55 +0100 | [diff] [blame] | 416 | def get_affected_cpus(self, cpu): |
| 417 | """ |
Sergei Trofimov | e206e9b | 2017-07-26 14:20:58 +0100 | [diff] [blame] | 418 | Get the online CPUs that share a frequency domain with the given CPU |
Sergei Trofimov | 36aa3af | 2017-07-26 13:59:55 +0100 | [diff] [blame] | 419 | """ |
| 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 Jackman | f1b4bf2 | 2016-10-10 14:05:09 +0100 | [diff] [blame] | 427 | @memoized |
Sergei Trofimov | 63e6040 | 2017-07-26 14:21:55 +0100 | [diff] [blame] | 428 | def get_related_cpus(self, cpu): |
Brendan Jackman | f1b4bf2 | 2016-10-10 14:05:09 +0100 | [diff] [blame] | 429 | """ |
| 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 Trofimov | 36aa3af | 2017-07-26 13:59:55 +0100 | [diff] [blame] | 435 | sysfile = '/sys/devices/system/cpu/{}/cpufreq/related_cpus'.format(cpu) |
Brendan Jackman | f1b4bf2 | 2016-10-10 14:05:09 +0100 | [diff] [blame] | 436 | |
| 437 | return [int(c) for c in self.target.read_value(sysfile).split()] |
Brendan Jackman | dc32fa9 | 2017-05-17 10:43:34 +0100 | [diff] [blame] | 438 | |
| 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 Trofimov | 63e6040 | 2017-07-26 14:21:55 +0100 | [diff] [blame] | 446 | domain = self.target.cpufreq.get_related_cpus(cpu) |
Brendan Jackman | dc32fa9 | 2017-05-17 10:43:34 +0100 | [diff] [blame] | 447 | yield domain |
| 448 | cpus = cpus.difference(domain) |
Marissa Wall | 07048ee | 2017-08-31 16:26:35 -0700 | [diff] [blame] | 449 | |
| 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) |