forked from OSchip/llvm-project
324 lines
12 KiB
Python
324 lines
12 KiB
Python
#!/usr/bin/env 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()
|