#!/usr/bin/env python # SPDX-License-Identifier: BSD-3-Clause # Copyright(c) 2016 Intel Corporation # # This script creates a visual representation for a configuration file used by # the DPDK ip_pipeline application. # # The input configuration file is translated to an output file in DOT syntax, # which is then used to create the image file using graphviz # (www.graphviz.org). # from __future__ import print_function import argparse import re import os # # Command to generate the image file # DOT_COMMAND = 'dot -Gsize=20,30 -Tpng %s > %s' # # Layout of generated DOT file # DOT_INTRO = \ '#\n# Command to generate image file:\n# \t%s\n#\n\n' DOT_GRAPH_BEGIN = \ 'digraph g {\n graph [ splines = true rankdir = "LR" ]\n' DOT_NODE_LINK_RX = \ ' "%s RX" [ shape = box style = filled fillcolor = yellowgreen ]\n' DOT_NODE_LINK_TX = \ ' "%s TX" [ shape = box style = filled fillcolor = yellowgreen ]\n' DOT_NODE_KNI_RX = \ ' "%s RX" [ shape = box style = filled fillcolor = orange ]\n' DOT_NODE_KNI_TX = \ ' "%s TX" [ shape = box style = filled fillcolor = orange ]\n' DOT_NODE_TAP_RX = \ ' "%s RX" [ shape = box style = filled fillcolor = gold ]\n' DOT_NODE_TAP_TX = \ ' "%s TX" [ shape = box style = filled fillcolor = gold ]\n' DOT_NODE_SOURCE = \ ' "%s" [ shape = box style = filled fillcolor = darkgreen ]\n' DOT_NODE_SINK = \ ' "%s" [ shape = box style = filled fillcolor = peachpuff ]\n' DOT_NODE_PIPELINE = \ ' "%s" [ shape = box style = filled fillcolor = royalblue ]\n' DOT_EDGE_PKTQ = \ ' "%s" -> "%s" [ label = "%s" color = gray ]\n' DOT_GRAPH_END = \ '}\n' # Relationships between the graph nodes and the graph edges: # # Edge ID | Edge Label | Writer Node | Reader Node | Dependencies # --------+------------+-------------+---------------+-------------- # RXQx.y | RXQx.y | LINKx | PIPELINEz | LINKx # TXQx.y | TXQx.y | PIPELINEz | LINKx | LINKx # SWQx | SWQx | PIPELINEy | PIPELINEz | - # TMx | TMx | PIPELINEy | PIPELINEz | LINKx # KNIx RX | KNIx | KNIx RX | PIPELINEy | KNIx, LINKx # KNIx TX | KNIx | PIPELINEy | KNIx TX | KNIx, LINKx # TAPx RX | TAPx | TAPx RX | PIPELINEy | TAPx # TAPx TX | TAPx | PIPELINEy | TAPx TX | TAPx # SOURCEx | SOURCEx | SOURCEx | PIPELINEy | SOURCEx # SINKx | SINKx | PIPELINEy | SINKx | SINKx # # Parse the input configuration file to detect the graph nodes and edges # def process_config_file(cfgfile): edges = {} links = set() knis = set() taps = set() sources = set() sinks = set() pipelines = set() pipeline = '' dotfile = cfgfile + '.txt' imgfile = cfgfile + '.png' # # Read configuration file # lines = open(cfgfile, 'r') for line in lines: # Remove any leading and trailing white space characters line = line.strip() # Remove any comment at end of line line, sep, tail = line.partition(';') # Look for next "PIPELINE" section match = re.search(r'\[(PIPELINE\d+)\]', line) if match: pipeline = match.group(1) continue # Look for next "pktq_in" section entry match = re.search(r'pktq_in\s*=\s*(.+)', line) if match: pipelines.add(pipeline) for q in re.findall('\S+', match.group(1)): match_rxq = re.search(r'^RXQ(\d+)\.\d+$', q) match_swq = re.search(r'^SWQ\d+$', q) match_tm = re.search(r'^TM(\d+)$', q) match_kni = re.search(r'^KNI(\d+)$', q) match_tap = re.search(r'^TAP\d+$', q) match_source = re.search(r'^SOURCE\d+$', q) # Set ID for the current packet queue (graph edge) q_id = '' if match_rxq or match_swq or match_tm or match_source: q_id = q elif match_kni or match_tap: q_id = q + ' RX' else: print('Error: Unrecognized pktq_in element "%s"' % q) return # Add current packet queue to the set of graph edges if q_id not in edges: edges[q_id] = {} if 'label' not in edges[q_id]: edges[q_id]['label'] = q if 'readers' not in edges[q_id]: edges[q_id]['readers'] = [] if 'writers' not in edges[q_id]: edges[q_id]['writers'] = [] # Add reader for the new edge edges[q_id]['readers'].append(pipeline) # Check for RXQ if match_rxq: link = 'LINK' + str(match_rxq.group(1)) edges[q_id]['writers'].append(link + ' RX') links.add(link) continue # Check for SWQ if match_swq: continue # Check for TM if match_tm: link = 'LINK' + str(match_tm.group(1)) links.add(link) continue # Check for KNI if match_kni: link = 'LINK' + str(match_kni.group(1)) edges[q_id]['writers'].append(q_id) knis.add(q) links.add(link) continue # Check for TAP if match_tap: edges[q_id]['writers'].append(q_id) taps.add(q) continue # Check for SOURCE if match_source: edges[q_id]['writers'].append(q) sources.add(q) continue continue # Look for next "pktq_out" section entry match = re.search(r'pktq_out\s*=\s*(.+)', line) if match: for q in re.findall('\S+', match.group(1)): match_txq = re.search(r'^TXQ(\d+)\.\d+$', q) match_swq = re.search(r'^SWQ\d+$', q) match_tm = re.search(r'^TM(\d+)$', q) match_kni = re.search(r'^KNI(\d+)$', q) match_tap = re.search(r'^TAP(\d+)$', q) match_sink = re.search(r'^SINK(\d+)$', q) # Set ID for the current packet queue (graph edge) q_id = '' if match_txq or match_swq or match_tm or match_sink: q_id = q elif match_kni or match_tap: q_id = q + ' TX' else: print('Error: Unrecognized pktq_out element "%s"' % q) return # Add current packet queue to the set of graph edges if q_id not in edges: edges[q_id] = {} if 'label' not in edges[q_id]: edges[q_id]['label'] = q if 'readers' not in edges[q_id]: edges[q_id]['readers'] = [] if 'writers' not in edges[q_id]: edges[q_id]['writers'] = [] # Add writer for the new edge edges[q_id]['writers'].append(pipeline) # Check for TXQ if match_txq: link = 'LINK' + str(match_txq.group(1)) edges[q_id]['readers'].append(link + ' TX') links.add(link) continue # Check for SWQ if match_swq: continue # Check for TM if match_tm: link = 'LINK' + str(match_tm.group(1)) links.add(link) continue # Check for KNI if match_kni: link = 'LINK' + str(match_kni.group(1)) edges[q_id]['readers'].append(q_id) knis.add(q) links.add(link) continue # Check for TAP if match_tap: edges[q_id]['readers'].append(q_id) taps.add(q) continue # Check for SINK if match_sink: edges[q_id]['readers'].append(q) sinks.add(q) continue continue # # Write DOT file # print('Creating DOT file "%s" ...' % dotfile) dot_cmd = DOT_COMMAND % (dotfile, imgfile) file = open(dotfile, 'w') file.write(DOT_INTRO % dot_cmd) file.write(DOT_GRAPH_BEGIN) # Write the graph nodes to the DOT file for l in sorted(links): file.write(DOT_NODE_LINK_RX % l) file.write(DOT_NODE_LINK_TX % l) for k in sorted(knis): file.write(DOT_NODE_KNI_RX % k) file.write(DOT_NODE_KNI_TX % k) for t in sorted(taps): file.write(DOT_NODE_TAP_RX % t) file.write(DOT_NODE_TAP_TX % t) for s in sorted(sources): file.write(DOT_NODE_SOURCE % s) for s in sorted(sinks): file.write(DOT_NODE_SINK % s) for p in sorted(pipelines): file.write(DOT_NODE_PIPELINE % p) # Write the graph edges to the DOT file for q in sorted(edges.keys()): rw = edges[q] if 'writers' not in rw: print('Error: "%s" has no writer' % q) return if 'readers' not in rw: print('Error: "%s" has no reader' % q) return for w in rw['writers']: for r in rw['readers']: file.write(DOT_EDGE_PKTQ % (w, r, rw['label'])) file.write(DOT_GRAPH_END) file.close() # # Execute the DOT command to create the image file # print('Creating image file "%s" ...' % imgfile) if os.system('which dot > /dev/null'): print('Error: Unable to locate "dot" executable.' 'Please install the "graphviz" package (www.graphviz.org).') return os.system(dot_cmd) if __name__ == '__main__': parser = argparse.ArgumentParser(description='Create diagram for IP ' 'pipeline configuration ' 'file.') parser.add_argument( '-f', '--file', help='input configuration file (e.g. "ip_pipeline.cfg")', required=True) args = parser.parse_args() process_config_file(args.file)