diff options
author | Dimitri Staessens <dimitri@ouroboros.rocks> | 2022-11-19 21:44:33 +0100 |
---|---|---|
committer | Dimitri Staessens <dimitri@ouroboros.rocks> | 2022-11-19 21:44:33 +0100 |
commit | 9d4efe67c01c8c5a3f3bd1ee71af8b7c03021a18 (patch) | |
tree | b2c767fb238614943831b962e2322238d3ef88be | |
parent | c05125b35934d25b44f3bf932420bc0f7f950093 (diff) | |
download | rumba-9d4efe67c01c8c5a3f3bd1ee71af8b7c03021a18.tar.gz rumba-9d4efe67c01c8c5a3f3bd1ee71af8b7c03021a18.zip |
rumba: Add visualisation tool
The draw_experiment(exp) function will draw a 3D representation of the
network. Depends on igraph and plotly packages.
Signed-off-by: Dimitri Staessens <dimitri@ouroboros.rocks>
-rwxr-xr-x | examples/scalingtime.py | 94 | ||||
-rw-r--r-- | rumba/prototypes/ouroboros.py | 2 | ||||
-rwxr-xr-x | rumba/visualizer.py | 447 |
3 files changed, 496 insertions, 47 deletions
diff --git a/examples/scalingtime.py b/examples/scalingtime.py index bf4a739..ff3c2ed 100755 --- a/examples/scalingtime.py +++ b/examples/scalingtime.py @@ -16,20 +16,20 @@ import rumba.prototypes.rlite as rl import rumba.prototypes.irati as irati import argparse -import sys +from rumba.visualizer import draw_experiment description = "Script to create the demo from the Rumba paper" -argparser = argparse.ArgumentParser(description = description) +argparser = argparse.ArgumentParser(description=description) # Parameters to set the experiment size -argparser.add_argument('--metro-networks', type = int, default = '2', - help = "Number of metro networks") -argparser.add_argument('--metro-nodes', type = int, default = '5', - help = "Number of nodes per metro") -argparser.add_argument('--end-nodes', type = int, default = '2', - help = "Number of nodes connected to each metro") +argparser.add_argument('--metro-networks', type=int, default='2', + help="Number of metro networks") +argparser.add_argument('--metro-nodes', type=int, default='5', + help="Number of nodes per metro") +argparser.add_argument('--end-nodes', type=int, default='2', + help="Number of nodes connected to each metro") # Other parameters argparser.add_argument('--verbosity', metavar='VERBOSITY', type=str, @@ -57,8 +57,8 @@ qemu_p.add_argument('--use_vhost', action='store_true', default=False, help='Use vhost') qemu_p.add_argument('--qemu_logs_dir', metavar='QEMU_LOGS', type=str, default=None, help='path to the folder for qemu logs') -qemu_p.add_argument('--username', type = str, help = "Username") -qemu_p.add_argument('--password', type = str, help = "Password") +qemu_p.add_argument('--username', type=str, help="Username") +qemu_p.add_argument('--password', type=str, help="Password") emulab_p.add_argument('--url', metavar='URL', type=str, default="wall2.ilabt.iminds.be", @@ -66,27 +66,27 @@ emulab_p.add_argument('--url', metavar='URL', type=str, emulab_p.add_argument('--image', metavar='IMG', type=str, default="UBUNTU14-64-STD", help='Ubuntu image') -emulab_p.add_argument('--username', type = str, required=True, - help = "Username") -emulab_p.add_argument('--password', type = str, help = "Password") -emulab_p.add_argument('--exp-name', type = str, required=True, - help = "Name of the experiment") +emulab_p.add_argument('--username', type=str, required=True, + help="Username") +emulab_p.add_argument('--password', type=str, help="Password") +emulab_p.add_argument('--exp-name', type=str, required=True, + help="Name of the experiment") -jfed_p.add_argument('--cert-file', type = str, +jfed_p.add_argument('--cert-file', type=str, required=True, - help = "Absolute path to certificate (.pem) file" + help="Absolute path to certificate (.pem) file" " to be used with jFed") -jfed_p.add_argument('--authority', type = str, default = 'exogeni.net', - help = "jFed testbed to use") +jfed_p.add_argument('--authority', type=str, default='exogeni.net', + help="jFed testbed to use") jfed_p.add_argument('--image', metavar='IMAGE', type=str, default=None, help='Image to be used') jfed_p.add_argument('--exp-hours', metavar='HOURS', type=str, default="2", help='Experiment hours') -jfed_p.add_argument('--username', type = str, required=True, - help = "Username") -jfed_p.add_argument('--exp-name', type = str, required=True, - help = "Name of the experiment") +jfed_p.add_argument('--username', type=str, required=True, + help="Username") +jfed_p.add_argument('--exp-name', type=str, required=True, + help="Name of the experiment") docker_p.add_argument('--base-image', metavar='DOCKIMAGE', type=str, default=None, required=True, @@ -117,19 +117,19 @@ for i in range(0, args.metro_networks): e = ShimEthDIF("e-" + str(i) + "-" + str(j)) else: e = None - if e_prev == None and e != None: + if e_prev is None and e is not None: node = Node("metro-" + str(i) + "-" + str(j), - difs = [m, pi, e], - dif_registrations = {pi : [m], m : [e]}) + difs=[m, pi, e], + dif_registrations={pi: [m], m: [e]}) m1 = node - elif e_prev != None and e != None: + elif e_prev is not None and e is not None: node = Node("metro-" + str(i) + "-" + str(j), - difs = [m, pi, e, e_prev], - dif_registrations = {pi : [m], m : [e, e_prev]}) + difs=[m, pi, e, e_prev], + dif_registrations={pi: [m], m: [e, e_prev]}) else: node = Node("metro-" + str(i) + "-" + str(j), - difs = [m, pi, e_prev], - dif_registrations = {pi : [m], m : [e_prev]}) + difs=[m, pi, e_prev], + dif_registrations={pi: [m], m: [e_prev]}) mn = node nodes.append(node) e_prev = e @@ -137,8 +137,8 @@ for i in range(0, args.metro_networks): for k in range(0, args.end_nodes): ee = ShimEthDIF("e-" + str(i) + "-" + str(j) + "-" + str(k)) end_node = Node("end-" + str(i) + "-" + str(j) + "-" + str(k), - difs = [pi, ee], - dif_registrations = {pi : [ee]}) + difs=[pi, ee], + dif_registrations={pi: [ee]}) node.add_dif(ee) node.add_dif_registration(pi, ee) nodes.append(end_node) @@ -155,12 +155,12 @@ for i in range(0, args.metro_networks): e2 = ShimEthDIF("c-e-" + str(i) + "-2") c1 = Node("c-" + str(i) + "-0", - difs = [c, m, pi, e0, e2], - dif_registrations = {pi : [m, c], m : [e0], c : [e2]}) + difs=[c, m, pi, e0, e2], + dif_registrations={pi: [m, c], m: [e0], c: [e2]}) c2 = Node("c-" + str(i) + "-1", - difs = [c, m, pi, e1, e2], - dif_registrations = {pi : [m, c], m : [e1], c : [e2]}) + difs=[c, m, pi, e1, e2], + dif_registrations={pi: [m, c], m: [e1], c: [e2]}) nodes.append(c1) nodes.append(c2) @@ -235,12 +235,14 @@ else: print(argparser.format_help()) exit(1) -exp = exp_class(testbed, nodes = nodes) - -with ExperimentManager(exp, swap_out_strategy=PAUSE_SWAPOUT): - exp.swap_in() - if not isinstance(testbed, docker.Testbed) \ - and not isinstance(testbed, qemu.Testbed) \ - and not isinstance(testbed, local.Testbed): - exp.install_prototype() - exp.bootstrap_prototype() +exp = exp_class(testbed, nodes=nodes) + +if __name__ == "__main__": + draw_experiment(exp) + with ExperimentManager(exp, swap_out_strategy=PROMPT_SWAPOUT): + exp.swap_in() + if not isinstance(testbed, docker.Testbed) \ + and not isinstance(testbed, qemu.Testbed) \ + and not isinstance(testbed, local.Testbed): + exp.install_prototype() + exp.bootstrap_prototype() diff --git a/rumba/prototypes/ouroboros.py b/rumba/prototypes/ouroboros.py index ed79b02..9cdd3c9 100644 --- a/rumba/prototypes/ouroboros.py +++ b/rumba/prototypes/ouroboros.py @@ -469,7 +469,7 @@ class Experiment(mod.Experiment): cmds.append('rm -rf /tmp/ouroboros || true') cmds.append('kill -9 $(ps axjf | grep \'sudo irmd\' | grep -v grep | cut -f4 -d " ") || true') else: - cmds.append('killall -15 irmd || true') + cmds.append('killall -15 irmd') logger.info("Killing Ouroboros...") if isinstance(self.testbed, local.Testbed): diff --git a/rumba/visualizer.py b/rumba/visualizer.py new file mode 100755 index 0000000..f8c18e6 --- /dev/null +++ b/rumba/visualizer.py @@ -0,0 +1,447 @@ +#!/usr/bin/python + +import igraph as ig +import plotly.graph_objs as go + +from math import sin, cos, pi, sqrt + +__all__ = ['draw_network', 'draw_experiment', 'get_default_test_network'] + +import rumba.elements.topology + +type_to_marker = { + 'eth-dix': 'diamond', + 'eth-llc': 'diamond', + 'eth-udp': 'diamond', + 'unicast': 'circle', + 'broadcast': 'square' +} + + +def rumba_to_type(_type): + if _type == rumba.elements.topology.ShimEthIPCP: + return 'eth-dix' + if _type == rumba.elements.topology.ShimUDPIPCP: + return 'eth-udp' + if _type == rumba.elements.topology.IPCP: + return 'unicast' + + +def get_default_test_network(): + return { + 'nodes': list(range(8)), + 'layers': { + 'bottom_layer1': { + 'type': 'eth-dix', + 'nodes': list(range(5)), + 'edges': [(0, 1), (0, 2), (0, 3), (1, 3), (2, 3), (1, 4), (2, 4)] + }, + 'bottom_layer2': { + 'type': 'eth-llc', + 'nodes': list(range(4, 8)), + 'edges': [(4, 5), (5, 6), (6, 7), (4, 7)] + }, + 'medium_layer': { + 'type': 'unicast', + 'nodes': [0, 2, 4, 5, 7], + 'edges': [(0, 2), (0, 4), (4, 5), (5, 7), (2, 5), (4, 7)] + }, + 'top_layer': { + 'type': 'unicast', + 'nodes': [0, 7], + 'edges': [(0, 7)] + } + }, + 'registrations': { + 'medium_layer': { + 'bottom_layer1': [0, 2, 4], + 'bottom_layer2': [4, 5, 7] + }, + 'top_layer': { + 'medium_layer': [0, 7] + } + } + } + + +def _get_nodes_in_dif(exp, dif): + nodes = [] + n = 0 + for node in exp.nodes: + if dif in [d.name for d in node.difs]: + nodes.append(n) + n += 1 + + return nodes + + +def _get_node_index(exp, node): + n = 0 + for _node in exp.nodes: + if _node.name == node.name: + return n + n += 1 + return 0 + + +def _get_network_from_rumba_experiment(exp): + print(exp) + print(exp.flows) + _nw = dict() + _nw['layers'] = dict() + + _nw['nodes'] = list(range(len(exp.nodes))) + + _nw['registrations'] = dict() + + for node in exp.nodes: + for dif in node.difs: + if dif.name not in _nw['layers']: + _nw['layers'][dif.name] = dict() + _nw['layers'][dif.name]['type'] = rumba_to_type(dif.get_ipcp_class()) + _nw['layers'][dif.name]['nodes'] = _get_nodes_in_dif(exp, dif.name) + _nw['layers'][dif.name]['edges'] = list() + if _nw['layers'][dif.name]['type'] != 'unicast': # shim + nodes = _nw['layers'][dif.name]['nodes'] + _nw['layers'][dif.name]['edges'].append((nodes[0], nodes[1])) + _nw['registrations'][dif.name] = dict() + + for layer in exp.flows: + for flow in layer: + if 'src' in flow and 'dst' in flow: + src = _get_node_index(exp, flow['src'].node) + dst = _get_node_index(exp, flow['dst'].node) + layer = flow['src'].dif.name + _nw['layers'][layer]['edges'].append((src, dst)) + src_regs = flow['src'].registrations + dst_regs = flow['dst'].registrations + for dif in src_regs: + if dif.name not in _nw['registrations'][layer]: + _nw['registrations'][layer][dif.name] = set() + _nw['registrations'][layer][dif.name].add(src) + for dif in dst_regs: + if dif.name not in _nw['registrations'][layer]: + _nw['registrations'][layer][dif.name] = set() + _nw['registrations'][layer][dif.name].add(dst) + + return _nw + + +def _get_rank(network, layer): + rank = 0 + + if layer not in network['registrations']: + return rank + + for _layer in network['registrations'][layer]: + n_1_rank = _get_rank(network, _layer) + if n_1_rank >= rank: + rank = n_1_rank + 1 + + return rank + + +def _get_ipcp_id(network, layer, node): + for ipcp in network['_ipcps']: + if ipcp['node'] == node and ipcp['layer'] == layer: + return ipcp['id'] + + +def _create_ipcp_network(network): + network['_ipcps'] = list() + network['_adjs'] = list() + network['_deps'] = list() + network['_systems'] = list() + + for node in network['nodes']: + network['_systems'].append({'node': node}) + + ipcp_id = 0 + color = 0 + + for layer in network['layers']: + for node in network['layers'][layer]['nodes']: + ipcp = { + 'id': ipcp_id, + 'node': node, + 'layer': layer, + 'color': color, + 'type': network['layers'][layer]['type'], + 'rank': _get_rank(network, layer) + } + network['_ipcps'].append(ipcp) + ipcp_id += 1 + for edge in network['layers'][layer]['edges']: + src = _get_ipcp_id(network, layer, edge[0]) + dst = _get_ipcp_id(network, layer, edge[1]) + adj = { + 'src': src, + 'dst': dst, + 'color': color + } + network['_adjs'].append(adj) + color += 1 + + for upper_layer in network['registrations']: + for lower_layer in network['registrations'][upper_layer]: + for node in network['registrations'][upper_layer][lower_layer]: + src = _get_ipcp_id(network, upper_layer, node) + dst = _get_ipcp_id(network, lower_layer, node) + network['_deps'].append((src, dst)) + + +def _create_system_graph(network): + edges = [] + + for layer in network['layers']: + for edge in network['layers'][layer]['edges']: + if edge not in edges: + edges.append(edge) + + return edges + + +def _create_system_coords(network): + """ + Create coordinates for the systems in the network + :param network: + :return: + """ + edges = _create_system_graph(network) + + g = ig.Graph(edges, directed=False) + layout = g.layout('drl', dim=2) + + for system in network['_systems']: + n = system['node'] + system['coords'] = (layout[n][0], layout[n][1]) + + _min = None + + for edge in edges: + x1, y1 = network['_systems'][edge[0]]['coords'] + x2, y2 = network['_systems'][edge[1]]['coords'] + d = sqrt((x2 - x1) * (x2 - x1) + (y2 - y1) * (y2 - y1)) + _min = d if _min is None else min(d, _min) + + return _min + + +def _get_ipcps_for_system(network, system): + node = system['node'] + return [ipcp for ipcp in network['_ipcps'] if ipcp['node'] == node] + + +def _get_ranks_for_ipcps(ipcps): + return set([ipcp['rank'] for ipcp in ipcps]) + + +def _get_ipcps_by_rank(ipcps, rank): + return [ipcp for ipcp in ipcps if ipcp['rank'] == rank] + + +def _create_ipcp_coords(network, radius): + """ + Create 3d coordinates for IPCPs based on the system layout + """ + height = 1 + + for system in network['_systems']: + ipcps = _get_ipcps_for_system(network, system) + ranks = _get_ranks_for_ipcps(ipcps) + for rank in ranks: + ri = _get_ipcps_by_rank(ipcps, rank) + n = len(ri) + m = 1 + for ipcp in ri: + if n == 1: + x, y = system['coords'] + ipcp['coords'] = (x, y, rank * height) + continue + x = system['coords'][0] + y = system['coords'][1] + new_x = x + radius * sin((m/n) * pi) + new_y = y + radius * cos((m/n) * pi) + ipcp['coords'] = (new_x, new_y, rank * height) + m += 1 + + +def _create_ipcp_graph_data(network): + _create_ipcp_network(network) + _min = _create_system_coords(network) + + print("_min = %s" % _min) + _create_ipcp_coords(network, _min / 5) + + print(network) + + +def _get_ipcp_attributes(network): + coords = list() + colors = list() + labels = list() + markers = list() + + for ipcp in network['_ipcps']: + coords.append(ipcp['coords']) + colors.append(ipcp['color']) + labels.append(ipcp['layer'] + ' ' + str(ipcp['node'])) + markers.append(type_to_marker[ipcp['type']]) + + return coords, colors, labels, markers + + +def _get_edge_attributes(network): + x_coords = list() + y_coords = list() + z_coords = list() + colors = list() + + for adj in network['_adjs']: + src = network['_ipcps'][adj['src']]['coords'] + dst = network['_ipcps'][adj['dst']]['coords'] + x_coords.extend([src[0], dst[0], None]) + y_coords.extend([src[1], dst[1], None]) + z_coords.extend([src[2], dst[2], None]) + colors.append(adj['color']) + + return x_coords, y_coords, z_coords, colors + + +def _get_deps_attributes(network): + x_coords = list() + y_coords = list() + z_coords = list() + colors = list() + + for dep in network['_deps']: + src = network['_ipcps'][dep[0]]['coords'] + dst = network['_ipcps'][dep[1]]['coords'] + x_coords.extend([src[0], dst[0], None]) + y_coords.extend([src[1], dst[1], None]) + z_coords.extend([src[2], dst[2], None]) + colors.append(0) + + return x_coords, y_coords, z_coords, colors + + +def _extract(coords): + x = [] + y = [] + z = [] + + for coord in coords: + x.append(coord[0]) + y.append(coord[1]) + z.append(coord[2]) + + return x, y, z + + +def draw_network(network, name='Ouroboros network'): + _create_ipcp_graph_data(network) + coords, colors, labels, markers = _get_ipcp_attributes(network) + + x_coords, y_coords, z_coords = _extract(coords) + + nodes = go.Scatter3d( + x=x_coords, + y=y_coords, + z=z_coords, + mode='markers+text', + name='actors', + marker=dict(symbol=markers, + size=6, + color=colors, + colorscale='Viridis', + line=dict(color='rgb(50,50,50)', width=0.5) + ), + text=labels, + hoverinfo='none' + ) + + x_coords, y_coords, z_coords, colors = _get_edge_attributes(network) + + layers = go.Scatter3d( + x=x_coords, + y=y_coords, + z=z_coords, + mode='lines', + line=dict(color=colors, + colorscale='Viridis', + dash='solid', + width=2), + hoverinfo='none' + ) + + x_coords, y_coords, z_coords, colors = _get_deps_attributes(network) + + deps = go.Scatter3d( + x=x_coords, + y=y_coords, + z=z_coords, + mode='lines', + line=dict(color=colors, + colorscale='Viridis', + dash='dashdot', + width=1), + hoverinfo='none' + ) + + axis = dict(showbackground=False, + showline=False, + zeroline=False, + showgrid=False, + showticklabels=False, + title='') + + layout = go.Layout( + title=name, + autosize=True, + width=3600, + height=1800, + showlegend=False, + scene=dict( + xaxis=dict(axis), + yaxis=dict(axis), + zaxis=dict(axis), + ), + margin=dict( + l=0, + r=0, + b=0, + t=100 + ), + hovermode='closest', + annotations=[ + dict( + showarrow=False, + text="Data source: Ouroboros", + xref='paper', + yref='paper', + xanchor='left', + yanchor='bottom', + x=0, + y=0.1, + font=dict( + size=14 + ) + ) + ], + ) + + data = [nodes, layers, deps] + fig = go.Figure(data=data, layout=layout) + fig.update_scenes(aspectmode='data') + + fig.show() + + +def draw_experiment(exp, name='experiment'): + _nw = _get_network_from_rumba_experiment(exp) + draw_network(_nw, name) + + +if __name__ == '__main__': + nw = get_default_test_network() + draw_network(nw) |