#!/usr/bin/env python from os.path import splitext, basename from optparse import make_option as o from tempfile import NamedTemporaryFile as Tmp from collections import defaultdict import numpy as np from util import load_csv_file, select import stats import defapp from plot import decode from gnuplot import gnuplot, FORMATS MACHINE_TOPOLOGY = { 'jupiter-cs' : (4, [('preempt', lambda x, y: x == y), ('mem', lambda x, y: x != y)]), # Socket0 Socket1 Socket2 Socket3 # ------ ------- ------- ------- # | 0, 4| | 1, 5| | 2, 6| | 3, 7| # | 8,12| | 9,13| |10,14| |11,15| # |16,20| |17,21| |18,22| |19,23| # ------- ------- ------- ------- 'ludwig.cs.unc.edu' : (24, [('preempt', lambda x, y: x == y), ('l2', lambda x, y: abs(y - x) == 4), ('l3', lambda x, y: abs(y - x) > 4 and \ abs(y - x) % 4 == 0), ('mem', lambda x, y: abs(y - x) % 4 != 0)]) } PMO_PARAM = { 'wss' : 'WSS', 'host' : 'host', 'wcycle' : 'write-cycle' } PMO_MEM = { 'mem' : 'a migration through main memory', 'l3' : 'a migration through a shared L3 cache', 'l2' : 'a migration through a shared L2 cache', 'preempt' : 'a preemption', 'all' : 'either a migration or preemption', } PMO_SUBPLOTS = [ # x, y, y-delta, split according to mem-hierarchy? (0, 6, None, False), (0, 7, None, False), (0, 8, None, False), (0, 9, None, False), (0, 10, None, True), (3, 10, None, True), (0, 10, 9, True), (3, 10, 9, True), ] PMO_AGGR_SUBPLOTS = [ # x, y, y-delta, split according to mem-hierarchy? (0, 6, None, False), (0, 7, None, False), (0, 8, None, False), (0, 9, None, False), (0, 10, None, True), (0, 10, 6, True), (0, 10, 7, True), (0, 10, 9, True), (0, 10, 8, True), ] PMO_COL_LABEL = [('measurement', 'sample', 'index'), ('write cycles', 'wcycle', 'every nth access'), ('WSS', 'wcc', 'kilobytes'), ('suspension length', 'delay', 'microseconds'), ('CPU (preempted on)', 'from', 'processor'), ('CPU (resumed on)', 'to', 'processor'), ('cold access', 'cold', 'cycles'), ('first hot access', 'hot1', 'cycles'), ('second hot access', 'hot2', 'cycles'), ('third hot access', 'hot3', 'cycles'), ('access after resuming', 'after', 'cycles') ] PMO_FROM_CPU = 4 PMO_TO_CPU = 5 options = [ o('-f', '--format', action='store', dest='format', type='choice', choices=FORMATS, help='output format'), o(None, '--paper', action='store_true', dest='paper'), o(None, '--wide', action='store_true', dest='wide'), o(None, '--split', action='store_true', dest='split'), o(None, '--extend', action='store', type='float', dest='extend'), o(None, '--aggregate', action='store_true', dest='aggregate'), ] defaults = { 'format' : 'show', 'paper' : False, 'split' : False, 'wide' : False, 'aggregate' : False, 'extend' : 1.5, } def extract_cols(data, xcol, ycol1, ycol2, cast=int, cpu_filter=lambda x, y: True): def matching_cpus(row): return cpu_filter(row[PMO_FROM_CPU], row[PMO_TO_CPU]) rows = select(matching_cpus, data) if not (ycol2 is None): rows[:,ycol1] -= rows[:,ycol2] return rows[:,(xcol, ycol1)] class CyclePlotter(defapp.App): def __init__(self): defapp.App.__init__(self, options, defaults, no_std_opts=True) self.aggregate_data = [] def setup_pmo_graphs(self, datafile, conf, subplots=PMO_SUBPLOTS): host = conf['host'] if host in MACHINE_TOPOLOGY: (cpus, hier) = MACHINE_TOPOLOGY[host] plots = [] data = load_csv_file(datafile, dtype=int) for (xcol, ycol, yminus, by_mem_hierarchy) in subplots: sub = [('all', lambda x, y: True)] if by_mem_hierarchy: sub += hier for tag, test in sub: rows = extract_cols(data, xcol, ycol, yminus, cpu_filter=test) plots.append((rows, xcol, ycol, yminus, tag)) return plots else: self.err('Unkown host: %s' % host) return None def write_aggregate(self, datafiles): # (wss, avg, wc, #avg, #wc) # by tag -> by wcycle -> list of data points) by_tag = defaultdict(lambda: defaultdict(list)) for i, datafile in enumerate(datafiles): print '[%d/%d] Processing %s...' % (i + 1, len(datafiles), datafile) bname = basename(datafile) name, ext = splitext(bname) if ext != '.csv': self.err("Warning: '%s' doesn't look like a CSV file." % bname) conf = decode(name) if 'pmo' in conf: plots = self.setup_pmo_graphs(datafile, conf, PMO_AGGR_SUBPLOTS) if plots is None: print "Skipping %s..." % datafile return wss = int(conf['wss']) wcycle = int(conf['wcycle']) host = conf['host'] for (rows, xcol, ycol, yminus, tag) in plots: clean = stats.iqr_remove_outliers(rows, extend=self.options.extend) vals = clean[:,1] avg = np.mean(vals) std = np.std(vals, ddof=1) wc = np.max(vals) n = len(vals) xtag = PMO_COL_LABEL[xcol][1] ytag = PMO_COL_LABEL[ycol][1] dtag = "-delta-%s" % PMO_COL_LABEL[yminus][1] if not yminus is None else "" code = "code=%s-%s-%s-%s" % \ (xcol, ycol, yminus, tag) figname = "host=%s_%s%s-vs-%s_%s_%s" % \ (host, ytag, dtag, xtag, tag, code) by_tag[figname][wcycle].append((wss, avg, std, wc, n, len(rows) - n)) del plots else: self.err("Warning: '%s' is not a PMO experiment; skipping." % bname) for figname in by_tag: for wcycle in by_tag[figname]: data = by_tag[figname][wcycle] # sort by increasing WSS data.sort(key=lambda row: row[0]) f = open('pmo-aggr_wcycle=%d_%s.csv' % (wcycle, figname), 'w') for row in data: f.write(", ".join([str(x) for x in row])) f.write('\n') f.close() def plot_preempt_migrate(self, datafile, name, conf): plots = self.setup_pmo_graphs(datafile, conf) if plots is None: print "Skipping %s..." % datafile return else: print 'Plotting %s...' % datafile for (rows, xcol, ycol, yminus, tag) in plots: # Write it to a temp file. tmp = Tmp() for row in rows: tmp.write("%s, %s\n" % (row[0], row[1])) tmp.flush() xtag = PMO_COL_LABEL[xcol][1] ytag = PMO_COL_LABEL[ycol][1] dtag = "-delta-%s" % PMO_COL_LABEL[yminus][1] if not yminus is None else "" figname = "%s_%s%s-vs-%s_%s" % (name, ytag, dtag, xtag, tag) xunit = PMO_COL_LABEL[xcol][2] yunit = PMO_COL_LABEL[ycol][2] ylabel = PMO_COL_LABEL[ycol][0] xlabel = PMO_COL_LABEL[xcol][0] title = "%s" % ylabel if ycol == 10: title += " from %s" % PMO_MEM[tag] for key in conf: if key in PMO_PARAM: title += " %s=%s" % (PMO_PARAM[key], conf[key]) graphs = [(tmp.name, 1, 2, ylabel)] # plot cutoff (s, lo, hi) = stats.iqr(rows[:,1]) lo -= s * self.options.extend hi += s * self.options.extend m99 = stats.cutoff_max(rows[:, 1]) graphs += [(lo, 'IQR cutoff (%d)' % lo, 'line'), (hi, 'IQR cutoff (%d)' % hi, 'line'), (m99,'99%% cutoff (%d)' % m99, 'line lw 2')] gnuplot(graphs, xlabel="%s (%s)" % (xlabel, xunit), ylabel="%s (%s)" % ("access cost" if yminus is None else "delta to %s" % PMO_COL_LABEL[yminus][0], yunit), title=title, style='points', format=self.options.format, fname=figname) del tmp # delete temporary file def plot_pmo_aggr(self, datafile, name, conf): fname = datafile code = conf['code'] (xcol, ycol, yminus, tag) = code.split('-') xcol = int(xcol) ycol = int(ycol) if yminus != "None": yminus = int(ycol) else: yminus = None xtag = PMO_COL_LABEL[xcol][1] ytag = PMO_COL_LABEL[ycol][1] dtag = "-delta-%s" % PMO_COL_LABEL[yminus][1] if not yminus is None else "" figname = "%s_%s%s-vs-%s_%s" % (name, ytag, dtag, xtag, tag) xunit = PMO_COL_LABEL[xcol][2] yunit = PMO_COL_LABEL[ycol][2] ylabel = PMO_COL_LABEL[ycol][0] xlabel = PMO_COL_LABEL[xcol][0] title = "%s" % ylabel ylabel="%s (%s)" % ("access cost" if yminus is None else "delta to %s" % PMO_COL_LABEL[yminus][0], yunit), if ycol == 10: title += " from %s" % PMO_MEM[tag] for key in conf: if key in PMO_PARAM: title += " %s=%s" % (PMO_PARAM[key], conf[key]) graphs = [ #(fname, 1, 2, "average"), "'%s' using 1:2:3 title 'average' with errorbars" % (fname), (fname, 1, 4, "maximum"), ] xlabel = "working set size (kilobytes)" gnuplot(graphs, xlabel=xlabel, ylabel=ylabel, title=title, fname=figname, logscale="xy 2" if yminus is None else "x 2", format=self.options.format) def plot_file(self, datafile): bname = basename(datafile) name, ext = splitext(bname) if ext != '.csv': self.err("Warning: '%s' doesn't look like a CSV file." % bname) conf = decode(name) if 'pmo' in conf: self.plot_preempt_migrate(datafile, name, conf) elif 'pmo-aggr' in conf: self.plot_pmo_aggr(datafile, name, conf) else: self.err("Skipped '%s'; unkown experiment type." % bname) def default(self, _): for datafile in self.args: self.plot_file(datafile) def do_aggregate(self, _): self.write_aggregate(self.args[1:]) if __name__ == "__main__": CyclePlotter().launch()