Commit f3065f9c authored by Patryk Wlazlyn's avatar Patryk Wlazlyn Committed by Len Brown
Browse files

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: default avatarPatryk Wlazlyn <patryk.wlazlyn@linux.intel.com>
Signed-off-by: default avatarLen Brown <len.brown@intel.com>
parent 1f8add13
Loading
Loading
Loading
Loading
+178 −0
Original line number Diff line number Diff line
#!/bin/env python3
# SPDX-License-Identifier: GPL-2.0

import subprocess
from shutil import which
from os import pread

class PerfCounterInfo:
	def __init__(self, subsys, event):
		self.subsys = subsys
		self.event = event

	def get_perf_event_name(self):
		return f'{self.subsys}/{self.event}/'

	def get_turbostat_perf_id(self, counter_scope, counter_type, column_name):
		return f'perf/{self.subsys}/{self.event},{counter_scope},{counter_type},{column_name}'

PERF_COUNTERS_CANDIDATES = [
	PerfCounterInfo('msr', 'mperf'),
	PerfCounterInfo('msr', 'aperf'),
	PerfCounterInfo('msr', 'tsc'),
	PerfCounterInfo('cstate_core', 'c1-residency'),
	PerfCounterInfo('cstate_core', 'c6-residency'),
	PerfCounterInfo('cstate_core', 'c7-residency'),
	PerfCounterInfo('cstate_pkg', 'c2-residency'),
	PerfCounterInfo('cstate_pkg', 'c3-residency'),
	PerfCounterInfo('cstate_pkg', 'c6-residency'),
	PerfCounterInfo('cstate_pkg', 'c7-residency'),
	PerfCounterInfo('cstate_pkg', 'c8-residency'),
	PerfCounterInfo('cstate_pkg', 'c9-residency'),
	PerfCounterInfo('cstate_pkg', 'c10-residency'),
]
present_perf_counters = []

def check_perf_access():
	perf = which('perf')
	if perf is None:
		print('SKIP: Could not find perf binary, thus could not determine perf access.')
		return False

	def has_perf_counter_access(counter_name):
		proc_perf = subprocess.run([perf, 'stat', '-e', counter_name, '--timeout', '10'],
							 capture_output = True)

		if proc_perf.returncode != 0:
			print(f'SKIP: Could not read {counter_name} perf counter.')
			return False

		if b'<not supported>' in proc_perf.stderr:
			print(f'SKIP: Could not read {counter_name} perf counter.')
			return False

		return True

	for counter in PERF_COUNTERS_CANDIDATES:
		if has_perf_counter_access(counter.get_perf_event_name()):
			present_perf_counters.append(counter)

	if len(present_perf_counters) == 0:
		print('SKIP: Could not read any perf counter.')
		return False

	if len(present_perf_counters) != len(PERF_COUNTERS_CANDIDATES):
		print(f'WARN: Could not access all of the counters - some will be left untested')

	return True

if not check_perf_access():
	exit(0)

turbostat_counter_source_opts = ['']

turbostat = which('turbostat')
if turbostat is None:
	print('Could not find turbostat binary')
	exit(1)

timeout = which('timeout')
if timeout is None:
	print('Could not find timeout binary')
	exit(1)

proc_turbostat = subprocess.run([turbostat, '--list'], capture_output = True)
if proc_turbostat.returncode != 0:
	print(f'turbostat failed with {proc_turbostat.returncode}')
	exit(1)

EXPECTED_COLUMNS_DEBUG_DEFAULT = [b'usec', b'Time_Of_Day_Seconds', b'APIC', b'X2APIC']

expected_columns = [b'CPU']
counters_argv = []
for counter in present_perf_counters:
	if counter.subsys == 'cstate_core':
		counter_scope = 'core'
	elif counter.subsys == 'cstate_pkg':
		counter_scope = 'package'
	else:
		counter_scope = 'cpu'

	counter_type = 'delta'
	column_name = counter.event

	cparams = counter.get_turbostat_perf_id(
		counter_scope = counter_scope,
		counter_type = counter_type,
		column_name = column_name
	)
	expected_columns.append(column_name.encode())
	counters_argv.extend(['--add', cparams])

expected_columns_debug = EXPECTED_COLUMNS_DEBUG_DEFAULT + expected_columns

def gen_user_friendly_cmdline(argv_):
	argv = argv_[:]
	ret = ''

	while len(argv) != 0:
		arg = argv.pop(0)
		arg_next = ''

		if arg in ('-i', '--show', '--add'):
			arg_next = argv.pop(0) if len(argv) > 0 else ''

		ret += f'{arg} {arg_next} \\\n\t'

	# Remove the last separator and return
	return ret[:-4]

#
# Run turbostat for some time and send SIGINT
#
timeout_argv = [timeout, '--preserve-status', '-s', 'SIGINT', '-k', '3', '0.2s']
turbostat_argv = [turbostat, '-i', '0.50', '--show', 'CPU'] + counters_argv

def check_columns_or_fail(expected_columns: list, actual_columns: list):
	if len(actual_columns) != len(expected_columns):
		print(f'turbostat column check failed\n{expected_columns=}\n{actual_columns=}')
		exit(1)

	failed = False
	for expected_column in expected_columns:
		if expected_column not in actual_columns:
			print(f'turbostat column check failed: missing column {expected_column.decode()}')
			failed = True

	if failed:
		exit(1)

cmdline = gen_user_friendly_cmdline(turbostat_argv)
print(f'Running turbostat with:\n\t{cmdline}\n... ', end = '', flush = True)
proc_turbostat = subprocess.run(timeout_argv + turbostat_argv, capture_output = True)
if proc_turbostat.returncode != 0:
	print(f'turbostat failed with {proc_turbostat.returncode}')
	exit(1)

actual_columns = proc_turbostat.stdout.split(b'\n')[0].split(b'\t')
check_columns_or_fail(expected_columns, actual_columns)
print('OK')

#
# Same, but with --debug
#
# We explicitly specify '--show CPU' to make sure turbostat
# don't show a bunch of default counters instead.
#
turbostat_argv.append('--debug')

cmdline = gen_user_friendly_cmdline(turbostat_argv)
print(f'Running turbostat (in debug mode) with:\n\t{cmdline}\n... ', end = '', flush = True)
proc_turbostat = subprocess.run(timeout_argv + turbostat_argv, capture_output = True)
if proc_turbostat.returncode != 0:
	print(f'turbostat failed with {proc_turbostat.returncode}')
	exit(1)

actual_columns = proc_turbostat.stdout.split(b'\n')[0].split(b'\t')
check_columns_or_fail(expected_columns_debug, actual_columns)
print('OK')