Update the state graph builder to use state machine type

To build the state diagrams switch from using a custom
algorithm that builds a state machine in a table to using
our state machine class to build those same machines before
translation into a dot diagram.

This also allows us to use the state machine that already
is being used in the action engine as the source of transitions
to build the dot diagram with.

Change-Id: I4a0d16a3fb7c620c2774b535ab952b5d5006e9e9
This commit is contained in:
Joshua Harlow
2014-09-08 14:57:49 -07:00
parent e1ef04492e
commit 155326b6e7
6 changed files with 91 additions and 61 deletions

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 21 KiB

After

Width:  |  Height:  |  Size: 22 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 25 KiB

After

Width:  |  Height:  |  Size: 25 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 20 KiB

After

Width:  |  Height:  |  Size: 20 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 18 KiB

After

Width:  |  Height:  |  Size: 18 KiB

View File

@@ -22,11 +22,17 @@ Engine
**SUCCESS** - Completed successfully.
**FAILURE** - Completed unsuccessfully.
**REVERTED** - Reverting was induced and all atoms were **not** completed
successfully.
**SUSPENDED** - Suspended while running.
**UNDEFINED** - *Internal state.*
**GAME_OVER** - *Internal state.*
Flow
====

View File

@@ -1,5 +1,19 @@
#!/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
@@ -8,13 +22,39 @@ top_dir = os.path.abspath(os.path.join(os.path.dirname(__file__),
os.pardir))
sys.path.insert(0, top_dir)
import networkx as nx
# 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 main():
@@ -45,77 +85,61 @@ def main():
if sum([int(i) for i in types]) > 1:
parser.error("Only one of task/retry/engines may be specified.")
disallowed = set()
start_node = states.PENDING
internal_states = list()
ordering = 'in'
if options.tasks:
source = list(states._ALLOWED_TASK_TRANSITIONS)
source_type = "Tasks"
disallowed.add(states.RETRYING)
source = make_machine(states.PENDING,
list(states._ALLOWED_TASK_TRANSITIONS),
[states.RETRYING])
elif options.retries:
source = list(states._ALLOWED_TASK_TRANSITIONS)
source_type = "Retries"
source = make_machine(states.PENDING,
list(states._ALLOWED_TASK_TRANSITIONS), [])
elif options.engines:
# TODO(harlowja): place this in states.py
source = [
(states.RESUMING, states.SCHEDULING),
(states.SCHEDULING, states.WAITING),
(states.WAITING, states.ANALYZING),
(states.ANALYZING, states.SCHEDULING),
(states.ANALYZING, states.WAITING),
]
for u in (states.SCHEDULING, states.ANALYZING):
for v in (states.SUSPENDED, states.SUCCESS, states.REVERTED):
source.append((u, v))
source_type = "Engines"
start_node = states.RESUMING
r = runner.Runner(DummyRuntime(), None)
source, memory = r.builder.build()
internal_states.extend(runner._META_STATES)
ordering = 'out'
else:
source = list(states._ALLOWED_FLOW_TRANSITIONS)
source_type = "Flow"
transitions = nx.DiGraph()
for (u, v) in source:
if u not in disallowed:
transitions.add_node(u)
if v not in disallowed:
transitions.add_node(v)
for (u, v) in source:
if not transitions.has_node(u) or not transitions.has_node(v):
continue
transitions.add_edge(u, v)
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='in')
splines='true', ordering=ordering)
node_attrs = {
'fontsize': '11',
}
nodes = {}
nodes_order = []
edges_added = []
for (u, v) in nx.bfs_edges(transitions, source=start_node):
if u not in nodes:
nodes[u] = pydot.Node(u, **node_attrs)
g.add_node(nodes[u])
nodes_order.append(u)
if v not in nodes:
nodes[v] = pydot.Node(v, **node_attrs)
g.add_node(nodes[v])
nodes_order.append(v)
for u in nodes_order:
for v in transitions.successors_iter(u):
if (u, v) not in edges_added:
g.add_edge(pydot.Edge(nodes[u], nodes[v]))
edges_added.append((u, v))
for (start_state, _on_event, end_state) in source:
if start_state not in nodes:
start_node_attrs = node_attrs.copy()
if start_state in internal_states:
start_node_attrs['fontcolor'] = 'blue'
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()
if end_state in internal_states:
end_node_attrs['fontcolor'] = 'blue'
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[start_node], style='dotted'))
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)