dot_writer.py revision 9853
18870SAli.Saidi@ARM.com# Copyright (c) 2012-2013 ARM Limited 27090SN/A# All rights reserved. 37090SN/A# 47090SN/A# The license below extends only to copyright in the software and shall 57090SN/A# not be construed as granting a license to any other intellectual 67090SN/A# property including but not limited to intellectual property relating 77090SN/A# to a hardware implementation of the functionality of the software 87090SN/A# licensed hereunder. You may use the software subject to the license 97090SN/A# terms below provided that you ensure that this notice is replicated 107090SN/A# unmodified and in its entirety in all distributions of the software, 117090SN/A# modified or unmodified, in source code or in binary form. 127090SN/A# 134486SN/A# Redistribution and use in source and binary forms, with or without 144486SN/A# modification, are permitted provided that the following conditions are 154486SN/A# met: redistributions of source code must retain the above copyright 164486SN/A# notice, this list of conditions and the following disclaimer; 174486SN/A# redistributions in binary form must reproduce the above copyright 184486SN/A# notice, this list of conditions and the following disclaimer in the 194486SN/A# documentation and/or other materials provided with the distribution; 204486SN/A# neither the name of the copyright holders nor the names of its 214486SN/A# contributors may be used to endorse or promote products derived from 224486SN/A# this software without specific prior written permission. 234486SN/A# 244486SN/A# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 254486SN/A# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 264486SN/A# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 274486SN/A# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 284486SN/A# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 294486SN/A# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 304486SN/A# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 314486SN/A# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 324486SN/A# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 334486SN/A# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 344486SN/A# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 354486SN/A# 364486SN/A# Authors: Andreas Hansson 374486SN/A# Uri Wiener 384486SN/A 397584SAli.Saidi@arm.com##################################################################### 407584SAli.Saidi@arm.com# 417754SWilliam.Wang@arm.com# System visualization using DOT 424486SN/A# 433630SN/A# While config.ini and config.json provide an almost complete listing 443630SN/A# of a system's components and connectivity, they lack a birds-eye 457587SAli.Saidi@arm.com# view. The output generated by do_dot() is a DOT-based figure (as a 468525SAli.Saidi@ARM.com# pdf and an editable svg file) and its source dot code. Nodes are 478525SAli.Saidi@ARM.com# components, and edges represent the memory hierarchy: the edges are 488212SAli.Saidi@ARM.com# directed, from a master to slave. Initially all nodes are 495478SN/A# generated, and then all edges are added. do_dot should be called 505478SN/A# with the top-most SimObject (namely root but not necessarily), the 517584SAli.Saidi@arm.com# output folder and the output dot source filename. From the given 528931Sandreas.hansson@arm.com# node, both processes (node and edge creation) is performed 539525SAndreas.Sandberg@ARM.com# recursivly, traversing all children of the given root. 543630SN/A# 559806Sstever@gmail.com# pydot is required. When missing, no output will be generated. 569806Sstever@gmail.com# 577584SAli.Saidi@arm.com##################################################################### 589338SAndreas.Sandberg@arm.com 597584SAli.Saidi@arm.comimport m5, os, re 603898SN/Afrom m5.SimObject import isRoot, isSimObjectVector 619806Sstever@gmail.comfrom m5.util import warn 627950SAli.Saidi@ARM.comtry: 637950SAli.Saidi@ARM.com import pydot 649338SAndreas.Sandberg@arm.comexcept: 659525SAndreas.Sandberg@ARM.com pydot = False 667950SAli.Saidi@ARM.com 677950SAli.Saidi@ARM.com# need to create all nodes (components) before creating edges (memory channels) 687950SAli.Saidi@ARM.comdef dot_create_nodes(simNode, callgraph): 697950SAli.Saidi@ARM.com if isRoot(simNode): 707587SAli.Saidi@arm.com label = "root" 717587SAli.Saidi@arm.com else: 727587SAli.Saidi@arm.com label = simNode._name 739338SAndreas.Sandberg@arm.com full_path = re.sub('\.', '_', simNode.path()) 747753SWilliam.Wang@arm.com # add class name under the label 757753SWilliam.Wang@arm.com label = "\"" + label + " \\n: " + simNode.__class__.__name__ + "\"" 769525SAndreas.Sandberg@ARM.com 777753SWilliam.Wang@arm.com # each component is a sub-graph (cluster) 787587SAli.Saidi@arm.com cluster = dot_create_cluster(simNode, full_path, label) 797587SAli.Saidi@arm.com 808282SAli.Saidi@ARM.com # create nodes per port 818282SAli.Saidi@ARM.com for port_name in simNode._ports.keys(): 829338SAndreas.Sandberg@arm.com port = simNode._port_refs.get(port_name, None) 838282SAli.Saidi@ARM.com if port != None: 847584SAli.Saidi@arm.com full_port_name = full_path + "_" + port_name 857584SAli.Saidi@arm.com port_node = dot_create_node(simNode, full_port_name, port_name) 869338SAndreas.Sandberg@arm.com cluster.add_node(port_node) 878524SAli.Saidi@ARM.com 888524SAli.Saidi@ARM.com # recurse to children 898299Schander.sudanthi@arm.com if simNode._children: 907584SAli.Saidi@arm.com for c in simNode._children: 919806Sstever@gmail.com child = simNode._children[c] 927584SAli.Saidi@arm.com if isSimObjectVector(child): 939338SAndreas.Sandberg@arm.com for obj in child: 947584SAli.Saidi@arm.com dot_create_nodes(obj, cluster) 957584SAli.Saidi@arm.com else: 967584SAli.Saidi@arm.com dot_create_nodes(child, cluster) 977584SAli.Saidi@arm.com 987584SAli.Saidi@arm.com callgraph.add_subgraph(cluster) 999338SAndreas.Sandberg@arm.com 1009525SAndreas.Sandberg@ARM.com# create all edges according to memory hierarchy 1017584SAli.Saidi@arm.comdef dot_create_edges(simNode, callgraph): 1027584SAli.Saidi@arm.com for port_name in simNode._ports.keys(): 1037584SAli.Saidi@arm.com port = simNode._port_refs.get(port_name, None) 1047584SAli.Saidi@arm.com if port != None: 1059806Sstever@gmail.com full_path = re.sub('\.', '_', simNode.path()) 1067584SAli.Saidi@arm.com full_port_name = full_path + "_" + port_name 1079338SAndreas.Sandberg@arm.com port_node = dot_create_node(simNode, full_port_name, port_name) 1089525SAndreas.Sandberg@ARM.com # create edges 1097584SAli.Saidi@arm.com if type(port) is m5.params.PortRef: 1107584SAli.Saidi@arm.com dot_add_edge(simNode, callgraph, full_port_name, port) 1117584SAli.Saidi@arm.com else: 1127584SAli.Saidi@arm.com for p in port.elements: 1137584SAli.Saidi@arm.com dot_add_edge(simNode, callgraph, full_port_name, p) 1147584SAli.Saidi@arm.com 1158512Sgeoffrey.blake@arm.com # recurse to children 1168512Sgeoffrey.blake@arm.com if simNode._children: 1179338SAndreas.Sandberg@arm.com for c in simNode._children: 1189525SAndreas.Sandberg@ARM.com child = simNode._children[c] 1198512Sgeoffrey.blake@arm.com if isSimObjectVector(child): 1208512Sgeoffrey.blake@arm.com for obj in child: 1218512Sgeoffrey.blake@arm.com dot_create_edges(obj, callgraph) 1228870SAli.Saidi@ARM.com else: 1238870SAli.Saidi@ARM.com dot_create_edges(child, callgraph) 1249338SAndreas.Sandberg@arm.com 1258870SAli.Saidi@ARM.comdef dot_add_edge(simNode, callgraph, full_port_name, peerPort): 1268870SAli.Saidi@ARM.com if peerPort.role == "MASTER": 1278870SAli.Saidi@ARM.com peer_port_name = re.sub('\.', '_', peerPort.peer.simobj.path() \ 1287950SAli.Saidi@ARM.com + "." + peerPort.peer.name) 1297754SWilliam.Wang@arm.com callgraph.add_edge(pydot.Edge(full_port_name, peer_port_name)) 1309338SAndreas.Sandberg@arm.com 1319330Schander.sudanthi@arm.comdef dot_create_cluster(simNode, full_path, label): 1327950SAli.Saidi@ARM.com return pydot.Cluster( \ 1337950SAli.Saidi@ARM.com full_path, \ 1347754SWilliam.Wang@arm.com shape = "Mrecord", \ 1357754SWilliam.Wang@arm.com label = label, \ 1367753SWilliam.Wang@arm.com style = "\"rounded, filled\"", \ 1377753SWilliam.Wang@arm.com color = "#000000", \ 1389338SAndreas.Sandberg@arm.com fillcolor = dot_gen_colour(simNode), \ 1399394Sandreas.hansson@arm.com fontname = "Arial", \ 1409330Schander.sudanthi@arm.com fontsize = "14", \ 1417753SWilliam.Wang@arm.com fontcolor = "#000000" \ 1427753SWilliam.Wang@arm.com ) 1439646SChris.Emmons@arm.com 1449646SChris.Emmons@arm.comdef dot_create_node(simNode, full_path, label): 1459646SChris.Emmons@arm.com return pydot.Node( \ 1469646SChris.Emmons@arm.com full_path, \ 1479646SChris.Emmons@arm.com shape = "Mrecord", \ 1489646SChris.Emmons@arm.com label = label, \ 1499646SChris.Emmons@arm.com style = "\"rounded, filled\"", \ 1509646SChris.Emmons@arm.com color = "#000000", \ 1519646SChris.Emmons@arm.com fillcolor = dot_gen_colour(simNode, True), \ 1529646SChris.Emmons@arm.com fontname = "Arial", \ 1537584SAli.Saidi@arm.com fontsize = "14", \ 1547584SAli.Saidi@arm.com fontcolor = "#000000" \ 1559338SAndreas.Sandberg@arm.com ) 1563630SN/A 1578525SAli.Saidi@ARM.com# an enumerator for different kinds of node types, at the moment we 1588870SAli.Saidi@ARM.com# discern the majority of node types, with the caches being the 1598870SAli.Saidi@ARM.com# notable exception 1608870SAli.Saidi@ARM.comclass NodeType: 1618870SAli.Saidi@ARM.com SYS = 0 1629835Sandreas.hansson@arm.com CPU = 1 1639835Sandreas.hansson@arm.com BUS = 2 1648870SAli.Saidi@ARM.com MEM = 3 1658870SAli.Saidi@ARM.com DEV = 4 1668870SAli.Saidi@ARM.com OTHER = 5 1673630SN/A 1687753SWilliam.Wang@arm.com# based on the sim object, determine the node type 1697753SWilliam.Wang@arm.comdef get_node_type(simNode): 1707753SWilliam.Wang@arm.com if isinstance(simNode, m5.objects.System): 1717584SAli.Saidi@arm.com return NodeType.SYS 1727584SAli.Saidi@arm.com # NULL ISA has no BaseCPU or PioDevice, so check if these names 1737584SAli.Saidi@arm.com # exists before using them 1749525SAndreas.Sandberg@ARM.com elif 'BaseCPU' in dir(m5.objects) and \ 1757584SAli.Saidi@arm.com isinstance(simNode, m5.objects.BaseCPU): 1767584SAli.Saidi@arm.com return NodeType.CPU 1778512Sgeoffrey.blake@arm.com elif 'PioDevice' in dir(m5.objects) and \ 1787753SWilliam.Wang@arm.com isinstance(simNode, m5.objects.PioDevice): 1797754SWilliam.Wang@arm.com return NodeType.DEV 1807950SAli.Saidi@ARM.com elif isinstance(simNode, m5.objects.BaseBus): 1818282SAli.Saidi@ARM.com return NodeType.BUS 1828525SAli.Saidi@ARM.com elif isinstance(simNode, m5.objects.AbstractMemory): 1838212SAli.Saidi@ARM.com return NodeType.MEM 1848212SAli.Saidi@ARM.com else: 1858212SAli.Saidi@ARM.com return NodeType.OTHER 1868212SAli.Saidi@ARM.com 1878212SAli.Saidi@ARM.com# based on the node type, determine the colour as an RGB tuple, the 1887584SAli.Saidi@arm.com# palette is rather arbitrary at this point (some coherent natural 1897731SAli.Saidi@ARM.com# tones), and someone that feels artistic should probably have a look 1908461SAli.Saidi@ARM.comdef get_type_colour(nodeType): 1918461SAli.Saidi@ARM.com if nodeType == NodeType.SYS: 1927696SAli.Saidi@ARM.com return (228, 231, 235) 1937696SAli.Saidi@ARM.com elif nodeType == NodeType.CPU: 1947696SAli.Saidi@ARM.com return (187, 198, 217) 1957696SAli.Saidi@ARM.com elif nodeType == NodeType.BUS: 1967696SAli.Saidi@ARM.com return (111, 121, 140) 1977696SAli.Saidi@ARM.com elif nodeType == NodeType.MEM: 1987696SAli.Saidi@ARM.com return (94, 89, 88) 1997696SAli.Saidi@ARM.com elif nodeType == NodeType.DEV: 2007696SAli.Saidi@ARM.com return (199, 167, 147) 2017696SAli.Saidi@ARM.com elif nodeType == NodeType.OTHER: 2027696SAli.Saidi@ARM.com # use a relatively gray shade 2037696SAli.Saidi@ARM.com return (186, 182, 174) 2047696SAli.Saidi@ARM.com 2057696SAli.Saidi@ARM.com# generate colour for a node, either corresponding to a sim object or a 2068906Skoansin.tan@gmail.com# port 2077696SAli.Saidi@ARM.comdef dot_gen_colour(simNode, isPort = False): 2087696SAli.Saidi@ARM.com # determine the type of the current node, and also its parent, if 2098713Sandreas.hansson@arm.com # the node is not the same type as the parent then we use the base 2108713Sandreas.hansson@arm.com # colour for its type 2118713Sandreas.hansson@arm.com node_type = get_node_type(simNode) 2128839Sandreas.hansson@arm.com if simNode._parent: 2138839Sandreas.hansson@arm.com parent_type = get_node_type(simNode._parent) 2148839Sandreas.hansson@arm.com else: 2158839Sandreas.hansson@arm.com parent_type = NodeType.OTHER 2168713Sandreas.hansson@arm.com 2178713Sandreas.hansson@arm.com # if this node is the same type as the parent, then scale the 2188713Sandreas.hansson@arm.com # colour based on the depth such that the deeper levels in the 2198713Sandreas.hansson@arm.com # hierarchy get darker colours 2208870SAli.Saidi@ARM.com if node_type == parent_type: 2218870SAli.Saidi@ARM.com # start out with a depth of zero 2228870SAli.Saidi@ARM.com depth = 0 2237696SAli.Saidi@ARM.com parent = simNode._parent 2247696SAli.Saidi@ARM.com # find the closes parent that is not the same type 2257696SAli.Saidi@ARM.com while parent and get_node_type(parent) == parent_type: 2267696SAli.Saidi@ARM.com depth = depth + 1 2277696SAli.Saidi@ARM.com parent = parent._parent 2288839Sandreas.hansson@arm.com node_colour = get_type_colour(parent_type) 2298839Sandreas.hansson@arm.com # slightly arbitrary, but assume that the depth is less than 2308839Sandreas.hansson@arm.com # five levels 2318839Sandreas.hansson@arm.com r, g, b = map(lambda x: x * max(1 - depth / 7.0, 0.3), node_colour) 2328839Sandreas.hansson@arm.com else: 2338839Sandreas.hansson@arm.com node_colour = get_type_colour(node_type) 2348839Sandreas.hansson@arm.com r, g, b = node_colour 2358839Sandreas.hansson@arm.com 2368839Sandreas.hansson@arm.com # if we are colouring a port, then make it a slightly darker shade 2378839Sandreas.hansson@arm.com # than the node that encapsulates it, once again use a magic constant 2388839Sandreas.hansson@arm.com if isPort: 2398839Sandreas.hansson@arm.com r, g, b = map(lambda x: 0.8 * x, (r, g, b)) 2408839Sandreas.hansson@arm.com 2418839Sandreas.hansson@arm.com return dot_rgb_to_html(r, g, b) 2428839Sandreas.hansson@arm.com 2438839Sandreas.hansson@arm.comdef dot_rgb_to_html(r, g, b): 2448839Sandreas.hansson@arm.com return "#%.2x%.2x%.2x" % (r, g, b) 2458839Sandreas.hansson@arm.com 2468839Sandreas.hansson@arm.comdef do_dot(root, outdir, dotFilename): 2478839Sandreas.hansson@arm.com if not pydot: 2488839Sandreas.hansson@arm.com return 2498839Sandreas.hansson@arm.com # * use ranksep > 1.0 for for vertical separation between nodes 2508839Sandreas.hansson@arm.com # especially useful if you need to annotate edges using e.g. visio 2518839Sandreas.hansson@arm.com # which accepts svg format 2528839Sandreas.hansson@arm.com # * no need for hoizontal separation as nothing moves horizonally 2538906Skoansin.tan@gmail.com callgraph = pydot.Dot(graph_type='digraph', ranksep='1.3') 2548839Sandreas.hansson@arm.com dot_create_nodes(root, callgraph) 2557696SAli.Saidi@ARM.com dot_create_edges(root, callgraph) 2567754SWilliam.Wang@arm.com dot_filename = os.path.join(outdir, dotFilename) 2577754SWilliam.Wang@arm.com callgraph.write(dot_filename) 2587754SWilliam.Wang@arm.com try: 2597696SAli.Saidi@ARM.com # dot crashes if the figure is extremely wide. 2607696SAli.Saidi@ARM.com # So avoid terminating simulation unnecessarily 2617696SAli.Saidi@ARM.com callgraph.write_svg(dot_filename + ".svg") 2629525SAndreas.Sandberg@ARM.com callgraph.write_pdf(dot_filename + ".pdf") 2637696SAli.Saidi@ARM.com except: 2647696SAli.Saidi@ARM.com warn("failed to generate dot output from %s", dot_filename) 2657754SWilliam.Wang@arm.com