aboutsummaryrefslogtreecommitdiff
path: root/final/runtime/tools/summarizeStats.py
diff options
context:
space:
mode:
Diffstat (limited to 'final/runtime/tools/summarizeStats.py')
-rw-r--r--final/runtime/tools/summarizeStats.py323
1 files changed, 323 insertions, 0 deletions
diff --git a/final/runtime/tools/summarizeStats.py b/final/runtime/tools/summarizeStats.py
new file mode 100644
index 0000000..f2c5f5e
--- /dev/null
+++ b/final/runtime/tools/summarizeStats.py
@@ -0,0 +1,323 @@
+#!/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()