[llvm] Stop including unordered_map (NFC)
[llvm-project.git] / openmp / runtime / tools / summarizeStats.py
blob7daed2e1cd5cc00ffafa1167165de65bd800f257
1 #!/usr/bin/env python
3 import pandas as pd
4 import numpy as np
5 import re
6 import sys
7 import os
8 import argparse
9 import matplotlib
10 from matplotlib import pyplot as plt
11 from matplotlib.projections.polar import PolarAxes
12 from matplotlib.projections import register_projection
14 """
15 Read the stats file produced by the OpenMP runtime
16 and produce a processed summary
18 The radar_factory original code was taken from
19 matplotlib.org/examples/api/radar_chart.html
20 We added support to handle negative values for radar charts
21 """
23 def radar_factory(num_vars, frame='circle'):
24 """Create a radar chart with num_vars axes."""
25 # calculate evenly-spaced axis angles
26 theta = 2*np.pi * np.linspace(0, 1-1./num_vars, num_vars)
27 # rotate theta such that the first axis is at the top
28 #theta += np.pi/2
30 def draw_poly_frame(self, x0, y0, r):
31 # TODO: use transforms to convert (x, y) to (r, theta)
32 verts = [(r*np.cos(t) + x0, r*np.sin(t) + y0) for t in theta]
33 return plt.Polygon(verts, closed=True, edgecolor='k')
35 def draw_circle_frame(self, x0, y0, r):
36 return plt.Circle((x0, y0), r)
38 frame_dict = {'polygon': draw_poly_frame, 'circle': draw_circle_frame}
39 if frame not in frame_dict:
40 raise ValueError, 'unknown value for `frame`: %s' % frame
42 class RadarAxes(PolarAxes):
43 """
44 Class for creating a radar chart (a.k.a. a spider or star chart)
46 http://en.wikipedia.org/wiki/Radar_chart
47 """
48 name = 'radar'
49 # use 1 line segment to connect specified points
50 RESOLUTION = 1
51 # define draw_frame method
52 draw_frame = frame_dict[frame]
54 def fill(self, *args, **kwargs):
55 """Override fill so that line is closed by default"""
56 closed = kwargs.pop('closed', True)
57 return super(RadarAxes, self).fill(closed=closed, *args, **kwargs)
59 def plot(self, *args, **kwargs):
60 """Override plot so that line is closed by default"""
61 lines = super(RadarAxes, self).plot(*args, **kwargs)
62 #for line in lines:
63 # self._close_line(line)
65 def set_varlabels(self, labels):
66 self.set_thetagrids(theta * 180/np.pi, labels,fontsize=14)
68 def _gen_axes_patch(self):
69 x0, y0 = (0.5, 0.5)
70 r = 0.5
71 return self.draw_frame(x0, y0, r)
73 register_projection(RadarAxes)
74 return theta
76 # Code to read the raw stats
77 def extractSI(s):
78 """Convert a measurement with a range suffix into a suitably scaled value"""
79 du = s.split()
80 num = float(du[0])
81 units = du[1] if len(du) == 2 else ' '
82 # http://physics.nist.gov/cuu/Units/prefixes.html
83 factor = {'Y': 1e24,
84 'Z': 1e21,
85 'E': 1e18,
86 'P': 1e15,
87 'T': 1e12,
88 'G': 1e9,
89 'M': 1e6,
90 'k': 1e3,
91 ' ': 1 ,
92 'm': -1e3, # Yes, I do mean that, see below for the explanation.
93 'u': -1e6,
94 'n': -1e9,
95 'p': -1e12,
96 'f': -1e15,
97 'a': -1e18,
98 'z': -1e21,
99 'y': -1e24}[units[0]]
100 # Minor trickery here is an attempt to preserve accuracy by using a single
101 # divide, rather than multiplying by 1/x, which introduces two roundings
102 # since 1/10 is not representable perfectly in IEEE floating point. (Not
103 # that this really matters, other than for cleanliness, since we're likely
104 # reading numbers with at most five decimal digits of precision).
105 return num*factor if factor > 0 else num/-factor
107 def readData(f):
108 line = f.readline()
109 fieldnames = [x.strip() for x in line.split(',')]
110 line = f.readline().strip()
111 data = []
112 while line != "":
113 if line[0] != '#':
114 fields = line.split(',')
115 data.append ((fields[0].strip(), [extractSI(v) for v in fields[1:]]))
116 line = f.readline().strip()
117 # Man, working out this next incantation out was non-trivial!
118 # They really want you to be snarfing data in csv or some other
119 # format they understand!
120 res = pd.DataFrame.from_items(data, columns=fieldnames[1:], orient='index')
121 return res
123 def readTimers(f):
124 """Skip lines with leading #"""
125 line = f.readline()
126 while line[0] == '#':
127 line = f.readline()
128 line = line.strip()
129 if line == "Statistics on exit\n" or "Aggregate for all threads\n":
130 line = f.readline()
131 return readData(f)
133 def readCounters(f):
134 """This can be just the same!"""
135 return readData(f)
137 def readFile(fname):
138 """Read the statistics from the file. Return a dict with keys "timers", "counters" """
139 res = {}
140 try:
141 with open(fname) as f:
142 res["timers"] = readTimers(f)
143 res["counters"] = readCounters(f)
144 return res
145 except (OSError, IOError):
146 print "Cannot open " + fname
147 return None
149 def usefulValues(l):
150 """I.e. values which are neither null nor zero"""
151 return [p and q for (p,q) in zip (pd.notnull(l), l != 0.0)]
153 def uselessValues(l):
154 """I.e. values which are null or zero"""
155 return [not p for p in usefulValues(l)]
157 interestingStats = ("counters", "timers")
158 statProperties = {"counters" : ("Count", "Counter Statistics"),
159 "timers" : ("Time (ticks)", "Timer Statistics")
162 def drawChart(data, kind, filebase):
163 """Draw a summary bar chart for the requested data frame into the specified file"""
164 data["Mean"].plot(kind="bar", logy=True, grid=True, colormap="GnBu",
165 yerr=data["SD"], ecolor="black")
166 plt.xlabel("OMP Constructs")
167 plt.ylabel(statProperties[kind][0])
168 plt.title (statProperties[kind][1])
169 plt.tight_layout()
170 plt.savefig(filebase+"_"+kind)
172 def normalizeValues(data, countField, factor):
173 """Normalize values into a rate by dividing them all by the given factor"""
174 data[[k for k in data.keys() if k != countField]] /= factor
177 def setRadarFigure(titles):
178 """Set the attributes for the radar plots"""
179 fig = plt.figure(figsize=(9,9))
180 rect = [0.1, 0.1, 0.8, 0.8]
181 labels = [0.2, 0.4, 0.6, 0.8, 1, 2, 3, 4, 5, 10]
182 matplotlib.rcParams.update({'font.size':13})
183 theta = radar_factory(len(titles))
184 ax = fig.add_axes(rect, projection='radar')
185 ax.set_rgrids(labels)
186 ax.set_varlabels(titles)
187 ax.text(theta[2], 1, "Linear->Log", horizontalalignment='center', color='green', fontsize=18)
188 return {'ax':ax, 'theta':theta}
191 def drawRadarChart(data, kind, filebase, params, color):
192 """Draw the radar plots"""
193 tmp_lin = data * 0
194 tmp_log = data * 0
195 for key in data.keys():
196 if data[key] >= 1:
197 tmp_log[key] = np.log10(data[key])
198 else:
199 tmp_lin[key] = (data[key])
200 params['ax'].plot(params['theta'], tmp_log, color='b', label=filebase+"_"+kind+"_log")
201 params['ax'].plot(params['theta'], tmp_lin, color='r', label=filebase+"_"+kind+"_linear")
202 params['ax'].legend(loc='best', bbox_to_anchor=(1.4,1.2))
203 params['ax'].set_rlim((0, np.ceil(max(tmp_log))))
205 def multiAppBarChartSettings(ax, plt, index, width, n, tmp, s):
206 ax.set_yscale('log')
207 ax.legend()
208 ax.set_xticks(index + width * n / 2)
209 ax.set_xticklabels(tmp[s]['Total'].keys(), rotation=50, horizontalalignment='right')
210 plt.xlabel("OMP Constructs")
211 plt.ylabel(statProperties[s][0])
212 plt.title(statProperties[s][1])
213 plt.tight_layout()
215 def derivedTimerStats(data):
216 stats = {}
217 for key in data.keys():
218 if key == 'OMP_worker_thread_life':
219 totalRuntime = data['OMP_worker_thread_life']
220 elif key in ('FOR_static_iterations', 'OMP_PARALLEL_args',
221 'OMP_set_numthreads', 'FOR_dynamic_iterations'):
222 break
223 else:
224 stats[key] = 100 * data[key] / totalRuntime
225 return stats
227 def compPie(data):
228 compKeys = {}
229 nonCompKeys = {}
230 for key in data.keys():
231 if key in ('OMP_critical', 'OMP_single', 'OMP_serial',
232 'OMP_parallel', 'OMP_master', 'OMP_task_immediate',
233 'OMP_task_taskwait', 'OMP_task_taskyield', 'OMP_task_taskgroup',
234 'OMP_task_join_bar', 'OMP_task_plain_bar', 'OMP_task_taskyield'):
235 compKeys[key] = data[key]
236 else:
237 nonCompKeys[key] = data[key]
238 print "comp keys:", compKeys, "\n\n non comp keys:", nonCompKeys
239 return [compKeys, nonCompKeys]
241 def drawMainPie(data, filebase, colors):
242 sizes = [sum(data[0].values()), sum(data[1].values())]
243 explode = [0,0]
244 labels = ["Compute - " + "%.2f" % sizes[0], "Non Compute - " + "%.2f" % sizes[1]]
245 patches = plt.pie(sizes, explode, colors=colors, startangle=90)
246 plt.title("Time Division")
247 plt.axis('equal')
248 plt.legend(patches[0], labels, loc='best', bbox_to_anchor=(-0.1,1), fontsize=16)
249 plt.savefig(filebase+"_main_pie", bbox_inches='tight')
251 def drawSubPie(data, tag, filebase, colors):
252 explode = []
253 labels = data.keys()
254 sizes = data.values()
255 total = sum(sizes)
256 percent = []
257 for i in range(len(sizes)):
258 explode.append(0)
259 percent.append(100 * sizes[i] / total)
260 labels[i] = labels[i] + " - %.2f" % percent[i]
261 patches = plt.pie(sizes, explode=explode, colors=colors, startangle=90)
262 plt.title(tag+"(Percentage of Total:"+" %.2f" % (sum(data.values()))+")")
263 plt.tight_layout()
264 plt.axis('equal')
265 plt.legend(patches[0], labels, loc='best', bbox_to_anchor=(-0.1,1), fontsize=16)
266 plt.savefig(filebase+"_"+tag, bbox_inches='tight')
268 def main():
269 parser = argparse.ArgumentParser(description='''This script takes a list
270 of files containing each of which contain output from a stats-gathering
271 enabled OpenMP runtime library. Each stats file is read, parsed, and
272 used to produce a summary of the statistics''')
273 parser.add_argument('files', nargs='+',
274 help='files to parse which contain stats-gathering output')
275 command_args = parser.parse_args()
276 colors = ['orange', 'b', 'r', 'yellowgreen', 'lightsage', 'lightpink',
277 'green', 'purple', 'yellow', 'cyan', 'mediumturquoise',
278 'olive']
279 stats = {}
280 matplotlib.rcParams.update({'font.size':22})
281 for s in interestingStats:
282 fig, ax = plt.subplots()
283 width = 0.45
284 n = 0
285 index = 0
287 for f in command_args.files:
288 filebase = os.path.splitext(f)[0]
289 tmp = readFile(f)
290 data = tmp[s]['Total']
291 """preventing repetition by removing rows similar to Total_OMP_work
292 as Total_OMP_work['Total'] is same as OMP_work['Total']"""
293 if s == 'counters':
294 elapsedTime = tmp["timers"]["Mean"]["OMP_worker_thread_life"]
295 normalizeValues(tmp["counters"], "SampleCount",
296 elapsedTime / 1.e9)
297 """Plotting radar charts"""
298 params = setRadarFigure(data.keys())
299 chartType = "radar"
300 drawRadarChart(data, s, filebase, params, colors[n])
301 """radar Charts finish here"""
302 plt.savefig(filebase+"_"+s+"_"+chartType, bbox_inches='tight')
303 elif s == 'timers':
304 print "overheads in "+filebase
305 numThreads = tmp[s]['SampleCount']['Total_OMP_parallel']
306 for key in data.keys():
307 if key[0:5] == 'Total':
308 del data[key]
309 stats[filebase] = derivedTimerStats(data)
310 dataSubSet = compPie(stats[filebase])
311 drawMainPie(dataSubSet, filebase, colors)
312 plt.figure(0)
313 drawSubPie(dataSubSet[0], "Computational Time", filebase, colors)
314 plt.figure(1)
315 drawSubPie(dataSubSet[1], "Non Computational Time", filebase, colors)
316 with open('derivedStats_{}.csv'.format(filebase), 'w') as f:
317 f.write('================={}====================\n'.format(filebase))
318 f.write(pd.DataFrame(stats[filebase].items()).to_csv()+'\n')
319 n += 1
320 plt.close()
322 if __name__ == "__main__":
323 main()