diff options
author | Samuel Just <rexludorum@gmail.com> | 2011-11-29 16:24:35 -0800 |
---|---|---|
committer | Samuel Just <samuel.just@dreamhost.com> | 2011-11-29 16:35:59 -0800 |
commit | 1c696b6566d4355fd6e52a9a60dabbcfa3d0c5b2 (patch) | |
tree | 977cdd4f3cf95ccdbfc25e40ec3b87014053187a /doc | |
parent | 30ede648fe14b04ce1d09623ca0b344f8e2b964b (diff) | |
download | ceph-1c696b6566d4355fd6e52a9a60dabbcfa3d0c5b2.tar.gz |
doc: Add peering state diagram
Signed-off-by: Samuel Just <samuel.just@dreamhost.com>
Diffstat (limited to 'doc')
-rw-r--r-- | doc/conf.py | 1 | ||||
-rw-r--r-- | doc/dev/peering.rst | 6 | ||||
-rwxr-xr-x | doc/scripts/gen_state_diagram.py | 201 |
3 files changed, 208 insertions, 0 deletions
diff --git a/doc/conf.py b/doc/conf.py index 4dab00c1604..670de467815 100644 --- a/doc/conf.py +++ b/doc/conf.py @@ -28,6 +28,7 @@ extensions = [ 'breathe', ] todo_include_todos = True +graphviz_output_format = 'svg' def _get_manpages(): import os diff --git a/doc/dev/peering.rst b/doc/dev/peering.rst new file mode 100644 index 00000000000..16fbdf5994b --- /dev/null +++ b/doc/dev/peering.rst @@ -0,0 +1,6 @@ +====================== +Peering +====================== +Overview of peering state machine structure: + +.. graphviz:: peering_graph.generated.dot diff --git a/doc/scripts/gen_state_diagram.py b/doc/scripts/gen_state_diagram.py new file mode 100755 index 00000000000..e843251c10c --- /dev/null +++ b/doc/scripts/gen_state_diagram.py @@ -0,0 +1,201 @@ +#!/usr/bin/python +import re +import sys + +def do_filter(generator): + return acc_lines(remove_multiline_comments(to_char(remove_single_line_comments(generator)))) + +def acc_lines(generator): + current = "" + for i in generator: + current += i + if i == ';' or \ + i == '{' or \ + i == '}': + yield current.lstrip("\n") + current = "" + +def to_char(generator): + for line in generator: + for char in line: + if char is not '\n': + yield char + else: + yield ' ' + +def remove_single_line_comments(generator): + for i in generator: + if len(i) and i[0] == '#': + continue + yield re.sub(r'//.*', '', i) + +def remove_multiline_comments(generator): + saw = "" + in_comment = False + for char in generator: + if in_comment: + if saw is "*": + if char is "/": + in_comment = False + saw = "" + if char is "*": + saw = "*" + continue + if saw is "/": + if char is '*': + in_comment = True + saw = "" + continue + else: + yield saw + saw = "" + if char is '/': + saw = "/" + continue + yield char + +class StateMachineRenderer(object): + def __init__(self): + self.states = {} # state -> parent + self.machines = {} # state-> initial + self.edges = {} # event -> [(state, state)] + + self.context = [] # [(context, depth_encountered)] + self.context_depth = 0 + self.state_contents = {} + self.subgraphnum = 0 + self.clusterlabel = {} + + def __str__(self): + return "-------------------\n\nstates: %s\n\n machines: %s\n\n edges: %s\n\n context %s\n\n state_contents %s\n\n--------------------" % ( + self.states, + self.machines, + self.edges, + self.context, + self.state_contents + ) + + def read_input(self, input_lines): + for line in input_lines: + self.get_state(line) + self.get_event(line) + self.get_context(line) + + def get_context(self, line): + match = re.search(r"\w+(::\w+)*\s*(\w+::)*::(?P<tag>\w+)::\w+\(const (?P<event>\w+)&", + line) + if match is not None: + self.context.append((match.group('tag'), self.context_depth, match.group('event'))) + if '{' in line: + self.context_depth += 1 + if '}' in line: + self.context_depth -= 1 + while len(self.context) and self.context[-1][1] == self.context_depth: + self.context.pop() + + def get_state(self, line): + if "boost::statechart::state_machine" in line: + tokens = re.search( + r"boost::statechart::state_machine<\s*(\w*),\s*(\w*)\s*>", + line) + if tokens is None: + raise "Error: malformed state_machine line: " + line + self.machines[tokens.group(1)] = tokens.group(2) + self.context.append((tokens.group(1), self.context_depth, "")) + return + if "boost::statechart::state" in line: + tokens = re.search( + r"boost::statechart::state<\s*(\w*),\s*(\w*)\s*,?\s*(\w*)\s*>", + line) + if tokens is None: + raise "Error: malformed state line: " + line + self.states[tokens.group(1)] = tokens.group(2) + if tokens.group(2) not in self.state_contents.keys(): + self.state_contents[tokens.group(2)] = [] + self.state_contents[tokens.group(2)].append(tokens.group(1)) + if tokens.group(3) is not "": + self.machines[tokens.group(1)] = tokens.group(3) + self.context.append((tokens.group(1), self.context_depth, "")) + return + + def get_event(self, line): + if "boost::statechart::transition" in line: + for i in re.finditer(r'boost::statechart::transition<\s*([\w:]*)\s*,\s*(\w*)\s*>', + line): + if i.group(1) not in self.edges.keys(): + self.edges[i.group(1)] = [] + if len(self.context) is 0: + raise "no context at line: " + line + self.edges[i.group(1)].append((self.context[-1][0], i.group(2))) + i = re.search("return\s+transit<\s*(\w*)\s*>()", line) + if i is not None: + if len(self.context) is 0: + raise "no context at line: " + line + if self.context[-1][2] is "": + raise "no event in context at line: " + line + if self.context[-1][2] not in self.edges.keys(): + self.edges[self.context[-1][2]] = [] + self.edges[self.context[-1][2]].append((self.context[-1][0], i.group(1))) + + def emit_dot(self): + top_level = [] + for state in self.machines.keys(): + if state not in self.states.keys(): + top_level.append(state) + print >>sys.stderr, "Top Level States: ", str(top_level) + print """digraph G {""" + print '\tsize="7,7"' + print """\tcompound=true;""" + for i in self.emit_state(top_level[0]): + print '\t' + i + for i in self.edges.keys(): + for j in self.emit_event(i): + print j + print """}""" + + def emit_state(self, state): + if state in self.state_contents.keys(): + self.clusterlabel[state] = "cluster%s"%(str(self.subgraphnum),) + yield "subgraph cluster%s {"%(str(self.subgraphnum),) + self.subgraphnum += 1 + yield """\tlabel = "%s";"""%(state,) + yield """\tcolor = "blue";""" + for j in self.state_contents[state]: + for i in self.emit_state(j): + yield "\t"+i + yield "}" + else: + found = False + for (k,v) in self.machines.items(): + if v == state: + yield state+"[shape=Mdiamond];" + found = True + break; + if not found: + yield state+";" + + def emit_event(self, event): + def ap(app): + retval = "[" + for i in app: + retval += (i + ",") + retval += "]" + return retval + for (fro, to) in self.edges[event]: + appendix = ['label="%s"'%(event,)] + if fro in self.machines.keys(): + appendix.append("ltail=%s"%(self.clusterlabel[fro],)) + while fro in self.machines.keys(): + fro = self.machines[fro] + if to in self.machines.keys(): + appendix.append("lhead=%s"%(self.clusterlabel[to],)) + while to in self.machines.keys(): + to = self.machines[to] + yield("%s -> %s %s;"%(fro, to, ap(appendix))) + + + +input_generator = do_filter(sys.stdin.xreadlines()) +renderer = StateMachineRenderer() +renderer.read_input(input_generator) +renderer.emit_dot() |