17488330700415be296a13297901c609cca77530
[deb_dpdk.git] / examples / ip_pipeline / config / diagram-generator.py
1 #!/usr/bin/env python
2
3 #   BSD LICENSE
4 #
5 #   Copyright(c) 2016 Intel Corporation. All rights reserved.
6 #   All rights reserved.
7 #
8 #   Redistribution and use in source and binary forms, with or without
9 #   modification, are permitted provided that the following conditions
10 #   are met:
11 #
12 #     * Redistributions of source code must retain the above copyright
13 #       notice, this list of conditions and the following disclaimer.
14 #     * Redistributions in binary form must reproduce the above copyright
15 #       notice, this list of conditions and the following disclaimer in
16 #       the documentation and/or other materials provided with the
17 #       distribution.
18 #     * Neither the name of Intel Corporation nor the names of its
19 #       contributors may be used to endorse or promote products derived
20 #       from this software without specific prior written permission.
21 #
22 #   THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
23 #   "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
24 #   LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
25 #   A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
26 #   OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
27 #   SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
28 #   LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
29 #   DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
30 #   THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
31 #   (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
32 #   OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
33
34 #
35 # This script creates a visual representation for a configuration file used by
36 # the DPDK ip_pipeline application.
37 #
38 # The input configuration file is translated to an output file in DOT syntax,
39 # which is then used to create the image file using graphviz
40 # (www.graphviz.org).
41 #
42
43 from __future__ import print_function
44 import argparse
45 import re
46 import os
47
48 #
49 # Command to generate the image file
50 #
51 DOT_COMMAND = 'dot -Gsize=20,30 -Tpng %s > %s'
52
53 #
54 # Layout of generated DOT file
55 #
56 DOT_INTRO = \
57     '#\n# Command to generate image file:\n# \t%s\n#\n\n'
58 DOT_GRAPH_BEGIN = \
59     'digraph g {\n  graph [ splines = true rankdir = "LR" ]\n'
60 DOT_NODE_LINK_RX = \
61     '  "%s RX" [ shape = box style = filled fillcolor = yellowgreen ]\n'
62 DOT_NODE_LINK_TX = \
63     '  "%s TX" [ shape = box style = filled fillcolor = yellowgreen ]\n'
64 DOT_NODE_KNI_RX = \
65     '  "%s RX" [ shape = box style = filled fillcolor = orange ]\n'
66 DOT_NODE_KNI_TX = \
67     '  "%s TX" [ shape = box style = filled fillcolor = orange ]\n'
68 DOT_NODE_TAP_RX = \
69     '  "%s RX" [ shape = box style = filled fillcolor = gold ]\n'
70 DOT_NODE_TAP_TX = \
71     '  "%s TX" [ shape = box style = filled fillcolor = gold ]\n'
72 DOT_NODE_SOURCE = \
73     '  "%s" [ shape = box style = filled fillcolor = darkgreen ]\n'
74 DOT_NODE_SINK = \
75     '  "%s" [ shape = box style = filled fillcolor = peachpuff ]\n'
76 DOT_NODE_PIPELINE = \
77     '  "%s" [ shape = box style = filled fillcolor = royalblue ]\n'
78 DOT_EDGE_PKTQ = \
79     '  "%s" -> "%s" [ label = "%s" color = gray ]\n'
80 DOT_GRAPH_END = \
81     '}\n'
82
83 # Relationships between the graph nodes and the graph edges:
84 #
85 # Edge ID | Edge Label | Writer Node | Reader Node   | Dependencies
86 # --------+------------+-------------+---------------+--------------
87 # RXQx.y  | RXQx.y     | LINKx       | PIPELINEz     | LINKx
88 # TXQx.y  | TXQx.y     | PIPELINEz   | LINKx         | LINKx
89 # SWQx    | SWQx       | PIPELINEy   | PIPELINEz     | -
90 # TMx     | TMx        | PIPELINEy   | PIPELINEz     | LINKx
91 # KNIx RX | KNIx       | KNIx RX     | PIPELINEy     | KNIx, LINKx
92 # KNIx TX | KNIx       | PIPELINEy   | KNIx TX       | KNIx, LINKx
93 # TAPx RX | TAPx       | TAPx RX     | PIPELINEy     | TAPx
94 # TAPx TX | TAPx       | PIPELINEy   | TAPx TX       | TAPx
95 # SOURCEx | SOURCEx    | SOURCEx     | PIPELINEy     | SOURCEx
96 # SINKx   | SINKx      | PIPELINEy   | SINKx         | SINKx
97
98
99 #
100 # Parse the input configuration file to detect the graph nodes and edges
101 #
102 def process_config_file(cfgfile):
103     edges = {}
104     links = set()
105     knis = set()
106     taps = set()
107     sources = set()
108     sinks = set()
109     pipelines = set()
110     pipeline = ''
111
112     dotfile = cfgfile + '.txt'
113     imgfile = cfgfile + '.png'
114
115     #
116     # Read configuration file
117     #
118     lines = open(cfgfile, 'r')
119     for line in lines:
120         # Remove any leading and trailing white space characters
121         line = line.strip()
122
123         # Remove any comment at end of line
124         line, sep, tail = line.partition(';')
125
126         # Look for next "PIPELINE" section
127         match = re.search(r'\[(PIPELINE\d+)\]', line)
128         if match:
129             pipeline = match.group(1)
130             continue
131
132         # Look for next "pktq_in" section entry
133         match = re.search(r'pktq_in\s*=\s*(.+)', line)
134         if match:
135             pipelines.add(pipeline)
136             for q in re.findall('\S+', match.group(1)):
137                 match_rxq = re.search(r'^RXQ(\d+)\.\d+$', q)
138                 match_swq = re.search(r'^SWQ\d+$', q)
139                 match_tm = re.search(r'^TM(\d+)$', q)
140                 match_kni = re.search(r'^KNI(\d+)$', q)
141                 match_tap = re.search(r'^TAP\d+$', q)
142                 match_source = re.search(r'^SOURCE\d+$', q)
143
144                 # Set ID for the current packet queue (graph edge)
145                 q_id = ''
146                 if match_rxq or match_swq or match_tm or match_source:
147                     q_id = q
148                 elif match_kni or match_tap:
149                     q_id = q + ' RX'
150                 else:
151                     print('Error: Unrecognized pktq_in element "%s"' % q)
152                     return
153
154                 # Add current packet queue to the set of graph edges
155                 if q_id not in edges:
156                     edges[q_id] = {}
157                 if 'label' not in edges[q_id]:
158                     edges[q_id]['label'] = q
159                 if 'readers' not in edges[q_id]:
160                     edges[q_id]['readers'] = []
161                 if 'writers' not in edges[q_id]:
162                     edges[q_id]['writers'] = []
163
164                 # Add reader for the new edge
165                 edges[q_id]['readers'].append(pipeline)
166
167                 # Check for RXQ
168                 if match_rxq:
169                     link = 'LINK' + str(match_rxq.group(1))
170                     edges[q_id]['writers'].append(link + ' RX')
171                     links.add(link)
172                     continue
173
174                 # Check for SWQ
175                 if match_swq:
176                     continue
177
178                 # Check for TM
179                 if match_tm:
180                     link = 'LINK' + str(match_tm.group(1))
181                     links.add(link)
182                     continue
183
184                 # Check for KNI
185                 if match_kni:
186                     link = 'LINK' + str(match_kni.group(1))
187                     edges[q_id]['writers'].append(q_id)
188                     knis.add(q)
189                     links.add(link)
190                     continue
191
192                 # Check for TAP
193                 if match_tap:
194                     edges[q_id]['writers'].append(q_id)
195                     taps.add(q)
196                     continue
197
198                 # Check for SOURCE
199                 if match_source:
200                     edges[q_id]['writers'].append(q)
201                     sources.add(q)
202                     continue
203
204                 continue
205
206         # Look for next "pktq_out" section entry
207         match = re.search(r'pktq_out\s*=\s*(.+)', line)
208         if match:
209             for q in re.findall('\S+', match.group(1)):
210                 match_txq = re.search(r'^TXQ(\d+)\.\d+$', q)
211                 match_swq = re.search(r'^SWQ\d+$', q)
212                 match_tm = re.search(r'^TM(\d+)$', q)
213                 match_kni = re.search(r'^KNI(\d+)$', q)
214                 match_tap = re.search(r'^TAP(\d+)$', q)
215                 match_sink = re.search(r'^SINK(\d+)$', q)
216
217                 # Set ID for the current packet queue (graph edge)
218                 q_id = ''
219                 if match_txq or match_swq or match_tm or match_sink:
220                     q_id = q
221                 elif match_kni or match_tap:
222                     q_id = q + ' TX'
223                 else:
224                     print('Error: Unrecognized pktq_out element "%s"' % q)
225                     return
226
227                 # Add current packet queue to the set of graph edges
228                 if q_id not in edges:
229                     edges[q_id] = {}
230                 if 'label' not in edges[q_id]:
231                     edges[q_id]['label'] = q
232                 if 'readers' not in edges[q_id]:
233                     edges[q_id]['readers'] = []
234                 if 'writers' not in edges[q_id]:
235                     edges[q_id]['writers'] = []
236
237                 # Add writer for the new edge
238                 edges[q_id]['writers'].append(pipeline)
239
240                 # Check for TXQ
241                 if match_txq:
242                     link = 'LINK' + str(match_txq.group(1))
243                     edges[q_id]['readers'].append(link + ' TX')
244                     links.add(link)
245                     continue
246
247                 # Check for SWQ
248                 if match_swq:
249                     continue
250
251                 # Check for TM
252                 if match_tm:
253                     link = 'LINK' + str(match_tm.group(1))
254                     links.add(link)
255                     continue
256
257                 # Check for KNI
258                 if match_kni:
259                     link = 'LINK' + str(match_kni.group(1))
260                     edges[q_id]['readers'].append(q_id)
261                     knis.add(q)
262                     links.add(link)
263                     continue
264
265                 # Check for TAP
266                 if match_tap:
267                     edges[q_id]['readers'].append(q_id)
268                     taps.add(q)
269                     continue
270
271                 # Check for SINK
272                 if match_sink:
273                     edges[q_id]['readers'].append(q)
274                     sinks.add(q)
275                     continue
276
277                 continue
278
279     #
280     # Write DOT file
281     #
282     print('Creating DOT file "%s" ...' % dotfile)
283     dot_cmd = DOT_COMMAND % (dotfile, imgfile)
284     file = open(dotfile, 'w')
285     file.write(DOT_INTRO % dot_cmd)
286     file.write(DOT_GRAPH_BEGIN)
287
288     # Write the graph nodes to the DOT file
289     for l in sorted(links):
290         file.write(DOT_NODE_LINK_RX % l)
291         file.write(DOT_NODE_LINK_TX % l)
292     for k in sorted(knis):
293         file.write(DOT_NODE_KNI_RX % k)
294         file.write(DOT_NODE_KNI_TX % k)
295     for t in sorted(taps):
296         file.write(DOT_NODE_TAP_RX % t)
297         file.write(DOT_NODE_TAP_TX % t)
298     for s in sorted(sources):
299         file.write(DOT_NODE_SOURCE % s)
300     for s in sorted(sinks):
301         file.write(DOT_NODE_SINK % s)
302     for p in sorted(pipelines):
303         file.write(DOT_NODE_PIPELINE % p)
304
305     # Write the graph edges to the DOT file
306     for q in sorted(edges.keys()):
307         rw = edges[q]
308         if 'writers' not in rw:
309             print('Error: "%s" has no writer' % q)
310             return
311         if 'readers' not in rw:
312             print('Error: "%s" has no reader' % q)
313             return
314         for w in rw['writers']:
315             for r in rw['readers']:
316                 file.write(DOT_EDGE_PKTQ % (w, r, rw['label']))
317
318     file.write(DOT_GRAPH_END)
319     file.close()
320
321     #
322     # Execute the DOT command to create the image file
323     #
324     print('Creating image file "%s" ...' % imgfile)
325     if os.system('which dot > /dev/null'):
326         print('Error: Unable to locate "dot" executable.'
327               'Please install the "graphviz" package (www.graphviz.org).')
328         return
329
330     os.system(dot_cmd)
331
332
333 if __name__ == '__main__':
334     parser = argparse.ArgumentParser(description='Create diagram for IP '
335                                                  'pipeline configuration '
336                                                  'file.')
337
338     parser.add_argument(
339         '-f',
340         '--file',
341         help='input configuration file (e.g. "ip_pipeline.cfg")',
342         required=True)
343
344     args = parser.parse_args()
345
346     process_config_file(args.file)