1 #!/usr/bin/env python3 2 # SPDX-License-Identifier: GPL-2.0-only 3 # -*- coding: utf-8 -*- 4 # 5 """ This utility can be used to debug and tune the performance of the 6 AMD P-State driver. It imports intel_pstate_tracer to analyze AMD P-State 7 trace event. 8 9 Prerequisites: 10 Python version 2.7.x or higher 11 gnuplot 5.0 or higher 12 gnuplot-py 1.8 or higher 13 (Most of the distributions have these required packages. They may be called 14 gnuplot-py, phython-gnuplot or phython3-gnuplot, gnuplot-nox, ... ) 15 16 Kernel config for Linux trace is enabled 17 18 see print_help(): for Usage and Output details 19 20 """ 21 from __future__ import print_function 22 from datetime import datetime 23 import subprocess 24 import os 25 import time 26 import re 27 import signal 28 import sys 29 import getopt 30 import Gnuplot 31 from numpy import * 32 from decimal import * 33 sys.path.append(os.path.join(os.path.dirname(__file__), "..", "intel_pstate_tracer")) 34 import intel_pstate_tracer as ipt 35 36 __license__ = "GPL version 2" 37 38 MAX_CPUS = 256 39 # Define the csv file columns 40 C_COMM = 15 41 C_ELAPSED = 14 42 C_SAMPLE = 13 43 C_DURATION = 12 44 C_LOAD = 11 45 C_TSC = 10 46 C_APERF = 9 47 C_MPERF = 8 48 C_FREQ = 7 49 C_MAX_PERF = 6 50 C_DES_PERF = 5 51 C_MIN_PERF = 4 52 C_USEC = 3 53 C_SEC = 2 54 C_CPU = 1 55 56 global sample_num, last_sec_cpu, last_usec_cpu, start_time, test_name, trace_file 57 58 getcontext().prec = 11 59 60 sample_num =0 61 last_sec_cpu = [0] * MAX_CPUS 62 last_usec_cpu = [0] * MAX_CPUS 63 64 def plot_per_cpu_freq(cpu_index): 65 """ Plot per cpu frequency """ 66 67 file_name = 'cpu{:0>3}.csv'.format(cpu_index) 68 if os.path.exists(file_name): 69 output_png = "cpu%03d_frequency.png" % cpu_index 70 g_plot = ipt.common_gnuplot_settings() 71 g_plot('set output "' + output_png + '"') 72 g_plot('set yrange [0:7]') 73 g_plot('set ytics 0, 1') 74 g_plot('set ylabel "CPU Frequency (GHz)"') 75 g_plot('set title "{} : frequency : CPU {:0>3} : {:%F %H:%M}"'.format(test_name, cpu_index, datetime.now())) 76 g_plot('set ylabel "CPU frequency"') 77 g_plot('set key off') 78 ipt.set_4_plot_linestyles(g_plot) 79 g_plot('plot "' + file_name + '" using {:d}:{:d} with linespoints linestyle 1 axis x1y1'.format(C_ELAPSED, C_FREQ)) 80 81 def plot_per_cpu_des_perf(cpu_index): 82 """ Plot per cpu desired perf """ 83 84 file_name = 'cpu{:0>3}.csv'.format(cpu_index) 85 if os.path.exists(file_name): 86 output_png = "cpu%03d_des_perf.png" % cpu_index 87 g_plot = ipt.common_gnuplot_settings() 88 g_plot('set output "' + output_png + '"') 89 g_plot('set yrange [0:255]') 90 g_plot('set ylabel "des perf"') 91 g_plot('set title "{} : cpu des perf : CPU {:0>3} : {:%F %H:%M}"'.format(test_name, cpu_index, datetime.now())) 92 g_plot('set key off') 93 ipt.set_4_plot_linestyles(g_plot) 94 g_plot('plot "' + file_name + '" using {:d}:{:d} with linespoints linestyle 1 axis x1y1'.format(C_ELAPSED, C_DES_PERF)) 95 96 def plot_per_cpu_load(cpu_index): 97 """ Plot per cpu load """ 98 99 file_name = 'cpu{:0>3}.csv'.format(cpu_index) 100 if os.path.exists(file_name): 101 output_png = "cpu%03d_load.png" % cpu_index 102 g_plot = ipt.common_gnuplot_settings() 103 g_plot('set output "' + output_png + '"') 104 g_plot('set yrange [0:100]') 105 g_plot('set ytics 0, 10') 106 g_plot('set ylabel "CPU load (percent)"') 107 g_plot('set title "{} : cpu load : CPU {:0>3} : {:%F %H:%M}"'.format(test_name, cpu_index, datetime.now())) 108 g_plot('set key off') 109 ipt.set_4_plot_linestyles(g_plot) 110 g_plot('plot "' + file_name + '" using {:d}:{:d} with linespoints linestyle 1 axis x1y1'.format(C_ELAPSED, C_LOAD)) 111 112 def plot_all_cpu_frequency(): 113 """ Plot all cpu frequencies """ 114 115 output_png = 'all_cpu_frequencies.png' 116 g_plot = ipt.common_gnuplot_settings() 117 g_plot('set output "' + output_png + '"') 118 g_plot('set ylabel "CPU Frequency (GHz)"') 119 g_plot('set title "{} : cpu frequencies : {:%F %H:%M}"'.format(test_name, datetime.now())) 120 121 title_list = subprocess.check_output('ls cpu???.csv | sed -e \'s/.csv//\'',shell=True).decode('utf-8').replace('\n', ' ') 122 plot_str = "plot for [i in title_list] i.'.csv' using {:d}:{:d} pt 7 ps 1 title i".format(C_ELAPSED, C_FREQ) 123 g_plot('title_list = "{}"'.format(title_list)) 124 g_plot(plot_str) 125 126 def plot_all_cpu_des_perf(): 127 """ Plot all cpu desired perf """ 128 129 output_png = 'all_cpu_des_perf.png' 130 g_plot = ipt.common_gnuplot_settings() 131 g_plot('set output "' + output_png + '"') 132 g_plot('set ylabel "des perf"') 133 g_plot('set title "{} : cpu des perf : {:%F %H:%M}"'.format(test_name, datetime.now())) 134 135 title_list = subprocess.check_output('ls cpu???.csv | sed -e \'s/.csv//\'',shell=True).decode('utf-8').replace('\n', ' ') 136 plot_str = "plot for [i in title_list] i.'.csv' using {:d}:{:d} pt 255 ps 1 title i".format(C_ELAPSED, C_DES_PERF) 137 g_plot('title_list = "{}"'.format(title_list)) 138 g_plot(plot_str) 139 140 def plot_all_cpu_load(): 141 """ Plot all cpu load """ 142 143 output_png = 'all_cpu_load.png' 144 g_plot = ipt.common_gnuplot_settings() 145 g_plot('set output "' + output_png + '"') 146 g_plot('set yrange [0:100]') 147 g_plot('set ylabel "CPU load (percent)"') 148 g_plot('set title "{} : cpu load : {:%F %H:%M}"'.format(test_name, datetime.now())) 149 150 title_list = subprocess.check_output('ls cpu???.csv | sed -e \'s/.csv//\'',shell=True).decode('utf-8').replace('\n', ' ') 151 plot_str = "plot for [i in title_list] i.'.csv' using {:d}:{:d} pt 255 ps 1 title i".format(C_ELAPSED, C_LOAD) 152 g_plot('title_list = "{}"'.format(title_list)) 153 g_plot(plot_str) 154 155 def store_csv(cpu_int, time_pre_dec, time_post_dec, min_perf, des_perf, max_perf, freq_ghz, mperf, aperf, tsc, common_comm, load, duration_ms, sample_num, elapsed_time, cpu_mask): 156 """ Store master csv file information """ 157 158 global graph_data_present 159 160 if cpu_mask[cpu_int] == 0: 161 return 162 163 try: 164 f_handle = open('cpu.csv', 'a') 165 string_buffer = "CPU_%03u, %05u, %06u, %u, %u, %u, %.4f, %u, %u, %u, %.2f, %.3f, %u, %.3f, %s\n" % (cpu_int, int(time_pre_dec), int(time_post_dec), int(min_perf), int(des_perf), int(max_perf), freq_ghz, int(mperf), int(aperf), int(tsc), load, duration_ms, sample_num, elapsed_time, common_comm) 166 f_handle.write(string_buffer) 167 f_handle.close() 168 except: 169 print('IO error cpu.csv') 170 return 171 172 graph_data_present = True; 173 174 175 def cleanup_data_files(): 176 """ clean up existing data files """ 177 178 if os.path.exists('cpu.csv'): 179 os.remove('cpu.csv') 180 f_handle = open('cpu.csv', 'a') 181 f_handle.write('common_cpu, common_secs, common_usecs, min_perf, des_perf, max_perf, freq, mperf, aperf, tsc, load, duration_ms, sample_num, elapsed_time, common_comm') 182 f_handle.write('\n') 183 f_handle.close() 184 185 def read_trace_data(file_name, cpu_mask): 186 """ Read and parse trace data """ 187 188 global current_max_cpu 189 global sample_num, last_sec_cpu, last_usec_cpu, start_time 190 191 try: 192 data = open(file_name, 'r').read() 193 except: 194 print('Error opening ', file_name) 195 sys.exit(2) 196 197 for line in data.splitlines(): 198 search_obj = \ 199 re.search(r'(^(.*?)\[)((\d+)[^\]])(.*?)(\d+)([.])(\d+)(.*?amd_min_perf=)(\d+)(.*?amd_des_perf=)(\d+)(.*?amd_max_perf=)(\d+)(.*?freq=)(\d+)(.*?mperf=)(\d+)(.*?aperf=)(\d+)(.*?tsc=)(\d+)' 200 , line) 201 202 if search_obj: 203 cpu = search_obj.group(3) 204 cpu_int = int(cpu) 205 cpu = str(cpu_int) 206 207 time_pre_dec = search_obj.group(6) 208 time_post_dec = search_obj.group(8) 209 min_perf = search_obj.group(10) 210 des_perf = search_obj.group(12) 211 max_perf = search_obj.group(14) 212 freq = search_obj.group(16) 213 mperf = search_obj.group(18) 214 aperf = search_obj.group(20) 215 tsc = search_obj.group(22) 216 217 common_comm = search_obj.group(2).replace(' ', '') 218 219 if sample_num == 0 : 220 start_time = Decimal(time_pre_dec) + Decimal(time_post_dec) / Decimal(1000000) 221 sample_num += 1 222 223 if last_sec_cpu[cpu_int] == 0 : 224 last_sec_cpu[cpu_int] = time_pre_dec 225 last_usec_cpu[cpu_int] = time_post_dec 226 else : 227 duration_us = (int(time_pre_dec) - int(last_sec_cpu[cpu_int])) * 1000000 + (int(time_post_dec) - int(last_usec_cpu[cpu_int])) 228 duration_ms = Decimal(duration_us) / Decimal(1000) 229 last_sec_cpu[cpu_int] = time_pre_dec 230 last_usec_cpu[cpu_int] = time_post_dec 231 elapsed_time = Decimal(time_pre_dec) + Decimal(time_post_dec) / Decimal(1000000) - start_time 232 load = Decimal(int(mperf)*100)/ Decimal(tsc) 233 freq_ghz = Decimal(freq)/Decimal(1000000) 234 store_csv(cpu_int, time_pre_dec, time_post_dec, min_perf, des_perf, max_perf, freq_ghz, mperf, aperf, tsc, common_comm, load, duration_ms, sample_num, elapsed_time, cpu_mask) 235 236 if cpu_int > current_max_cpu: 237 current_max_cpu = cpu_int 238 # Now separate the main overall csv file into per CPU csv files. 239 ipt.split_csv(current_max_cpu, cpu_mask) 240 241 242 def signal_handler(signal, frame): 243 print(' SIGINT: Forcing cleanup before exit.') 244 if interval: 245 ipt.disable_trace(trace_file) 246 ipt.clear_trace_file() 247 ipt.free_trace_buffer() 248 sys.exit(0) 249 250 trace_file = "/sys/kernel/tracing/events/amd_cpu/enable" 251 signal.signal(signal.SIGINT, signal_handler) 252 253 interval = "" 254 file_name = "" 255 cpu_list = "" 256 test_name = "" 257 memory = "10240" 258 graph_data_present = False; 259 260 valid1 = False 261 valid2 = False 262 263 cpu_mask = zeros((MAX_CPUS,), dtype=int) 264 265 266 try: 267 opts, args = getopt.getopt(sys.argv[1:],"ht:i:c:n:m:",["help","trace_file=","interval=","cpu=","name=","memory="]) 268 except getopt.GetoptError: 269 ipt.print_help('amd_pstate') 270 sys.exit(2) 271 for opt, arg in opts: 272 if opt == '-h': 273 print() 274 sys.exit() 275 elif opt in ("-t", "--trace_file"): 276 valid1 = True 277 location = os.path.realpath(os.path.join(os.getcwd(), os.path.dirname(__file__))) 278 file_name = os.path.join(location, arg) 279 elif opt in ("-i", "--interval"): 280 valid1 = True 281 interval = arg 282 elif opt in ("-c", "--cpu"): 283 cpu_list = arg 284 elif opt in ("-n", "--name"): 285 valid2 = True 286 test_name = arg 287 elif opt in ("-m", "--memory"): 288 memory = arg 289 290 if not (valid1 and valid2): 291 ipt.print_help('amd_pstate') 292 sys.exit() 293 294 if cpu_list: 295 for p in re.split("[,]", cpu_list): 296 if int(p) < MAX_CPUS : 297 cpu_mask[int(p)] = 1 298 else: 299 for i in range (0, MAX_CPUS): 300 cpu_mask[i] = 1 301 302 if not os.path.exists('results'): 303 os.mkdir('results') 304 ipt.fix_ownership('results') 305 306 os.chdir('results') 307 if os.path.exists(test_name): 308 print('The test name directory already exists. Please provide a unique test name. Test re-run not supported, yet.') 309 sys.exit() 310 os.mkdir(test_name) 311 ipt.fix_ownership(test_name) 312 os.chdir(test_name) 313 314 cur_version = sys.version_info 315 print('python version (should be >= 2.7):') 316 print(cur_version) 317 318 cleanup_data_files() 319 320 if interval: 321 file_name = "/sys/kernel/tracing/trace" 322 ipt.clear_trace_file() 323 ipt.set_trace_buffer_size(memory) 324 ipt.enable_trace(trace_file) 325 time.sleep(int(interval)) 326 ipt.disable_trace(trace_file) 327 328 current_max_cpu = 0 329 330 read_trace_data(file_name, cpu_mask) 331 332 if interval: 333 ipt.clear_trace_file() 334 ipt.free_trace_buffer() 335 336 if graph_data_present == False: 337 print('No valid data to plot') 338 sys.exit(2) 339 340 for cpu_no in range(0, current_max_cpu + 1): 341 plot_per_cpu_freq(cpu_no) 342 plot_per_cpu_des_perf(cpu_no) 343 plot_per_cpu_load(cpu_no) 344 345 plot_all_cpu_des_perf() 346 plot_all_cpu_frequency() 347 plot_all_cpu_load() 348 349 for root, dirs, files in os.walk('.'): 350 for f in files: 351 ipt.fix_ownership(f) 352 353 os.chdir('../../')
Linux® is a registered trademark of Linus Torvalds in the United States and other countries.
TOMOYO® is a registered trademark of NTT DATA CORPORATION.