From: Alina Quereilhac Date: Fri, 1 Aug 2014 16:32:40 +0000 (+0200) Subject: Added EC plotter X-Git-Tag: nepi-3.2.0~105^2~4 X-Git-Url: http://git.onelab.eu/?p=nepi.git;a=commitdiff_plain;h=15b4dd83cb8063d81a7ecad20b4c60e639dcda5f Added EC plotter --- diff --git a/src/nepi/execution/ec.py b/src/nepi/execution/ec.py index cf419838..31e11ef5 100644 --- a/src/nepi/execution/ec.py +++ b/src/nepi/execution/ec.py @@ -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 index 00000000..c21f5988 --- /dev/null +++ b/src/nepi/util/plotter.py @@ -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 . +# +# Author: Alina Quereilhac + +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 diff --git a/test/util/parallel.py b/test/util/parallel.py old mode 100644 new mode 100755 diff --git a/test/util/plot.py b/test/util/plot.py deleted file mode 100755 index 38001de2..00000000 --- a/test/util/plot.py +++ /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 . -# -# Author: Alina Quereilhac - - -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 index 00000000..4e1a553e --- /dev/null +++ b/test/util/plotter.py @@ -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 . +# +# Author: Alina Quereilhac + +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() + diff --git a/test/util/serializer.py b/test/util/serializer.py index 482c12d4..3896ea2e 100755 --- a/test/util/serializer.py +++ b/test/util/serializer.py @@ -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