#!/usr/bin/env python import defapp from plot import decode from util import load_csv_file, write_csv_file from math import ceil from numpy import amin, amax, mean, median, std, histogram, zeros, arange 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'), # formatting options # These may or may not be supported by a particular experiment plotter. o(None, '--smooth', action='store_true', dest='smooth'), o(None, '--hist', action='store_true', dest='histogram'), ] defaults = { # output options 'format' : 'pdf', 'save_script' : False, 'prefix' : '', # formatting options 'histogram' : False, 'smooth' : False, } def get_stats_label(samples): avg = mean(samples) med = median(samples) dev = std(samples) max = amax(samples) min = amin(samples) return "min=%.2f max=%.2f avg=%.2f median=%.2f std=%.2f" \ % (min, max, avg, med, dev) class NetsecPlotter(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 load(self, datafile): data = load_csv_file(datafile) print "loaded %d lines" % len(data) return data def write_histogram(self, samples, name): max = amax(samples) (hist, edges) = histogram(samples, bins=max, range=(0.5,max+.5)) data = zeros((len(edges)-1, 3)) cumulative = 0 for i in xrange(len(hist)): data[i, 0] = (edges[i] + edges[i + 1]) / 2.0 data[i, 1] = hist[i] cumulative += hist[i] data[i, 2] = cumulative if len(hist) > 20: label_freq = 10 else: label_freq = 1 for_file = [] for i, row in enumerate(data): label = '%d' % row[0] if row[0] % label_freq == 0 else '' for_file.append([row[0], row[1], row[2], label]) return (data, self.write(for_file, name, ext='hist')) def render(self, p): if self.options.save_script: p.gnuplot_save(p.output + '.plot') else: p.gnuplot_exec() def plot_hc(self, datafile, name, conf): current_host_num = 0 host_mapping = {} name += "_counts" data = self.load(datafile) for row in data: if row[0] not in host_mapping: current_host_num += 1 host_mapping[row[0]] = current_host_num row[0] = host_mapping[row[0]] del host_mapping fname = self.write(data, name, ext='data') p = self.make_plot(name) p.labels = [label(0.5, 0.9, get_stats_label(data[:,1]), coord=['graph', 'screen'], align='center')] # plot raw samples p.title = "hop counts by hosts" p.title += self.get_as_title(conf) p.ylabel = "hop count" p.xlabel = "host id" p.xrange = (0, current_host_num) #p.xticks = (0, 100) #p.yticks = (0, 1) p.yrange = (1, amax(data[:,1]) + 1) if amax(data[:,1]) > 100: p.ylog = True p.curves = [curve(fname=fname, xcol=1, ycol=2, title="hop count")] #### Styling. if not self.setup_png(p): p.rounded_caps = True p.font = 'Helvetica' p.font_size = '10' p.size = ('20cm', '10cm') p.monochrome = False p.dashed_lines = False p.key = 'off' p.default_style = 'points lw 1' if self.options.smooth: p.default_style += " smooth bezier" self.render(p) def get_as_title(self, conf): if 'as-num' in conf: return " AS=%s, IP/mask=%s/%s MB=%s Unique Hosts=%s" % \ (conf['as-num'], conf['as-str'], conf['as-mask'], \ conf['megabytes'], conf['num-hosts']) return "" def plot_hchisto(self, datafile, name, conf): data = self.load(datafile) #max_val = amax(data[:,1]) if self.options.histogram: name += '_hist' p = self.make_plot(name) # place a label on the graph p.labels = [label(0.5, 0.9, get_stats_label(data[:,1]), coord=['graph', 'screen'], align='center')] (data, fname) = self.write_histogram(data[:,1], name) p.xlabel = "hop count" p.ylabel = "number of sources" p.setup_histogram(gap=1, boxwidth=1.0) p.title = "hop counts;" if 'per-host' in conf: p.title += " HC per-host: %s" % conf['per-host'] p.title += self.get_as_title(conf) # p.xrange = (0, ceil(max_cost)) p.xticks = (0, 10) # p.yticks = (0, 1) #p.yrange = (0, (ceil(amax(data[:,1]) / 100) * 100)) ymax = amax(data[:,1]) p.curves = [curve(histogram=fname, col=2, labels_col=4)] p.yrange = (1, amax(data[:,1]) + 2) if ymax > 10000: p.ylog = True #### Styling. if not self.setup_png(p): p.rounded_caps = True p.font = 'Helvetica' p.font_size = '10' p.size = ('20cm', '10cm') p.monochrome = False p.dashed_lines = False p.key = 'off' p.default_style = 'points lw 1' if self.options.smooth: p.default_style += " smooth bezier" self.render(p) def plot_file(self, datafile): bname = basename(datafile) name, ext = splitext(bname) conf = decode(name) self.plot_hc(datafile, name, conf) return if 'per-host' in conf or 'as-num' in conf: self.plot_hchisto(datafile, name, conf) #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)." % err) # break #else: # self.err("Skipped '%s'; unkown experiment type." # % bname) # release all tmp files self.tmpfiles = [] def default(self, _): 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__": NetsecPlotter().launch()