| #!/usr/bin/python |
| |
| import pandas as pd |
| import numpy as np |
| import re |
| import sys |
| import os |
| import argparse |
| import matplotlib |
| from matplotlib import pyplot as plt |
| from matplotlib.projections.polar import PolarAxes |
| from matplotlib.projections import register_projection |
| |
| """ |
| Read the stats file produced by the OpenMP runtime |
| and produce a processed summary |
| |
| The radar_factory original code was taken from |
| matplotlib.org/examples/api/radar_chart.html |
| We added support to handle negative values for radar charts |
| """ |
| |
| def radar_factory(num_vars, frame='circle'): |
| """Create a radar chart with num_vars axes.""" |
| # calculate evenly-spaced axis angles |
| theta = 2*np.pi * np.linspace(0, 1-1./num_vars, num_vars) |
| # rotate theta such that the first axis is at the top |
| #theta += np.pi/2 |
| |
| def draw_poly_frame(self, x0, y0, r): |
| # TODO: use transforms to convert (x, y) to (r, theta) |
| verts = [(r*np.cos(t) + x0, r*np.sin(t) + y0) for t in theta] |
| return plt.Polygon(verts, closed=True, edgecolor='k') |
| |
| def draw_circle_frame(self, x0, y0, r): |
| return plt.Circle((x0, y0), r) |
| |
| frame_dict = {'polygon': draw_poly_frame, 'circle': draw_circle_frame} |
| if frame not in frame_dict: |
| raise ValueError, 'unknown value for `frame`: %s' % frame |
| |
| class RadarAxes(PolarAxes): |
| """ |
| Class for creating a radar chart (a.k.a. a spider or star chart) |
| |
| http://en.wikipedia.org/wiki/Radar_chart |
| """ |
| name = 'radar' |
| # use 1 line segment to connect specified points |
| RESOLUTION = 1 |
| # define draw_frame method |
| draw_frame = frame_dict[frame] |
| |
| def fill(self, *args, **kwargs): |
| """Override fill so that line is closed by default""" |
| closed = kwargs.pop('closed', True) |
| return super(RadarAxes, self).fill(closed=closed, *args, **kwargs) |
| |
| def plot(self, *args, **kwargs): |
| """Override plot so that line is closed by default""" |
| lines = super(RadarAxes, self).plot(*args, **kwargs) |
| #for line in lines: |
| # self._close_line(line) |
| |
| def set_varlabels(self, labels): |
| self.set_thetagrids(theta * 180/np.pi, labels,fontsize=14) |
| |
| def _gen_axes_patch(self): |
| x0, y0 = (0.5, 0.5) |
| r = 0.5 |
| return self.draw_frame(x0, y0, r) |
| |
| register_projection(RadarAxes) |
| return theta |
| |
| # Code to read the raw stats |
| def extractSI(s): |
| """Convert a measurement with a range suffix into a suitably scaled value""" |
| du = s.split() |
| num = float(du[0]) |
| units = du[1] if len(du) == 2 else ' ' |
| # http://physics.nist.gov/cuu/Units/prefixes.html |
| factor = {'Y': 1e24, |
| 'Z': 1e21, |
| 'E': 1e18, |
| 'P': 1e15, |
| 'T': 1e12, |
| 'G': 1e9, |
| 'M': 1e6, |
| 'k': 1e3, |
| ' ': 1 , |
| 'm': -1e3, # Yes, I do mean that, see below for the explanation. |
| 'u': -1e6, |
| 'n': -1e9, |
| 'p': -1e12, |
| 'f': -1e15, |
| 'a': -1e18, |
| 'z': -1e21, |
| 'y': -1e24}[units[0]] |
| # Minor trickery here is an attempt to preserve accuracy by using a single |
| # divide, rather than multiplying by 1/x, which introduces two roundings |
| # since 1/10 is not representable perfectly in IEEE floating point. (Not |
| # that this really matters, other than for cleanliness, since we're likely |
| # reading numbers with at most five decimal digits of precision). |
| return num*factor if factor > 0 else num/-factor |
| |
| def readData(f): |
| line = f.readline() |
| fieldnames = [x.strip() for x in line.split(',')] |
| line = f.readline().strip() |
| data = [] |
| while line != "": |
| if line[0] != '#': |
| fields = line.split(',') |
| data.append ((fields[0].strip(), [extractSI(v) for v in fields[1:]])) |
| line = f.readline().strip() |
| # Man, working out this next incantation out was non-trivial! |
| # They really want you to be snarfing data in csv or some other |
| # format they understand! |
| res = pd.DataFrame.from_items(data, columns=fieldnames[1:], orient='index') |
| return res |
| |
| def readTimers(f): |
| """Skip lines with leading #""" |
| line = f.readline() |
| while line[0] == '#': |
| line = f.readline() |
| line = line.strip() |
| if line == "Statistics on exit\n" or "Aggregate for all threads\n": |
| line = f.readline() |
| return readData(f) |
| |
| def readCounters(f): |
| """This can be just the same!""" |
| return readData(f) |
| |
| def readFile(fname): |
| """Read the statistics from the file. Return a dict with keys "timers", "counters" """ |
| res = {} |
| try: |
| with open(fname) as f: |
| res["timers"] = readTimers(f) |
| res["counters"] = readCounters(f) |
| return res |
| except (OSError, IOError): |
| print "Cannot open " + fname |
| return None |
| |
| def usefulValues(l): |
| """I.e. values which are neither null nor zero""" |
| return [p and q for (p,q) in zip (pd.notnull(l), l != 0.0)] |
| |
| def uselessValues(l): |
| """I.e. values which are null or zero""" |
| return [not p for p in usefulValues(l)] |
| |
| interestingStats = ("counters", "timers") |
| statProperties = {"counters" : ("Count", "Counter Statistics"), |
| "timers" : ("Time (ticks)", "Timer Statistics") |
| } |
| |
| def drawChart(data, kind, filebase): |
| """Draw a summary bar chart for the requested data frame into the specified file""" |
| data["Mean"].plot(kind="bar", logy=True, grid=True, colormap="GnBu", |
| yerr=data["SD"], ecolor="black") |
| plt.xlabel("OMP Constructs") |
| plt.ylabel(statProperties[kind][0]) |
| plt.title (statProperties[kind][1]) |
| plt.tight_layout() |
| plt.savefig(filebase+"_"+kind) |
| |
| def normalizeValues(data, countField, factor): |
| """Normalize values into a rate by dividing them all by the given factor""" |
| data[[k for k in data.keys() if k != countField]] /= factor |
| |
| |
| def setRadarFigure(titles): |
| """Set the attributes for the radar plots""" |
| fig = plt.figure(figsize=(9,9)) |
| rect = [0.1, 0.1, 0.8, 0.8] |
| labels = [0.2, 0.4, 0.6, 0.8, 1, 2, 3, 4, 5, 10] |
| matplotlib.rcParams.update({'font.size':13}) |
| theta = radar_factory(len(titles)) |
| ax = fig.add_axes(rect, projection='radar') |
| ax.set_rgrids(labels) |
| ax.set_varlabels(titles) |
| ax.text(theta[2], 1, "Linear->Log", horizontalalignment='center', color='green', fontsize=18) |
| return {'ax':ax, 'theta':theta} |
| |
| |
| def drawRadarChart(data, kind, filebase, params, color): |
| """Draw the radar plots""" |
| tmp_lin = data * 0 |
| tmp_log = data * 0 |
| for key in data.keys(): |
| if data[key] >= 1: |
| tmp_log[key] = np.log10(data[key]) |
| else: |
| tmp_lin[key] = (data[key]) |
| params['ax'].plot(params['theta'], tmp_log, color='b', label=filebase+"_"+kind+"_log") |
| params['ax'].plot(params['theta'], tmp_lin, color='r', label=filebase+"_"+kind+"_linear") |
| params['ax'].legend(loc='best', bbox_to_anchor=(1.4,1.2)) |
| params['ax'].set_rlim((0, np.ceil(max(tmp_log)))) |
| |
| def multiAppBarChartSettings(ax, plt, index, width, n, tmp, s): |
| ax.set_yscale('log') |
| ax.legend() |
| ax.set_xticks(index + width * n / 2) |
| ax.set_xticklabels(tmp[s]['Total'].keys(), rotation=50, horizontalalignment='right') |
| plt.xlabel("OMP Constructs") |
| plt.ylabel(statProperties[s][0]) |
| plt.title(statProperties[s][1]) |
| plt.tight_layout() |
| |
| def derivedTimerStats(data): |
| stats = {} |
| for key in data.keys(): |
| if key == 'OMP_worker_thread_life': |
| totalRuntime = data['OMP_worker_thread_life'] |
| elif key in ('FOR_static_iterations', 'OMP_PARALLEL_args', |
| 'OMP_set_numthreads', 'FOR_dynamic_iterations'): |
| break |
| else: |
| stats[key] = 100 * data[key] / totalRuntime |
| return stats |
| |
| def compPie(data): |
| compKeys = {} |
| nonCompKeys = {} |
| for key in data.keys(): |
| if key in ('OMP_critical', 'OMP_single', 'OMP_serial', |
| 'OMP_parallel', 'OMP_master', 'OMP_task_immediate', |
| 'OMP_task_taskwait', 'OMP_task_taskyield', 'OMP_task_taskgroup', |
| 'OMP_task_join_bar', 'OMP_task_plain_bar', 'OMP_task_taskyield'): |
| compKeys[key] = data[key] |
| else: |
| nonCompKeys[key] = data[key] |
| print "comp keys:", compKeys, "\n\n non comp keys:", nonCompKeys |
| return [compKeys, nonCompKeys] |
| |
| def drawMainPie(data, filebase, colors): |
| sizes = [sum(data[0].values()), sum(data[1].values())] |
| explode = [0,0] |
| labels = ["Compute - " + "%.2f" % sizes[0], "Non Compute - " + "%.2f" % sizes[1]] |
| patches = plt.pie(sizes, explode, colors=colors, startangle=90) |
| plt.title("Time Division") |
| plt.axis('equal') |
| plt.legend(patches[0], labels, loc='best', bbox_to_anchor=(-0.1,1), fontsize=16) |
| plt.savefig(filebase+"_main_pie", bbox_inches='tight') |
| |
| def drawSubPie(data, tag, filebase, colors): |
| explode = [] |
| labels = data.keys() |
| sizes = data.values() |
| total = sum(sizes) |
| percent = [] |
| for i in range(len(sizes)): |
| explode.append(0) |
| percent.append(100 * sizes[i] / total) |
| labels[i] = labels[i] + " - %.2f" % percent[i] |
| patches = plt.pie(sizes, explode=explode, colors=colors, startangle=90) |
| plt.title(tag+"(Percentage of Total:"+" %.2f" % (sum(data.values()))+")") |
| plt.tight_layout() |
| plt.axis('equal') |
| plt.legend(patches[0], labels, loc='best', bbox_to_anchor=(-0.1,1), fontsize=16) |
| plt.savefig(filebase+"_"+tag, bbox_inches='tight') |
| |
| def main(): |
| parser = argparse.ArgumentParser(description='''This script takes a list |
| of files containing each of which contain output from a stats-gathering |
| enabled OpenMP runtime library. Each stats file is read, parsed, and |
| used to produce a summary of the statistics''') |
| parser.add_argument('files', nargs='+', |
| help='files to parse which contain stats-gathering output') |
| command_args = parser.parse_args() |
| colors = ['orange', 'b', 'r', 'yellowgreen', 'lightsage', 'lightpink', |
| 'green', 'purple', 'yellow', 'cyan', 'mediumturquoise', |
| 'olive'] |
| stats = {} |
| matplotlib.rcParams.update({'font.size':22}) |
| for s in interestingStats: |
| fig, ax = plt.subplots() |
| width = 0.45 |
| n = 0 |
| index = 0 |
| |
| for f in command_args.files: |
| filebase = os.path.splitext(f)[0] |
| tmp = readFile(f) |
| data = tmp[s]['Total'] |
| """preventing repetition by removing rows similar to Total_OMP_work |
| as Total_OMP_work['Total'] is same as OMP_work['Total']""" |
| if s == 'counters': |
| elapsedTime = tmp["timers"]["Mean"]["OMP_worker_thread_life"] |
| normalizeValues(tmp["counters"], "SampleCount", |
| elapsedTime / 1.e9) |
| """Plotting radar charts""" |
| params = setRadarFigure(data.keys()) |
| chartType = "radar" |
| drawRadarChart(data, s, filebase, params, colors[n]) |
| """radar Charts finish here""" |
| plt.savefig(filebase+"_"+s+"_"+chartType, bbox_inches='tight') |
| elif s == 'timers': |
| print "overheads in "+filebase |
| numThreads = tmp[s]['SampleCount']['Total_OMP_parallel'] |
| for key in data.keys(): |
| if key[0:5] == 'Total': |
| del data[key] |
| stats[filebase] = derivedTimerStats(data) |
| dataSubSet = compPie(stats[filebase]) |
| drawMainPie(dataSubSet, filebase, colors) |
| plt.figure(0) |
| drawSubPie(dataSubSet[0], "Computational Time", filebase, colors) |
| plt.figure(1) |
| drawSubPie(dataSubSet[1], "Non Computational Time", filebase, colors) |
| with open('derivedStats_{}.csv'.format(filebase), 'w') as f: |
| f.write('================={}====================\n'.format(filebase)) |
| f.write(pd.DataFrame(stats[filebase].items()).to_csv()+'\n') |
| n += 1 |
| plt.close() |
| |
| if __name__ == "__main__": |
| main() |