Commit d140edb1 authored by Alex Pearce's avatar Alex Pearce
Browse files

Create a control flow graph by default.

Closes #62.
parent c6e3be02
......@@ -9,9 +9,15 @@
# or submit itself to any jurisdiction. #
###############################################################################
from __future__ import absolute_import, division, print_function
try:
from html import escape as html_escape
except ImportError:
from cgi import escape as html_escape
from enum import Enum
import pydot
from .components import Algorithm
from .dataflow import DataHandle
__all__ = [
......@@ -70,3 +76,54 @@ class CompositeNode(object):
def represent(self):
return (self.name, self.combineLogic.value,
[c.fullname for c in self.children], self.forceOrder)
def _graph(self, graph):
own_name = html_escape(self.name)
sg = pydot.Subgraph(graph_name='cluster_' + own_name)
label = ('<<B>{}</B><BR/>{}, {}>'.format(
own_name,
str(self.combineLogic).replace('NodeLogic.', ''),
'ordered' if self.forceOrder else 'unordered'))
sg.set_label(label)
sg.set_edge_defaults(dir='forward' if self.forceOrder else 'none')
prev_node = None
for child in self.children:
if isinstance(child, Algorithm):
# Must name nodes uniquely within a node, otherwise they will
# only be drawn one (which makes sense for the *data* flow!)
node = pydot.Node(html_escape('{}_{}'.format(self.name, child.fullname)), label=child.fullname)
sg.add_node(node)
else:
node = child._graph(sg)
if prev_node is not None:
# When drawing edges to/from subgraphs, the target node must be
# a node inside the subgraph, which we take as the first.
# However we want the arrow to start/from from the edge of the
# subgraph, so must set the ltail/lhead attribute appropriately
if isinstance(prev_node, pydot.Subgraph):
tail_node = prev_node.get_nodes()[0]
ltail = prev_node.get_name()
else:
tail_node = prev_node
ltail = None
if isinstance(node, pydot.Subgraph):
head_node = node.get_nodes()[0]
lhead = node.get_name()
else:
head_node = node
lhead = None
edge = pydot.Edge(tail_node, head_node)
if ltail is not None:
edge.set_ltail(ltail)
if lhead is not None:
edge.set_lhead(lhead)
sg.add_edge(edge)
prev_node = node
graph.add_subgraph(sg)
return sg
......@@ -9,7 +9,7 @@
# or submit itself to any jurisdiction. #
###############################################################################
from __future__ import absolute_import, division, print_function
import datetime
import logging
import pydot
......@@ -176,6 +176,14 @@ def setupOutput(filename, filetype):
return writer
def _gaudi_datetime_format(dt):
"""Return the datetime object formatted as Gaudi does it.
As seen in the application "Welcome" banner.
"""
return dt.strftime('%a %h %d %H:%M:%S %Y')
class PythonLoggingConf(ConfigurableUser):
"""Takes care of configuring the python logging verbosity."""
# Make sure we're applied before anything else by listing
......@@ -341,7 +349,7 @@ class EverythingHandler(object):
self._lines.append(line)
self.addNode(line)
def plot_data_flow(self, filename='data_flow', extensions=['dot']):
def plot_data_flow(self, filename='data_flow', extensions=('gv',)):
"""Save a visualisation of the current data flow.
Parameters
......@@ -350,24 +358,64 @@ class EverythingHandler(object):
Basename of the file to create.
extensions : list
List of file extensions to create. One file is created per
extensions. Possible values include `'dot'` for saving the raw
extensions. Possible values include `'gv'` for saving the raw
graphviz representation, and `'png'` and `'pdf'` for saving
graphics.
**Note**: The `dot` binary must be present on the system for saving
files with graphical extensions. The raw `.dot` format can be
files with graphical extensions. The raw `.gv` format can be
convert be hand like::
dot -Tpdf data_flow.dot > data_flow.pdf
dot -Tpdf data_flow.gv > data_flow.pdf
"""
top = pydot.Dot(graph_name='HLT', strict=True)
now = _gaudi_datetime_format(datetime.datetime.now())
label = 'Data flow generated at {}'.format(now)
top = pydot.Dot(graph_name='Data flow', label=label, strict=True)
top.set_node_defaults(shape='box')
for alg in self._algs:
alg._graph(top)
for ext in extensions:
format = 'raw' if ext == 'dot' else ext
format = 'raw' if ext == 'gv' else ext
top.write('{}.{}'.format(filename, ext), format=format)
def plot_control_flow(self, filename='control_flow', extensions=('gv',)):
"""Save a visualisation of the current control flow.
Parameters
----------
filename : str
Basename of the file to create.
extensions : list
List of file extensions to create. One file is created per
extensions. Possible values include `'gv'` for saving the raw
graphviz representation, and `'png'` and `'pdf'` for saving
graphics.
**Note**: The `dot` binary must be present on the system for saving
files with graphical extensions. The raw `.gv` format can be
convert be hand like::
dot -Tpdf data_flow.gv > data_flow.pdf
"""
# Find the node at the top of the control flow
# This is the node which is not a child of any other node
child_nodes = set(child for node in self._nodes for child in node.children)
top_node = None
for node in self._nodes:
if node not in child_nodes:
top_node = node
if top_node is None:
raise ConfigurationError('Not not find top control flow node')
now = _gaudi_datetime_format(datetime.datetime.now())
label = 'Control flow generated at {}'.format(now)
graph = pydot.Dot(graph_name='control_flow', label=label, strict=True, compound=True)
top_node._graph(graph)
for ext in extensions:
format = 'raw' if ext == 'gv' else ext
graph.write('{}.{}'.format(filename, ext), format=format)
def configure(self):
"""Instantiate all underlying Configurables.
......@@ -407,3 +455,4 @@ class EverythingHandler(object):
]
self.plot_data_flow()
self.plot_control_flow()
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment