]> git-server-git.apps.pok.os.sepia.ceph.com Git - ceph.git/commitdiff
doc: Add peering state diagram
authorSamuel Just <rexludorum@gmail.com>
Wed, 30 Nov 2011 00:24:35 +0000 (16:24 -0800)
committerSamuel Just <samuel.just@dreamhost.com>
Wed, 30 Nov 2011 00:35:59 +0000 (16:35 -0800)
Signed-off-by: Samuel Just <samuel.just@dreamhost.com>
.gitignore
admin/build-doc
doc/conf.py
doc/dev/peering.rst [new file with mode: 0644]
doc/scripts/gen_state_diagram.py [new file with mode: 0755]

index e986c9b64a74c6e9b0a3423e26ee68075c4ff0b6..4f3be30b82de18553adc842af6d2fddaeab71580 100644 (file)
@@ -55,6 +55,7 @@ core
 /build-doc
 /doc/object_store.png
 /src/test_*
+*.generated.dot
 
 # temporary directory used by e.g. "make distcheck", e.g. ceph-0.42
 /ceph-[0-9]*/
index 2b3beedc074f4dbadd2351f90a9954d10a663cf1..52c3870a9f4bde3439201fb06b6f613e1340b4a9 100755 (executable)
@@ -13,6 +13,8 @@ dia --filter=png-libart --export=doc/overview.png.tmp doc/overview.dia
 
 mv -- doc/overview.png.tmp doc/overview.png
 
+cat src/osd/PG.h src/osd/PG.cc | doc/scripts/gen_state_diagram.py > doc/dev/peering_graph.generated.dot
+
 cd build-doc
 
 if [ ! -e virtualenv ]; then
index 4dab00c1604b5f0677cbabeee41460397e15118b..670de4678153074161692645d1c0f469abadeb41 100644 (file)
@@ -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 (file)
index 0000000..16fbdf5
--- /dev/null
@@ -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 (executable)
index 0000000..e843251
--- /dev/null
@@ -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()