Skip to content

Commit 3b648e8

Browse files
committed
cli: add dependency graph printing
Introduce a new command 'show-dependencies' that prints producent/ consumer dependencies between all actors in a given workflow in the DOT format that allows easy visualization.
1 parent 34ff47e commit 3b648e8

1 file changed

Lines changed: 66 additions & 0 deletions

File tree

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
import itertools
2+
from collections import defaultdict
3+
import datetime
4+
import sys
5+
6+
from leapp.exceptions import CommandError, LeappError
7+
from leapp.logger import configure_logger
8+
from leapp.repository.scan import find_and_scan_repositories
9+
from leapp.snactor.context import with_snactor_context
10+
from leapp.utils.clicmd import command, command_arg
11+
from leapp.utils.repository import find_repository_basedir, requires_repository
12+
13+
_LONG_DESCRIPTION = '''
14+
Outputs the graph of dependencies between actors formed by producer/consumer their relations in the DOT langauge.
15+
The DOT language allows easy visualization of the graph.
16+
'''
17+
18+
19+
@command('show-dependencies',
20+
help='Print dependency graph of a workflow in the DOT language.',
21+
description=_LONG_DESCRIPTION)
22+
@command_arg('workflow_name')
23+
@requires_repository
24+
@with_snactor_context
25+
def cli(params):
26+
start = datetime.datetime.utcnow()
27+
configure_logger()
28+
repository = find_and_scan_repositories(find_repository_basedir('.'), include_locals=True)
29+
try:
30+
repository.load()
31+
except LeappError as exc:
32+
sys.stderr.write(exc.message)
33+
sys.stderr.write('\n')
34+
sys.exit(1)
35+
36+
workflow = repository.lookup_workflow(params.workflow_name)
37+
if not workflow:
38+
raise CommandError('Could not find any workflow named "{}"'.format(params.name))
39+
40+
workflow_instance = workflow()
41+
42+
# Information about message consuments and producers:
43+
dependency_graph = defaultdict(lambda: {'producers': set(), 'consumers': set()})
44+
45+
for staged_phase in workflow_instance.phase_actors:
46+
# staged_phase = ('name' <str>, 'Before' <PhaseActors>, 'Main' <PhaseActors>, 'After' <PhaseActors>)
47+
before_stage, main_stage, after_stage = staged_phase[1:]
48+
49+
for actor in itertools.chain(before_stage.actors, main_stage.actors, after_stage.actors):
50+
for consumed_message in actor.consumes:
51+
dependency_graph[consumed_message.__name__]['consumers'].add(actor.name)
52+
for produced_message in actor.produces:
53+
dependency_graph[produced_message.__name__]['producents'].add(actor.name)
54+
55+
# Display the dependency graph in the DOT format. We don't want to add dependencies on another library,
56+
# so we do the formatting ourselves since its pretty straightforward
57+
dot_output_lines = ['digraph LeappDependencyGraph {']
58+
59+
for message, message_info in dependency_graph.items():
60+
for producent, consument in itertools.product(message_info['producents'], message_info['consuments']):
61+
output_line = ' "{0}" -> "{1}" [label="{2}"]'.format(producent, consument, message)
62+
dot_output_lines.append(output_line)
63+
64+
dot_output_lines.append('}')
65+
dot_output = '\n'.join(dot_output_lines) + '\n'
66+
print(dot_output, end='')

0 commit comments

Comments
 (0)