|
@@ 490-520 (lines=31) @@
|
| 487 |
|
|
| 488 |
|
|
| 489 |
|
class DependencyGraph(object): |
| 490 |
|
""" |
| 491 |
|
A visual representation of the dependency graph, using `Graphviz <http://graphviz.org/>`__. |
| 492 |
|
""" |
| 493 |
|
|
| 494 |
|
def __init__(self, action): |
| 495 |
|
self.__graphviz_graph = graphviz.Digraph("action", node_attr={"shape": "box"}) |
| 496 |
|
nodes = {} |
| 497 |
|
for (i, action) in enumerate(action.get_possible_execution_order()): |
| 498 |
|
node = str(i) |
| 499 |
|
nodes[action] = node |
| 500 |
|
self.__graphviz_graph.node(node, str(action.label)) |
| 501 |
|
for dependency in action.dependencies: |
| 502 |
|
assert dependency in nodes # Because we are iterating a possible execution order |
| 503 |
|
self.__graphviz_graph.edge(node, nodes[dependency]) |
| 504 |
|
|
| 505 |
|
def write_to_png(self, filename): # Not unittested: too difficult |
| 506 |
|
""" |
| 507 |
|
Write the graph as a PNG image to the specified file. |
| 508 |
|
|
| 509 |
|
See also :meth:`get_graphviz_graph` if you want to draw the graph somewhere else. |
| 510 |
|
""" |
| 511 |
|
directory = os.path.dirname(filename) |
| 512 |
|
filename = os.path.basename(filename) |
| 513 |
|
filename, ext = os.path.splitext(filename) |
| 514 |
|
g = self.get_graphviz_graph() |
| 515 |
|
g.format = "png" |
| 516 |
|
g.render(directory=directory, filename=filename, cleanup=True) |
| 517 |
|
|
| 518 |
|
def get_graphviz_graph(self): |
| 519 |
|
""" |
| 520 |
|
Return a :class:`graphviz.Digraph` of this dependency graph. |
| 521 |
|
|
| 522 |
|
See also :meth:`write_to_png` for the simplest use-case. |
| 523 |
|
""" |
|
@@ 522-551 (lines=30) @@
|
| 519 |
|
""" |
| 520 |
|
Return a :class:`graphviz.Digraph` of this dependency graph. |
| 521 |
|
|
| 522 |
|
See also :meth:`write_to_png` for the simplest use-case. |
| 523 |
|
""" |
| 524 |
|
return self.__graphviz_graph.copy() |
| 525 |
|
|
| 526 |
|
|
| 527 |
|
class GanttChart(object): # Not unittested: too difficult |
| 528 |
|
""" |
| 529 |
|
A visual representation of the timing of an execution. |
| 530 |
|
""" |
| 531 |
|
|
| 532 |
|
def __init__(self, report): |
| 533 |
|
self.__actions = { |
| 534 |
|
id(action): self.__make_action(action, status) |
| 535 |
|
for (action, status) in report.get_actions_and_statuses() |
| 536 |
|
} |
| 537 |
|
|
| 538 |
|
self.__ordinates = {} |
| 539 |
|
|
| 540 |
|
dependents = {} |
| 541 |
|
for (action, _) in report.get_actions_and_statuses(): |
| 542 |
|
dependents.setdefault(action, set()) |
| 543 |
|
for dependency in action.dependencies: |
| 544 |
|
dependents.setdefault(dependency, set()).add(action) |
| 545 |
|
|
| 546 |
|
def compute(action, ordinate): |
| 547 |
|
self.__ordinates[id(action)] = len(self.__actions) - ordinate |
| 548 |
|
for d in sorted( |
| 549 |
|
action.dependencies, |
| 550 |
|
key=lambda d: report.get_action_status(d).success_time or report.get_action_status(d).failure_time |
| 551 |
|
): |
| 552 |
|
if len(dependents[d]) == 1: |
| 553 |
|
ordinate = compute(d, ordinate - 1) |
| 554 |
|
else: |