Logo AND Algorithmique Numérique Distribuée

Public GIT Repository
b18d2a17382a2e8c9c9d3d857f33ce9310dd5598
[simgrid.git] / examples / platforms / supernode.py
1 #! /usr/bin/env python3
2
3 # Copyright (c) 2006-2022. The SimGrid Team. All rights reserved.
4
5 # This program is free software; you can redistribute it and/or modify it
6 # under the terms of the license (GNU LGPL) which comes with this package.
7
8
9 # This script takes as input a C++ platform file, compiles it, then dumps the
10 # routing graph as a CSV and generates an image.
11 # The layout should be alright for any platform file, but the colors are very
12 # ad-hoc for file supernode.cpp : do not hesitate to adapt this script to your needs.
13 # An option is provided to "simplify" the graph by removing the link vertices. It assumes that these vertices have
14 # "link" in their name.
15
16 import sys
17 import subprocess
18 import pandas
19 import matplotlib.pyplot as plt
20 import networkx as nx
21 import argparse
22 import tempfile
23 import os
24
25 try:
26     from palettable.colorbrewer.qualitative import Set1_9
27     colors = Set1_9.hex_colors
28 except ImportError:
29     print('Warning: could not import palettable, will use a default palette.')
30     colors = [None]*10
31
32
33 def run_command(cmd):
34     print(cmd)
35     proc = subprocess.Popen(cmd.split(), stdout=subprocess.PIPE, stderr=subprocess.PIPE)
36     stdout, stderr = proc.communicate()
37     if proc.returncode != 0:
38         sys.exit(f'Command failed:\n{stderr.decode()}')
39
40
41 def compile_platform(platform_cpp, platform_so):
42     cmd = f'g++ -g -fPIC -shared -o {platform_so} {platform_cpp} -lsimgrid'
43     run_command(cmd)
44
45
46 def dump_csv(platform_so, platform_csv):
47     cmd = f'graphicator {platform_so} {platform_csv}'
48     run_command(cmd)
49
50
51 def merge_updown(graph):
52     '''
53     Merge all the UP and DOWN links.
54     '''
55     H = graph.copy()
56     downlinks = [v for v in graph if 'DOWN' in v]
57     mapping = {}
58     for down in downlinks:
59         up = down.replace('DOWN', 'UP')
60         H = nx.contracted_nodes(H, down, up)
61         mapping[down] = down.replace('_DOWN', '')
62     return nx.relabel_nodes(H, mapping)
63
64
65 def contract_links(graph):
66     '''
67     Remove all the 'link' vertices from the graph to directly connect the nodes.
68     Note: it assumes that link vertices have the "link" string in their name.
69     '''
70     H = graph.copy()
71     links = [v for v in graph if 'link' in v]
72     new_edges = []
73     for v in links:
74         neigh = [u for u in graph.neighbors(v) if 'link' not in u]  # with Floyd zones, we have links connected to links
75         assert len(neigh) == 2
76         new_edges.append(neigh)
77     # Adding edges from graph that have no links
78     for u, v in graph.edges:
79         if 'link' not in u and 'link' not in v:
80             new_edges.append((u, v))
81     return nx.from_edgelist(new_edges)
82
83
84 def load_graph(platform_csv, simplify_graph):
85     edges = pandas.read_csv(platform_csv)
86     graph = nx.Graph()
87     graph.add_edges_from([e for _, e in edges.drop_duplicates().iterrows()])
88     print(f'Loaded a graph with {len(graph)} vertices with {len(graph.edges)} edges')
89     if simplify_graph:
90         graph = contract_links(merge_updown(graph))
91         print(f'Simplified the graph, it now has {len(graph)} vertices with {len(graph.edges)} edges')
92     return graph
93
94
95 def plot_graph(graph, label=False, groups=[]):
96     # First, we compute the graph layout, i.e. the position of the nodes.
97     # The neato algorithm from graphviz is nicer, but this is an extra-dependency.
98     # The spring_layout is also not too bad.
99     try:
100         pos = nx.nx_agraph.graphviz_layout(graph, 'neato')
101     except ImportError:
102         print('Warning: could not import pygraphviz, will use another layout algorithm.')
103         pos = nx.spring_layout(graph, k=0.5, iterations=1000, seed=42)
104     plt.figure(figsize=(20, 15))
105     plt.axis('off')
106     all_nodes = set(graph)
107     # We then iterate on all the specified groups, to plot each of them in the right color.
108     # Note that the order of the groups is important here, as we are looking at substrings in the node names.
109     for i, grp in enumerate(groups):
110         nodes = {u for u in all_nodes if grp in u}
111         all_nodes -= nodes
112         nx.draw_networkx_nodes(graph, pos, nodelist=nodes, node_size=50, node_color=colors[i], label=grp.replace('_', ''))
113     nx.draw_networkx_nodes(graph, pos, nodelist=all_nodes, node_size=50, node_color=colors[-1], label='unknown')
114     # Finally we draw the edges, the (optional) labels, and the legend.
115     nx.draw_networkx_edges(graph, pos, alpha=0.3)
116     if label:
117         nx.draw_networkx_labels(graph, pos)
118     plt.legend(scatterpoints = 1)
119
120
121 def generate_svg(platform_csv, output_file, simplify_graph):
122     graph = load_graph(platform_csv, simplify_graph)
123     plot_graph(graph, label=False, groups=['router', 'link', 'cpu', '_node', 'supernode', 'cluster'])
124     plt.savefig(output_file)
125     print(f'Generated file {output_file}')
126
127
128 if __name__ == '__main__':
129     parser = argparse.ArgumentParser(description='Visualization of topologies for SimGrid C++ platforms')
130     parser.add_argument('input', type=str, help='SimGrid C++ platform file name (input)')
131     parser.add_argument('output', type=str, help='File name of the output image')
132     parser.add_argument('--simplify', action='store_true', help='Simplify the topology (removing link vertices)')
133     args = parser.parse_args()
134     if not args.input.endswith('.cpp'):
135         parser.error(f'SimGrid platform must be a C++ file (with .cpp extension), got {args.input}')
136     if not os.path.isfile(args.input):
137         parser.error(f'File {args.input} not found')
138     output_dir = os.path.dirname(args.output)
139     if output_dir != '' and not os.path.isdir(output_dir):
140         parser.error(f'Not a directory: {output_dir}')
141     with tempfile.TemporaryDirectory() as tmpdirname:
142         platform_cpp = args.input
143         platform_csv = os.path.join(tmpdirname, 'platform.csv')
144         platform_so = os.path.join(tmpdirname, 'platform.so')
145         compile_platform(platform_cpp, platform_so)
146         dump_csv(platform_so, platform_csv)
147         generate_svg(platform_csv, args.output, args.simplify)