From 155326b6e7066f01bb82d02d184c3c2069159b45 Mon Sep 17 00:00:00 2001 From: Joshua Harlow Date: Mon, 8 Sep 2014 14:57:49 -0700 Subject: [PATCH] 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 --- doc/source/img/engine_states.svg | 6 +- doc/source/img/flow_states.svg | 6 +- doc/source/img/retry_states.svg | 6 +- doc/source/img/task_states.svg | 6 +- doc/source/states.rst | 6 ++ tools/state_graph.py | 122 ++++++++++++++++++------------- 6 files changed, 91 insertions(+), 61 deletions(-) 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)