2019-06-14 07:51:57 +00:00
|
|
|
#!/usr/bin/env python3
|
|
|
|
# -*- encoding: utf-8 -*-
|
2019-06-15 06:31:09 +00:00
|
|
|
|
|
|
|
import sys
|
|
|
|
import time
|
|
|
|
import json
|
|
|
|
import tkinter
|
|
|
|
import networkx
|
2019-06-19 01:26:28 +00:00
|
|
|
import traceback
|
2019-06-15 06:31:09 +00:00
|
|
|
import threading
|
|
|
|
from pathlib import Path
|
|
|
|
from graphtools import Dijkstra, graph_from_topo
|
|
|
|
from sortundirectednodepair import _sort_pair
|
|
|
|
from networkx.drawing.layout import spring_layout
|
|
|
|
from networkx.drawing.nx_pylab import draw_networkx
|
|
|
|
from networkx.drawing.nx_pylab import draw_networkx_labels
|
|
|
|
from networkx.drawing.nx_pylab import draw_networkx_nodes
|
|
|
|
from networkx.drawing.nx_pylab import draw_networkx_edges
|
|
|
|
from networkx.drawing.nx_pylab import draw_networkx_edge_labels
|
|
|
|
import matplotlib
|
|
|
|
from matplotlib import pyplot as plt
|
|
|
|
from matplotlib.figure import Figure
|
|
|
|
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
|
|
|
|
|
|
|
|
matplotlib.use("TkAgg")
|
|
|
|
|
|
|
|
|
|
|
|
def nx_graph_from_topo(topo):
|
|
|
|
g = networkx.Graph()
|
|
|
|
g.add_nodes_from(topo[0])
|
|
|
|
g.add_nodes_from(topo[1])
|
|
|
|
g.add_edges_from([(x, y) for x, y, z in topo[2]])
|
2019-07-01 23:42:25 +00:00
|
|
|
for h1, h2, bw in topo[2]:
|
|
|
|
g[h1][h2]['bw'] = bw
|
2019-06-15 06:31:09 +00:00
|
|
|
return g
|
|
|
|
|
|
|
|
|
|
|
|
def parse_state(s):
|
|
|
|
pathb, loadb = [], []
|
|
|
|
current = 0
|
|
|
|
for line in s.splitlines():
|
|
|
|
if line.startswith('@'):
|
|
|
|
if line.startswith('@start.'):
|
|
|
|
if line.endswith('path'):
|
|
|
|
current = 1
|
|
|
|
elif line.endswith('load'):
|
|
|
|
current = 2
|
|
|
|
else:
|
|
|
|
raise ValueError(f"Start of unexpected block: {line}")
|
|
|
|
elif line.startswith('@end'):
|
|
|
|
current = 0
|
|
|
|
else:
|
|
|
|
raise ValueError(f"Unexpected command: {line}")
|
|
|
|
elif current == 1:
|
|
|
|
pathb.append(eval(line))
|
|
|
|
elif current == 2:
|
|
|
|
loadb.append(eval(line))
|
2019-07-01 23:42:25 +00:00
|
|
|
loads, path_segments = dict(), dict()
|
|
|
|
for multiplepath in pathb:
|
|
|
|
ws = sum([weight for weight in multiplepath.values()])
|
|
|
|
for path, weight in multiplepath.items():
|
|
|
|
for i in range(len(path)-1):
|
|
|
|
path_segments[_sort_pair(*path[i:i+2])] = weight/ws
|
2019-06-15 06:31:09 +00:00
|
|
|
for load in loadb:
|
|
|
|
loads[_sort_pair(load[0], load[1])] = load[2]
|
2019-07-01 23:42:25 +00:00
|
|
|
return pathb, path_segments, loads
|
2019-06-15 06:31:09 +00:00
|
|
|
|
|
|
|
|
2019-07-03 20:52:11 +00:00
|
|
|
def get_loaded_switches():
|
|
|
|
pt = Path("~current.sws.state")
|
|
|
|
if not pt.exists():
|
|
|
|
return set()
|
|
|
|
else:
|
|
|
|
return set(filter(len, pt.read_text().splitlines()))
|
|
|
|
|
|
|
|
|
2019-06-15 06:31:09 +00:00
|
|
|
CLR = {True: 'FF', False: '00'}
|
|
|
|
|
2019-07-01 23:42:25 +00:00
|
|
|
topos = dict()
|
|
|
|
topoPos = dict()
|
|
|
|
topoDjkt = dict()
|
|
|
|
nxgs = dict()
|
|
|
|
|
|
|
|
|
|
|
|
def plot(ax, currentfile):
|
2019-07-03 06:15:15 +00:00
|
|
|
ax.clear()
|
|
|
|
ax.axis('off')
|
2019-07-01 23:42:25 +00:00
|
|
|
if not currentfile.exists():
|
|
|
|
return
|
|
|
|
toponame = currentfile.read_text().strip()
|
|
|
|
statepath = Path(f'{toponame}.state')
|
|
|
|
if toponame not in topos:
|
|
|
|
topos[toponame] = json.loads(Path(f'{toponame}.json').read_text())
|
|
|
|
topo = topos[toponame]
|
|
|
|
if toponame not in nxgs:
|
|
|
|
nxgs[toponame] = nx_graph_from_topo(topo)
|
|
|
|
nxg = nxgs[toponame]
|
|
|
|
if toponame not in topoPos:
|
|
|
|
topoPos[toponame] = spring_layout(nxg, iterations=2000)
|
|
|
|
pos = topoPos[toponame]
|
|
|
|
if toponame not in topoDjkt:
|
|
|
|
djkt = Dijkstra(graph_from_topo(topo))
|
|
|
|
djkt_pairs = list()
|
|
|
|
for h1 in topo[0]:
|
|
|
|
for h2 in topo[0]:
|
|
|
|
if h1 != h2:
|
|
|
|
h1, h2 = _sort_pair(h1, h2)
|
|
|
|
djkt_seq = djkt(h1)(h2)[0]
|
|
|
|
for i in range(len(djkt_seq)-1):
|
|
|
|
djkt_pairs.append(_sort_pair(*djkt_seq[i:i+2]))
|
|
|
|
topoDjkt[toponame] = djkt_pairs
|
|
|
|
djkt = topoDjkt[toponame]
|
2019-06-15 06:31:09 +00:00
|
|
|
state_txt = ""
|
|
|
|
if statepath.exists():
|
|
|
|
state_txt = statepath.read_text()
|
|
|
|
paths, path_segments, loads = parse_state(state_txt)
|
|
|
|
labels = dict()
|
|
|
|
for unsortededge in nxg.edges:
|
|
|
|
edge = _sort_pair(*list(map(str, unsortededge)))
|
|
|
|
labels[unsortededge] = "%0.4f" % (loads.get(edge, 0.0),)
|
|
|
|
# labels[unsortededge] = repr(loads.get(edge, 0.0),)
|
|
|
|
edlab = draw_networkx_edge_labels(
|
|
|
|
nxg, pos, ax=ax,
|
|
|
|
edge_labels=labels,
|
|
|
|
bbox=dict(
|
|
|
|
facecolor='none',
|
|
|
|
edgecolor='none'
|
|
|
|
)
|
|
|
|
)
|
2019-07-03 20:52:11 +00:00
|
|
|
loaded_sws = set(topo[1]).intersection(get_loaded_switches())
|
|
|
|
unloaded_sws = set(topo[1]).difference(loaded_sws)
|
2019-06-15 06:31:09 +00:00
|
|
|
for unsortededge in nxg.edges:
|
|
|
|
edge = _sort_pair(*list(map(str, unsortededge)))
|
|
|
|
in_djkt = edge in djkt
|
2019-07-01 23:42:25 +00:00
|
|
|
in_sgmt = min(
|
|
|
|
1.0,
|
|
|
|
max(
|
|
|
|
0.0,
|
|
|
|
path_segments.get(
|
|
|
|
edge,
|
|
|
|
path_segments.get(
|
|
|
|
tuple(reversed(edge)),
|
|
|
|
0.0
|
2019-07-03 20:52:11 +00:00
|
|
|
))))
|
2019-07-01 23:42:25 +00:00
|
|
|
in_sgmt = ('0'+(hex(round(in_sgmt*255))[2:]))[-2:].upper()
|
2019-07-03 20:52:11 +00:00
|
|
|
in_unld = (edge[0] in unloaded_sws) or (edge[1] in unloaded_sws)
|
|
|
|
color = '#'+CLR[in_djkt]+CLR[in_unld]+in_sgmt
|
2019-06-15 06:31:09 +00:00
|
|
|
draw_networkx_edges(
|
|
|
|
nxg, pos, ax=ax,
|
|
|
|
edgelist=[edge], edge_color=color
|
|
|
|
)
|
|
|
|
draw_networkx_nodes(
|
|
|
|
nxg, pos, ax=ax,
|
|
|
|
nodelist=topo[0], node_color='lightgreen'
|
|
|
|
)
|
|
|
|
draw_networkx_nodes(
|
|
|
|
nxg, pos, ax=ax,
|
2019-07-03 20:52:11 +00:00
|
|
|
nodelist=unloaded_sws, node_color='pink'
|
|
|
|
)
|
|
|
|
draw_networkx_nodes(
|
|
|
|
nxg, pos, ax=ax,
|
|
|
|
nodelist=loaded_sws, node_color='cyan'
|
2019-06-15 06:31:09 +00:00
|
|
|
)
|
|
|
|
draw_networkx_labels(nxg, pos, ax=ax)
|
|
|
|
|
|
|
|
|
|
|
|
continuePlotting = True
|
|
|
|
|
|
|
|
|
|
|
|
def to_closing_state():
|
|
|
|
global continuePlotting
|
|
|
|
continuePlotting = False
|
|
|
|
|
|
|
|
|
2019-07-01 23:42:25 +00:00
|
|
|
def show_window(currentfile):
|
2019-06-15 06:31:09 +00:00
|
|
|
root = tkinter.Tk()
|
|
|
|
root.title("Network graph viewer")
|
|
|
|
root.geometry("1000x600")
|
|
|
|
|
|
|
|
lab = tkinter.Label(root, text="Plotting network")
|
|
|
|
lab.pack()
|
|
|
|
|
|
|
|
fig = Figure()
|
|
|
|
|
|
|
|
ax = fig.add_subplot(111)
|
|
|
|
ax.axis('off')
|
|
|
|
fig.patch.set_facecolor("#D9D9D9")
|
|
|
|
|
|
|
|
graph = FigureCanvasTkAgg(fig, master=root)
|
|
|
|
graph.get_tk_widget().pack(side="top", fill='both', expand=True)
|
|
|
|
|
|
|
|
b = tkinter.Button(root, text="Update GUI", command=graph.draw)
|
|
|
|
b.pack()
|
|
|
|
|
|
|
|
def plotter():
|
|
|
|
while continuePlotting:
|
2019-06-19 01:26:28 +00:00
|
|
|
try:
|
2019-07-01 23:42:25 +00:00
|
|
|
plot(ax, currentfile)
|
2019-06-19 01:26:28 +00:00
|
|
|
b.invoke()
|
2019-07-01 23:42:25 +00:00
|
|
|
except BaseException:
|
2019-06-19 01:26:28 +00:00
|
|
|
traceback.print_exc(file=sys.stderr)
|
2019-06-15 06:31:09 +00:00
|
|
|
time.sleep(0.1)
|
|
|
|
|
|
|
|
def close_cmd():
|
|
|
|
to_closing_state()
|
|
|
|
root.destroy()
|
|
|
|
|
|
|
|
thread = threading.Thread(target=plotter)
|
|
|
|
root.protocol("WM_DELETE_WINDOW", close_cmd)
|
|
|
|
thread.start()
|
|
|
|
|
|
|
|
root.mainloop()
|
|
|
|
|
|
|
|
|
2019-07-01 23:42:25 +00:00
|
|
|
def main(currentfile):
|
|
|
|
show_window(currentfile)
|
2019-06-15 06:31:09 +00:00
|
|
|
|
|
|
|
|
|
|
|
if __name__ == "__main__":
|
2019-07-01 23:42:25 +00:00
|
|
|
currentfile = Path(f'~current.state')
|
|
|
|
main(currentfile)
|