mininet-n-ryu-routing-algor.../topoviewer.py

222 lines
6.3 KiB
Python
Executable File

#!/usr/bin/env python3
# -*- encoding: utf-8 -*-
import sys
import time
import json
import tkinter
import networkx
import traceback
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]])
for h1, h2, bw in topo[2]:
g[h1][h2]['bw'] = bw
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))
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
for load in loadb:
loads[_sort_pair(load[0], load[1])] = load[2]
return pathb, path_segments, loads
def get_loaded_switches():
pt = Path("~current.sws.state")
if not pt.exists():
return set()
else:
return set(filter(len, pt.read_text().splitlines()))
CLR = {True: 'FF', False: '00'}
topos = dict()
topoPos = dict()
topoDjkt = dict()
nxgs = dict()
def plot(ax, currentfile):
ax.clear()
ax.axis('off')
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]
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'
)
)
loaded_sws = set(topo[1]).intersection(get_loaded_switches())
unloaded_sws = set(topo[1]).difference(loaded_sws)
for unsortededge in nxg.edges:
edge = _sort_pair(*list(map(str, unsortededge)))
in_djkt = edge in djkt
in_sgmt = min(
1.0,
max(
0.0,
path_segments.get(
edge,
path_segments.get(
tuple(reversed(edge)),
0.0
))))
in_sgmt = ('0'+(hex(round(in_sgmt*255))[2:]))[-2:].upper()
in_unld = (edge[0] in unloaded_sws) or (edge[1] in unloaded_sws)
color = '#'+CLR[in_djkt]+CLR[in_unld]+in_sgmt
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,
nodelist=unloaded_sws, node_color='pink'
)
draw_networkx_nodes(
nxg, pos, ax=ax,
nodelist=loaded_sws, node_color='cyan'
)
draw_networkx_labels(nxg, pos, ax=ax)
continuePlotting = True
def to_closing_state():
global continuePlotting
continuePlotting = False
def show_window(currentfile):
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:
try:
plot(ax, currentfile)
b.invoke()
except BaseException:
traceback.print_exc(file=sys.stderr)
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()
def main(currentfile):
show_window(currentfile)
if __name__ == "__main__":
currentfile = Path(f'~current.state')
main(currentfile)