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