#!/usr/bin/env python import defapp from plot import decode, get_data_tmpfile, scenario_heading from util import load_csv_file, load_binary_file, write_csv_file from stats import iqr_cutoff from binary_data import get_data from math import ceil import numpy import csv from os.path import splitext, basename from optparse import make_option as o from gnuplot import gnuplot, FORMATS, Plot, label, curve options = [ # output options o('-f', '--format', action='store', dest='format', type='choice', choices=FORMATS, help='output format'), o(None, '--save-script', action='store_true', dest='save_script'), o('-p', '--prefix', action='store', dest='prefix'), o('-x', '--xmax', action='store', dest='xmax', type='int', help='determines x-axis range'), o('-y', '--ymax', action='store', dest='ymax', type='float', help='determines y-axis range'), o(None, '--ylog', action='store_true', dest='ylog', help='use logarithmic y-axis'), o(None, '--pd2-only', action='store_true', dest='pd2_only'), o(None, '--edf-only', action='store_true', dest='edf_only'), o(None, '--ed-only', action='store_true', dest='ed_only'), o('-s', '--sched', action='append', dest='schedulers'), o(None, '--cpmd', action='append', dest='cpmd', help='which CPMD cost to use'), o('-m', '--max-tardiness', action='store_true', dest='max_tard', help='plot maximum and not average tardiness'), o(None, '--metric', action='store', dest='metric', help='which metric to use for CPMD plot', choices=['avg', 'max', 'std']), o(None, '--slides', action='store_true', dest='slides'), o(None, '--smooth', action='store_true', dest='smooth'), o(None, '--zoom', action='store_true', dest='zoom'), ] defaults = { # output options 'format' : 'pdf', 'save_script' : False, 'prefix' : '', 'slides' : False, 'smooth' : False, 'lines' : True, 'zoom' : False, 'schedulers' : None, 'pd2_only' : False, 'edf_only' : False, 'ed_only' : False, 'max_tard' : False, 'metric' : 'avg', 'cpmd' : None, 'xmax' : None, 'ymax' : None, 'ylog' : False, } HOST_CPUS = { 'ludwig' : 24, } HOST_WSS = { 'ludwig' : 3072 } SCHEDULERS = [ 'C-EDF-L2', 'C-EDF-L2-RM', 'C-EDF-L3', 'C-EDF-L3-RM', 'G-EDF', 'G-EDF-RM', 'P-EDF', 'P-EDF-RM', 'P-FP', 'P-FP-RM', 'PD2', 'PD2-L2', 'PD2-L2-RM', 'PD2-L3', 'PD2-L3-RM', 'PD2-RM', 'S-PD2', 'S-PD2-L2', 'S-PD2-L2-RM', 'S-PD2-L3', 'S-PD2-L3-RM', 'S-PD2-RM', ] SCHEDPOS = {} for (i, s) in enumerate(SCHEDULERS): SCHEDPOS[s] = i DIST_NAME = { 'bimo-heavy' : '', 'bimo-heavy-250' : '', 'bimo-heavy-33' : '', 'bimo-light' : '', 'bimo-light-250' : '', 'bimo-light-33' : '', 'bimo-medium' : '', 'bimo-medium-250' : '', 'bimo-medium-33' : '', 'exp-10-10-100' : '', 'exp-25-10-100' : '', 'exp-50-10-100' : '', 'uni-heavy' : 'utilization uniformly in []', 'uni-heavy-250' : '', 'uni-heavy-33' : '', 'uni-light' : '', 'uni-light-250' : '', 'uni-light-33' : '', 'uni-medium' : '', 'uni-medium-250' : '', 'uni-medium-33' : '', } def sched_name(alg): staggered = alg.startswith('S-') dedicated = alg.endswith('-RM') l3 = 'L3' in alg l2 = 'L2' in alg l1 = alg.startswith('P-') edf = 'EDF' in alg pd2 = 'PD2' in alg fp = 'FP' in alg if l3: clust = 'C6-' elif l2: clust = 'C2-' elif l1: clust = 'P-' else: clust = 'G-' if edf: policy = 'EDF' elif fp: policy = 'FP' elif pd2: if staggered: policy = 'sPD2' else: policy = 'aPD2' else: policy = '???' if dedicated: irq = '-R1' else: irq = '-Rm' return clust + policy + irq class DissPlotter(defapp.App): def __init__(self): defapp.App.__init__(self, options, defaults, no_std_opts=True) self.tmpfiles = [] def make_plot(self, fname=None): p = Plot() p.output = "%s%s.%s" % (self.options.prefix, fname, self.options.format) p.format = self.options.format return p def setup_png(self, plot): # standard png options; usually correct; never tweaked for paper if self.options.format == 'png': plot.font_size = 'large' plot.size = (1024, 768) plot.xticks = (0, 1) plot.yticks = (0, 0.1) plot.default_style = "linespoints" return True else: return False def write(self, data, name, ext='data'): if self.options.save_script: fname = "%s.%s" % (name, ext) write_csv_file(fname, data) return fname else: tmp = write_csv_file(None, data) # keep a reference so that it isn't deleted self.tmpfiles.append(tmp) return tmp.name def prepare(self, name, fname): data = load_csv_file(fname) return self.write(data, name) def render(self, p): if self.options.save_script: p.gnuplot_save(p.output + '.plot') else: p.gnuplot_exec() def diss_title(self, p, conf): p.title = scenario_heading(conf, True) if 'key' in conf: p.title += '; WSS=%sKB' % conf['key'] if len(self.options.cpmd) == 1: p.title += '; %s CPMD' % self.options.cpmd[0] def diss_style(self, p, top_left=False): if self.options.lines: marker = 'lines' else: marger = 'linespoints' if not self.setup_png(p): p.rounded_caps = True p.font = 'Helvetica' if len(p.curves) > 2: p.curves[2].style = marker + " ls 4" if len(p.curves) > 3: p.curves[3].style = marker + " ls 6" if False: for i, c in enumerate(p.curves): c.style = "linespoints ls %d" % (i + 1) p.line_styles = [ (1, 'lw 8 lc rgbcolor "#000000"'), (2, 'lw 8 lc rgbcolor "#ff0000"'), (3, 'lw 8 lc rgbcolor "#0000ff"'), (4, 'lw 8 lc rgbcolor "#ff910d"'), # (5, 'lw 2.5 lc rgbcolor "#ff910d"'), (6, "lw 2.5"), (7, 'lw 2.5 lc rgbcolor "#000000"'), (8, "lw 2.5"), ] p.font_size = '7' p.size = ('6in', '2.50in') p.monochrome = True #False p.dashed_lines = True #True if top_left: p.key = 'top left' else: p.key = 'top right' p.default_style = marker + ' lw 1' p.pointsize = 2 if self.options.slides: p.dashed_lines = False p.monochrome = False p.rounded_caps = True p.default_style = 'lines lw 10' p.key = 'below' if self.options.smooth: p.default_style += " smooth bezier" def plot_wsched(self, datafile, name, conf): tmpfile = self.prepare(name, datafile) p = self.make_plot(name) if len(self.options.cpmd) > 1: fmts = ['%s (load)', '%s (idle)'] else: fmts = ['%s', '%s'] for sched in self.options.schedulers: idx = SCHEDPOS[sched] if 'load' in self.options.cpmd: p.curves += [ curve(fname=tmpfile, xcol=1, ycol=2 + (idx * 2) + 1, title=fmts[0] % sched_name(sched)) ] if 'idle' in self.options.cpmd: p.curves += [ curve(fname=tmpfile, xcol=1, ycol=2 + (idx * 2) + 2, title=fmts[1] % sched_name(sched)), ] self.diss_title(p, conf) p.xlabel = "working set size (WSS) in KB" p.ylabel = "weighted schedulability score" if 'hard' in conf: p.ylabel += " [hard]" else: p.ylabel += " [soft]" if self.options.xmax: p.xrange = (0, self.options.xmax) elif 'host' in conf and conf['host'] in HOST_WSS: p.xrange = (0, HOST_WSS[conf['host']] + 1) p.xticks = (0, 64) if self.options.ymax: p.yrange = (-0.05, self.options.ymax) else: p.yrange = (-0.05, 1.05) p.yticks = (0, 0.1) self.diss_style(p) self.render(p) def plot_sched(self, datafile, name, conf): tmpfile = self.prepare(name, datafile) p = self.make_plot(name) for sched in self.options.schedulers: idx = SCHEDPOS[sched] if len(self.options.cpmd) > 1: fmts = ['%s (load)', '%s (idle)'] else: fmts = ['%s', '%s'] if 'load' in self.options.cpmd: p.curves += [ curve(fname=tmpfile, xcol=2, ycol=2 + (idx * 2) + 1, title=fmts[0] % sched_name(sched)) ] if 'idle' in self.options.cpmd: p.curves += [ curve(fname=tmpfile, xcol=2, ycol=2 + (idx * 2) + 2, title=fmts[1] % sched_name(sched)), ] self.diss_title(p, conf) p.xlabel = "utilization cap (prior to overhead accounting)" p.ylabel = "schedulability" if 'hard' in conf: p.ylabel += " [hard]" else: p.ylabel += " [soft]" # if self.options.xmax: # p.xrange = (0.5, self.options.xmax) if 'host' in conf and conf['host'] in HOST_CPUS: p.xrange = (0.5, HOST_CPUS[conf['host']] + 0.5) p.xticks = (0, 2) if self.options.ymax: p.yrange = (-0.05, self.options.ymax) else: p.yrange = (-0.05, 1.05) self.diss_style(p) self.render(p) def plot_tardiness(self, datafile, name, conf): tmpfile = self.prepare(name, datafile) p = self.make_plot(name) if self.options.max_tard: offset = [1, 4] # use MAX column tag = 'max' else: offset = [2, 5] # use AVG column tag = 'avg' if len(self.options.cpmd) > 1: fmts = ['%s (%s, load)', '%s (%s, idle)'] else: fmts = ['%s (%s)', '%s (%s)'] for sched in self.options.schedulers: idx = SCHEDPOS[sched] if 'load' in self.options.cpmd: p.curves += [ curve(fname=tmpfile, xcol=2, ycol=2 + (idx * 6) + offset[0], title=fmts[0] % (sched_name(sched), tag)) ] if 'idle' in self.options.cpmd: p.curves += [ curve(fname=tmpfile, xcol=2, ycol=2 + (idx * 6) + offset[1], title=fmts[1] % (sched_name(sched), tag)), ] self.diss_title(p, conf) p.xlabel = "utilization cap (prior to overhead accounting)" if 'rel-tard' in conf: p.ylabel = "relative tardiness bound" else: p.ylabel = "absolute tardiness bound (in us)" if self.options.xmax: p.xrange = (0.5, self.options.xmax) elif 'host' in conf and conf['host'] in HOST_CPUS: p.xrange = (0.5, HOST_CPUS[conf['host']] + 0.5) p.xticks = (0, 2) if self.options.ymax: p.yrange = (-0.05, self.options.ymax) elif 'rel-tard' in conf and not self.options.max_tard: p.yrange = (-0.05, 5) p.yticks = (0, 0.5) self.diss_style(p, top_left=True) self.render(p) def plot_cpmd(self, datafile, name, conf): # index avg, std, max L1 = [32, 33, 34] L2 = [35, 36, 37] L3 = [38, 39, 40] MEM = [41, 42, 43] NAMES = ["preemption", "L2 migration", "L3 migration", "memory migration" ] INDICES = [L1, L2, L3, MEM] tmpfile = self.prepare(name, datafile) p = self.make_plot(name) if self.options.metric == 'max': col = 2 tag = 'maximum observed delay' elif self.options.metric == 'avg': col = 0 tag = 'average observed delay' elif self.options.metric == 'std': col = 1 tag = 'standard deviation of observed delays' else: assert False # what metric? for (name, index) in zip(NAMES, INDICES): p.curves += [curve(fname=tmpfile, xcol=1, ycol=index[col], title=name)] perc = int((1.0 / int(conf['wcycle'])) * 100) if conf['bg'] == 'idle': bg = 'no background workload (idle CPMD)' else: bg = 'with cache polluters (load CPMD)' p.title = "%s; %d%% writes; %s" % (tag, perc, bg) p.xlabel = "working set size (in KB)" if self.options.metric == 'std': p.ylabel = "standard deviation of measurements" else: p.ylabel = "measured cache-related delay (in us)" if self.options.zoom: p.xticks = (0, 64) p.xrange = (0, 1024) else: p.xticks = (0, 1024) p.xrange = (0, 12288) if self.options.metric in ['max'] and conf['bg'] == 'load': if self.options.zoom: p.yrange = (0, 5000) p.yticks = (0, 500) else: p.yrange = (0, 40000) p.yticks = (0, 5000) elif self.options.metric in ['std'] and conf['bg'] == 'load': if self.options.zoom: p.yrange = (0, 1000) p.yticks = (0, 100) else: p.yrange = (-150, 10000) p.yticks = (0, 1000) else: if self.options.zoom: p.yrange = (-50, 1000) p.yticks = (0, 100) else: p.yrange = (-150, 3000) p.yticks = (0, 250) self.diss_style(p) if self.options.zoom or \ (conf['bg'] == 'load' and self.options.metric in ['max', 'std']): p.key = 'top left' self.render(p) def plot_file(self, datafile): bname = basename(datafile) name, ext = splitext(bname) conf = decode(name) plotters = { 'wsched' : self.plot_wsched, 'sched' : self.plot_sched, 'rel-tard' : self.plot_tardiness, 'abs-tard' : self.plot_tardiness, 'cpmd' : self.plot_cpmd, } for plot_type in plotters: if plot_type in conf: try: plotters[plot_type](datafile, name, conf) except IOError as err: self.err("Skipped '%s' (%s)." % (datafile, err)) break else: self.err("Skipped '%s'; unkown experiment type." % bname) # release all tmp files self.tmpfiles = [] def default(self, _): if self.options.schedulers is None: self.options.schedulers = SCHEDULERS if self.options.cpmd is None: self.options.cpmd = ['load', 'idle'] if self.options.pd2_only: self.options.schedulers = [s for s in self.options.schedulers if 'PD2' in s] if self.options.edf_only: self.options.schedulers = [s for s in self.options.schedulers if 'EDF' in s] if self.options.ed_only: self.options.schedulers = [s for s in self.options.schedulers if 'EDF' in s or 'FP' in s] for i, datafile in enumerate(self.args): self.out("[%d/%d] Processing %s ..." % (i + 1, len(self.args), datafile)) self.plot_file(datafile) if __name__ == "__main__": DissPlotter().launch()