Skip to content

Commit f3065f9

Browse files
Patryk Wlazlynlenb
authored andcommitted
tools/power turbostat: Add selftests for added perf counters
Test adds several perf counters from msr, cstate_core and cstate_pkg groups and checks if the columns for those counters show up. The test skips the counters that are not present. It is not an error, but the test may not be as exhaustive. Signed-off-by: Patryk Wlazlyn <[email protected]> Signed-off-by: Len Brown <[email protected]>
1 parent 1f8add1 commit f3065f9

File tree

1 file changed

+178
-0
lines changed

1 file changed

+178
-0
lines changed
Lines changed: 178 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,178 @@
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

Comments
 (0)