#!/usr/bin/python # # Intel GPU Top data analyser # import sys import os import getopt import tempfile HEADER=""" Analysis for %(title)s

Analysis for %(title)s

Summary:

Execution lasted %(duration).2f seconds

""" PERF_REFERENCE="""

Kernel perf results analysis

""" MINMAXAVG="""

%(descr)s:

Minimum: %(min)s
Maximum: %(max)s
Average: %(avg)s

""" FIGURE="""



%(title)s

""" PERF_ITERATION="""

Second %(sec)d

Processes in execution: """ PERF_SECOND_TITLE=""" Second %(sec)d
""" PERF_PROCESS_REF=""" %(process)s """ PERF_PROCESS="""

Process %(process)s

""" PERF_TOP="""
%(top)s
""" TAIL=""" """ def print_header(output): """Prints the header""" print >>output, HEADER def collect(fd): """Collects statistics for a given command""" columns={} results = {} while True: line = fd.readline() if not line: break line.strip() if line[0] == "#": # detecting column names cols = line[1:].strip().split('\t') for pos in range(len(cols)): columns[cols[pos]] = pos for col in columns: results[col] = [] continue data = line.split() # fill in results from the headers for item in columns.keys(): pos = columns[item] results[item].append(float(data[pos])) return results def analyse_perf(logfile, out_dir, perf="perf.html"): """Analyses perf results""" if not os.access(logfile, os.R_OK): print "Error: unable to access perf log file %s" % logfile return perf_data = os.popen("perf script -f comm,pid,time,ip,sym -i %s 2>/dev/null" % logfile) if not perf_data: print "Error: unable to process perf log %s" % logfile return results = {} time_start = -1 time_prev = -1 while 1: line = perf_data.readline() if not line: break fields = line.strip().split() process = fields[0] pid = int(fields[1]) time = float(fields[2][:-1]) # remove the trailing ':' ip = fields[3] if len(fields) > 4: function = fields[4] else: function = "unknown" # calculate time if time_start == -1: time_start = int(time) # floor down to the corresponding second # are we on the next second already? if time - time_start > 1.0: time_start = int(time) if time_start not in results: results[time_start] = {} # is the current process being tracked? if process not in results[time_start]: results[time_start][process]={} # calculate times for functions if function not in results[time_start][process]: results[time_start][process][function] = 0 # ok, so now calculate per-function per-process stats results[time_start][process][function] += (time - time_prev) time_prev = time # all done, now process the results seconds = results.keys() seconds.sort() output = open("%s/%s" % (out_dir, perf), "w") print >>output, HEADER % {'title': 'Perf results for %s' % logfile, 'duration': len(seconds) } for sec in seconds: # print TOC print >>output, PERF_SECOND_TITLE % {'sec': sec - seconds[0]} for sec in seconds: print >>output, PERF_ITERATION % {'sec': sec - seconds[0], 'processes': ', '.join(results[sec].keys())} for process in results[sec]: print >>output, PERF_PROCESS_REF % {'sec': sec - seconds[0], 'process': process} for process in results[sec]: print >>output, PERF_PROCESS % {'sec': sec - seconds[0], 'process': process} # let's sort functions functions_by_time = sorted(results[sec][process], key=lambda key: results[sec][process][key], reverse=True) top = "" for function in functions_by_time: top += "%.6f\t\t%s\n" % (results[sec][process][function], function) print >>output, PERF_TOP % { 'top': top } print >>output, TAIL output.close() def plot_series(timeline, values, results, axis, title, linestyle='solid'): """A helper function to simplify plotting of a function from the array of results. It checks if the column does, in fact, exists; and if it does, carries on with plotting""" for column in [timeline, values]: if column not in results: print "Column %s not available for plotting" % values return axis.plot(results[timeline], results[values], label=title, linestyle=linestyle) def analyse(results, title, out_dir, perf_logfile=None, summary="index.html"): """Analyses intel_gpu_top results""" # calculate min/max/avg values keys = results.keys() for iter in keys: if not results[iter]: print "ERROR: no results collected for '%s', skipping" % iter continue results["%s_min" % iter] = min(results[iter]) results["%s_max" % iter] = max(results[iter]) results["%s_avg" % iter] = sum(results[iter]) / len(results[iter]) # start composing the output output = open("%s/%s" % (out_dir, summary), "w") print >>output, HEADER % {'title': title, 'duration': results['time'][-1] } # print summaries for iter, descr in [('utime', 'User time % of CPU'), ('stime', 'System time % of CPU'), ('power', 'Power consumption (W)'), ('render', 'Render engine usage % of GPU'), ('render.ops', 'Render engine ops per interval'), ('bitstream', 'Bitstream engine usage % of GPU'), ('bitstream.ops', 'Bitstream engine ops per interval'), ('bitstream6', 'Bitstream 6 engine usage % of GPU'), ('bitstream6.ops', 'Bitstream 6 engine ops per interval'), ('blitter', 'Blitter engine usage % of GPU'), ('blitter.ops', 'Blitter engine ops per interval'), ]: if iter not in results: print "Column %s not present in results, skipping" % iter continue minval = results['%s_min' % iter] maxval = results['%s_max' % iter] avgval = results['%s_avg' % iter] if minval == maxval == avgval: # No variation in data, skipping print "No variations in %s, skipping" % iter continue minval_s = "%.2f" % minval maxval_s = "%.2f" % maxval avgval_s = "%.2f" % avgval print >>output, MINMAXAVG % { 'descr': descr, 'min': minval_s, 'max': maxval_s, 'avg': avgval_s, } # Do we have perf results? if perf_logfile: print >>output, PERF_REFERENCE % {'perf': 'perf.html'} # graphics try: import pylab except: print "Error: unable to import pylab: %s" % sys.exc_value return fig = pylab.figure() ax = pylab.subplot(111) box = ax.get_position() pylab.title("Summary of CPU/GPU/Power usage") pylab.ylabel("Usage (%)") pylab.xlabel("Time (s)") plot_series('time', 'utime', results, ax, "User time") plot_series('time', 'stime', results, ax, "System time") num_axis = 2 for ring in ["render", "bitstream", "bitstream6", "blitter"]: if results.get("%s_avg" % ring, -1) == -1: print "No data for %s, skipping" % ring continue plot_series('time', ring, results, ax, ring) num_axis += 1 # Do we have power? if results.get("power_avg", -1) != -1: # plotting power ax2 = ax.twinx() plot_series('time', 'power', results, ax2, "Power", "dotted") ax2.set_ylabel('Watts') ax2.set_position([box.x0, box.y0 + box.height * 0.1, box.width, box.height * 0.9]) ax2.legend(loc = 'upper center', ncol=3, fancybox=True, shadow=True, bbox_to_anchor = (0.5, 0.0)) pylab.grid() # Shink current axis's height by 10% on the bottom ax.set_position([box.x0, box.y0 + box.height * 0.1, box.width, box.height * 0.9]) ax.legend(loc = 'upper center', ncol=num_axis, fancybox=True, shadow=True, bbox_to_anchor = (0.5, -0.1)) pylab.savefig("%s/plot_summary.svg" % out_dir, format="svg", dpi=200) print >>output, FIGURE % {'img': 'plot_summary.svg', 'title': 'Summary of the execution, outlining the CPU usage (in user and system mode), per-ring GPU usage, and power usage (if available) ' } # graphics ops/per/second fig = pylab.figure() ax = pylab.subplot(111) pylab.title("GPU rings ops per interval") pylab.ylabel("Ops (n)") pylab.xlabel("Time (s)") num_axis = 0 for ring in ["render.ops", "bitstream.ops", "bitstream6.ops", "blitter.ops"]: plot_series('time', ring, results, ax, ring) num_axis += 1 pylab.grid() # Shink current axis's height by 10% on the bottom box = ax.get_position() ax.set_position([box.x0, box.y0 + box.height * 0.1, box.width, box.height * 0.9]) ax.legend(loc = 'upper center', ncol=num_axis, fancybox=True, shadow=True, bbox_to_anchor = (0.5, -0.1)) pylab.savefig("%s/plot_gpu_ops.svg" % out_dir, format="svg", dpi=200) print >>output, FIGURE % {'img': 'plot_gpu_ops.svg', 'title': 'Ops per interval for each GPU ring during the execution' } # power fig = pylab.figure() pylab.title("Power usage summary") pylab.ylabel("Power (W)") pylab.xlabel("Time (s)") ax = pylab.subplot(111) plot_series('time', 'power', results, ax, "Power") plot_series('time', 'power.chipset', results, ax, "Chipset") plot_series('time', 'power.gfx', results, ax, "GFX") ax.set_position([box.x0, box.y0 + box.height * 0.1, box.width, box.height * 0.9]) ax.legend(loc = 'upper center', ncol=3, fancybox=True, shadow=True, bbox_to_anchor = (0.5, -0.1)) pylab.grid() # Shink current axis's height by 10% on the bottom pylab.savefig("%s/plot_power.svg" % out_dir, format="svg", dpi=200) print >>output, FIGURE % {'img': 'plot_power.svg', 'title': 'Power usage over the course of execution' } # power vs CPU fig = pylab.figure() ax = pylab.subplot(111) ax.set_position([box.x0, box.y0 + box.height * 0.1, box.width, box.height * 0.9]) pylab.title("CPU vs Power usage") pylab.ylabel("Usage (%)") pylab.xlabel("Time (s)") plot_series('time', 'utime', results, ax, "User time") plot_series('time', 'stime', results, ax, "System time") # plotting power if 'power' in results: ax2 = ax.twinx() plot_series('time', 'power', results, ax2, "Power", "dashed") ax2.set_ylabel('Watts') ax2.legend(loc = 'upper center', ncol=1, fancybox=True, shadow=True, bbox_to_anchor = (0.5, 0.0)) pylab.grid() # Shink current axis's height by 10% on the bottom box = ax.get_position() ax.legend(loc = 'upper center', ncol=3, fancybox=True, shadow=True, bbox_to_anchor = (0.5, -0.1)) pylab.savefig("%s/plot_power_cpu.svg" % out_dir, format="svg", dpi=200) print >>output, FIGURE % {'img': 'plot_power_cpu.svg', 'title': 'Power utilization co-related to CPU' } # power vs GPU fig = pylab.figure() ax = pylab.subplot(111) pylab.title("GPU vs Power usage") pylab.ylabel("Usage (%)") pylab.xlabel("Time (s)") num_axis = 0 for ring in ["render", "bitstream", "bitstream6", "blitter"]: plot_series('time', ring, results, ax, ring, "dashed") num_axis += 1 # Do we have power? # plotting power ax2 = ax.twinx() plot_series('time', 'power', results, ax2, "Power") plot_series('time', 'power.chipset', results, ax2, "Chipset") plot_series('time', 'power.gfx', results, ax2, "GFX") ax2.set_ylabel('Watts') num_axis += 1 pylab.grid() # Shink current axis's height by 10% on the bottom box = ax.get_position() ax.set_position([box.x0, box.y0 + box.height * 0.1, box.width, box.height * 0.9]) ax2.set_position([box.x0, box.y0 + box.height * 0.1, box.width, box.height * 0.9]) ax.legend(loc = 'upper center', ncol=num_axis, fancybox=True, shadow=True, bbox_to_anchor = (0.5, -0.1)) ax2.legend(loc = 'upper center', ncol=3, fancybox=True, shadow=True, bbox_to_anchor = (0.5, 0.0)) pylab.savefig("%s/plot_power_gpu.svg" % out_dir, format="svg", dpi=200) print >>output, FIGURE % {'img': 'plot_power_gpu.svg', 'title': 'Power utilization co-related to all GPU rings' } # per-ring power for ring in ["render", "bitstream", "bitstream6", "blitter"]: fig = pylab.figure() ax = pylab.subplot(111) box = ax.get_position() ax.set_position([box.x0, box.y0 + box.height * 0.1, box.width, box.height * 0.9]) pylab.title("Power usage vs %s ring" % ring) pylab.ylabel("Usage (%)") pylab.xlabel("Time (s)") plot_series('time', ring, results, ax, ring, "dashed") # plotting power ax2 = ax.twinx() plot_series('time', 'power', results, ax2, "Power") plot_series('time', 'power.chipset', results, ax2, "Chipset") plot_series('time', 'power.gfx', results, ax2, "GFX") ax2.set_ylabel('Watts') pylab.grid() # Shink current axis's height by 10% on the bottom ax2.set_position([box.x0, box.y0 + box.height * 0.1, box.width, box.height * 0.9]) ax.legend(loc = 'upper center', ncol=1, fancybox=True, shadow=True, bbox_to_anchor = (0.5, -0.1)) ax2.legend(loc = 'upper center', ncol=3, fancybox=True, shadow=True, bbox_to_anchor = (0.5, 0.0)) pylab.savefig("%s/plot_power_gpu_%s.svg" % (out_dir, ring), format="svg", dpi=200) print >>output, FIGURE % {'img': 'plot_power_gpu_%s.svg' % ring, 'title': 'Power utilization co-related to the %s ring' % ring } pass print >>output, TAIL output.close() def usage(cmd): """Prints usage message""" print """Intel-gpu-analyser: intel GPU data analyser Usage: %s [parameters]. The following arguments are accepted: -h, --help display help message -l, --logfile intel-gpu-top log file to analyse -c, --command command to profile -t, --title description of the command analysed -o, --output output directory for the results -p, --perf kernel perf log file to analyse. You need to specify either a log file to analyse, or a command to profile, or a perf log file to process. When you specify both -l and -c, the last one takes predence. If you do not give a log file to analyse, or a command to profile, this tool will become sad, depressed and suicide itself with exit code of -1, and you will feel bad about it. """ % cmd if __name__ == "__main__": # parse command line try: opt, args = getopt.getopt(sys.argv[1:], 'hl:c:t:o:p:', ['help', 'logfile=', 'command=', 'title=', 'output=', 'perf=']) except getopt.error: usage(sys.argv[0]) sys.exit(1) logfile = None perf_logfile = None title = None output = os.curdir for o in opt: # help if o[0] == '-h' or o[0] == '--help': usage(sys.argv[0]) sys.exit(0) # list elif o[0] == '-p' or o[0] == '--perf': perf_logfile = o[1] print "Analysing perf log file: %s" % perf_logfile elif o[0] == '-l' or o[0] == '--logfile': logfile = open(o[1], "r") title = "Log file '%s'" % o[1] elif o[0] == '-c' or o[0] == '--command': logfile = os.popen("intel_gpu_top -o - -e \"%s\"" % o[1]) title = "Execution of '%s'" % o[1] # force new level elif o[0] == '-t' or o[0] == '--title': title = o[1] elif o[0] == '-o' or o[0] == '--output': output = o[1] if not logfile and not perf_logfile: usage(sys.argv[0]) print "Error: no log file and no command to profile, don't know what to do" sys.exit(1) try: os.makedirs(output) except: pass if logfile: results = collect(logfile) analyse(results, title, output, perf_logfile=perf_logfile) if perf_logfile: analyse_perf(perf_logfile, output)