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
6intel_pstate driver. This utility can be used in two ways:
7- If there is Linux trace file with pstate_sample events enabled, then
8this utility can parse the trace file and generate performance plots.
9- If user has not specified a trace file as input via command line parameters,
10then this utility enables and collects trace data for a user specified interval
11and generates performance plots.
12
13Prerequisites:
14    Python version 3.6.x or higher
15    gnuplot 5.0 or higher
16    python3-gnuplot 1.8 or higher
17    (Most of the distributions have these required packages. They may be called
18     gnuplot-py, python-gnuplot or python3-gnuplot, gnuplot-nox, ... )
19
20    HWP (Hardware P-States are disabled)
21    Kernel config for Linux trace is enabled
22
23    see print_help(): for Usage and Output details
24
25"""
26
27from datetime import datetime
28import subprocess
29import os
30import time
31import re
32import signal
33import sys
34import getopt
35import Gnuplot
36from numpy import *
37from decimal import *
38
39__author__ = "Srinivas Pandruvada"
40__copyright__ = " Copyright (c) 2017, Intel Corporation. "
41__license__ = "GPL version 2"
42
43
44MAX_CPUS = 256
45
46# Define the csv file columns
47C_COMM = 18
48C_GHZ = 17
49C_ELAPSED = 16
50C_SAMPLE = 15
51C_DURATION = 14
52C_LOAD = 13
53C_BOOST = 12
54C_FREQ = 11
55C_TSC = 10
56C_APERF = 9
57C_MPERF = 8
58C_TO = 7
59C_FROM = 6
60C_SCALED = 5
61C_CORE = 4
62C_USEC = 3
63C_SEC = 2
64C_CPU = 1
65
66global sample_num, last_sec_cpu, last_usec_cpu, start_time, testname, trace_file
67
68# 11 digits covers uptime to 115 days
69getcontext().prec = 11
70
71sample_num =0
72last_sec_cpu = [0] * MAX_CPUS
73last_usec_cpu = [0] * MAX_CPUS
74
75def print_help(driver_name):
76    print('%s_tracer.py:'%driver_name)
77    print('  Usage:')
78    print('    If the trace file is available, then to simply parse and plot, use (sudo not required):')
79    print('      ./%s_tracer.py [-c cpus] -t <trace_file> -n <test_name>'%driver_name)
80    print('    Or')
81    print('      ./%s_tracer.py [--cpu cpus] ---trace_file <trace_file> --name <test_name>'%driver_name)
82    print('    To generate trace file, parse and plot, use (sudo required):')
83    print('      sudo ./%s_tracer.py [-c cpus] -i <interval> -n <test_name> -m <kbytes>'%driver_name)
84    print('    Or')
85    print('      sudo ./%s_tracer.py [--cpu cpus] --interval <interval> --name <test_name> --memory <kbytes>'%driver_name)
86    print('    Optional argument:')
87    print('      cpus:   comma separated list of CPUs')
88    print('      kbytes: Kilo bytes of memory per CPU to allocate to the trace buffer. Default: 10240')
89    print('  Output:')
90    print('    If not already present, creates a "results/test_name" folder in the current working directory with:')
91    print('      cpu.csv - comma seperated values file with trace contents and some additional calculations.')
92    print('      cpu???.csv - comma seperated values file for CPU number ???.')
93    print('      *.png - a variety of PNG format plot files created from the trace contents and the additional calculations.')
94    print('  Notes:')
95    print('    Avoid the use of _ (underscore) in test names, because in gnuplot it is a subscript directive.')
96    print('    Maximum number of CPUs is {0:d}. If there are more the script will abort with an error.'.format(MAX_CPUS))
97    print('    Off-line CPUs cause the script to list some warnings, and create some empty files. Use the CPU mask feature for a clean run.')
98    print('    Empty y range warnings for autoscaled plots can occur and can be ignored.')
99
100def plot_perf_busy_with_sample(cpu_index):
101    """ Plot method to per cpu information """
102
103    file_name = 'cpu{:0>3}.csv'.format(cpu_index)
104    if os.path.exists(file_name):
105        output_png = "cpu%03d_perf_busy_vs_samples.png" % cpu_index
106        g_plot = common_all_gnuplot_settings(output_png)
107#   autoscale this one, no set y1 range
108        g_plot('set y2range [0:200]')
109        g_plot('set y2tics 0, 10')
110        g_plot('set title "{} : cpu perf busy vs. sample : CPU {:0>3} : {:%F %H:%M}"'.format(testname, cpu_index, datetime.now()))
111#       Override common
112        g_plot('set xlabel "Samples"')
113        g_plot('set ylabel "P-State"')
114        g_plot('set y2label "Scaled Busy/performance/io-busy(%)"')
115        set_4_plot_linestyles(g_plot)
116        g_plot('plot "' + file_name + '" using {:d}:{:d} with linespoints linestyle 1 axis x1y2 title "performance",\\'.format(C_SAMPLE, C_CORE))
117        g_plot('"' + file_name + '" using {:d}:{:d} with linespoints linestyle 2 axis x1y2 title "scaled-busy",\\'.format(C_SAMPLE, C_SCALED))
118        g_plot('"' + file_name + '" using {:d}:{:d} with linespoints linestyle 3 axis x1y2 title "io-boost",\\'.format(C_SAMPLE, C_BOOST))
119        g_plot('"' + file_name + '" using {:d}:{:d} with linespoints linestyle 4 axis x1y1 title "P-State"'.format(C_SAMPLE, C_TO))
120
121def plot_perf_busy(cpu_index):
122    """ Plot some per cpu information """
123
124    file_name = 'cpu{:0>3}.csv'.format(cpu_index)
125    if os.path.exists(file_name):
126        output_png = "cpu%03d_perf_busy.png" % cpu_index
127        g_plot = common_all_gnuplot_settings(output_png)
128#   autoscale this one, no set y1 range
129        g_plot('set y2range [0:200]')
130        g_plot('set y2tics 0, 10')
131        g_plot('set title "{} : perf busy : CPU {:0>3} : {:%F %H:%M}"'.format(testname, cpu_index, datetime.now()))
132        g_plot('set ylabel "P-State"')
133        g_plot('set y2label "Scaled Busy/performance/io-busy(%)"')
134        set_4_plot_linestyles(g_plot)
135        g_plot('plot "' + file_name + '" using {:d}:{:d} with linespoints linestyle 1 axis x1y2 title "performance",\\'.format(C_ELAPSED, C_CORE))
136        g_plot('"' + file_name + '" using {:d}:{:d} with linespoints linestyle 2 axis x1y2 title "scaled-busy",\\'.format(C_ELAPSED, C_SCALED))
137        g_plot('"' + file_name + '" using {:d}:{:d} with linespoints linestyle 3 axis x1y2 title "io-boost",\\'.format(C_ELAPSED, C_BOOST))
138        g_plot('"' + file_name + '" using {:d}:{:d} with linespoints linestyle 4 axis x1y1 title "P-State"'.format(C_ELAPSED, C_TO))
139
140def plot_durations(cpu_index):
141    """ Plot per cpu durations """
142
143    file_name = 'cpu{:0>3}.csv'.format(cpu_index)
144    if os.path.exists(file_name):
145        output_png = "cpu%03d_durations.png" % cpu_index
146        g_plot = common_all_gnuplot_settings(output_png)
147#       autoscale this one, no set y range
148        g_plot('set title "{} : durations : CPU {:0>3} : {:%F %H:%M}"'.format(testname, cpu_index, datetime.now()))
149        g_plot('set ylabel "Timer Duration (MilliSeconds)"')
150#       override common
151        g_plot('set key off')
152        set_4_plot_linestyles(g_plot)
153        g_plot('plot "' + file_name + '" using {:d}:{:d} with linespoints linestyle 1 axis x1y1'.format(C_ELAPSED, C_DURATION))
154
155def plot_loads(cpu_index):
156    """ Plot per cpu loads """
157
158    file_name = 'cpu{:0>3}.csv'.format(cpu_index)
159    if os.path.exists(file_name):
160        output_png = "cpu%03d_loads.png" % cpu_index
161        g_plot = common_all_gnuplot_settings(output_png)
162        g_plot('set yrange [0:100]')
163        g_plot('set ytics 0, 10')
164        g_plot('set title "{} : loads : CPU {:0>3} : {:%F %H:%M}"'.format(testname, cpu_index, datetime.now()))
165        g_plot('set ylabel "CPU load (percent)"')
166#       override common
167        g_plot('set key off')
168        set_4_plot_linestyles(g_plot)
169        g_plot('plot "' + file_name + '" using {:d}:{:d} with linespoints linestyle 1 axis x1y1'.format(C_ELAPSED, C_LOAD))
170
171def plot_pstate_cpu_with_sample():
172    """ Plot all cpu information """
173
174    if os.path.exists('cpu.csv'):
175        output_png = 'all_cpu_pstates_vs_samples.png'
176        g_plot = common_all_gnuplot_settings(output_png)
177#       autoscale this one, no set y range
178#       override common
179        g_plot('set xlabel "Samples"')
180        g_plot('set ylabel "P-State"')
181        g_plot('set title "{} : cpu pstate vs. sample : {:%F %H:%M}"'.format(testname, datetime.now()))
182        title_list = subprocess.check_output('ls cpu???.csv | sed -e \'s/.csv//\'',shell=True).decode('utf-8').replace('\n', ' ')
183        plot_str = "plot for [i in title_list] i.'.csv' using {:d}:{:d} pt 7 ps 1 title i".format(C_SAMPLE, C_TO)
184        g_plot('title_list = "{}"'.format(title_list))
185        g_plot(plot_str)
186
187def plot_pstate_cpu():
188    """ Plot all cpu information from csv files """
189
190    output_png = 'all_cpu_pstates.png'
191    g_plot = common_all_gnuplot_settings(output_png)
192#   autoscale this one, no set y range
193    g_plot('set ylabel "P-State"')
194    g_plot('set title "{} : cpu pstates : {:%F %H:%M}"'.format(testname, datetime.now()))
195
196#    the following command is really cool, but doesn't work with the CPU masking option because it aborts on the first missing file.
197#    plot_str = 'plot for [i=0:*] file=sprintf("cpu%03d.csv",i) title_s=sprintf("cpu%03d",i) file using 16:7 pt 7 ps 1 title title_s'
198#
199    title_list = subprocess.check_output('ls cpu???.csv | sed -e \'s/.csv//\'',shell=True).decode('utf-8').replace('\n', ' ')
200    plot_str = "plot for [i in title_list] i.'.csv' using {:d}:{:d} pt 7 ps 1 title i".format(C_ELAPSED, C_TO)
201    g_plot('title_list = "{}"'.format(title_list))
202    g_plot(plot_str)
203
204def plot_load_cpu():
205    """ Plot all cpu loads """
206
207    output_png = 'all_cpu_loads.png'
208    g_plot = common_all_gnuplot_settings(output_png)
209    g_plot('set yrange [0:100]')
210    g_plot('set ylabel "CPU load (percent)"')
211    g_plot('set title "{} : cpu loads : {:%F %H:%M}"'.format(testname, datetime.now()))
212
213    title_list = subprocess.check_output('ls cpu???.csv | sed -e \'s/.csv//\'',shell=True).decode('utf-8').replace('\n', ' ')
214    plot_str = "plot for [i in title_list] i.'.csv' using {:d}:{:d} pt 7 ps 1 title i".format(C_ELAPSED, C_LOAD)
215    g_plot('title_list = "{}"'.format(title_list))
216    g_plot(plot_str)
217
218def plot_frequency_cpu():
219    """ Plot all cpu frequencies """
220
221    output_png = 'all_cpu_frequencies.png'
222    g_plot = common_all_gnuplot_settings(output_png)
223#   autoscale this one, no set y range
224    g_plot('set ylabel "CPU Frequency (GHz)"')
225    g_plot('set title "{} : cpu frequencies : {:%F %H:%M}"'.format(testname, datetime.now()))
226
227    title_list = subprocess.check_output('ls cpu???.csv | sed -e \'s/.csv//\'',shell=True).decode('utf-8').replace('\n', ' ')
228    plot_str = "plot for [i in title_list] i.'.csv' using {:d}:{:d} pt 7 ps 1 title i".format(C_ELAPSED, C_FREQ)
229    g_plot('title_list = "{}"'.format(title_list))
230    g_plot(plot_str)
231
232def plot_duration_cpu():
233    """ Plot all cpu durations """
234
235    output_png = 'all_cpu_durations.png'
236    g_plot = common_all_gnuplot_settings(output_png)
237#   autoscale this one, no set y range
238    g_plot('set ylabel "Timer Duration (MilliSeconds)"')
239    g_plot('set title "{} : cpu durations : {:%F %H:%M}"'.format(testname, datetime.now()))
240
241    title_list = subprocess.check_output('ls cpu???.csv | sed -e \'s/.csv//\'',shell=True).decode('utf-8').replace('\n', ' ')
242    plot_str = "plot for [i in title_list] i.'.csv' using {:d}:{:d} pt 7 ps 1 title i".format(C_ELAPSED, C_DURATION)
243    g_plot('title_list = "{}"'.format(title_list))
244    g_plot(plot_str)
245
246def plot_scaled_cpu():
247    """ Plot all cpu scaled busy """
248
249    output_png = 'all_cpu_scaled.png'
250    g_plot = common_all_gnuplot_settings(output_png)
251#   autoscale this one, no set y range
252    g_plot('set ylabel "Scaled Busy (Unitless)"')
253    g_plot('set title "{} : cpu scaled busy : {:%F %H:%M}"'.format(testname, datetime.now()))
254
255    title_list = subprocess.check_output('ls cpu???.csv | sed -e \'s/.csv//\'',shell=True).decode('utf-8').replace('\n', ' ')
256    plot_str = "plot for [i in title_list] i.'.csv' using {:d}:{:d} pt 7 ps 1 title i".format(C_ELAPSED, C_SCALED)
257    g_plot('title_list = "{}"'.format(title_list))
258    g_plot(plot_str)
259
260def plot_boost_cpu():
261    """ Plot all cpu IO Boosts """
262
263    output_png = 'all_cpu_boost.png'
264    g_plot = common_all_gnuplot_settings(output_png)
265    g_plot('set yrange [0:100]')
266    g_plot('set ylabel "CPU IO Boost (percent)"')
267    g_plot('set title "{} : cpu io boost : {:%F %H:%M}"'.format(testname, datetime.now()))
268
269    title_list = subprocess.check_output('ls cpu???.csv | sed -e \'s/.csv//\'',shell=True).decode('utf-8').replace('\n', ' ')
270    plot_str = "plot for [i in title_list] i.'.csv' using {:d}:{:d} pt 7 ps 1 title i".format(C_ELAPSED, C_BOOST)
271    g_plot('title_list = "{}"'.format(title_list))
272    g_plot(plot_str)
273
274def plot_ghz_cpu():
275    """ Plot all cpu tsc ghz """
276
277    output_png = 'all_cpu_ghz.png'
278    g_plot = common_all_gnuplot_settings(output_png)
279#   autoscale this one, no set y range
280    g_plot('set ylabel "TSC Frequency (GHz)"')
281    g_plot('set title "{} : cpu TSC Frequencies (Sanity check calculation) : {:%F %H:%M}"'.format(testname, datetime.now()))
282
283    title_list = subprocess.check_output('ls cpu???.csv | sed -e \'s/.csv//\'',shell=True).decode('utf-8').replace('\n', ' ')
284    plot_str = "plot for [i in title_list] i.'.csv' using {:d}:{:d} pt 7 ps 1 title i".format(C_ELAPSED, C_GHZ)
285    g_plot('title_list = "{}"'.format(title_list))
286    g_plot(plot_str)
287
288def common_all_gnuplot_settings(output_png):
289    """ common gnuplot settings for multiple CPUs one one graph. """
290
291    g_plot = common_gnuplot_settings()
292    g_plot('set output "' + output_png + '"')
293    return(g_plot)
294
295def common_gnuplot_settings():
296    """ common gnuplot settings. """
297
298    g_plot = Gnuplot.Gnuplot(persist=1)
299#   The following line is for rigor only. It seems to be assumed for .csv files
300    g_plot('set datafile separator \",\"')
301    g_plot('set ytics nomirror')
302    g_plot('set xtics nomirror')
303    g_plot('set xtics font ", 10"')
304    g_plot('set ytics font ", 10"')
305    g_plot('set tics out scale 1.0')
306    g_plot('set grid')
307    g_plot('set key out horiz')
308    g_plot('set key bot center')
309    g_plot('set key samplen 2 spacing .8 font ", 9"')
310    g_plot('set term png size 1200, 600')
311    g_plot('set title font ", 11"')
312    g_plot('set ylabel font ", 10"')
313    g_plot('set xlabel font ", 10"')
314    g_plot('set xlabel offset 0, 0.5')
315    g_plot('set xlabel "Elapsed Time (Seconds)"')
316    return(g_plot)
317
318def set_4_plot_linestyles(g_plot):
319    """ set the linestyles used for 4 plots in 1 graphs. """
320
321    g_plot('set style line 1 linetype 1 linecolor rgb "green" pointtype -1')
322    g_plot('set style line 2 linetype 1 linecolor rgb "red" pointtype -1')
323    g_plot('set style line 3 linetype 1 linecolor rgb "purple" pointtype -1')
324    g_plot('set style line 4 linetype 1 linecolor rgb "blue" pointtype -1')
325
326def store_csv(cpu_int, time_pre_dec, time_post_dec, core_busy, scaled, _from, _to, mperf, aperf, tsc, freq_ghz, io_boost, common_comm, load, duration_ms, sample_num, elapsed_time, tsc_ghz, cpu_mask):
327    """ Store master csv file information """
328
329    global graph_data_present
330
331    if cpu_mask[cpu_int] == 0:
332        return
333
334    try:
335        f_handle = open('cpu.csv', 'a')
336        string_buffer = "CPU_%03u, %05u, %06u, %u, %u, %u, %u, %u, %u, %u, %.4f, %u, %.2f, %.3f, %u, %.3f, %.3f, %s\n" % (cpu_int, int(time_pre_dec), int(time_post_dec), int(core_busy), int(scaled), int(_from), int(_to), int(mperf), int(aperf), int(tsc), freq_ghz, int(io_boost), load, duration_ms, sample_num, elapsed_time, tsc_ghz, common_comm)
337        f_handle.write(string_buffer);
338        f_handle.close()
339    except:
340        print('IO error cpu.csv')
341        return
342
343    graph_data_present = True;
344
345def split_csv(current_max_cpu, cpu_mask):
346    """ seperate the all csv file into per CPU csv files. """
347
348    if os.path.exists('cpu.csv'):
349        for index in range(0, current_max_cpu + 1):
350            if cpu_mask[int(index)] != 0:
351                os.system('grep -m 1 common_cpu cpu.csv > cpu{:0>3}.csv'.format(index))
352                os.system('grep CPU_{:0>3} cpu.csv >> cpu{:0>3}.csv'.format(index, index))
353
354def fix_ownership(path):
355    """Change the owner of the file to SUDO_UID, if required"""
356
357    uid = os.environ.get('SUDO_UID')
358    gid = os.environ.get('SUDO_GID')
359    if uid is not None:
360        os.chown(path, int(uid), int(gid))
361
362def cleanup_data_files():
363    """ clean up existing data files """
364
365    if os.path.exists('cpu.csv'):
366        os.remove('cpu.csv')
367    f_handle = open('cpu.csv', 'a')
368    f_handle.write('common_cpu, common_secs, common_usecs, core_busy, scaled_busy, from, to, mperf, aperf, tsc, freq, boost, load, duration_ms, sample_num, elapsed_time, tsc_ghz, common_comm')
369    f_handle.write('\n')
370    f_handle.close()
371
372def clear_trace_file():
373    """ Clear trace file """
374
375    try:
376        f_handle = open('/sys/kernel/tracing/trace', 'w')
377        f_handle.close()
378    except:
379        print('IO error clearing trace file ')
380        sys.exit(2)
381
382def enable_trace(trace_file):
383    """ Enable trace """
384
385    try:
386        open(trace_file,'w').write("1")
387    except:
388        print('IO error enabling trace ')
389        sys.exit(2)
390
391def disable_trace(trace_file):
392    """ Disable trace """
393
394    try:
395       open(trace_file, 'w').write("0")
396    except:
397        print('IO error disabling trace ')
398        sys.exit(2)
399
400def set_trace_buffer_size(memory):
401    """ Set trace buffer size """
402
403    try:
404       with open('/sys/kernel/tracing/buffer_size_kb', 'w') as fp:
405          fp.write(memory)
406    except:
407       print('IO error setting trace buffer size ')
408       sys.exit(2)
409
410def free_trace_buffer():
411    """ Free the trace buffer memory """
412
413    try:
414       open('/sys/kernel/tracing/buffer_size_kb'
415                 , 'w').write("1")
416    except:
417        print('IO error freeing trace buffer ')
418        sys.exit(2)
419
420def read_trace_data(filename, cpu_mask):
421    """ Read and parse trace data """
422
423    global current_max_cpu
424    global sample_num, last_sec_cpu, last_usec_cpu, start_time
425
426    try:
427        data = open(filename, 'r').read()
428    except:
429        print('Error opening ', filename)
430        sys.exit(2)
431
432    for line in data.splitlines():
433        search_obj = \
434            re.search(r'(^(.*?)\[)((\d+)[^\]])(.*?)(\d+)([.])(\d+)(.*?core_busy=)(\d+)(.*?scaled=)(\d+)(.*?from=)(\d+)(.*?to=)(\d+)(.*?mperf=)(\d+)(.*?aperf=)(\d+)(.*?tsc=)(\d+)(.*?freq=)(\d+)'
435                      , line)
436
437        if search_obj:
438            cpu = search_obj.group(3)
439            cpu_int = int(cpu)
440            cpu = str(cpu_int)
441
442            time_pre_dec = search_obj.group(6)
443            time_post_dec = search_obj.group(8)
444            core_busy = search_obj.group(10)
445            scaled = search_obj.group(12)
446            _from = search_obj.group(14)
447            _to = search_obj.group(16)
448            mperf = search_obj.group(18)
449            aperf = search_obj.group(20)
450            tsc = search_obj.group(22)
451            freq = search_obj.group(24)
452            common_comm = search_obj.group(2).replace(' ', '')
453
454            # Not all kernel versions have io_boost field
455            io_boost = '0'
456            search_obj = re.search(r'.*?io_boost=(\d+)', line)
457            if search_obj:
458                io_boost = search_obj.group(1)
459
460            if sample_num == 0 :
461                start_time = Decimal(time_pre_dec) + Decimal(time_post_dec) / Decimal(1000000)
462            sample_num += 1
463
464            if last_sec_cpu[cpu_int] == 0 :
465                last_sec_cpu[cpu_int] = time_pre_dec
466                last_usec_cpu[cpu_int] = time_post_dec
467            else :
468                duration_us = (int(time_pre_dec) - int(last_sec_cpu[cpu_int])) * 1000000 + (int(time_post_dec) - int(last_usec_cpu[cpu_int]))
469                duration_ms = Decimal(duration_us) / Decimal(1000)
470                last_sec_cpu[cpu_int] = time_pre_dec
471                last_usec_cpu[cpu_int] = time_post_dec
472                elapsed_time = Decimal(time_pre_dec) + Decimal(time_post_dec) / Decimal(1000000) - start_time
473                load = Decimal(int(mperf)*100)/ Decimal(tsc)
474                freq_ghz = Decimal(freq)/Decimal(1000000)
475#               Sanity check calculation, typically anomalies indicate missed samples
476#               However, check for 0 (should never occur)
477                tsc_ghz = Decimal(0)
478                if duration_ms != Decimal(0) :
479                    tsc_ghz = Decimal(tsc)/duration_ms/Decimal(1000000)
480                store_csv(cpu_int, time_pre_dec, time_post_dec, core_busy, scaled, _from, _to, mperf, aperf, tsc, freq_ghz, io_boost, common_comm, load, duration_ms, sample_num, elapsed_time, tsc_ghz, cpu_mask)
481
482            if cpu_int > current_max_cpu:
483                current_max_cpu = cpu_int
484# End of for each trace line loop
485# Now seperate the main overall csv file into per CPU csv files.
486    split_csv(current_max_cpu, cpu_mask)
487
488def signal_handler(signal, frame):
489    print(' SIGINT: Forcing cleanup before exit.')
490    if interval:
491        disable_trace(trace_file)
492        clear_trace_file()
493        # Free the memory
494        free_trace_buffer()
495        sys.exit(0)
496
497if __name__ == "__main__":
498    trace_file = "/sys/kernel/tracing/events/power/pstate_sample/enable"
499    signal.signal(signal.SIGINT, signal_handler)
500
501    interval = ""
502    filename = ""
503    cpu_list = ""
504    testname = ""
505    memory = "10240"
506    graph_data_present = False;
507
508    valid1 = False
509    valid2 = False
510
511    cpu_mask = zeros((MAX_CPUS,), dtype=int)
512
513    try:
514        opts, args = getopt.getopt(sys.argv[1:],"ht:i:c:n:m:",["help","trace_file=","interval=","cpu=","name=","memory="])
515    except getopt.GetoptError:
516        print_help('intel_pstate')
517        sys.exit(2)
518    for opt, arg in opts:
519        if opt == '-h':
520            print_help('intel_pstate')
521            sys.exit()
522        elif opt in ("-t", "--trace_file"):
523            valid1 = True
524            location = os.path.realpath(os.path.join(os.getcwd(), os.path.dirname(__file__)))
525            filename = os.path.join(location, arg)
526        elif opt in ("-i", "--interval"):
527            valid1 = True
528            interval = arg
529        elif opt in ("-c", "--cpu"):
530            cpu_list = arg
531        elif opt in ("-n", "--name"):
532            valid2 = True
533            testname = arg
534        elif opt in ("-m", "--memory"):
535            memory = arg
536
537    if not (valid1 and valid2):
538        print_help('intel_pstate')
539        sys.exit()
540
541    if cpu_list:
542        for p in re.split("[,]", cpu_list):
543            if int(p) < MAX_CPUS :
544                cpu_mask[int(p)] = 1
545    else:
546        for i in range (0, MAX_CPUS):
547            cpu_mask[i] = 1
548
549    if not os.path.exists('results'):
550        os.mkdir('results')
551        # The regular user needs to own the directory, not root.
552        fix_ownership('results')
553
554    os.chdir('results')
555    if os.path.exists(testname):
556        print('The test name directory already exists. Please provide a unique test name. Test re-run not supported, yet.')
557        sys.exit()
558    os.mkdir(testname)
559    # The regular user needs to own the directory, not root.
560    fix_ownership(testname)
561    os.chdir(testname)
562
563    # Temporary (or perhaps not)
564    cur_version = sys.version_info
565    print('python version (should be >= 3.6):')
566    print(cur_version)
567
568    # Left as "cleanup" for potential future re-run ability.
569    cleanup_data_files()
570
571    if interval:
572        filename = "/sys/kernel/tracing/trace"
573        clear_trace_file()
574        set_trace_buffer_size(memory)
575        enable_trace(trace_file)
576        print('Sleeping for ', interval, 'seconds')
577        time.sleep(int(interval))
578        disable_trace(trace_file)
579
580    current_max_cpu = 0
581
582    read_trace_data(filename, cpu_mask)
583
584    if interval:
585        clear_trace_file()
586        # Free the memory
587        free_trace_buffer()
588
589    if graph_data_present == False:
590        print('No valid data to plot')
591        sys.exit(2)
592
593    for cpu_no in range(0, current_max_cpu + 1):
594        plot_perf_busy_with_sample(cpu_no)
595        plot_perf_busy(cpu_no)
596        plot_durations(cpu_no)
597        plot_loads(cpu_no)
598
599    plot_pstate_cpu_with_sample()
600    plot_pstate_cpu()
601    plot_load_cpu()
602    plot_frequency_cpu()
603    plot_duration_cpu()
604    plot_scaled_cpu()
605    plot_boost_cpu()
606    plot_ghz_cpu()
607
608    # It is preferrable, but not necessary, that the regular user owns the files, not root.
609    for root, dirs, files in os.walk('.'):
610        for f in files:
611            fix_ownership(f)
612
613    os.chdir('../../')
614