0001
0002
0003
0004
0005 """ This utility can be used to debug and tune the performance of the
0006 AMD P-State driver. It imports intel_pstate_tracer to analyze AMD P-State
0007 trace event.
0008
0009 Prerequisites:
0010 Python version 2.7.x or higher
0011 gnuplot 5.0 or higher
0012 gnuplot-py 1.8 or higher
0013 (Most of the distributions have these required packages. They may be called
0014 gnuplot-py, phython-gnuplot or phython3-gnuplot, gnuplot-nox, ... )
0015
0016 Kernel config for Linux trace is enabled
0017
0018 see print_help(): for Usage and Output details
0019
0020 """
0021 from __future__ import print_function
0022 from datetime import datetime
0023 import subprocess
0024 import os
0025 import time
0026 import re
0027 import signal
0028 import sys
0029 import getopt
0030 import Gnuplot
0031 from numpy import *
0032 from decimal import *
0033 sys.path.append('../intel_pstate_tracer')
0034
0035 import intel_pstate_tracer as ipt
0036
0037 __license__ = "GPL version 2"
0038
0039 MAX_CPUS = 256
0040
0041 C_COMM = 15
0042 C_ELAPSED = 14
0043 C_SAMPLE = 13
0044 C_DURATION = 12
0045 C_LOAD = 11
0046 C_TSC = 10
0047 C_APERF = 9
0048 C_MPERF = 8
0049 C_FREQ = 7
0050 C_MAX_PERF = 6
0051 C_DES_PERF = 5
0052 C_MIN_PERF = 4
0053 C_USEC = 3
0054 C_SEC = 2
0055 C_CPU = 1
0056
0057 global sample_num, last_sec_cpu, last_usec_cpu, start_time, test_name, trace_file
0058
0059 getcontext().prec = 11
0060
0061 sample_num =0
0062 last_sec_cpu = [0] * MAX_CPUS
0063 last_usec_cpu = [0] * MAX_CPUS
0064
0065 def plot_per_cpu_freq(cpu_index):
0066 """ Plot per cpu frequency """
0067
0068 file_name = 'cpu{:0>3}.csv'.format(cpu_index)
0069 if os.path.exists(file_name):
0070 output_png = "cpu%03d_frequency.png" % cpu_index
0071 g_plot = ipt.common_gnuplot_settings()
0072 g_plot('set output "' + output_png + '"')
0073 g_plot('set yrange [0:7]')
0074 g_plot('set ytics 0, 1')
0075 g_plot('set ylabel "CPU Frequency (GHz)"')
0076 g_plot('set title "{} : frequency : CPU {:0>3} : {:%F %H:%M}"'.format(test_name, cpu_index, datetime.now()))
0077 g_plot('set ylabel "CPU frequency"')
0078 g_plot('set key off')
0079 ipt.set_4_plot_linestyles(g_plot)
0080 g_plot('plot "' + file_name + '" using {:d}:{:d} with linespoints linestyle 1 axis x1y1'.format(C_ELAPSED, C_FREQ))
0081
0082 def plot_per_cpu_des_perf(cpu_index):
0083 """ Plot per cpu desired perf """
0084
0085 file_name = 'cpu{:0>3}.csv'.format(cpu_index)
0086 if os.path.exists(file_name):
0087 output_png = "cpu%03d_des_perf.png" % cpu_index
0088 g_plot = ipt.common_gnuplot_settings()
0089 g_plot('set output "' + output_png + '"')
0090 g_plot('set yrange [0:255]')
0091 g_plot('set ylabel "des perf"')
0092 g_plot('set title "{} : cpu des perf : CPU {:0>3} : {:%F %H:%M}"'.format(test_name, cpu_index, datetime.now()))
0093 g_plot('set key off')
0094 ipt.set_4_plot_linestyles(g_plot)
0095 g_plot('plot "' + file_name + '" using {:d}:{:d} with linespoints linestyle 1 axis x1y1'.format(C_ELAPSED, C_DES_PERF))
0096
0097 def plot_per_cpu_load(cpu_index):
0098 """ Plot per cpu load """
0099
0100 file_name = 'cpu{:0>3}.csv'.format(cpu_index)
0101 if os.path.exists(file_name):
0102 output_png = "cpu%03d_load.png" % cpu_index
0103 g_plot = ipt.common_gnuplot_settings()
0104 g_plot('set output "' + output_png + '"')
0105 g_plot('set yrange [0:100]')
0106 g_plot('set ytics 0, 10')
0107 g_plot('set ylabel "CPU load (percent)"')
0108 g_plot('set title "{} : cpu load : CPU {:0>3} : {:%F %H:%M}"'.format(test_name, cpu_index, datetime.now()))
0109 g_plot('set key off')
0110 ipt.set_4_plot_linestyles(g_plot)
0111 g_plot('plot "' + file_name + '" using {:d}:{:d} with linespoints linestyle 1 axis x1y1'.format(C_ELAPSED, C_LOAD))
0112
0113 def plot_all_cpu_frequency():
0114 """ Plot all cpu frequencies """
0115
0116 output_png = 'all_cpu_frequencies.png'
0117 g_plot = ipt.common_gnuplot_settings()
0118 g_plot('set output "' + output_png + '"')
0119 g_plot('set ylabel "CPU Frequency (GHz)"')
0120 g_plot('set title "{} : cpu frequencies : {:%F %H:%M}"'.format(test_name, datetime.now()))
0121
0122 title_list = subprocess.check_output('ls cpu???.csv | sed -e \'s/.csv//\'',shell=True).decode('utf-8').replace('\n', ' ')
0123 plot_str = "plot for [i in title_list] i.'.csv' using {:d}:{:d} pt 7 ps 1 title i".format(C_ELAPSED, C_FREQ)
0124 g_plot('title_list = "{}"'.format(title_list))
0125 g_plot(plot_str)
0126
0127 def plot_all_cpu_des_perf():
0128 """ Plot all cpu desired perf """
0129
0130 output_png = 'all_cpu_des_perf.png'
0131 g_plot = ipt.common_gnuplot_settings()
0132 g_plot('set output "' + output_png + '"')
0133 g_plot('set ylabel "des perf"')
0134 g_plot('set title "{} : cpu des perf : {:%F %H:%M}"'.format(test_name, datetime.now()))
0135
0136 title_list = subprocess.check_output('ls cpu???.csv | sed -e \'s/.csv//\'',shell=True).decode('utf-8').replace('\n', ' ')
0137 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)
0138 g_plot('title_list = "{}"'.format(title_list))
0139 g_plot(plot_str)
0140
0141 def plot_all_cpu_load():
0142 """ Plot all cpu load """
0143
0144 output_png = 'all_cpu_load.png'
0145 g_plot = ipt.common_gnuplot_settings()
0146 g_plot('set output "' + output_png + '"')
0147 g_plot('set yrange [0:100]')
0148 g_plot('set ylabel "CPU load (percent)"')
0149 g_plot('set title "{} : cpu load : {:%F %H:%M}"'.format(test_name, datetime.now()))
0150
0151 title_list = subprocess.check_output('ls cpu???.csv | sed -e \'s/.csv//\'',shell=True).decode('utf-8').replace('\n', ' ')
0152 plot_str = "plot for [i in title_list] i.'.csv' using {:d}:{:d} pt 255 ps 1 title i".format(C_ELAPSED, C_LOAD)
0153 g_plot('title_list = "{}"'.format(title_list))
0154 g_plot(plot_str)
0155
0156 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):
0157 """ Store master csv file information """
0158
0159 global graph_data_present
0160
0161 if cpu_mask[cpu_int] == 0:
0162 return
0163
0164 try:
0165 f_handle = open('cpu.csv', 'a')
0166 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)
0167 f_handle.write(string_buffer)
0168 f_handle.close()
0169 except:
0170 print('IO error cpu.csv')
0171 return
0172
0173 graph_data_present = True;
0174
0175
0176 def cleanup_data_files():
0177 """ clean up existing data files """
0178
0179 if os.path.exists('cpu.csv'):
0180 os.remove('cpu.csv')
0181 f_handle = open('cpu.csv', 'a')
0182 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')
0183 f_handle.write('\n')
0184 f_handle.close()
0185
0186 def read_trace_data(file_name, cpu_mask):
0187 """ Read and parse trace data """
0188
0189 global current_max_cpu
0190 global sample_num, last_sec_cpu, last_usec_cpu, start_time
0191
0192 try:
0193 data = open(file_name, 'r').read()
0194 except:
0195 print('Error opening ', file_name)
0196 sys.exit(2)
0197
0198 for line in data.splitlines():
0199 search_obj = \
0200 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+)'
0201 , line)
0202
0203 if search_obj:
0204 cpu = search_obj.group(3)
0205 cpu_int = int(cpu)
0206 cpu = str(cpu_int)
0207
0208 time_pre_dec = search_obj.group(6)
0209 time_post_dec = search_obj.group(8)
0210 min_perf = search_obj.group(10)
0211 des_perf = search_obj.group(12)
0212 max_perf = search_obj.group(14)
0213 freq = search_obj.group(16)
0214 mperf = search_obj.group(18)
0215 aperf = search_obj.group(20)
0216 tsc = search_obj.group(22)
0217
0218 common_comm = search_obj.group(2).replace(' ', '')
0219
0220 if sample_num == 0 :
0221 start_time = Decimal(time_pre_dec) + Decimal(time_post_dec) / Decimal(1000000)
0222 sample_num += 1
0223
0224 if last_sec_cpu[cpu_int] == 0 :
0225 last_sec_cpu[cpu_int] = time_pre_dec
0226 last_usec_cpu[cpu_int] = time_post_dec
0227 else :
0228 duration_us = (int(time_pre_dec) - int(last_sec_cpu[cpu_int])) * 1000000 + (int(time_post_dec) - int(last_usec_cpu[cpu_int]))
0229 duration_ms = Decimal(duration_us) / Decimal(1000)
0230 last_sec_cpu[cpu_int] = time_pre_dec
0231 last_usec_cpu[cpu_int] = time_post_dec
0232 elapsed_time = Decimal(time_pre_dec) + Decimal(time_post_dec) / Decimal(1000000) - start_time
0233 load = Decimal(int(mperf)*100)/ Decimal(tsc)
0234 freq_ghz = Decimal(freq)/Decimal(1000000)
0235 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)
0236
0237 if cpu_int > current_max_cpu:
0238 current_max_cpu = cpu_int
0239
0240 ipt.split_csv(current_max_cpu, cpu_mask)
0241
0242
0243 def signal_handler(signal, frame):
0244 print(' SIGINT: Forcing cleanup before exit.')
0245 if interval:
0246 ipt.disable_trace(trace_file)
0247 ipt.clear_trace_file()
0248 ipt.free_trace_buffer()
0249 sys.exit(0)
0250
0251 trace_file = "/sys/kernel/debug/tracing/events/amd_cpu/enable"
0252 signal.signal(signal.SIGINT, signal_handler)
0253
0254 interval = ""
0255 file_name = ""
0256 cpu_list = ""
0257 test_name = ""
0258 memory = "10240"
0259 graph_data_present = False;
0260
0261 valid1 = False
0262 valid2 = False
0263
0264 cpu_mask = zeros((MAX_CPUS,), dtype=int)
0265
0266
0267 try:
0268 opts, args = getopt.getopt(sys.argv[1:],"ht:i:c:n:m:",["help","trace_file=","interval=","cpu=","name=","memory="])
0269 except getopt.GetoptError:
0270 ipt.print_help('amd_pstate')
0271 sys.exit(2)
0272 for opt, arg in opts:
0273 if opt == '-h':
0274 print()
0275 sys.exit()
0276 elif opt in ("-t", "--trace_file"):
0277 valid1 = True
0278 location = os.path.realpath(os.path.join(os.getcwd(), os.path.dirname(__file__)))
0279 file_name = os.path.join(location, arg)
0280 elif opt in ("-i", "--interval"):
0281 valid1 = True
0282 interval = arg
0283 elif opt in ("-c", "--cpu"):
0284 cpu_list = arg
0285 elif opt in ("-n", "--name"):
0286 valid2 = True
0287 test_name = arg
0288 elif opt in ("-m", "--memory"):
0289 memory = arg
0290
0291 if not (valid1 and valid2):
0292 ipt.print_help('amd_pstate')
0293 sys.exit()
0294
0295 if cpu_list:
0296 for p in re.split("[,]", cpu_list):
0297 if int(p) < MAX_CPUS :
0298 cpu_mask[int(p)] = 1
0299 else:
0300 for i in range (0, MAX_CPUS):
0301 cpu_mask[i] = 1
0302
0303 if not os.path.exists('results'):
0304 os.mkdir('results')
0305 ipt.fix_ownership('results')
0306
0307 os.chdir('results')
0308 if os.path.exists(test_name):
0309 print('The test name directory already exists. Please provide a unique test name. Test re-run not supported, yet.')
0310 sys.exit()
0311 os.mkdir(test_name)
0312 ipt.fix_ownership(test_name)
0313 os.chdir(test_name)
0314
0315 cur_version = sys.version_info
0316 print('python version (should be >= 2.7):')
0317 print(cur_version)
0318
0319 cleanup_data_files()
0320
0321 if interval:
0322 file_name = "/sys/kernel/debug/tracing/trace"
0323 ipt.clear_trace_file()
0324 ipt.set_trace_buffer_size(memory)
0325 ipt.enable_trace(trace_file)
0326 time.sleep(int(interval))
0327 ipt.disable_trace(trace_file)
0328
0329 current_max_cpu = 0
0330
0331 read_trace_data(file_name, cpu_mask)
0332
0333 if interval:
0334 ipt.clear_trace_file()
0335 ipt.free_trace_buffer()
0336
0337 if graph_data_present == False:
0338 print('No valid data to plot')
0339 sys.exit(2)
0340
0341 for cpu_no in range(0, current_max_cpu + 1):
0342 plot_per_cpu_freq(cpu_no)
0343 plot_per_cpu_des_perf(cpu_no)
0344 plot_per_cpu_load(cpu_no)
0345
0346 plot_all_cpu_des_perf()
0347 plot_all_cpu_frequency()
0348 plot_all_cpu_load()
0349
0350 for root, dirs, files in os.walk('.'):
0351 for f in files:
0352 ipt.fix_ownership(f)
0353
0354 os.chdir('../../')