Added EC plotter
authorAlina Quereilhac <alina.quereilhac@inria.fr>
Fri, 1 Aug 2014 16:32:40 +0000 (18:32 +0200)
committerAlina Quereilhac <alina.quereilhac@inria.fr>
Fri, 1 Aug 2014 16:32:40 +0000 (18:32 +0200)
src/nepi/execution/ec.py
src/nepi/util/plotter.py [new file with mode: 0644]
test/util/parallel.py [changed mode: 0644->0755]
test/util/plot.py [deleted file]
test/util/plotter.py [new file with mode: 0755]
test/util/serializer.py

index cf41983..31e11ef 100644 (file)
@@ -25,6 +25,7 @@ from nepi.execution.resource import ResourceFactory, ResourceAction, \
 from nepi.execution.scheduler import HeapScheduler, Task, TaskStatus
 from nepi.execution.trace import TraceAttr
 from nepi.util.serializer import ECSerializer, SFormats
+from nepi.util.plotter import ECPlotter, PFormats
 
 # TODO: use multiprocessing instead of threading
 # TODO: Allow to reconnect to a running experiment instance! (reconnect mode vs deploy mode)
@@ -381,6 +382,12 @@ class ExperimentController(object):
 
                 time.sleep(0.5)
 
+    def plot(self, fpath = None, format= PFormats.FIGURE, persist = False):
+        plotter = ECPlotter()
+        fpath = plotter.plot(self, fpath = fpath, format= format, 
+                persist = persist)
+        return fpath
+
     def serialize(self, format = SFormats.XML):
         serializer = ECSerializer()
         sec = serializer.load(self, format = format)
diff --git a/src/nepi/util/plotter.py b/src/nepi/util/plotter.py
new file mode 100644 (file)
index 0000000..c21f598
--- /dev/null
@@ -0,0 +1,94 @@
+#
+#    NEPI, a framework to manage network experiments
+#    Copyright (C) 2013 INRIA
+#
+#    This program is free software: you can redistribute it and/or modify
+#    it under the terms of the GNU General Public License as published by
+#    the Free Software Foundation, either version 3 of the License, or
+#    (at your option) any later version.
+#
+#    This program is distributed in the hope that it will be useful,
+#    but WITHOUT ANY WARRANTY; without even the implied warranty of
+#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#    GNU General Public License for more details.
+#
+#    You should have received a copy of the GNU General Public License
+#    along with this program.  If not, see <http://www.gnu.org/licenses/>.
+#
+# Author: Alina Quereilhac <alina.quereilhac@inria.fr>
+
+import networkx
+import os
+
+class PFormats:
+    DOT = "dot"
+    FIGURE = "figure"
+
+class ECPlotter(object):
+    def plot(self, ec, fpath = None, format= PFormats.FIGURE, persist = False):
+        graph, labels = self._ec2graph(ec)
+
+        add_extension = False
+
+        if persist and not fpath:
+            import tempfile
+            dirpath = tempfile.mkdtemp()
+            fpath = os.path.join(dirpath, "%s_%s" % (ec.exp_id, ec.run_id)) 
+            add_extension = True
+
+        if format == PFormats.FIGURE:
+            import matplotlib.pyplot as plt
+            pos = networkx.graphviz_layout(graph, prog="neato")
+            networkx.draw(graph, pos = pos, node_color="white", 
+                    node_size = 500, with_labels=True)
+           
+            label = "\n".join(map(lambda v: "%s: %s" % (v[0], v[1]), labels.iteritems()))
+            plt.annotate(label, xy=(0.0, 0.95), xycoords='axes fraction')
+            
+            if persist:
+                if add_extension:
+                    fpath += ".png"
+
+                plt.savefig(fpath, bbox_inches="tight")
+            else:
+                plt.show()
+
+        elif format == PFormats.DOT:
+            if persist:
+                if add_extension:
+                    fpath += ".dot"
+
+                networkx.write_dot(graph, fpath)
+            else:
+                import subprocess
+                subprocess.call(["dot", "-Tps", fpath, "-o", "%s.ps" % fpath])
+                subprocess.call(["evince","%s.ps" % fpath])
+        
+        return fpath
+
+    def _ec2graph(self, ec):
+        graph = networkx.Graph(graph = dict(overlap = "false"))
+
+        labels = dict()
+        connections = set()
+
+        for guid, rm in ec._resources.iteritems():
+            label = rm.get_rtype()
+
+            graph.add_node(guid,
+                label = "%d %s" % (guid, label),
+                width = 50/72.0, # 1 inch = 72 points
+                height = 50/72.0, 
+                shape = "circle")
+
+            labels[guid] = label
+
+            for guid2 in rm.connections:
+                # Avoid adding a same connection twice
+                if (guid2, guid) not in connections:
+                    connections.add((guid, guid2))
+
+        for (guid1, guid2) in connections:
+            graph.add_edge(guid1, guid2)
+
+        return graph, labels
old mode 100644 (file)
new mode 100755 (executable)
diff --git a/test/util/plot.py b/test/util/plot.py
deleted file mode 100755 (executable)
index 38001de..0000000
+++ /dev/null
@@ -1,56 +0,0 @@
-#!/usr/bin/env python
-#
-#    NEPI, a framework to manage network experiments
-#    Copyright (C) 2013 INRIA
-#
-#    This program is free software: you can redistribute it and/or modify
-#    it under the terms of the GNU General Public License as published by
-#    the Free Software Foundation, either version 3 of the License, or
-#    (at your option) any later version.
-#
-#    This program is distributed in the hope that it will be useful,
-#    but WITHOUT ANY WARRANTY; without even the implied warranty of
-#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-#    GNU General Public License for more details.
-#
-#    You should have received a copy of the GNU General Public License
-#    along with this program.  If not, see <http://www.gnu.org/licenses/>.
-#
-# Author: Alina Quereilhac <alina.quereilhac@inria.fr>
-
-
-from nepi.design.box import Box 
-from nepi.util.plot import Plotter
-
-import subprocess
-import unittest
-
-class BoxPlotTestCase(unittest.TestCase):
-    def xtest_plot(self):
-        """ XXX: This test is interactive, it will open an evince instance,
-        so it should not run automatically """
-        node1 = Box(label="node1")
-        ping1 = Box(label="ping")
-        mobility1 = Box(label="mob1")
-        node2 = Box(label="node2")
-        mobility2 = Box(label="mob2")
-        iface1 = Box(label="iface1")
-        iface2 = Box(label="iface2")
-        channel = Box(label="chan")
-
-        node1.connect(ping1)
-        node1.connect(mobility1)
-        node1.connect(iface1)
-        channel.connect(iface1)
-        channel.connect(iface2)
-        node2.connect(iface2)
-        node2.connect(mobility2)
-
-        plotter = Plotter(node1)
-        fname = plotter.plot()
-        subprocess.call(["dot", "-Tps", fname, "-o", "%s.ps"%fname])
-        subprocess.call(["evince","%s.ps"%fname])
-       
-if __name__ == '__main__':
-    unittest.main()
-
diff --git a/test/util/plotter.py b/test/util/plotter.py
new file mode 100755 (executable)
index 0000000..4e1a553
--- /dev/null
@@ -0,0 +1,138 @@
+#!/usr/bin/env python
+#
+#    NEPI, a framework to manage network experiments
+#    Copyright (C) 2013 INRIA
+#
+#    This program is free software: you can redistribute it and/or modify
+#    it under the terms of the GNU General Public License as published by
+#    the Free Software Foundation, either version 3 of the License, or
+#    (at your option) any later version.
+#
+#    This program is distributed in the hope that it will be useful,
+#    but WITHOUT ANY WARRANTY; without even the implied warranty of
+#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#    GNU General Public License for more details.
+#
+#    You should have received a copy of the GNU General Public License
+#    along with this program.  If not, see <http://www.gnu.org/licenses/>.
+#
+# Author: Alina Quereilhac <alina.quereilhac@inria.fr>
+
+from nepi.execution.ec import ExperimentController
+from nepi.execution.resource import ResourceManager, ResourceState, \
+        clsinit_copy, ResourceAction, ResourceFactory
+from nepi.util.plotter import PFormats
+
+import os
+import tempfile
+import time
+import unittest
+
+reschedule_delay = "0.5s"
+deploy_time = 0
+run_time = 0
+
+class Link(ResourceManager):
+    _rtype = "dummy::Link"
+    def do_deploy(self):
+        time.sleep(deploy_time)
+        super(Link, self).do_deploy()
+        self.logger.debug(" -------- DEPLOYED ------- ")
+
+class Interface(ResourceManager):
+    _rtype = "dummy::Interface"
+
+    def do_deploy(self):
+        node = self.get_connected(Node.get_rtype())[0]
+        link = self.get_connected(Link.get_rtype())[0]
+
+        if node.state < ResourceState.READY or \
+                link.state < ResourceState.READY:
+            self.ec.schedule(reschedule_delay, self.deploy)
+            self.logger.debug(" -------- RESCHEDULING ------- ")
+        else:
+            time.sleep(deploy_time)
+            super(Interface, self).do_deploy()
+            self.logger.debug(" -------- DEPLOYED ------- ")
+
+class Node(ResourceManager):
+    _rtype = "dummy::Node"
+
+    def do_deploy(self):
+        self.logger.debug(" -------- DO_DEPLOY ------- ")
+        time.sleep(deploy_time)
+        super(Node, self).do_deploy()
+        self.logger.debug(" -------- DEPLOYED ------- ")
+
+class Application(ResourceManager):
+    _rtype = "dummy::Application"
+
+    def do_deploy(self):
+        node = self.get_connected(Node.get_rtype())[0]
+
+        if node.state < ResourceState.READY: 
+            self.ec.schedule(reschedule_delay, self.deploy)
+            self.logger.debug(" -------- RESCHEDULING ------- ")
+        else:
+            time.sleep(deploy_time)
+            super(Application, self).do_deploy()
+            self.logger.debug(" -------- DEPLOYED ------- ")
+
+    def do_start(self):
+        super(Application, self).do_start()
+        time.sleep(run_time)
+        self.ec.schedule("0s", self.stop)
+
+ResourceFactory.register_type(Application)
+ResourceFactory.register_type(Node)
+ResourceFactory.register_type(Interface)
+ResourceFactory.register_type(Link)
+
+class PlotterTestCase(unittest.TestCase):
+    def test_serialize(self):
+        node_count = 4
+        app_count = 2
+
+        ec = ExperimentController(exp_id = "plotter-test")
+       
+        # Add simulated nodes and applications
+        nodes = list()
+        apps = list()
+        ifaces = list()
+
+        for i in xrange(node_count):
+            node = ec.register_resource("dummy::Node")
+            nodes.append(node)
+            
+            iface = ec.register_resource("dummy::Interface")
+            ec.register_connection(node, iface)
+            ifaces.append(iface)
+
+            for i in xrange(app_count):
+                app = ec.register_resource("dummy::Application")
+                ec.register_connection(node, app)
+                apps.append(app)
+
+        link = ec.register_resource("dummy::Link")
+
+        for iface in ifaces:
+            ec.register_connection(link, iface)
+       
+        fpath = ec.plot(persist = True)
+        statinfo = os.stat(fpath)
+        size = statinfo.st_size
+        self.assertTrue(size > 0)
+        self.assertTrue(fpath.endswith(".png"))
+
+        fpath = ec.plot(persist = True, format = PFormats.DOT)
+        statinfo = os.stat(fpath)
+        size = statinfo.st_size
+        self.assertTrue(size > 0)
+        self.assertTrue(fpath.endswith(".dot"))
+
+        print fpath
+
+
+if __name__ == '__main__':
+    unittest.main()
+
index 482c12d..3896ea2 100755 (executable)
@@ -88,7 +88,7 @@ ResourceFactory.register_type(Node)
 ResourceFactory.register_type(Interface)
 ResourceFactory.register_type(Link)
 
-class SerializeTestCase(unittest.TestCase):
+class SerializerTestCase(unittest.TestCase):
     def test_serialize(self):
         node_count = 4
         app_count = 2