]>
Commit | Line | Data |
---|---|---|
7c673cae FG |
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 (www.graphviz.org). | |
40 | # | |
41 | ||
42 | from __future__ import print_function | |
43 | import argparse | |
44 | import re | |
45 | import os | |
46 | ||
47 | # | |
48 | # Command to generate the image file | |
49 | # | |
50 | DOT_COMMAND = 'dot -Gsize=20,30 -Tpng %s > %s' | |
51 | ||
52 | # | |
53 | # Layout of generated DOT file | |
54 | # | |
55 | DOT_INTRO = \ | |
56 | '#\n# Command to generate image file:\n# \t%s\n#\n\n' | |
57 | DOT_GRAPH_BEGIN = \ | |
58 | 'digraph g {\n graph [ splines = true rankdir = "LR" ]\n' | |
59 | DOT_NODE_LINK_RX = \ | |
60 | ' "%s RX" [ shape = box style = filled fillcolor = yellowgreen ]\n' | |
61 | DOT_NODE_LINK_TX = \ | |
62 | ' "%s TX" [ shape = box style = filled fillcolor = yellowgreen ]\n' | |
63 | DOT_NODE_KNI_RX = \ | |
64 | ' "%s RX" [ shape = box style = filled fillcolor = orange ]\n' | |
65 | DOT_NODE_KNI_TX = \ | |
66 | ' "%s TX" [ shape = box style = filled fillcolor = orange ]\n' | |
67 | DOT_NODE_TAP_RX = \ | |
68 | ' "%s RX" [ shape = box style = filled fillcolor = gold ]\n' | |
69 | DOT_NODE_TAP_TX = \ | |
70 | ' "%s TX" [ shape = box style = filled fillcolor = gold ]\n' | |
71 | DOT_NODE_SOURCE = \ | |
72 | ' "%s" [ shape = box style = filled fillcolor = darkgreen ]\n' | |
73 | DOT_NODE_SINK = \ | |
74 | ' "%s" [ shape = box style = filled fillcolor = peachpuff ]\n' | |
75 | DOT_NODE_PIPELINE = \ | |
76 | ' "%s" [ shape = box style = filled fillcolor = royalblue ]\n' | |
77 | DOT_EDGE_PKTQ = \ | |
78 | ' "%s" -> "%s" [ label = "%s" color = gray ]\n' | |
79 | DOT_GRAPH_END = \ | |
80 | '}\n' | |
81 | ||
82 | # Relationships between the graph nodes and the graph edges: | |
83 | # | |
84 | # Edge ID | Edge Label | Writer Node | Reader Node | Dependencies | |
85 | # --------+------------+-------------+---------------+-------------- | |
86 | # RXQx.y | RXQx.y | LINKx | PIPELINEz | LINKx | |
87 | # TXQx.y | TXQx.y | PIPELINEz | LINKx | LINKx | |
88 | # SWQx | SWQx | PIPELINEy | PIPELINEz | - | |
89 | # TMx | TMx | PIPELINEy | PIPELINEz | LINKx | |
90 | # KNIx RX | KNIx | KNIx RX | PIPELINEy | KNIx, LINKx | |
91 | # KNIx TX | KNIx | PIPELINEy | KNIx TX | KNIx, LINKx | |
92 | # TAPx RX | TAPx | TAPx RX | PIPELINEy | TAPx | |
93 | # TAPx TX | TAPx | PIPELINEy | TAPx TX | TAPx | |
94 | # SOURCEx | SOURCEx | SOURCEx | PIPELINEy | SOURCEx | |
95 | # SINKx | SINKx | PIPELINEy | SINKx | SINKx | |
96 | ||
97 | # | |
98 | # Parse the input configuration file to detect the graph nodes and edges | |
99 | # | |
100 | def process_config_file(cfgfile): | |
101 | edges = {} | |
102 | links = set() | |
103 | knis = set() | |
104 | taps = set() | |
105 | sources = set() | |
106 | sinks = set() | |
107 | pipelines = set() | |
108 | pipeline = '' | |
109 | ||
110 | dotfile = cfgfile + '.txt' | |
111 | imgfile = cfgfile + '.png' | |
112 | ||
113 | # | |
114 | # Read configuration file | |
115 | # | |
116 | lines = open(cfgfile, 'r') | |
117 | for line in lines: | |
118 | # Remove any leading and trailing white space characters | |
119 | line = line.strip() | |
120 | ||
121 | # Remove any comment at end of line | |
122 | line, sep, tail = line.partition(';') | |
123 | ||
124 | # Look for next "PIPELINE" section | |
125 | match = re.search(r'\[(PIPELINE\d+)\]', line) | |
126 | if match: | |
127 | pipeline = match.group(1) | |
128 | continue | |
129 | ||
130 | # Look for next "pktq_in" section entry | |
131 | match = re.search(r'pktq_in\s*=\s*(.+)', line) | |
132 | if match: | |
133 | pipelines.add(pipeline) | |
134 | for q in re.findall('\S+', match.group(1)): | |
135 | match_rxq = re.search(r'^RXQ(\d+)\.\d+$', q) | |
136 | match_swq = re.search(r'^SWQ\d+$', q) | |
137 | match_tm = re.search(r'^TM(\d+)$', q) | |
138 | match_kni = re.search(r'^KNI(\d+)$', q) | |
139 | match_tap = re.search(r'^TAP\d+$', q) | |
140 | match_source = re.search(r'^SOURCE\d+$', q) | |
141 | ||
142 | # Set ID for the current packet queue (graph edge) | |
143 | q_id = '' | |
144 | if match_rxq or match_swq or match_tm or match_source: | |
145 | q_id = q | |
146 | elif match_kni or match_tap: | |
147 | q_id = q + ' RX' | |
148 | else: | |
149 | print('Error: Unrecognized pktq_in element "%s"' % q) | |
150 | return | |
151 | ||
152 | # Add current packet queue to the set of graph edges | |
153 | if q_id not in edges: | |
154 | edges[q_id] = {} | |
155 | if 'label' not in edges[q_id]: | |
156 | edges[q_id]['label'] = q | |
157 | if 'readers' not in edges[q_id]: | |
158 | edges[q_id]['readers'] = [] | |
159 | if 'writers' not in edges[q_id]: | |
160 | edges[q_id]['writers'] = [] | |
161 | ||
162 | # Add reader for the new edge | |
163 | edges[q_id]['readers'].append(pipeline) | |
164 | ||
165 | # Check for RXQ | |
166 | if match_rxq: | |
167 | link = 'LINK' + str(match_rxq.group(1)) | |
168 | edges[q_id]['writers'].append(link + ' RX') | |
169 | links.add(link) | |
170 | continue | |
171 | ||
172 | # Check for SWQ | |
173 | if match_swq: | |
174 | continue | |
175 | ||
176 | # Check for TM | |
177 | if match_tm: | |
178 | link = 'LINK' + str(match_tm.group(1)) | |
179 | links.add(link) | |
180 | continue | |
181 | ||
182 | # Check for KNI | |
183 | if match_kni: | |
184 | link = 'LINK' + str(match_kni.group(1)) | |
185 | edges[q_id]['writers'].append(q_id) | |
186 | knis.add(q) | |
187 | links.add(link) | |
188 | continue | |
189 | ||
190 | # Check for TAP | |
191 | if match_tap: | |
192 | edges[q_id]['writers'].append(q_id) | |
193 | taps.add(q) | |
194 | continue | |
195 | ||
196 | # Check for SOURCE | |
197 | if match_source: | |
198 | edges[q_id]['writers'].append(q) | |
199 | sources.add(q) | |
200 | continue | |
201 | ||
202 | continue | |
203 | ||
204 | # Look for next "pktq_out" section entry | |
205 | match = re.search(r'pktq_out\s*=\s*(.+)', line) | |
206 | if match: | |
207 | for q in re.findall('\S+', match.group(1)): | |
208 | match_txq = re.search(r'^TXQ(\d+)\.\d+$', q) | |
209 | match_swq = re.search(r'^SWQ\d+$', q) | |
210 | match_tm = re.search(r'^TM(\d+)$', q) | |
211 | match_kni = re.search(r'^KNI(\d+)$', q) | |
212 | match_tap = re.search(r'^TAP(\d+)$', q) | |
213 | match_sink = re.search(r'^SINK(\d+)$', q) | |
214 | ||
215 | # Set ID for the current packet queue (graph edge) | |
216 | q_id = '' | |
217 | if match_txq or match_swq or match_tm or match_sink: | |
218 | q_id = q | |
219 | elif match_kni or match_tap: | |
220 | q_id = q + ' TX' | |
221 | else: | |
222 | print('Error: Unrecognized pktq_out element "%s"' % q) | |
223 | return | |
224 | ||
225 | # Add current packet queue to the set of graph edges | |
226 | if q_id not in edges: | |
227 | edges[q_id] = {} | |
228 | if 'label' not in edges[q_id]: | |
229 | edges[q_id]['label'] = q | |
230 | if 'readers' not in edges[q_id]: | |
231 | edges[q_id]['readers'] = [] | |
232 | if 'writers' not in edges[q_id]: | |
233 | edges[q_id]['writers'] = [] | |
234 | ||
235 | # Add writer for the new edge | |
236 | edges[q_id]['writers'].append(pipeline) | |
237 | ||
238 | # Check for TXQ | |
239 | if match_txq: | |
240 | link = 'LINK' + str(match_txq.group(1)) | |
241 | edges[q_id]['readers'].append(link + ' TX') | |
242 | links.add(link) | |
243 | continue | |
244 | ||
245 | # Check for SWQ | |
246 | if match_swq: | |
247 | continue | |
248 | ||
249 | # Check for TM | |
250 | if match_tm: | |
251 | link = 'LINK' + str(match_tm.group(1)) | |
252 | links.add(link) | |
253 | continue | |
254 | ||
255 | # Check for KNI | |
256 | if match_kni: | |
257 | link = 'LINK' + str(match_kni.group(1)) | |
258 | edges[q_id]['readers'].append(q_id) | |
259 | knis.add(q) | |
260 | links.add(link) | |
261 | continue | |
262 | ||
263 | # Check for TAP | |
264 | if match_tap: | |
265 | edges[q_id]['readers'].append(q_id) | |
266 | taps.add(q) | |
267 | continue | |
268 | ||
269 | # Check for SINK | |
270 | if match_sink: | |
271 | edges[q_id]['readers'].append(q) | |
272 | sinks.add(q) | |
273 | continue | |
274 | ||
275 | continue | |
276 | ||
277 | # | |
278 | # Write DOT file | |
279 | # | |
280 | print('Creating DOT file "%s" ...' % dotfile) | |
281 | dot_cmd = DOT_COMMAND % (dotfile, imgfile) | |
282 | file = open(dotfile, 'w') | |
283 | file.write(DOT_INTRO % dot_cmd) | |
284 | file.write(DOT_GRAPH_BEGIN) | |
285 | ||
286 | # Write the graph nodes to the DOT file | |
287 | for l in sorted(links): | |
288 | file.write(DOT_NODE_LINK_RX % l) | |
289 | file.write(DOT_NODE_LINK_TX % l) | |
290 | for k in sorted(knis): | |
291 | file.write(DOT_NODE_KNI_RX % k) | |
292 | file.write(DOT_NODE_KNI_TX % k) | |
293 | for t in sorted(taps): | |
294 | file.write(DOT_NODE_TAP_RX % t) | |
295 | file.write(DOT_NODE_TAP_TX % t) | |
296 | for s in sorted(sources): | |
297 | file.write(DOT_NODE_SOURCE % s) | |
298 | for s in sorted(sinks): | |
299 | file.write(DOT_NODE_SINK % s) | |
300 | for p in sorted(pipelines): | |
301 | file.write(DOT_NODE_PIPELINE % p) | |
302 | ||
303 | # Write the graph edges to the DOT file | |
304 | for q in sorted(edges.keys()): | |
305 | rw = edges[q] | |
306 | if 'writers' not in rw: | |
307 | print('Error: "%s" has no writer' % q) | |
308 | return | |
309 | if 'readers' not in rw: | |
310 | print('Error: "%s" has no reader' % q) | |
311 | return | |
312 | for w in rw['writers']: | |
313 | for r in rw['readers']: | |
314 | file.write(DOT_EDGE_PKTQ % (w, r, rw['label'])) | |
315 | ||
316 | file.write(DOT_GRAPH_END) | |
317 | file.close() | |
318 | ||
319 | # | |
320 | # Execute the DOT command to create the image file | |
321 | # | |
322 | print('Creating image file "%s" ...' % imgfile) | |
323 | if os.system('which dot > /dev/null'): | |
324 | print('Error: Unable to locate "dot" executable.' \ | |
325 | 'Please install the "graphviz" package (www.graphviz.org).') | |
326 | return | |
327 | ||
328 | os.system(dot_cmd) | |
329 | ||
330 | ||
331 | if __name__ == '__main__': | |
332 | parser = argparse.ArgumentParser(description=\ | |
333 | 'Create diagram for IP pipeline configuration file.') | |
334 | ||
335 | parser.add_argument( | |
336 | '-f', | |
337 | '--file', | |
338 | help='input configuration file (e.g. "ip_pipeline.cfg")', | |
339 | required=True) | |
340 | ||
341 | args = parser.parse_args() | |
342 | ||
343 | process_config_file(args.file) |