added_perf_counters.py 5.2 KB

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