ufes-20191-redes-mininet/proj-impl/topoboxplot.py

433 lines
12 KiB
Python
Raw Normal View History

2019-07-03 06:15:15 +00:00
#!/usr/bin/env python3
# -*- encoding: utf-8 -*-
import sys
import json
2019-07-05 08:52:59 +00:00
import ternary
import numpy as np
from matplotlib import pyplot as plt
2019-07-03 06:15:15 +00:00
from pathlib import Path
2019-07-05 08:52:59 +00:00
from operator import sub
from topotable import tbl2md
from topotable import tbl2csv
from topotable import build_table
from topotable import Phrases
2019-07-03 06:15:15 +00:00
2019-07-05 08:52:59 +00:00
def sub_itr(itr):
return sub(*itr)
def listfind(self: list, search):
try:
return self.index(search)
except ValueError:
return -1
def tbl2tex(table):
columns = len(table[0])
clh = 'l|'+'c'*(columns-1)
head, _, *tail = [
x[2:]
for x in (
tbl2md(table)
.replace('|', '&')
.replace('&\n', '\\\\\n')
[:-1]+'\\\\\n'
).splitlines()
]
return (
'\\begin{tabular}{' + clh + '}\n' +
'\n'.join([head, '\\hline', *tail]) +
'\n\\end{tabular}'
)
def avg(itr, ifempty=0):
2019-07-03 06:15:15 +00:00
if len(itr) == 0:
2019-07-05 08:52:59 +00:00
return ifempty
2019-07-03 06:15:15 +00:00
else:
return sum(itr)/len(itr)
2019-07-05 08:52:59 +00:00
def flatten(itr):
return [el for subitr in itr for el in subitr]
def make_boxplot(data, labels, ylabel=None, title=None, rotate_x=False):
fig, ax = plt.subplots()
ax.grid(True)
red_cross = dict(markeredgecolor='r', alpha=0.5, marker='x')
meanpointprops = dict(
marker='D',
markeredgecolor='navy',
markerfacecolor='dodgerblue'
)
medianprops = dict(
color='green',
linewidth=2.5
)
ax.boxplot(
data,
labels=labels,
flierprops=red_cross,
meanprops=meanpointprops,
medianprops=medianprops,
showmeans=True
)
if ylabel is not None:
ax.set_ylabel(ylabel)
if title is not None:
ax.set_title(title)
if rotate_x:
plt.xticks(rotation=45, horizontalalignment='right')
return ax
FMT_LATENCY = (lambda a: '%.2f ms' % (a,),)[0]
FMT_SPEED = (lambda a: '%0.1f kbps' % (a/1000,),)[0]
FMT_SEC = (lambda a: '%0.1f s' % (a,),)[0]
FMT_PASSTHROUGH = (lambda a: a,)[0]
FMT_STRING = (lambda a: str(a),)[0]
FMTS = {
'sl': FMT_LATENCY,
'pl': FMT_LATENCY,
'il': FMT_LATENCY,
'sj': FMT_LATENCY,
'pj': FMT_LATENCY,
'ij': FMT_LATENCY,
'sb': FMT_SPEED,
'pb': FMT_SPEED,
'ib': FMT_SPEED,
'rd': FMT_SEC,
}
FMTS_YLABEL = {
'sl': 'milisseconds',
'pl': 'milisseconds',
'il': 'milisseconds',
'sj': 'milisseconds',
'pj': 'milisseconds',
'ij': 'milisseconds',
'sb': 'kilobits per second',
'pb': 'kilobits per second',
'ib': 'kilobits per second',
'rd': 'seconds',
}
FMTS_YSCALEMULTIPLIER = {
'sl': 1,
'pl': 1,
'il': 1,
'sj': 1,
'pj': 1,
'ij': 1,
'sb': 1/1000,
'pb': 1/1000,
'ib': 1/1000,
'rd': 1,
}
PHRASES = {
'sl': 'Latency (sequential)',
'pl': 'Latency (concurrent)',
'il': 'Latency (impact)',
'sj': 'Jitter (sequential)',
'pj': 'Jitter (concurrent)',
'ij': 'Jitter (impact)',
'sb': 'Bandwidth (sequential)',
'pb': 'Bandwidth (concurrent)',
'ib': 'Bandwidth (impact)',
'rd': 'Routing response time',
'ospf': 'OSPF (single-path)',
'ecmp': 'ECMP (multi-path)',
'ldr': 'LDR (multi-path)',
'minmax-single': 'Minmax (single-path)',
'ldr-single': 'LDR (single-path)',
'all': 'All',
'clos': '3-layered CLOS',
'clos5': '5-layered CLOS',
'grid': 'Grid',
'simpletree': 'Binary Tree',
'fattree': 'Fat Tree',
'bipartite': 'Bipartite',
'principle': 'Triangle',
'dcell': 'D-Cell',
'dcellswitched': 'D-Cell',
'bcube': 'B-Cube',
'bcubeswitched': 'B-Cube',
}
PHRASES_BOXPLOT_PATCH = {
'ospf': 'OSPF (SP)',
'ecmp': 'ECMP (MP)',
'ldr': 'LDR (MP)',
'minmax-single': 'Minmax (SP)',
'ldr-single': 'LDR (SP)',
'clos': 'CLOS-3',
'clos5': 'CLOS-5',
'grid': 'Grid',
'simpletree': 'Bin Tree',
'fattree': 'Fat Tree',
'bipartite': 'Bipartite',
'principle': 'Triangle',
}
ALGOS_PREFERRED_ORDER = [
'ospf',
'ldr-single',
'minmax-single',
'ldr',
'ecmp',
]
TOPOS_PREFERRED_ORDER = [
'simpletree',
'clos5',
'bcubeswitched',
'bcube',
'fattree',
'grid',
'dcellswitched',
'dcell',
'bipartite',
'clos',
'principle',
]
2019-07-03 06:15:15 +00:00
def main(results, outfolder):
2019-07-05 08:52:59 +00:00
algos_raw = list(results.keys())
topos_raw = list(results.values().__iter__().__next__().keys())
algos = list(sorted(
algos_raw,
key=lambda a: (listfind(ALGOS_PREFERRED_ORDER, a), a)
))
topos = list(sorted(
topos_raw,
key=lambda a: (listfind(TOPOS_PREFERRED_ORDER, a), a)
))
metrics = ['sl', 'sj', 'sb', 'pl', 'pj', 'pb', 'il', 'ij', 'ib', 'rd']
2019-07-03 06:15:15 +00:00
db = {
algo: {
topo: {
'sl': [res['sequential']['ping']['avg'] for res in results[algo][topo]],
2019-07-05 08:52:59 +00:00
'sj': [res['sequential']['ping']['mdev'] for res in results[algo][topo]],
2019-07-03 06:15:15 +00:00
'sb': [avg(res['sequential']['iperfs']) for res in results[algo][topo]],
'pl': [res['parallel']['ping']['avg'] for res in results[algo][topo]],
2019-07-05 08:52:59 +00:00
'pj': [res['parallel']['ping']['mdev'] for res in results[algo][topo]],
2019-07-03 06:15:15 +00:00
'pb': [avg(res['parallel']['iperfs']) for res in results[algo][topo]],
2019-07-05 08:52:59 +00:00
'rd': [res['routing_time'] for res in results[algo][topo]],
2019-07-03 06:15:15 +00:00
} for topo in topos
2019-07-05 08:52:59 +00:00
} for algo in algos}
db = {
algo: {
topo: {
**db[algo][topo],
'il': list(map(sub_itr, zip(db[algo][topo]['pl'], db[algo][topo]['sl']))),
'ij': list(map(sub_itr, zip(db[algo][topo]['pj'], db[algo][topo]['sj']))),
'ib': list(map(sub_itr, zip(db[algo][topo]['pb'], db[algo][topo]['sb']))),
} for topo in topos
} for algo in algos}
db = {
algo: {
topo: db[algo][topo] if topo != 'all' else dict([
(metric, flatten([
db[algo][topo2][metric]
for topo2 in topos
]))
for metric in metrics
])
for topo in [*topos, 'all']
} for algo in algos}
topos = [*topos, 'all']
db = {
algo: {
topo: db[algo][topo] if algo != 'all' else dict([
(metric, flatten([
db[algo2][topo][metric]
for algo2 in algos
]))
for metric in metrics
])
for topo in topos
} for algo in [*algos, 'all']}
algos = [*algos, 'all']
2019-07-03 06:15:15 +00:00
avgdb = {
algo: {
topo: {
persp: avg(db[algo][topo][persp])
2019-07-05 08:52:59 +00:00
for persp in metrics
2019-07-03 06:15:15 +00:00
} for topo in topos
2019-07-05 08:52:59 +00:00
} for algo in algos}
for metric in metrics:
formatter = FMTS[metric]
tbl = build_table(avgdb, algos, topos, metric, formatter, PHRASES)
outtexfile = Path(outfolder).joinpath(f'avg.{metric}.tex')
outcsvfile = Path(outfolder).joinpath(f'avg.{metric}.csv')
outmdfile = Path(outfolder).joinpath(f'avg.{metric}.md')
outtexfile.write_text(tbl2tex(tbl))
outcsvfile.write_text(tbl2csv(tbl))
outmdfile.write_text(tbl2md(tbl))
longer_phrases = Phrases(PHRASES)
phrases = Phrases({**PHRASES, **PHRASES_BOXPLOT_PATCH})
for algo in algos:
for metric in metrics:
ylabel = FMTS_YLABEL[metric]
yscalemultiplier = FMTS_YSCALEMULTIPLIER[metric]
thistopos = [topo for topo in topos if topo!="all"]
data = [
sorted([
y*yscalemultiplier
for y in db[algo][topo][metric]
], reverse=True)
for topo in thistopos
]
bp = make_boxplot(
data,
[phrases[x] for x in thistopos],
ylabel,
f"{longer_phrases[metric]} using {longer_phrases[algo]}",
True
)
bp.figure.savefig(
outfolder.joinpath(f"am.{algo}.{metric}.pdf"),
bbox_inches='tight'
)
plt.cla()
plt.clf()
plt.close()
print(f"am.{algo}.{metric}.pdf")
for topo in topos:
for metric in metrics:
ylabel = FMTS_YLABEL[metric]
yscalemultiplier = FMTS_YSCALEMULTIPLIER[metric]
thisalgos = [algo for algo in algos if algo!="all"]
data = [
sorted([
y*yscalemultiplier
for y in db[algo][topo][metric]
], reverse=True)
for algo in thisalgos
]
bp = make_boxplot(
data,
[phrases[x] for x in thisalgos],
ylabel,
f"{longer_phrases[metric]} on {longer_phrases[topo]}"
)
bp.figure.savefig(
outfolder.joinpath(f"tm.{topo}.{metric}.pdf"),
bbox_inches='tight'
)
plt.cla()
plt.clf()
plt.close()
print(f"tm.{topo}.{metric}.pdf")
MARKERS = [ # <https://matplotlib.org/3.1.0/api/markers_api.html>
"D", # m19 diamond
"o", # m02 circle
".", # m00 point
"*", # m14 star
"x", # m18 x
"d", # m20 thin_diamond
"X", # m24 x (filled)
"+", # m17 plus
"v", # m03 triangle_down
"p", # m13 pentagon
"^", # m04 triangle_up
"<", # m05 triangle_left
">", # m06 triangle_right
"1", # m07 tri_down
"2", # m08 tri_up
"3", # m09 tri_left
"4", # m10 tri_right
"8", # m11 octagon
"s", # m12 square
"P", # m23 plus (filled)
"h", # m15 hexagon1
"H", # m16 hexagon2
"|", # m21 vline
"_", # m22 hline
",", # m01 pixel
]
COLORS = [ # <https://matplotlib.org/2.0.0/examples/color/named_colors.html>
'orange',
'c',
'magenta',
'green',
'blue',
'm',
'gold',
]
for flavour in 'ps':
for topo in topos:
figure, ax = plt.subplots()
ax.axis('off')
tax = ternary.TernaryAxesSubplot(ax=ax, scale=1)
tax.gridlines(multiple=0.1, color="gray")
tax.boundary(linewidth=2.0)
fontsize = 12
offset = 0.14
allthistopo = db['all'][topo]
minl = min(allthistopo[flavour+'l'])
minj = min(allthistopo[flavour+'j'])
minb = min(allthistopo[flavour+'b'])
maxl = max(allthistopo[flavour+'l'])
maxj = max(allthistopo[flavour+'j'])
maxb = max(allthistopo[flavour+'b'])
dltl = maxl-minl
dltj = maxj-minj
dltb = maxb-minb
dltl = dltl if dltl!=0 else 1
dltj = dltj if dltj!=0 else 1
dltb = dltb if dltb!=0 else 1
for i, algo in enumerate(algos):
if algo == 'all':
continue
data = db[algo][topo]
points = list(zip(
[(i-minl)/dltl for i in data[flavour+'l']], # top
[(i-minj)/dltj for i in data[flavour+'j']], # right
[(i-minb)/dltb for i in data[flavour+'b']] # left
))
tax.scatter(
points,
marker=MARKERS[i],
color=COLORS[i],
label=longer_phrases[algo]
)
# tax.ticks(axis='lbr', linewidth=1, multiple=0.2)
tax.top_corner_label("High latency")
tax.right_corner_label("High jitter")
tax.left_corner_label("High bandwidth")
tax.bottom_axis_label("Low latency")
tax.left_axis_label("Low jitter")
tax.right_axis_label("Low bandwidth")
tax.legend()
tax.savefig(
outfolder.joinpath(f"tern.{flavour}.{topo}.pdf")
)
plt.cla()
plt.clf()
plt.close()
print(f"tern.{flavour}.{topo}.pdf")
2019-07-03 06:15:15 +00:00
if __name__ == '__main__':
if len(sys.argv) != 3:
print("Usage:", file=sys.stderr)
print(f" {sys.argv[0]} <__all__.autotests.json> <outfolder>",
file=sys.stderr)
else:
all_tests = Path(sys.argv[1])
outfolder = Path(sys.argv[2])
outfolder.mkdir(parents=True, exist_ok=True)
main(json.loads(all_tests.read_text()), outfolder)