Skip to content
Snippets Groups Projects
Commit 5d033fbd authored by Todd Gamblin's avatar Todd Gamblin
Browse files

Expansion works properly, simplified graph code.

parent b4b8339d
Branches
Tags
No related merge requests found
...@@ -55,7 +55,7 @@ def graph(parser, args): ...@@ -55,7 +55,7 @@ def graph(parser, args):
graph_dot(*specs) graph_dot(*specs)
elif specs: # ascii is default: user doesn't need to provide it explicitly elif specs: # ascii is default: user doesn't need to provide it explicitly
graph_ascii(specs[0]) graph_ascii(specs[0], debug=spack.debug)
for spec in specs[1:]: for spec in specs[1:]:
print # extra line bt/w independent graphs print # extra line bt/w independent graphs
graph_ascii(spec) graph_ascii(spec, debug=spack.debug)
...@@ -29,7 +29,30 @@ ...@@ -29,7 +29,30 @@
about: about:
graph_ascii() will output a colored graph of a spec in ascii format, graph_ascii() will output a colored graph of a spec in ascii format,
knd of like the graph git shows with "git log --graph". kind of like the graph git shows with "git log --graph", e.g.::
o mpileaks
|\
| |\
| o | callpath
|/| |
| |\|
| |\ \
| | |\ \
| | | | o adept-utils
| |_|_|/|
|/| | | |
o | | | | mpi
/ / / /
| | o | dyninst
| |/| |
|/|/| |
| | |/
| o | libdwarf
|/ /
o | libelf
/
o boost
graph_dot() will output a graph of a spec (or multiple specs) in dot graph_dot() will output a graph of a spec (or multiple specs) in dot
format. format.
...@@ -102,11 +125,16 @@ def find(seq, predicate): ...@@ -102,11 +125,16 @@ def find(seq, predicate):
return -1 return -1
# Names of different graph line states. We Record previous line
# states so that we can easily determine what to do when connecting.
states = ('node', 'collapse', 'merge-right', 'expand-right', 'back-edge')
NODE, COLLAPSE, MERGE_RIGHT, EXPAND_RIGHT, BACK_EDGE = states
class AsciiGraph(object): class AsciiGraph(object):
def __init__(self): def __init__(self):
# These can be set after initialization or after a call to # These can be set after initialization or after a call to
# graph() to change behavior. # graph() to change behavior.
self.node_character = 'o' self.node_character = '*'
self.debug = False self.debug = False
self.indent = 0 self.indent = 0
...@@ -120,6 +148,7 @@ def __init__(self): ...@@ -120,6 +148,7 @@ def __init__(self):
self._out = None # Output stream self._out = None # Output stream
self._frontier = None # frontier self._frontier = None # frontier
self._nodes = None # dict from name -> node self._nodes = None # dict from name -> node
self._prev_state = None # State of previous line
def _indent(self): def _indent(self):
...@@ -133,7 +162,7 @@ def _write_edge(self, string, index, sub=0): ...@@ -133,7 +162,7 @@ def _write_edge(self, string, index, sub=0):
self._out.write(edge) self._out.write(edge)
def _connect_deps(self, i, deps, collapse, label): def _connect_deps(self, i, deps, label=None):
"""Connect dependencies to existing edges in the frontier. """Connect dependencies to existing edges in the frontier.
``deps`` are to be inserted at position i in the ``deps`` are to be inserted at position i in the
...@@ -147,9 +176,6 @@ def _connect_deps(self, i, deps, collapse, label): ...@@ -147,9 +176,6 @@ def _connect_deps(self, i, deps, collapse, label):
Parameters: Parameters:
collapse -- whether the frontier is collapsing or staying the
same size.
label -- optional debug label for the connection. label -- optional debug label for the connection.
Returns: True if the deps were connected to another edge Returns: True if the deps were connected to another edge
...@@ -161,20 +187,25 @@ def _connect_deps(self, i, deps, collapse, label): ...@@ -161,20 +187,25 @@ def _connect_deps(self, i, deps, collapse, label):
if len(deps) == 1 and deps in self._frontier: if len(deps) == 1 and deps in self._frontier:
j = self._frontier.index(deps) j = self._frontier.index(deps)
# connect to the left # convert a right connection into a left connection
if j < i: if i < j:
if i-j > 1: # two lines if distance > 1 self._frontier.pop(j)
self._back_edge([], j, i, True, label) self._frontier.insert(i, deps)
self._back_edge([j], -1, -1, (i-j == 1), label) return self._connect_deps(j, deps, label)
# connect to the right collapse = True
else: if self._prev_state == EXPAND_RIGHT:
if i < j: # Special case for when prev. line expanded (spacing is off by 1)
self._frontier.pop(j) # Need two lines here even when distance in frontier is 1.
self._frontier.insert(i, deps) self._back_edge_line([], j, i+1, True, label + "-1.5 " + str((i,j)))
if j-i > 1: collapse = False
self._back_edge([], i, j+1, collapse, label)
self._back_edge([i], -1, -1, not (j-i > 1) and collapse, label) elif i-j > 1:
# We need two lines to connect if distance > 1
self._back_edge_line([], j, i, True, label + "-1 " + str((i,j)))
collapse = False
self._back_edge_line([j], -1, -1, collapse, label + "-2 " + str((i,j)))
return True return True
elif deps: elif deps:
...@@ -182,22 +213,20 @@ def _connect_deps(self, i, deps, collapse, label): ...@@ -182,22 +213,20 @@ def _connect_deps(self, i, deps, collapse, label):
return False return False
def _add_deps_to_frontier(self, node, i): def _set_state(self, state, label=None):
"""Add dependencies to frontier. if state not in states:
raise ValueError("Invalid graph state!")
Adds the dependencies of <node> to the frontier, and connects self._prev_state = state
them to other open edges if they match. Also deletes parent
pointers in the node to mark edges as covered.
"""
deps = sorted((d for d in node.dependencies), reverse=True)
self._connect_deps(i, deps, True, "add_deps")
for d in deps:
del self._nodes[d].dependents[node.name]
if self.debug:
self._out.write(" " * 20)
self._out.write("%-20s" % (
str(self._prev_state) if self._prev_state else ''))
self._out.write("%-20s" % (str(label) if label else ''))
self._out.write("%s" % self._frontier)
def _back_edge(self, prev_ends, end, start, collapse, label=None): def _back_edge_line(self, prev_ends, end, start, collapse, label=None):
"""Write part of a backwards edge in the graph. """Write part of a backwards edge in the graph.
Writes single- or multi-line backward edges in an ascii graph. Writes single- or multi-line backward edges in an ascii graph.
...@@ -267,12 +296,64 @@ def advance(to_pos, edges): ...@@ -267,12 +296,64 @@ def advance(to_pos, edges):
else: else:
advance(flen, lambda: [("| ", self._pos)] ) advance(flen, lambda: [("| ", self._pos)] )
if self.debug: self._set_state(BACK_EDGE, label)
self._out.write(" " * 10) self._out.write("\n")
if label:
self._out.write(label)
self._out.write("%s" % self._frontier) def _node_line(self, index, name):
"""Writes a line with a node at index."""
self._indent()
for c in range(index):
self._write_edge("| ", c)
self._out.write("%s " % self.node_character)
for c in range(index+1, len(self._frontier)):
self._write_edge("| ", c)
self._out.write(" %s" % name)
self._set_state(NODE)
self._out.write("\n")
def _collapse_line(self, index):
"""Write a collapsing line after a node was added at index."""
self._indent()
for c in range(index):
self._write_edge("| ", c)
for c in range(index, len(self._frontier)):
self._write_edge(" /", c)
self._set_state(COLLAPSE)
self._out.write("\n")
def _merge_right_line(self, index):
"""Edge at index is same as edge to right. Merge directly with '\'"""
self._indent()
for c in range(index):
self._write_edge("| ", c)
self._write_edge("|", index)
self._write_edge("\\", index+1)
for c in range(index+1, len(self._frontier)):
self._write_edge("| ", c )
self._set_state(MERGE_RIGHT)
self._out.write("\n")
def _expand_right_line(self, index):
self._indent()
for c in range(index):
self._write_edge("| ", c)
self._write_edge("|", index)
self._write_edge("\\", index+1)
for c in range(index+2, len(self._frontier)):
self._write_edge(" \\", c)
self._set_state(EXPAND_RIGHT)
self._out.write("\n") self._out.write("\n")
...@@ -311,27 +392,22 @@ def write(self, spec, **kwargs): ...@@ -311,27 +392,22 @@ def write(self, spec, **kwargs):
self._name_to_color = dict((name, self.colors[i % len(self.colors)]) self._name_to_color = dict((name, self.colors[i % len(self.colors)])
for i, name in enumerate(topo_order)) for i, name in enumerate(topo_order))
# This array tracks the open edges at the frontier of the # Frontier tracks open edges of the graph as it's written out.
# graph we're writing out. self._frontier = [[spec.name]]
self._frontier = []
self._add_deps_to_frontier(spec, 0)
self._indent()
self._out.write('%s %s\n' % (self.node_character, spec.name))
topo_order.pop()
while self._frontier: while self._frontier:
# Find an unexpanded part of frontier # Find an unexpanded part of frontier
i = find(self._frontier, lambda f: len(f) > 1) i = find(self._frontier, lambda f: len(f) > 1)
# Expand frontier until there are enough columns for all children.
if i >= 0: if i >= 0:
# Expand frontier until there are enough columns for all children.
# Figure out how many back connections there are and # Figure out how many back connections there are and
# sort them so we do them in order # sort them so we do them in order
back = [] back = []
for d in self._frontier[i]: for d in self._frontier[i]:
b = find(self._frontier[:i], lambda f: f == [d]) b = find(self._frontier[:i], lambda f: f == [d])
if b != -1: back.append((b, d)) if b != -1:
back.append((b, d))
# Do all back connections in sorted order so we can # Do all back connections in sorted order so we can
# pipeline them and save space. # pipeline them and save space.
...@@ -341,79 +417,61 @@ def write(self, spec, **kwargs): ...@@ -341,79 +417,61 @@ def write(self, spec, **kwargs):
for j, (b, d) in enumerate(back): for j, (b, d) in enumerate(back):
self._frontier[i].remove(d) self._frontier[i].remove(d)
if i-b > 1: if i-b > 1:
self._back_edge(prev_ends, b, i, False) self._back_edge_line(prev_ends, b, i, False, 'left-1')
del prev_ends[:] del prev_ends[:]
prev_ends.append(b) prev_ends.append(b)
self._back_edge(prev_ends, -1, -1, False) self._back_edge_line(prev_ends, -1, -1, False, 'left-2')
if not self._frontier[i]: if not self._frontier[i]:
self._frontier.pop(i) self._frontier.pop(i)
elif len(self._frontier[i]) > 1: elif len(self._frontier[i]) > 1:
# Expand forawrd after doing all back connections # Expand forward after doing all back connections
self._indent()
for c in range(i):
self._write_edge("| ", c)
self._write_edge("|", i)
if (i+1 < len(self._frontier) and len(self._frontier[i+1]) == 1 if (i+1 < len(self._frontier) and len(self._frontier[i+1]) == 1
and self._frontier[i+1][0] in self._frontier[i]): and self._frontier[i+1][0] in self._frontier[i]):
# We need to connect to the element to the right. # We need to connect to the element to the right.
# Keep lines straight by connecting directly and # Keep lines straight by connecting directly and
# avoiding immediate expand/contract. # avoiding unnecessary expand/contract.
name = self._frontier[i+1][0] name = self._frontier[i+1][0]
self._frontier[i].remove(name) self._frontier[i].remove(name)
self._merge_right_line(i)
self._write_edge("\\", i+1)
for c in range(i+1, len(self._frontier)):
self._write_edge("| ", c )
self._out.write("\n")
else: else:
# Just allow the expansion here. # Just allow the expansion here.
name = self._frontier[i].pop(0) name = self._frontier[i].pop(0)
deps = [name] deps = [name]
self._write_edge("\\", i) self._frontier.insert(i, deps)
for c in range(i+1, len(self._frontier)): self._expand_right_line(i)
self._write_edge(" \\", c)
self._out.write("\n") self._frontier.pop(i)
self._connect_deps(i, deps, True, "expansion") self._connect_deps(i, deps, "post-expand")
# Handle any remaining back edges to the right # Handle any remaining back edges to the right
j = i+1 j = i+1
while j < len(self._frontier): while j < len(self._frontier):
deps = self._frontier.pop(j) deps = self._frontier.pop(j)
if not self._connect_deps(j, deps, True, "rem_back"): if not self._connect_deps(j, deps, "back-from-right"):
j += 1 j += 1
else: else:
# Nothing to expand; add dependencies for a node.
name = topo_order.pop() name = topo_order.pop()
node = self._nodes[name] node = self._nodes[name]
# Find the next node in topo order and remove it from # Find the named node in the frontier and draw it.
# the frontier. Since specs are single-rooted DAGs,
# the node is always there. If the graph had multiple
# roots, we'd need to handle that case case of a new root.
i = find(self._frontier, lambda f: name in f) i = find(self._frontier, lambda f: name in f)
self._frontier.pop(i) self._node_line(i, name)
self._indent()
for c in range(i):
self._write_edge("| ", c)
self._out.write("%s " % self.node_character)
for c in range(i, len(self._frontier)):
self._write_edge("| ", c)
self._out.write(" %s\n" % name)
# Replace node with its dependencies
self._frontier.pop(i)
if node.dependencies: if node.dependencies:
self._add_deps_to_frontier(node, i) deps = sorted((d for d in node.dependencies), reverse=True)
self._connect_deps(i, deps, "new-deps") # anywhere.
elif self._frontier: elif self._frontier:
self._indent() self._collapse_line(i)
for c in range(i):
self._write_edge("| ", c)
for c in range(i, len(self._frontier)):
self._write_edge(" /", c)
self._out.write("\n")
def graph_ascii(spec, **kwargs): def graph_ascii(spec, **kwargs):
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment