diff options
| -rwxr-xr-x | dplot.py | 497 |
1 files changed, 497 insertions, 0 deletions
diff --git a/dplot.py b/dplot.py new file mode 100755 index 0000000..9643db6 --- /dev/null +++ b/dplot.py | |||
| @@ -0,0 +1,497 @@ | |||
| 1 | #!/usr/bin/env python | ||
| 2 | import defapp | ||
| 3 | |||
| 4 | from plot import decode, get_data_tmpfile, scenario_heading | ||
| 5 | from util import load_csv_file, load_binary_file, write_csv_file | ||
| 6 | from stats import iqr_cutoff | ||
| 7 | |||
| 8 | from binary_data import get_data | ||
| 9 | |||
| 10 | from math import ceil | ||
| 11 | |||
| 12 | import numpy | ||
| 13 | import csv | ||
| 14 | from os.path import splitext, basename | ||
| 15 | from optparse import make_option as o | ||
| 16 | |||
| 17 | from gnuplot import gnuplot, FORMATS, Plot, label, curve | ||
| 18 | |||
| 19 | options = [ | ||
| 20 | # output options | ||
| 21 | o('-f', '--format', action='store', dest='format', type='choice', | ||
| 22 | choices=FORMATS, help='output format'), | ||
| 23 | o(None, '--save-script', action='store_true', dest='save_script'), | ||
| 24 | o('-p', '--prefix', action='store', dest='prefix'), | ||
| 25 | |||
| 26 | o('-x', '--xmax', action='store', dest='xmax', type='int', | ||
| 27 | help='determines x-axis range'), | ||
| 28 | |||
| 29 | o('-y', '--ymax', action='store', dest='ymax', type='float', | ||
| 30 | help='determines y-axis range'), | ||
| 31 | |||
| 32 | o(None, '--ylog', action='store_true', dest='ylog', | ||
| 33 | help='use logarithmic y-axis'), | ||
| 34 | |||
| 35 | |||
| 36 | o(None, '--pd2-only', action='store_true', dest='pd2_only'), | ||
| 37 | o(None, '--edf-only', action='store_true', dest='edf_only'), | ||
| 38 | o(None, '--ed-only', action='store_true', dest='ed_only'), | ||
| 39 | |||
| 40 | |||
| 41 | o('-s', '--sched', action='append', dest='schedulers'), | ||
| 42 | |||
| 43 | o(None, '--cpmd', action='append', dest='cpmd', | ||
| 44 | help='which CPMD cost to use'), | ||
| 45 | |||
| 46 | o('-m', '--max-tardiness', action='store_true', dest='max_tard', | ||
| 47 | help='plot maximum and not average tardiness'), | ||
| 48 | |||
| 49 | o(None, '--slides', action='store_true', dest='slides'), | ||
| 50 | o(None, '--smooth', action='store_true', dest='smooth'), | ||
| 51 | ] | ||
| 52 | |||
| 53 | defaults = { | ||
| 54 | # output options | ||
| 55 | 'format' : 'pdf', | ||
| 56 | 'save_script' : False, | ||
| 57 | 'prefix' : '', | ||
| 58 | |||
| 59 | 'slides' : False, | ||
| 60 | 'smooth' : False, | ||
| 61 | 'lines' : True, | ||
| 62 | |||
| 63 | 'schedulers' : None, | ||
| 64 | 'pd2_only' : False, | ||
| 65 | 'edf_only' : False, | ||
| 66 | 'ed_only' : False, | ||
| 67 | |||
| 68 | 'max_tard' : False, | ||
| 69 | |||
| 70 | 'cpmd' : None, | ||
| 71 | |||
| 72 | |||
| 73 | 'xmax' : None, | ||
| 74 | 'ymax' : None, | ||
| 75 | 'ylog' : False, | ||
| 76 | } | ||
| 77 | |||
| 78 | HOST_CPUS = { | ||
| 79 | 'ludwig' : 24, | ||
| 80 | } | ||
| 81 | |||
| 82 | HOST_WSS = { | ||
| 83 | 'ludwig' : 3072 | ||
| 84 | } | ||
| 85 | |||
| 86 | SCHEDULERS = [ | ||
| 87 | 'C-EDF-L2', | ||
| 88 | 'C-EDF-L2-RM', | ||
| 89 | 'C-EDF-L3', | ||
| 90 | 'C-EDF-L3-RM', | ||
| 91 | 'G-EDF', | ||
| 92 | 'G-EDF-RM', | ||
| 93 | 'P-EDF', | ||
| 94 | 'P-EDF-RM', | ||
| 95 | 'P-FP', | ||
| 96 | 'P-FP-RM', | ||
| 97 | 'PD2', | ||
| 98 | 'PD2-L2', | ||
| 99 | 'PD2-L2-RM', | ||
| 100 | 'PD2-L3', | ||
| 101 | 'PD2-L3-RM', | ||
| 102 | 'PD2-RM', | ||
| 103 | 'S-PD2', | ||
| 104 | 'S-PD2-L2', | ||
| 105 | 'S-PD2-L2-RM', | ||
| 106 | 'S-PD2-L3', | ||
| 107 | 'S-PD2-L3-RM', | ||
| 108 | 'S-PD2-RM', | ||
| 109 | ] | ||
| 110 | |||
| 111 | SCHEDPOS = {} | ||
| 112 | for (i, s) in enumerate(SCHEDULERS): | ||
| 113 | SCHEDPOS[s] = i | ||
| 114 | |||
| 115 | |||
| 116 | DIST_NAME = { | ||
| 117 | 'bimo-heavy' : '', | ||
| 118 | 'bimo-heavy-250' : '', | ||
| 119 | 'bimo-heavy-33' : '', | ||
| 120 | 'bimo-light' : '', | ||
| 121 | 'bimo-light-250' : '', | ||
| 122 | 'bimo-light-33' : '', | ||
| 123 | 'bimo-medium' : '', | ||
| 124 | 'bimo-medium-250' : '', | ||
| 125 | 'bimo-medium-33' : '', | ||
| 126 | 'exp-10-10-100' : '', | ||
| 127 | 'exp-25-10-100' : '', | ||
| 128 | 'exp-50-10-100' : '', | ||
| 129 | 'uni-heavy' : 'utilization uniformly in []', | ||
| 130 | 'uni-heavy-250' : '', | ||
| 131 | 'uni-heavy-33' : '', | ||
| 132 | 'uni-light' : '', | ||
| 133 | 'uni-light-250' : '', | ||
| 134 | 'uni-light-33' : '', | ||
| 135 | 'uni-medium' : '', | ||
| 136 | 'uni-medium-250' : '', | ||
| 137 | 'uni-medium-33' : '', | ||
| 138 | } | ||
| 139 | |||
| 140 | def sched_name(alg): | ||
| 141 | staggered = alg.startswith('S-') | ||
| 142 | dedicated = alg.endswith('-RM') | ||
| 143 | l3 = 'L3' in alg | ||
| 144 | l2 = 'L2' in alg | ||
| 145 | l1 = alg.startswith('P-') | ||
| 146 | edf = 'EDF' in alg | ||
| 147 | pd2 = 'PD2' in alg | ||
| 148 | fp = 'FP' in alg | ||
| 149 | |||
| 150 | if l3: | ||
| 151 | clust = 'C6-' | ||
| 152 | elif l2: | ||
| 153 | clust = 'C2-' | ||
| 154 | elif l1: | ||
| 155 | clust = 'P-' | ||
| 156 | else: | ||
| 157 | clust = 'G-' | ||
| 158 | |||
| 159 | if edf: | ||
| 160 | policy = 'EDF' | ||
| 161 | elif fp: | ||
| 162 | policy = 'FP' | ||
| 163 | elif pd2: | ||
| 164 | if staggered: | ||
| 165 | policy = 'PD2s' | ||
| 166 | else: | ||
| 167 | policy = 'PD2a' | ||
| 168 | else: | ||
| 169 | policy = '???' | ||
| 170 | |||
| 171 | if dedicated: | ||
| 172 | irq = '-R1' | ||
| 173 | else: | ||
| 174 | irq = '-Rm' | ||
| 175 | |||
| 176 | return clust + policy + irq | ||
| 177 | |||
| 178 | class DissPlotter(defapp.App): | ||
| 179 | def __init__(self): | ||
| 180 | defapp.App.__init__(self, options, defaults, no_std_opts=True) | ||
| 181 | self.tmpfiles = [] | ||
| 182 | |||
| 183 | def make_plot(self, fname=None): | ||
| 184 | p = Plot() | ||
| 185 | p.output = "%s%s.%s" % (self.options.prefix, fname, self.options.format) | ||
| 186 | p.format = self.options.format | ||
| 187 | return p | ||
| 188 | |||
| 189 | def setup_png(self, plot): | ||
| 190 | # standard png options; usually correct; never tweaked for paper | ||
| 191 | if self.options.format == 'png': | ||
| 192 | plot.font_size = 'large' | ||
| 193 | plot.size = (1024, 768) | ||
| 194 | plot.xticks = (0, 1) | ||
| 195 | plot.yticks = (0, 0.1) | ||
| 196 | plot.default_style = "linespoints" | ||
| 197 | return True | ||
| 198 | else: | ||
| 199 | return False | ||
| 200 | |||
| 201 | def write(self, data, name, ext='data'): | ||
| 202 | if self.options.save_script: | ||
| 203 | fname = "%s.%s" % (name, ext) | ||
| 204 | write_csv_file(fname, data) | ||
| 205 | return fname | ||
| 206 | else: | ||
| 207 | tmp = write_csv_file(None, data) | ||
| 208 | # keep a reference so that it isn't deleted | ||
| 209 | self.tmpfiles.append(tmp) | ||
| 210 | return tmp.name | ||
| 211 | |||
| 212 | def prepare(self, name, fname): | ||
| 213 | data = load_csv_file(fname) | ||
| 214 | return self.write(data, name) | ||
| 215 | |||
| 216 | def render(self, p): | ||
| 217 | if self.options.save_script: | ||
| 218 | p.gnuplot_save(p.output + '.plot') | ||
| 219 | else: | ||
| 220 | p.gnuplot_exec() | ||
| 221 | |||
| 222 | def diss_title(self, p, conf): | ||
| 223 | p.title = scenario_heading(conf, True) | ||
| 224 | |||
| 225 | if 'key' in conf: | ||
| 226 | p.title += '; WSS=%sKB ' % conf['key'] | ||
| 227 | |||
| 228 | if len(self.options.cpmd) == 1: | ||
| 229 | p.title += '; %s CPMD' % self.options.cpmd[0] | ||
| 230 | |||
| 231 | |||
| 232 | def diss_style(self, p, top_left=False): | ||
| 233 | if self.options.lines: | ||
| 234 | marker = 'lines' | ||
| 235 | else: | ||
| 236 | marger = 'linespoints' | ||
| 237 | |||
| 238 | if not self.setup_png(p): | ||
| 239 | p.rounded_caps = True | ||
| 240 | p.font = 'Helvetica' | ||
| 241 | |||
| 242 | |||
| 243 | if len(p.curves) > 2: | ||
| 244 | p.curves[2].style = marker + " ls 4" | ||
| 245 | |||
| 246 | if len(p.curves) > 3: | ||
| 247 | p.curves[3].style = marker + " ls 6" | ||
| 248 | |||
| 249 | if False: | ||
| 250 | for i, c in enumerate(p.curves): | ||
| 251 | c.style = "linespoints ls %d" % (i + 1) | ||
| 252 | |||
| 253 | p.line_styles = [ | ||
| 254 | (1, 'lw 8 lc rgbcolor "#000000"'), | ||
| 255 | (2, 'lw 8 lc rgbcolor "#ff0000"'), | ||
| 256 | (3, 'lw 8 lc rgbcolor "#0000ff"'), | ||
| 257 | (4, 'lw 8 lc rgbcolor "#ff910d"'), | ||
| 258 | # (5, 'lw 2.5 lc rgbcolor "#ff910d"'), | ||
| 259 | (6, "lw 2.5"), | ||
| 260 | (7, 'lw 2.5 lc rgbcolor "#000000"'), | ||
| 261 | (8, "lw 2.5"), | ||
| 262 | ] | ||
| 263 | |||
| 264 | |||
| 265 | p.font_size = '7' | ||
| 266 | p.size = ('6in', '2.50in') | ||
| 267 | p.monochrome = True #False | ||
| 268 | p.dashed_lines = True #True | ||
| 269 | if top_left: | ||
| 270 | p.key = 'top left' | ||
| 271 | else: | ||
| 272 | p.key = 'top right' | ||
| 273 | p.default_style = marker + ' lw 1' | ||
| 274 | p.pointsize = 2 | ||
| 275 | |||
| 276 | if self.options.slides: | ||
| 277 | p.dashed_lines = False | ||
| 278 | p.monochrome = False | ||
| 279 | p.rounded_caps = True | ||
| 280 | p.default_style = 'lines lw 10' | ||
| 281 | p.key = 'below' | ||
| 282 | |||
| 283 | if self.options.smooth: | ||
| 284 | p.default_style += " smooth bezier" | ||
| 285 | |||
| 286 | |||
| 287 | def plot_wsched(self, datafile, name, conf): | ||
| 288 | tmpfile = self.prepare(name, datafile) | ||
| 289 | |||
| 290 | p = self.make_plot(name) | ||
| 291 | |||
| 292 | if len(self.options.cpmd) > 1: | ||
| 293 | fmts = ['%s (load)', '%s (idle)'] | ||
| 294 | else: | ||
| 295 | fmts = ['%s', '%s'] | ||
| 296 | |||
| 297 | for sched in self.options.schedulers: | ||
| 298 | idx = SCHEDPOS[sched] | ||
| 299 | |||
| 300 | if 'load' in self.options.cpmd: | ||
| 301 | p.curves += [ | ||
| 302 | curve(fname=tmpfile, xcol=1, ycol=2 + (idx * 2) + 1, | ||
| 303 | title=fmts[0] % sched_name(sched)) | ||
| 304 | ] | ||
| 305 | |||
| 306 | if 'idle' in self.options.cpmd: | ||
| 307 | p.curves += [ | ||
| 308 | curve(fname=tmpfile, xcol=1, ycol=2 + (idx * 2) + 2, | ||
| 309 | title=fmts[1] % sched_name(sched)), | ||
| 310 | ] | ||
| 311 | |||
| 312 | self.diss_title(p, conf) | ||
| 313 | p.xlabel = "working set size (WSS) in KB" | ||
| 314 | p.ylabel = "weighted schedulability score" | ||
| 315 | |||
| 316 | |||
| 317 | if 'hard' in conf: | ||
| 318 | p.ylabel += " [hard]" | ||
| 319 | else: | ||
| 320 | p.ylabel += " [soft]" | ||
| 321 | |||
| 322 | if self.options.xmax: | ||
| 323 | p.xrange = (0, self.options.xmax) | ||
| 324 | elif 'host' in conf and conf['host'] in HOST_WSS: | ||
| 325 | p.xrange = (0, HOST_WSS[conf['host']] + 1) | ||
| 326 | |||
| 327 | p.xticks = (0, 64) | ||
| 328 | |||
| 329 | if self.options.ymax: | ||
| 330 | p.yrange = (-0.05, self.options.ymax) | ||
| 331 | else: | ||
| 332 | p.yrange = (-0.05, 1.05) | ||
| 333 | |||
| 334 | p.yticks = (0, 0.1) | ||
| 335 | |||
| 336 | self.diss_style(p) | ||
| 337 | |||
| 338 | self.render(p) | ||
| 339 | |||
| 340 | |||
| 341 | def plot_sched(self, datafile, name, conf): | ||
| 342 | |||
| 343 | tmpfile = self.prepare(name, datafile) | ||
| 344 | |||
| 345 | p = self.make_plot(name) | ||
| 346 | |||
| 347 | for sched in self.options.schedulers: | ||
| 348 | idx = SCHEDPOS[sched] | ||
| 349 | |||
| 350 | if len(self.options.cpmd) > 1: | ||
| 351 | fmts = ['%s (load)', '%s (idle)'] | ||
| 352 | else: | ||
| 353 | fmts = ['%s', '%s'] | ||
| 354 | |||
| 355 | if 'load' in self.options.cpmd: | ||
| 356 | p.curves += [ | ||
| 357 | curve(fname=tmpfile, xcol=2, ycol=2 + (idx * 2) + 1, | ||
| 358 | title=fmts[0] % sched_name(sched)) | ||
| 359 | ] | ||
| 360 | |||
| 361 | if 'idle' in self.options.cpmd: | ||
| 362 | p.curves += [ | ||
| 363 | curve(fname=tmpfile, xcol=2, ycol=2 + (idx * 2) + 2, | ||
| 364 | title=fmts[1] % sched_name(sched)), | ||
| 365 | ] | ||
| 366 | |||
| 367 | self.diss_title(p, conf) | ||
| 368 | p.xlabel = "utilization cap (prior to overhead accounting)" | ||
| 369 | p.ylabel = "schedulability" | ||
| 370 | |||
| 371 | if 'hard' in conf: | ||
| 372 | p.ylabel += " [hard]" | ||
| 373 | else: | ||
| 374 | p.ylabel += " [soft]" | ||
| 375 | |||
| 376 | # if self.options.xmax: | ||
| 377 | # p.xrange = (0.5, self.options.xmax) | ||
| 378 | if 'host' in conf and conf['host'] in HOST_CPUS: | ||
| 379 | p.xrange = (0.5, HOST_CPUS[conf['host']] + 0.5) | ||
| 380 | |||
| 381 | p.xticks = (0, 2) | ||
| 382 | |||
| 383 | if self.options.ymax: | ||
| 384 | p.yrange = (-0.05, self.options.ymax) | ||
| 385 | else: | ||
| 386 | p.yrange = (-0.05, 1.05) | ||
| 387 | |||
| 388 | self.diss_style(p) | ||
| 389 | |||
| 390 | self.render(p) | ||
| 391 | |||
| 392 | def plot_tardiness(self, datafile, name, conf): | ||
| 393 | tmpfile = self.prepare(name, datafile) | ||
| 394 | |||
| 395 | p = self.make_plot(name) | ||
| 396 | |||
| 397 | if self.options.max_tard: | ||
| 398 | offset = [1, 4] # use MAX column | ||
| 399 | tag = 'max' | ||
| 400 | else: | ||
| 401 | offset = [2, 5] # use AVG column | ||
| 402 | tag = 'avg' | ||
| 403 | |||
| 404 | if len(self.options.cpmd) > 1: | ||
| 405 | fmts = ['%s (%s, load)', '%s (%s, idle)'] | ||
| 406 | else: | ||
| 407 | fmts = ['%s (%s)', '%s (%s)'] | ||
| 408 | |||
| 409 | for sched in self.options.schedulers: | ||
| 410 | idx = SCHEDPOS[sched] | ||
| 411 | |||
| 412 | if 'load' in self.options.cpmd: | ||
| 413 | p.curves += [ | ||
| 414 | curve(fname=tmpfile, xcol=2, ycol=2 + (idx * 6) + offset[0], | ||
| 415 | title=fmts[0] % (sched_name(sched), tag)) | ||
| 416 | ] | ||
| 417 | |||
| 418 | if 'idle' in self.options.cpmd: | ||
| 419 | p.curves += [ | ||
| 420 | curve(fname=tmpfile, xcol=2, ycol=2 + (idx * 6) + offset[1], | ||
| 421 | title=fmts[1] % (sched_name(sched), tag)), | ||
| 422 | ] | ||
| 423 | |||
| 424 | self.diss_title(p, conf) | ||
| 425 | p.xlabel = "utilization cap (prior to overhead accounting)" | ||
| 426 | if 'rel-tard' in conf: | ||
| 427 | p.ylabel = "relative tardiness bound" | ||
| 428 | else: | ||
| 429 | p.ylabel = "absolute tardiness bound (in us)" | ||
| 430 | |||
| 431 | if self.options.xmax: | ||
| 432 | p.xrange = (0.5, self.options.xmax) | ||
| 433 | elif 'host' in conf and conf['host'] in HOST_CPUS: | ||
| 434 | p.xrange = (0.5, HOST_CPUS[conf['host']] + 0.5) | ||
| 435 | |||
| 436 | p.xticks = (0, 2) | ||
| 437 | |||
| 438 | if self.options.ymax: | ||
| 439 | p.yrange = (-0.05, self.options.ymax) | ||
| 440 | elif 'rel-tard' in conf and not self.options.max_tard: | ||
| 441 | p.yrange = (-0.05, 5) | ||
| 442 | p.yticks = (0, 0.5) | ||
| 443 | |||
| 444 | self.diss_style(p, top_left=True) | ||
| 445 | |||
| 446 | self.render(p) | ||
| 447 | |||
| 448 | |||
| 449 | def plot_file(self, datafile): | ||
| 450 | bname = basename(datafile) | ||
| 451 | name, ext = splitext(bname) | ||
| 452 | conf = decode(name) | ||
| 453 | plotters = { | ||
| 454 | 'wsched' : self.plot_wsched, | ||
| 455 | 'sched' : self.plot_sched, | ||
| 456 | 'rel-tard' : self.plot_tardiness, | ||
| 457 | 'abs-tard' : self.plot_tardiness, | ||
| 458 | } | ||
| 459 | |||
| 460 | for plot_type in plotters: | ||
| 461 | if plot_type in conf: | ||
| 462 | try: | ||
| 463 | plotters[plot_type](datafile, name, conf) | ||
| 464 | except IOError as err: | ||
| 465 | self.err("Skipped '%s' (%s)." % (datafile, err)) | ||
| 466 | break | ||
| 467 | else: | ||
| 468 | self.err("Skipped '%s'; unkown experiment type." | ||
| 469 | % bname) | ||
| 470 | # release all tmp files | ||
| 471 | self.tmpfiles = [] | ||
| 472 | |||
| 473 | def default(self, _): | ||
| 474 | if self.options.schedulers is None: | ||
| 475 | self.options.schedulers = SCHEDULERS | ||
| 476 | |||
| 477 | if self.options.cpmd is None: | ||
| 478 | self.options.cpmd = ['load', 'idle'] | ||
| 479 | |||
| 480 | if self.options.pd2_only: | ||
| 481 | self.options.schedulers = [s for s in self.options.schedulers | ||
| 482 | if 'PD2' in s] | ||
| 483 | |||
| 484 | if self.options.edf_only: | ||
| 485 | self.options.schedulers = [s for s in self.options.schedulers | ||
| 486 | if 'EDF' in s] | ||
| 487 | |||
| 488 | if self.options.ed_only: | ||
| 489 | self.options.schedulers = [s for s in self.options.schedulers | ||
| 490 | if 'EDF' in s or 'FP' in s] | ||
| 491 | |||
| 492 | for i, datafile in enumerate(self.args): | ||
| 493 | self.out("[%d/%d] Processing %s ..." % (i + 1, len(self.args), datafile)) | ||
| 494 | self.plot_file(datafile) | ||
| 495 | |||
| 496 | if __name__ == "__main__": | ||
| 497 | DissPlotter().launch() | ||
