diff --git a/doc/source/img/engine_states.svg b/doc/source/img/engine_states.svg index 497c31ef..8ef68c3e 100644 --- a/doc/source/img/engine_states.svg +++ b/doc/source/img/engine_states.svg @@ -1,8 +1,8 @@ - - -Engines statesRESUMINGSCHEDULINGWAITINGSUCCESSSUSPENDEDREVERTEDANALYZINGstart + +Engines statesGAME_OVERREVERTEDSUCCESSSUSPENDEDFAILUREUNDEFINEDRESUMINGSCHEDULINGANALYZINGWAITINGstart diff --git a/doc/source/img/flow_states.svg b/doc/source/img/flow_states.svg index c6d9825e..5a1cdcbd 100644 --- a/doc/source/img/flow_states.svg +++ b/doc/source/img/flow_states.svg @@ -1,8 +1,8 @@ - - -Flow statesPENDINGRUNNINGRESUMINGFAILURESUCCESSREVERTEDSUSPENDINGSUSPENDEDstart + +Flow statesPENDINGRUNNINGFAILURESUSPENDINGREVERTEDSUCCESSRESUMINGSUSPENDEDstart diff --git a/doc/source/img/retry_states.svg b/doc/source/img/retry_states.svg index 014516e0..a2ba2fa9 100644 --- a/doc/source/img/retry_states.svg +++ b/doc/source/img/retry_states.svg @@ -1,8 +1,8 @@ - - -Retries statesPENDINGRUNNINGFAILURESUCCESSREVERTINGRETRYINGREVERTEDstart + +Retries statesPENDINGRUNNINGSUCCESSFAILURERETRYINGREVERTINGREVERTEDstart diff --git a/doc/source/img/task_states.svg b/doc/source/img/task_states.svg index f40501ac..c281be8e 100644 --- a/doc/source/img/task_states.svg +++ b/doc/source/img/task_states.svg @@ -1,8 +1,8 @@ - - -Tasks statesPENDINGRUNNINGFAILURESUCCESSREVERTINGREVERTEDstart + +Tasks statesPENDINGRUNNINGSUCCESSFAILUREREVERTINGREVERTEDstart diff --git a/doc/source/states.rst b/doc/source/states.rst index 02fcaf15..432e0079 100644 --- a/doc/source/states.rst +++ b/doc/source/states.rst @@ -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 ==== diff --git a/tools/state_graph.py b/tools/state_graph.py index 77b85636..e83426bf 100755 --- a/tools/state_graph.py +++ b/tools/state_graph.py @@ -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)