taskflow/tools/state_graph.py
Joshua Harlow 96014cbba3 Color some of the states depending on there meaning
Instead of just having black text color adjust some of the
states text color depending on there type/name and highlight
some as red, green, orange depending on there underlying
meaning.

Color names are from:

http://www.graphviz.org/doc/info/colors.html

Change-Id: I89f8f90837551a257936d254516ada6130e7b6da
2014-09-20 20:39:13 -07:00

169 lines
6.0 KiB
Python
Executable File

#!/usr/bin/env python
# Copyright (C) 2014 Yahoo! Inc. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import optparse
import os
import sys
top_dir = os.path.abspath(os.path.join(os.path.dirname(__file__),
os.pardir))
sys.path.insert(0, top_dir)
# To get this installed you may have to follow:
# https://code.google.com/p/pydot/issues/detail?id=93 (until fixed).
import pydot
from taskflow.engines.action_engine import runner
from taskflow import states
from taskflow.types import fsm
# This is just needed to get at the runner builder object (we will not
# actually be running it...).
class DummyRuntime(object):
def __init__(self):
self.analyzer = None
self.completer = None
self.scheduler = None
self.storage = None
def make_machine(start_state, transitions, disallowed):
machine = fsm.FSM(start_state)
machine.add_state(start_state)
for (start_state, end_state) in transitions:
if start_state in disallowed or end_state in disallowed:
continue
if start_state not in machine:
machine.add_state(start_state)
if end_state not in machine:
machine.add_state(end_state)
# Make a fake event (not used anyway)...
event = "on_%s" % (end_state)
machine.add_transition(start_state, end_state, event.lower())
return machine
def map_color(internal_states, state):
if state in internal_states:
return 'blue'
if state == states.FAILURE:
return 'red'
if state == states.REVERTED:
return 'darkorange'
if state == states.SUCCESS:
return 'green'
return None
def main():
parser = optparse.OptionParser()
parser.add_option("-f", "--file", dest="filename",
help="write svg to FILE", metavar="FILE")
parser.add_option("-t", "--tasks", dest="tasks",
action='store_true',
help="use task state transitions",
default=False)
parser.add_option("-r", "--retries", dest="retries",
action='store_true',
help="use retry state transitions",
default=False)
parser.add_option("-e", "--engines", dest="engines",
action='store_true',
help="use engine state transitions",
default=False)
parser.add_option("-T", "--format", dest="format",
help="output in given format",
default='svg')
(options, args) = parser.parse_args()
if options.filename is None:
options.filename = 'states.%s' % options.format
types = [options.engines, options.retries, options.tasks]
if sum([int(i) for i in types]) > 1:
parser.error("Only one of task/retry/engines may be specified.")
internal_states = list()
ordering = 'in'
if options.tasks:
source_type = "Tasks"
source = make_machine(states.PENDING,
list(states._ALLOWED_TASK_TRANSITIONS),
[states.RETRYING])
elif options.retries:
source_type = "Retries"
source = make_machine(states.PENDING,
list(states._ALLOWED_TASK_TRANSITIONS), [])
elif options.engines:
source_type = "Engines"
r = runner.Runner(DummyRuntime(), None)
source, memory = r.builder.build()
internal_states.extend(runner._META_STATES)
ordering = 'out'
else:
source_type = "Flow"
source = make_machine(states.PENDING,
list(states._ALLOWED_FLOW_TRANSITIONS), [])
graph_name = "%s states" % source_type
g = pydot.Dot(graph_name=graph_name, rankdir='LR',
nodesep='0.25', overlap='false',
ranksep="0.5", size="11x8.5",
splines='true', ordering=ordering)
node_attrs = {
'fontsize': '11',
}
nodes = {}
for (start_state, _on_event, end_state) in source:
if start_state not in nodes:
start_node_attrs = node_attrs.copy()
text_color = map_color(internal_states, start_state)
if text_color:
start_node_attrs['fontcolor'] = text_color
nodes[start_state] = pydot.Node(start_state, **start_node_attrs)
g.add_node(nodes[start_state])
if end_state not in nodes:
end_node_attrs = node_attrs.copy()
text_color = map_color(internal_states, end_state)
if text_color:
end_node_attrs['fontcolor'] = text_color
nodes[end_state] = pydot.Node(end_state, **end_node_attrs)
g.add_node(nodes[end_state])
g.add_edge(pydot.Edge(nodes[start_state], nodes[end_state]))
start = pydot.Node("__start__", shape="point", width="0.1",
xlabel='start', fontcolor='green', **node_attrs)
g.add_node(start)
g.add_edge(pydot.Edge(start, nodes[source.start_state], style='dotted'))
print("*" * len(graph_name))
print(graph_name)
print("*" * len(graph_name))
print(source.pformat())
print(g.to_string().strip())
g.write(options.filename, format=options.format)
print("Created %s at '%s'" % (options.format, options.filename))
# To make the svg more pretty use the following:
# $ xsltproc ../diagram-tools/notugly.xsl ./states.svg > pretty-states.svg
# Get diagram-tools from https://github.com/vidarh/diagram-tools.git
if __name__ == '__main__':
main()