|
| 1 | +#!/bin/env python3 |
| 2 | +# SPDX-License-Identifier: GPL-2.0 |
| 3 | + |
| 4 | +import subprocess |
| 5 | +from shutil import which |
| 6 | +from os import pread |
| 7 | + |
| 8 | +class PerfCounterInfo: |
| 9 | + def __init__(self, subsys, event): |
| 10 | + self.subsys = subsys |
| 11 | + self.event = event |
| 12 | + |
| 13 | + def get_perf_event_name(self): |
| 14 | + return f'{self.subsys}/{self.event}/' |
| 15 | + |
| 16 | + def get_turbostat_perf_id(self, counter_scope, counter_type, column_name): |
| 17 | + return f'perf/{self.subsys}/{self.event},{counter_scope},{counter_type},{column_name}' |
| 18 | + |
| 19 | +PERF_COUNTERS_CANDIDATES = [ |
| 20 | + PerfCounterInfo('msr', 'mperf'), |
| 21 | + PerfCounterInfo('msr', 'aperf'), |
| 22 | + PerfCounterInfo('msr', 'tsc'), |
| 23 | + PerfCounterInfo('cstate_core', 'c1-residency'), |
| 24 | + PerfCounterInfo('cstate_core', 'c6-residency'), |
| 25 | + PerfCounterInfo('cstate_core', 'c7-residency'), |
| 26 | + PerfCounterInfo('cstate_pkg', 'c2-residency'), |
| 27 | + PerfCounterInfo('cstate_pkg', 'c3-residency'), |
| 28 | + PerfCounterInfo('cstate_pkg', 'c6-residency'), |
| 29 | + PerfCounterInfo('cstate_pkg', 'c7-residency'), |
| 30 | + PerfCounterInfo('cstate_pkg', 'c8-residency'), |
| 31 | + PerfCounterInfo('cstate_pkg', 'c9-residency'), |
| 32 | + PerfCounterInfo('cstate_pkg', 'c10-residency'), |
| 33 | +] |
| 34 | +present_perf_counters = [] |
| 35 | + |
| 36 | +def check_perf_access(): |
| 37 | + perf = which('perf') |
| 38 | + if perf is None: |
| 39 | + print('SKIP: Could not find perf binary, thus could not determine perf access.') |
| 40 | + return False |
| 41 | + |
| 42 | + def has_perf_counter_access(counter_name): |
| 43 | + proc_perf = subprocess.run([perf, 'stat', '-e', counter_name, '--timeout', '10'], |
| 44 | + capture_output = True) |
| 45 | + |
| 46 | + if proc_perf.returncode != 0: |
| 47 | + print(f'SKIP: Could not read {counter_name} perf counter.') |
| 48 | + return False |
| 49 | + |
| 50 | + if b'<not supported>' in proc_perf.stderr: |
| 51 | + print(f'SKIP: Could not read {counter_name} perf counter.') |
| 52 | + return False |
| 53 | + |
| 54 | + return True |
| 55 | + |
| 56 | + for counter in PERF_COUNTERS_CANDIDATES: |
| 57 | + if has_perf_counter_access(counter.get_perf_event_name()): |
| 58 | + present_perf_counters.append(counter) |
| 59 | + |
| 60 | + if len(present_perf_counters) == 0: |
| 61 | + print('SKIP: Could not read any perf counter.') |
| 62 | + return False |
| 63 | + |
| 64 | + if len(present_perf_counters) != len(PERF_COUNTERS_CANDIDATES): |
| 65 | + print(f'WARN: Could not access all of the counters - some will be left untested') |
| 66 | + |
| 67 | + return True |
| 68 | + |
| 69 | +if not check_perf_access(): |
| 70 | + exit(0) |
| 71 | + |
| 72 | +turbostat_counter_source_opts = [''] |
| 73 | + |
| 74 | +turbostat = which('turbostat') |
| 75 | +if turbostat is None: |
| 76 | + print('Could not find turbostat binary') |
| 77 | + exit(1) |
| 78 | + |
| 79 | +timeout = which('timeout') |
| 80 | +if timeout is None: |
| 81 | + print('Could not find timeout binary') |
| 82 | + exit(1) |
| 83 | + |
| 84 | +proc_turbostat = subprocess.run([turbostat, '--list'], capture_output = True) |
| 85 | +if proc_turbostat.returncode != 0: |
| 86 | + print(f'turbostat failed with {proc_turbostat.returncode}') |
| 87 | + exit(1) |
| 88 | + |
| 89 | +EXPECTED_COLUMNS_DEBUG_DEFAULT = [b'usec', b'Time_Of_Day_Seconds', b'APIC', b'X2APIC'] |
| 90 | + |
| 91 | +expected_columns = [b'CPU'] |
| 92 | +counters_argv = [] |
| 93 | +for counter in present_perf_counters: |
| 94 | + if counter.subsys == 'cstate_core': |
| 95 | + counter_scope = 'core' |
| 96 | + elif counter.subsys == 'cstate_pkg': |
| 97 | + counter_scope = 'package' |
| 98 | + else: |
| 99 | + counter_scope = 'cpu' |
| 100 | + |
| 101 | + counter_type = 'delta' |
| 102 | + column_name = counter.event |
| 103 | + |
| 104 | + cparams = counter.get_turbostat_perf_id( |
| 105 | + counter_scope = counter_scope, |
| 106 | + counter_type = counter_type, |
| 107 | + column_name = column_name |
| 108 | + ) |
| 109 | + expected_columns.append(column_name.encode()) |
| 110 | + counters_argv.extend(['--add', cparams]) |
| 111 | + |
| 112 | +expected_columns_debug = EXPECTED_COLUMNS_DEBUG_DEFAULT + expected_columns |
| 113 | + |
| 114 | +def gen_user_friendly_cmdline(argv_): |
| 115 | + argv = argv_[:] |
| 116 | + ret = '' |
| 117 | + |
| 118 | + while len(argv) != 0: |
| 119 | + arg = argv.pop(0) |
| 120 | + arg_next = '' |
| 121 | + |
| 122 | + if arg in ('-i', '--show', '--add'): |
| 123 | + arg_next = argv.pop(0) if len(argv) > 0 else '' |
| 124 | + |
| 125 | + ret += f'{arg} {arg_next} \\\n\t' |
| 126 | + |
| 127 | + # Remove the last separator and return |
| 128 | + return ret[:-4] |
| 129 | + |
| 130 | +# |
| 131 | +# Run turbostat for some time and send SIGINT |
| 132 | +# |
| 133 | +timeout_argv = [timeout, '--preserve-status', '-s', 'SIGINT', '-k', '3', '0.2s'] |
| 134 | +turbostat_argv = [turbostat, '-i', '0.50', '--show', 'CPU'] + counters_argv |
| 135 | + |
| 136 | +def check_columns_or_fail(expected_columns: list, actual_columns: list): |
| 137 | + if len(actual_columns) != len(expected_columns): |
| 138 | + print(f'turbostat column check failed\n{expected_columns=}\n{actual_columns=}') |
| 139 | + exit(1) |
| 140 | + |
| 141 | + failed = False |
| 142 | + for expected_column in expected_columns: |
| 143 | + if expected_column not in actual_columns: |
| 144 | + print(f'turbostat column check failed: missing column {expected_column.decode()}') |
| 145 | + failed = True |
| 146 | + |
| 147 | + if failed: |
| 148 | + exit(1) |
| 149 | + |
| 150 | +cmdline = gen_user_friendly_cmdline(turbostat_argv) |
| 151 | +print(f'Running turbostat with:\n\t{cmdline}\n... ', end = '', flush = True) |
| 152 | +proc_turbostat = subprocess.run(timeout_argv + turbostat_argv, capture_output = True) |
| 153 | +if proc_turbostat.returncode != 0: |
| 154 | + print(f'turbostat failed with {proc_turbostat.returncode}') |
| 155 | + exit(1) |
| 156 | + |
| 157 | +actual_columns = proc_turbostat.stdout.split(b'\n')[0].split(b'\t') |
| 158 | +check_columns_or_fail(expected_columns, actual_columns) |
| 159 | +print('OK') |
| 160 | + |
| 161 | +# |
| 162 | +# Same, but with --debug |
| 163 | +# |
| 164 | +# We explicitly specify '--show CPU' to make sure turbostat |
| 165 | +# don't show a bunch of default counters instead. |
| 166 | +# |
| 167 | +turbostat_argv.append('--debug') |
| 168 | + |
| 169 | +cmdline = gen_user_friendly_cmdline(turbostat_argv) |
| 170 | +print(f'Running turbostat (in debug mode) with:\n\t{cmdline}\n... ', end = '', flush = True) |
| 171 | +proc_turbostat = subprocess.run(timeout_argv + turbostat_argv, capture_output = True) |
| 172 | +if proc_turbostat.returncode != 0: |
| 173 | + print(f'turbostat failed with {proc_turbostat.returncode}') |
| 174 | + exit(1) |
| 175 | + |
| 176 | +actual_columns = proc_turbostat.stdout.split(b'\n')[0].split(b'\t') |
| 177 | +check_columns_or_fail(expected_columns_debug, actual_columns) |
| 178 | +print('OK') |
0 commit comments