Expansion works properly, simplified graph code.
This commit is contained in:
parent
b4b8339d0d
commit
5d033fbd0a
2 changed files with 147 additions and 89 deletions
|
@ -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 @@
|
||||||
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):
|
||||||
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):
|
||||||
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):
|
||||||
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):
|
||||||
|
|
||||||
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):
|
||||||
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 > 1: # two lines if distance > 1
|
|
||||||
self._back_edge([], j, i, True, label)
|
|
||||||
self._back_edge([j], -1, -1, (i-j == 1), label)
|
|
||||||
|
|
||||||
# connect to the right
|
|
||||||
else:
|
|
||||||
if i < j:
|
if i < j:
|
||||||
self._frontier.pop(j)
|
self._frontier.pop(j)
|
||||||
self._frontier.insert(i, deps)
|
self._frontier.insert(i, deps)
|
||||||
if j-i > 1:
|
return self._connect_deps(j, deps, label)
|
||||||
self._back_edge([], i, j+1, collapse, label)
|
|
||||||
self._back_edge([i], -1, -1, not (j-i > 1) and collapse, label)
|
collapse = True
|
||||||
|
if self._prev_state == EXPAND_RIGHT:
|
||||||
|
# Special case for when prev. line expanded (spacing is off by 1)
|
||||||
|
# Need two lines here even when distance in frontier is 1.
|
||||||
|
self._back_edge_line([], j, i+1, True, label + "-1.5 " + str((i,j)))
|
||||||
|
collapse = False
|
||||||
|
|
||||||
|
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):
|
||||||
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!")
|
||||||
|
self._prev_state = state
|
||||||
|
|
||||||
Adds the dependencies of <node> to the frontier, and connects
|
if self.debug:
|
||||||
them to other open edges if they match. Also deletes parent
|
self._out.write(" " * 20)
|
||||||
pointers in the node to mark edges as covered.
|
self._out.write("%-20s" % (
|
||||||
|
str(self._prev_state) if self._prev_state else ''))
|
||||||
"""
|
self._out.write("%-20s" % (str(label) if label else ''))
|
||||||
deps = sorted((d for d in node.dependencies), reverse=True)
|
self._out.write("%s" % self._frontier)
|
||||||
self._connect_deps(i, deps, True, "add_deps")
|
|
||||||
for d in deps:
|
|
||||||
del self._nodes[d].dependents[node.name]
|
|
||||||
|
|
||||||
|
|
||||||
|
def _back_edge_line(self, prev_ends, end, start, collapse, label=None):
|
||||||
def _back_edge(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):
|
||||||
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):
|
||||||
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):
|
||||||
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._node_line(i, name)
|
||||||
|
|
||||||
|
# Replace node with its dependencies
|
||||||
self._frontier.pop(i)
|
self._frontier.pop(i)
|
||||||
|
|
||||||
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)
|
|
||||||
|
|
||||||
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):
|
||||||
|
|
Loading…
Reference in a new issue