aboutsummaryrefslogtreecommitdiffstats
path: root/plot_pm2.py
diff options
context:
space:
mode:
Diffstat (limited to 'plot_pm2.py')
-rwxr-xr-xplot_pm2.py407
1 files changed, 383 insertions, 24 deletions
diff --git a/plot_pm2.py b/plot_pm2.py
index d53a6da..c2fcbf3 100755
--- a/plot_pm2.py
+++ b/plot_pm2.py
@@ -1,17 +1,50 @@
1#!/usr/bin/env python 1#!/usr/bin/env python
2import defapp
3from os.path import splitext, basename 2from os.path import splitext, basename
4from optparse import make_option as o 3from optparse import make_option as o
5from tempfile import NamedTemporaryFile as Tmp 4from tempfile import NamedTemporaryFile as Tmp
6 5
7import csv 6from collections import defaultdict
7from itertools import izip
8
9import numpy as np
10from util import *
11
12import stats
13import defapp
8 14
9from plot import decode 15from plot import decode
10from gnuplot import gnuplot, FORMATS 16from gnuplot import gnuplot, FileGraph, FORMATS
17
18
19
20def ludwig_l2(x, y):
21 # x left column, y right column, or # y left column, x, right column
22 return (x % 8 < 4 and x + 4 == y) or \
23 (y % 8 < 4 and x - 4 == y)
24
25def ludwig_l3(x, y):
26 # same socket
27 # not a a shared L2
28 # not identical
29 return (y % 4) == (x % 4) and \
30 not ludwig_l2(x, y) and \
31 x != y
11 32
12 33
13MACHINE_TOPOLOGY = { 34MACHINE_TOPOLOGY = {
14 'jupiter-cs' : (4, [('preempt', lambda x, y: x == y), ('mem', lambda x, y: x != y)]) 35 'jupiter-cs' : (4, [('preempt', lambda x, y: x == y),
36 ('mem', lambda x, y: x != y)]),
37
38 # Socket0 Socket1 Socket2 Socket3
39 # ------ ------- ------- -------
40 # | 0, 4| | 1, 5| | 2, 6| | 3, 7|
41 # | 8,12| | 9,13| |10,14| |11,15|
42 # |16,20| |17,21| |18,22| |19,23|
43 # ------- ------- ------- -------
44 'ludwig.cs.unc.edu' : (24, [('preempt', lambda x, y: x == y),
45 ('l2', ludwig_l2),
46 ('l3', ludwig_l3),
47 ('mem', lambda x, y: abs(y - x) % 4 != 0)])
15} 48}
16 49
17PMO_PARAM = { 50PMO_PARAM = {
@@ -22,6 +55,8 @@ PMO_PARAM = {
22 55
23PMO_MEM = { 56PMO_MEM = {
24 'mem' : 'a migration through main memory', 57 'mem' : 'a migration through main memory',
58 'l3' : 'a migration through a shared L3 cache',
59 'l2' : 'a migration through a shared L2 cache',
25 'preempt' : 'a preemption', 60 'preempt' : 'a preemption',
26 'all' : 'either a migration or preemption', 61 'all' : 'either a migration or preemption',
27} 62}
@@ -38,6 +73,25 @@ PMO_SUBPLOTS = [
38 (3, 10, 9, True), 73 (3, 10, 9, True),
39] 74]
40 75
76PMO_AGGR_SUBPLOTS = [
77 # x, y, y-delta, split according to mem-hierarchy?
78 (0, 6, None, False),
79 (0, 7, None, False),
80 (0, 8, None, False),
81 (0, 9, None, False),
82 (0, 10, None, True),
83# (0, 10, 6, True),
84# (0, 10, 7, True),
85# (0, 10, 8, True),
86 (0, 10, 9, True),
87 (0, 8, 7, False), # difference of second to first hot access
88 (0, 9, 8, False), # difference of third to second hot access
89]
90
91PMO_AGGR_COMBINE = [
92 [(6, 'all'), (7, 'all'), (8, 'all'), (9, 'all')]
93]
94
41PMO_COL_LABEL = [('measurement', 'sample', 'index'), 95PMO_COL_LABEL = [('measurement', 'sample', 'index'),
42 ('write cycles', 'wcycle', 'every nth access'), 96 ('write cycles', 'wcycle', 'every nth access'),
43 ('WSS', 'wcc', 'kilobytes'), 97 ('WSS', 'wcc', 'kilobytes'),
@@ -60,6 +114,11 @@ options = [
60 o(None, '--paper', action='store_true', dest='paper'), 114 o(None, '--paper', action='store_true', dest='paper'),
61 o(None, '--wide', action='store_true', dest='wide'), 115 o(None, '--wide', action='store_true', dest='wide'),
62 o(None, '--split', action='store_true', dest='split'), 116 o(None, '--split', action='store_true', dest='split'),
117 o(None, '--log-y', action='store_true', dest='logy'),
118 o(None, '--errorbar', action='store_true', dest='errbar'),
119 o(None, '--extend', action='store', type='float', dest='extend'),
120 o(None, '--aggregate', action='store_true', dest='aggregate'),
121 o('-c', '--cycles-per-usec', action='store', type='float', dest='cycles_per_usec'),
63 ] 122 ]
64 123
65defaults = { 124defaults = {
@@ -67,48 +126,155 @@ defaults = {
67 'paper' : False, 126 'paper' : False,
68 'split' : False, 127 'split' : False,
69 'wide' : False, 128 'wide' : False,
129 'aggregate' : False,
130 'extend' : 1.5,
131 'cycles_per_usec' : None,
132 'logy' : False,
133 'errbar' : False,
70 } 134 }
71 135
72def extract_cols(data, xcol, ycol1, ycol2, cast=int, cpu_filter=lambda x, y: True): 136def extract_cols(data, xcol, ycol1, ycol2, cast=int, cpu_filter=lambda x, y: True):
73 for row in data: 137 def matching_cpus(row):
74 fcpu = int(row[PMO_FROM_CPU]) 138 return cpu_filter(row[PMO_FROM_CPU], row[PMO_TO_CPU])
75 tcpu = int(row[PMO_TO_CPU]) 139 rows = select(matching_cpus, data)
76 if cpu_filter(fcpu, tcpu): 140 if not (ycol2 is None):
77 if ycol2 is None: 141 rows[:,ycol1] -= rows[:,ycol2]
78 yield (row[xcol], cast(row[ycol1])) 142 return rows[:,(xcol, ycol1)]
79 else:
80 yield (row[xcol], cast(row[ycol1]) - cast(row[ycol2]))
81 143
82class CyclePlotter(defapp.App): 144class CyclePlotter(defapp.App):
83 def __init__(self): 145 def __init__(self):
84 defapp.App.__init__(self, options, defaults, no_std_opts=True) 146 defapp.App.__init__(self, options, defaults, no_std_opts=True)
147 self.aggregate_data = []
85 148
86 def setup_pmo_graphs(self, datafile, conf): 149 def setup_pmo_graphs(self, datafile, conf, subplots=PMO_SUBPLOTS):
87 host = conf['host'] 150 host = conf['host']
88 if host in MACHINE_TOPOLOGY: 151 if host in MACHINE_TOPOLOGY:
89 (cpus, hier) = MACHINE_TOPOLOGY[host] 152 (cpus, hier) = MACHINE_TOPOLOGY[host]
90 plots = [] 153 plots = []
91 data = list(csv.reader(open(datafile))) 154 data = load_csv_file(datafile, dtype=int)
92 for (xcol, ycol, yminus, by_mem_hierarchy) in PMO_SUBPLOTS: 155 for (xcol, ycol, yminus, by_mem_hierarchy) in subplots:
93 sub = [('all', lambda x, y: True)] 156 sub = [('all', lambda x, y: True)]
94 if by_mem_hierarchy: 157 if by_mem_hierarchy:
95 sub += hier 158 sub += hier
96 for tag, test in sub: 159 for tag, test in sub:
97 tmp = Tmp() 160 rows = extract_cols(data,
98 for row in extract_cols(data, 161 xcol, ycol, yminus,
99 xcol, ycol, yminus, 162 cpu_filter=test)
100 cpu_filter=test): 163 plots.append((rows, xcol, ycol, yminus, tag))
101 tmp.write("%s, %s\n" % row)
102 tmp.flush()
103 plots.append((tmp, xcol, ycol, yminus, tag))
104 return plots 164 return plots
105 else: 165 else:
106 self.err('Unkown host: %s' % host) 166 self.err('Unkown host: %s' % host)
107 return None 167 return None
108 168
169 def write_aggregate(self, datafiles):
170 # (wss, avg, wc, #avg, #wc)
171 # by tag -> by wcycle -> list of data points)
172 by_tag = defaultdict(lambda: defaultdict(list))
173
174 host = None
175
176 for i, datafile in enumerate(datafiles):
177 print '[%d/%d] Processing %s...' % (i + 1, len(datafiles), datafile)
178 bname = basename(datafile)
179 name, ext = splitext(bname)
180 if ext != '.csv':
181 self.err("Warning: '%s' doesn't look like a CSV file."
182 % bname)
183 conf = decode(name)
184 if 'pmo' in conf:
185 plots = self.setup_pmo_graphs(datafile, conf, PMO_AGGR_SUBPLOTS)
186 if plots is None:
187 print "Skipping %s..." % datafile
188 return
189 if not host:
190 host = conf['host']
191 if host != conf['host']:
192 self.err('Mixing data from two hosts! (%s, %s)' % (host, conf['host']))
193 self.err('Aborting.')
194 return
195 wss = int(conf['wss'])
196 wcycle = int(conf['wcycle'])
197 for (rows, xcol, ycol, yminus, tag) in plots:
198 clean = stats.iqr_remove_outliers(rows, extend=self.options.extend)
199 vals = clean[:,1]
200 avg = np.mean(vals)
201 std = np.std(vals, ddof=1)
202 wc = np.max(vals)
203 n = len(vals)
204
205 key = (xcol, ycol, yminus, tag)
206 by_tag[key][wcycle].append((wss, avg, std, wc, n, len(rows) - n))
207 del plots
208 else:
209 self.err("Warning: '%s' is not a PMO experiment; skipping." % bname)
210
211 all_wss = set()
212 all_wcycle = set()
213
214 for key in by_tag:
215 for wcycle in by_tag[key]:
216 all_wcycle.add(wcycle)
217
218 data = by_tag[key][wcycle]
219 # sort by increasing WSS
220 data.sort(key=lambda row: row[0])
221 for row in data:
222 all_wss.add(row[0])
223
224 (xcol, ycol, yminus, tag) = key
225
226 xtag = PMO_COL_LABEL[xcol][1]
227 ytag = PMO_COL_LABEL[ycol][1]
228 dtag = "-delta-%s" % PMO_COL_LABEL[yminus][1] if not yminus is None else ""
229 code = "code=%s-%s-%s-%s" % key
230 figname = "host=%s_%s%s-vs-%s_%s_%s" % \
231 (host, ytag, dtag, xtag, tag, code)
232
233 write_csv_file('pmo-aggr_wcycle=%d_%s.csv' % (wcycle, figname), data)
234
235
236 mems = [tag for (tag, _) in MACHINE_TOPOLOGY[host][1]]
237
238 for wcycle in all_wcycle:
239 try:
240 rows = [[wss] for wss in sorted(all_wss)]
241 header = ['wss']
242 for (x, y, yminus, split) in PMO_AGGR_SUBPLOTS:
243 tags = ['all']
244 if split:
245 tags += mems
246 for tag in tags:
247 col_name = "%s %s" % (PMO_COL_LABEL[ycol][1], tag)
248 if not yminus is None:
249 col_name += ' - ' + PMO_COL_LABEL[yminus][1]
250 header += [col_name + " avg", col_name + " std", col_name + " wc"]
251 key = (x, y, yminus, tag)
252 data = by_tag[key][wcycle]
253 for r, d in izip(rows, data):
254 if r[0] != d[0]:
255 print "mismatch", r[0], d[0], key, wcycle
256 assert r[0] == d[0] # working set size must match
257 r += d[1:4] # (average, std, wc)
258 write_csv_file('pmo-all_wcycle=%d_host=%s.csv' % (wcycle, host),
259 rows, header, width=max([len(h) for h in header]))
260 except AssertionError:
261 self.err("Data missing for wcycle=%d!" % wcycle)
262
263
109 def plot_preempt_migrate(self, datafile, name, conf): 264 def plot_preempt_migrate(self, datafile, name, conf):
110 plots = self.setup_pmo_graphs(datafile, conf) 265 plots = self.setup_pmo_graphs(datafile, conf)
111 for (tmp, xcol, ycol, yminus, tag) in plots: 266 if plots is None:
267 print "Skipping %s..." % datafile
268 return
269 else:
270 print 'Plotting %s...' % datafile
271 for (rows, xcol, ycol, yminus, tag) in plots:
272 # Write it to a temp file.
273 tmp = Tmp()
274 for row in rows:
275 tmp.write("%s, %s\n" % (row[0], row[1]))
276 tmp.flush()
277
112 xtag = PMO_COL_LABEL[xcol][1] 278 xtag = PMO_COL_LABEL[xcol][1]
113 ytag = PMO_COL_LABEL[ycol][1] 279 ytag = PMO_COL_LABEL[ycol][1]
114 dtag = "-delta-%s" % PMO_COL_LABEL[yminus][1] if not yminus is None else "" 280 dtag = "-delta-%s" % PMO_COL_LABEL[yminus][1] if not yminus is None else ""
@@ -123,7 +289,16 @@ class CyclePlotter(defapp.App):
123 for key in conf: 289 for key in conf:
124 if key in PMO_PARAM: 290 if key in PMO_PARAM:
125 title += " %s=%s" % (PMO_PARAM[key], conf[key]) 291 title += " %s=%s" % (PMO_PARAM[key], conf[key])
126 gnuplot([(tmp.name, 1, 2, ylabel)], 292 graphs = [(tmp.name, 1, 2, ylabel)]
293 # plot cutoff
294 (s, lo, hi) = stats.iqr(rows[:,1])
295 lo -= s * self.options.extend
296 hi += s * self.options.extend
297 m99 = stats.cutoff_max(rows[:, 1])
298 graphs += [(lo, 'IQR cutoff (%d)' % lo, 'line'),
299 (hi, 'IQR cutoff (%d)' % hi, 'line'),
300 (m99,'99%% cutoff (%d)' % m99, 'line lw 2')]
301 gnuplot(graphs,
127 xlabel="%s (%s)" % (xlabel, xunit), 302 xlabel="%s (%s)" % (xlabel, xunit),
128 ylabel="%s (%s)" % ("access cost" if yminus is None 303 ylabel="%s (%s)" % ("access cost" if yminus is None
129 else "delta to %s" % PMO_COL_LABEL[yminus][0], 304 else "delta to %s" % PMO_COL_LABEL[yminus][0],
@@ -132,6 +307,183 @@ class CyclePlotter(defapp.App):
132 style='points', 307 style='points',
133 format=self.options.format, 308 format=self.options.format,
134 fname=figname) 309 fname=figname)
310 del tmp # delete temporary file
311
312 def plot_pmo_aggr(self, datafile, name, conf):
313 fname = datafile
314 code = conf['code']
315 (xcol, ycol, yminus, tag) = code.split('-')
316
317 xcol = int(xcol)
318 ycol = int(ycol)
319 if yminus != "None":
320 yminus = int(ycol)
321 else:
322 yminus = None
323
324 xtag = PMO_COL_LABEL[xcol][1]
325 ytag = PMO_COL_LABEL[ycol][1]
326 dtag = "-delta-%s" % PMO_COL_LABEL[yminus][1] if not yminus is None else ""
327 figname = name #"%s_%s%s-vs-%s_%s" % (name, ytag, dtag, xtag, tag)
328 xunit = PMO_COL_LABEL[xcol][2]
329 yunit = PMO_COL_LABEL[ycol][2]
330 ylabel = PMO_COL_LABEL[ycol][0]
331 xlabel = PMO_COL_LABEL[xcol][0]
332 title = "%s" % ylabel
333
334 ylabel="%s (%s)" % ("access cost" if yminus is None
335 else "delta to %s" % PMO_COL_LABEL[yminus][0],
336 yunit),
337 if ycol == 10:
338 title += " from %s" % PMO_MEM[tag]
339 for key in conf:
340 if key in PMO_PARAM:
341 title += " %s=%s" % (PMO_PARAM[key], conf[key])
342
343 graphs = [
344 #(fname, 1, 2, "average"),
345 "'%s' using 1:2:3 title 'average' with errorbars" % (fname),
346 (fname, 1, 4, "maximum"),
347 ]
348 xlabel = "working set size (kilobytes)"
349
350 yrange = (4096, 2**26) if yminus is None else None
351
352 gnuplot(graphs, xlabel=xlabel, ylabel=ylabel, title=title, fname=figname,
353 yrange=yrange,
354 logscale="xy 2" if yminus is None else "x 2",
355 format=self.options.format)
356
357 def plot_pmo_all(self, datafile, name, conf):
358 host = conf['host']
359 mems = [tag for (tag, _) in MACHINE_TOPOLOGY[host][1]]
360 columns = []
361 idx = 2
362 header = ["wss"]
363 for (x, y, yminus, split) in PMO_AGGR_SUBPLOTS:
364 tags = ['all']
365 if split:
366 tags += mems
367 for tag in tags:
368 col_name = "%s %s" % (PMO_COL_LABEL[y][1], tag)
369 if not yminus is None:
370 col_name += ' - ' + PMO_COL_LABEL[yminus][1]
371 header += [col_name + " avg", col_name + " std", col_name + " wc"]
372 columns.append((x, y, yminus, tag, idx))
373 idx += 3
374
375 data = load_csv_file(datafile)
376 if self.options.cycles_per_usec:
377 yunit = "(us)"
378 data[:, 1:] /= self.options.cycles_per_usec
379 else:
380 yunit = "(cycles)"
381
382 csvfile = "xxx-%s" % datafile
383
384 write_csv_file(csvfile, data, header, width=max([len(h) for h in header]))
385
386 rw = int(conf['wcycle'])
387 rw = 1.0 / rw * 100 if rw != 0 else 0
388
389 if self.options.logy:
390 axis = ("x 2", "y 10")
391 else:
392 axis = "x 2"
393
394 # raw measures
395 for offset, kind, long in [(0, 'avg', 'average'), (2, 'wc', 'maximum')]:
396 graphs = []
397 for (x, y, yminus, tag, idx) in columns:
398 if yminus is None:
399 label = PMO_COL_LABEL[y][0]
400 if y == 10:
401 label += " from %s" % PMO_MEM[tag]
402 graphs.append(
403 FileGraph(
404 csvfile, xcol=1, ycol=idx + offset, title=label,
405 error=idx + offset + 1 if kind == 'avg' and self.options.errbar else None))
406 xlabel = "working set size (kilobytes)"
407 ylabel = "time to complete access " + yunit
408 title = "measured %s WSS access time (%.2f%% writes)" % (long, rw)
409 yrange = None #(4096, 2**26)
410
411 fname = "%s_full_%s" % (name, kind)
412
413 gnuplot(graphs, xlabel=xlabel, ylabel=ylabel, title=title, fname=fname,
414 yrange=yrange, logscale=axis, format=self.options.format)
415
416 # per-sample delta measures
417 for offset, kind, long in [(0, 'avg', 'average'), (2, 'wc', 'maximum')]:
418 graphs = []
419 for (x, y, yminus, tag, idx) in columns:
420 if not (yminus is None) and tag != 'all':
421 label = "%s" % PMO_MEM[tag]
422 graphs.append(
423 FileGraph(
424 csvfile, xcol=1, ycol=idx + offset, title=label,
425 error=idx + offset + 1 if kind == 'avg' and self.options.errbar else None))
426 xlabel = "working set size (kilobytes)"
427 ylabel = "per-sample delta to hot access " + yunit
428 title = "measured %s overhead (%.2f%% writes)" % (long, rw)
429 yrange = None
430
431 fname = "%s_delta_%s" % (name, kind)
432 gnuplot(graphs, xlabel=xlabel, ylabel=ylabel, title=title, fname=fname,
433 yrange=yrange, logscale=axis, format=self.options.format)
434
435 graphs = []
436 for (x, y, yminus, tag, idx) in columns:
437 if y in [8, 9] and yminus in [7, 8] and tag == 'all':
438 label = "%s to %s" % (PMO_COL_LABEL[yminus][0], PMO_COL_LABEL[y][0])
439 graphs.append(
440 FileGraph(
441 csvfile, xcol=1, ycol=idx + offset, title=label,
442 error=idx + offset + 1 if kind == 'avg' and self.options.errbar else None))
443 xlabel = "working set size (kilobytes)"
444 ylabel = "per-sample delta to previous hot access " + yunit
445 title = "measured %s differences (%.2f%% writes)" % (long, rw)
446 yrange = None
447
448 fname = "%s_delta-h_%s" % (name, kind)
449 gnuplot(graphs, xlabel=xlabel, ylabel=ylabel, title=title, fname=fname,
450 yrange=yrange, logscale=axis, format=self.options.format)
451
452# del tmp
453
454 # stats delta
455 # find hot column
456 col = None
457 for (x, y, yminus, tag, idx) in columns:
458 if x == 0 and y == 9 and yminus is None and tag == 'all':
459 col = idx
460 break
461 # normalize based on third hot access
462 # +1/-1 to get zero-based indices; Gnuplot wants 1-based indices
463 hot_avg = data[:,col - 1].copy()
464 hot_wc = data[:,col + 1].copy()
465 for (x, y, yminus, tag, idx) in columns:
466 data[:,idx - 1] -= hot_avg
467 data[:,idx + 1] -= hot_wc
468
469 tmp = write_csv_file(None, data)
470
471 for offset, kind, long in [(0, 'avg', 'average'), (2, 'wc', 'maximum')]:
472 graphs = []
473 for (x, y, yminus, tag, idx) in columns:
474 if yminus is None and tag != 'all':
475 label = PMO_COL_LABEL[y][0]
476 label = PMO_MEM[tag]
477 graphs.append(FileGraph(tmp.name, xcol=1, ycol=idx+offset, title=label))
478 xlabel = "working set size (kilobytes)"
479 ylabel = "delta to third hot access " + yunit
480 title = "difference of %s access costs (%.2f%% writes)" % (long, rw)
481 yrange = None
482
483 fname = "%s_diff_%s" % (name, kind)
484 gnuplot(graphs, xlabel=xlabel, ylabel=ylabel, title=title, fname=fname,
485 yrange=yrange, logscale=axis, format=self.options.format)
486# del tmp
135 487
136 def plot_file(self, datafile): 488 def plot_file(self, datafile):
137 bname = basename(datafile) 489 bname = basename(datafile)
@@ -142,6 +494,10 @@ class CyclePlotter(defapp.App):
142 conf = decode(name) 494 conf = decode(name)
143 if 'pmo' in conf: 495 if 'pmo' in conf:
144 self.plot_preempt_migrate(datafile, name, conf) 496 self.plot_preempt_migrate(datafile, name, conf)
497 elif 'pmo-aggr' in conf:
498 self.plot_pmo_aggr(datafile, name, conf)
499 elif 'pmo-all' in conf:
500 self.plot_pmo_all(datafile, name, conf)
145 else: 501 else:
146 self.err("Skipped '%s'; unkown experiment type." 502 self.err("Skipped '%s'; unkown experiment type."
147 % bname) 503 % bname)
@@ -150,5 +506,8 @@ class CyclePlotter(defapp.App):
150 for datafile in self.args: 506 for datafile in self.args:
151 self.plot_file(datafile) 507 self.plot_file(datafile)
152 508
509 def do_aggregate(self, _):
510 self.write_aggregate(self.args[1:])
511
153if __name__ == "__main__": 512if __name__ == "__main__":
154 CyclePlotter().launch() 513 CyclePlotter().launch()