adding ccnx tree topology streamming over Internet example
[nepi.git] / examples / streamming / planetlab_multiple_vlc.py
1 #!/usr/bin/env python
2
3 from nepi.core.design import ExperimentDescription, FactoriesProvider
4 from nepi.core.execute import ExperimentController
5 from nepi.util.constants import ApplicationStatus as AS
6 from optparse import OptionParser, SUPPRESS_HELP
7 import os
8 import tempfile
9 import time
10 import uuid
11
12 """
13 This experiment evaluates the consumption of computer resources when using
14 VLC for Internet broasdcasting using PlanetLab nodes as both server and clients. 
15 A root node (server) streams a broadcast in a loop, while the clients retrieve
16 the same video over and over until experiment run time is elapsed.
17
18 While the experiment is running cpu and memory usage, and the amount of bytes 
19 transmitted per stream are traced to files.
20
21 """
22
23 # Trak SIGTERM, and set global termination flag instead of dying
24 import signal
25 TERMINATE = []
26 def _finalize(sig,frame):
27     global TERMINATE
28     TERMINATE.append(None)
29 signal.signal(signal.SIGTERM, _finalize)
30 signal.signal(signal.SIGINT, _finalize)
31
32 class MonitorInfo(object):
33     TYPE_ROOT = "root"
34     TYPE_LEAF = "leaf"
35
36     def __init__(self, hostname, type):
37         self.hostname = hostname
38         self.type = type
39         self.cpumem_monitor = None
40         self.net_monitor = None
41
42 def create_slice(exp_desc, slicename, plc_host, pl_user, pl_pwd, 
43         pl_ssh_key, root_dir):
44     pl_provider = FactoriesProvider("planetlab")
45     slice_desc = exp_desc.add_testbed_description(pl_provider)
46     slice_desc.set_attribute_value("homeDirectory", root_dir)
47     slice_desc.set_attribute_value("slice", slicename)
48     slice_desc.set_attribute_value("sliceSSHKey", pl_ssh_key)
49     slice_desc.set_attribute_value("authUser", pl_user)
50     slice_desc.set_attribute_value("authPass", pl_pwd)
51     slice_desc.set_attribute_value("plcHost", plc_host)
52     # Kills all running processes before starting the experiment
53     slice_desc.set_attribute_value("cleanProc", True)
54     # NOTICE: Setting 'cleanHome' to 'True' will erase all previous
55     # folders in the sliver Home directory, including result files!
56     slice_desc.set_attribute_value("cleanHome", True)
57     slice_desc.set_attribute_value("plLogLevel", "DEBUG")
58     return slice_desc
59  
60 def create_node(hostname, pl_inet, slice_desc):
61     pl_node = slice_desc.create("Node")
62     pl_node.set_attribute_value("hostname", hostname)
63     pl_node.set_attribute_value("label", "%d" % pl_node.guid)
64     pl_node.set_attribute_value("operatingSystem", "f12")
65     pl_iface = slice_desc.create("NodeInterface")
66     pl_iface.set_attribute_value("label", "iface_%d" % pl_node.guid)
67     pl_iface.connector("inet").connect(pl_inet.connector("devs"))
68     pl_node.connector("devs").connect(pl_iface.connector("node"))
69     return pl_node, pl_iface
70
71 def create_vlc_server(movie, pl_node, slice_desc):
72     mv = os.path.basename(movie)
73     pl_app = slice_desc.create("Application")
74     pl_app.set_attribute_value("rpmFusion", True)
75     pl_app.set_attribute_value("depends", "vlc")
76     pl_app.set_attribute_value("build", 
77     #    "echo -e 'new TEST vod enabled\\nsetup TEST input %s' > ${SOURCES}/VOD.vlm" % mv)
78        "echo -e 'new TEST broadcast enabled loop\\n"\
79        "setup TEST input %s\\n"\
80        "setup TEST output #rtp{mux=ts,sdp=rtsp://0.0.0.0:8554/TEST}\\n\\n"\
81        "new test_sched schedule enabled\\n"\
82        "setup test_sched append control TEST play' > ${SOURCES}/VOD.vlm" % mv)
83
84     pl_app.set_attribute_value("sources", "%s" % movie)
85     pl_app.set_attribute_value("command",
86     #        "sudo -S dbus-uuidgen --ensure ; vlc -vvv -I dummy --vlm-conf VOD.vlm --rtsp-host=0.0.0.0:8554")
87         "sudo -S dbus-uuidgen --ensure ; vlc -vvv -I dummy --vlm-conf VOD.vlm")
88     pl_app.enable_trace("stdout")
89     pl_app.enable_trace("stderr")
90     pl_node.connector("apps").connect(pl_app.connector("node"))
91     return pl_app
92
93 def create_vlc_client(root_node, pl_node, slice_desc):
94     label = "%d_app" % pl_node.guid
95     hostname = root_node.get_attribute_value("hostname")
96     pl_app = slice_desc.create("Application")
97     pl_app.set_attribute_value("label", label)
98     pl_app.set_attribute_value("rpmFusion", True)
99     pl_app.set_attribute_value("depends", "vlc")
100     pl_app.set_attribute_value("command",
101        "sudo -S dbus-uuidgen --ensure ; sleep 5;" \
102        "vlc -I dummy --repeat rtsp://%s:8554/TEST --sout '#std{access=file,mux=ts,dst=/dev/null}'" % (hostname))
103     pl_app.enable_trace("stdout")
104     pl_app.enable_trace("stderr")
105     pl_node.connector("apps").connect(pl_app.connector("node"))
106     return pl_app
107
108 def create_cpumem_monitor(pl_node, slice_desc):
109     """ This function creates a monitoring application for the
110     utilization of node resources by the vlc application.
111
112     The format of the stdout trace file is the following:
113     'timestamp cpu(%) mem(%) time'
114     """
115     label = "%d_cpumem" % pl_node.guid
116     pl_app = slice_desc.create("Application")
117     pl_app.set_attribute_value("label", label)
118     pl_app.set_attribute_value("command", 
119             "while true ; do echo $(date +%Y%m%d%H%M%S%z) " \
120             " $(top -b -n 1 | grep 'vlc' | head -1 | sed 's/\s\s*/ /g' | cut -d' ' -f9,10,11)" \
121             "; sleep 1 ; done")
122     pl_app.enable_trace("stdout")
123     pl_app.enable_trace("stderr")
124     pl_node.connector("apps").connect(pl_app.connector("node"))
125     return pl_app
126
127 def create_net_monitor(pl_node, slice_desc, pl_ifaces):
128     """ This function creates a monitoring application for the
129     amount of bytes transmitted/received by the vlc application.
130
131     The format of the stdout trace file is the following:
132     'total-Mbytes total-time'
133     """
134     label = "%d_net" % pl_node.guid
135     hosts = " or ".join(map(lambda pl_iface: " ( host {#[%s].addr[0].[Address]#} ) " % 
136         pl_iface.get_attribute_value("label"), pl_ifaces))
137     pl_app = slice_desc.create("Application")
138     pl_app.set_attribute_value("label", label)
139     pl_app.set_attribute_value("rpmFusion", True)
140     pl_app.set_attribute_value("sudo", True)
141     pl_app.set_attribute_value("depends", "tcpdump pv")
142     pl_app.set_attribute_value("command", 
143             "tcpdump -l -i eth0 -nNqttf '(%s)' -w - | pv -fbt >/dev/null 2>>{#[%s].trace[stdout].[name]#}" %
144             (hosts, label))
145     pl_app.enable_trace("stdout")
146     pl_app.enable_trace("stderr")
147     pl_node.connector("apps").connect(pl_app.connector("node"))
148     return pl_app
149
150 def store_results(controller, monitors, results_dir, exp_label):
151     # create results directory for experiment
152     root_path = os.path.join(results_dir, exp_label)
153
154     print "STORING RESULTS in ", root_path
155
156     try:
157         os.makedirs(root_path)
158     except OSError:
159         pass
160
161     # collect information on nodes
162     hosts_info = ""
163
164     for mon in monitors:
165         hosts_info += "%s %s\n" % (mon.hostname, mon.type)
166
167         # create a subdir per hostname
168         node_path = os.path.join(root_path, mon.hostname)
169         try:
170             os.makedirs(node_path)
171         except OSError:
172             pass
173
174         # store monitoring results
175         cpumem_stdout = controller.trace(mon.cpumem_monitor.guid, "stdout")
176         net_stdout = controller.trace(mon.net_monitor.guid, "stdout")
177         results = dict({"cpumem": cpumem_stdout, "net": net_stdout})
178         for name, stdout in results.iteritems():
179             fpath = os.path.join(node_path, name)
180             f = open(fpath, "w")
181             f.write(stdout)
182             f.close()
183
184     # store node info file
185     fpath = os.path.join(root_path, "hosts")
186     f = open(fpath, "w")
187     f.write(hosts_info)
188     f.close()
189
190 def get_options():
191     slicename = os.environ.get("PL_SLICE")
192     pl_host = os.environ.get("PL_HOST", "www.planet-lab.eu")
193     pl_ssh_key = os.environ.get(
194         "PL_SSH_KEY",
195         "%s/.ssh/id_rsa_planetlab" % (os.environ['HOME'],) )
196     pl_user = os.environ.get('PL_USER')
197     pl_pwd = os.environ.get('PL_PASS')
198     exp_label = "%s" % uuid.uuid4()
199
200     usage = "usage: %prog -s <pl_slice> -H <pl_host> -k <ssh_key> -u <pl_user> \
201             -p <pl_password> -m <movie> -r <results-dir> -l <experiment-label>"
202
203     parser = OptionParser(usage=usage)
204     parser.add_option("-s", "--slicename", dest="slicename", 
205             help="PlanetLab slicename", default=slicename, type="str")
206     parser.add_option("-H", "--pl-host", dest="pl_host", 
207             help="PlanetLab site (e.g. www.planet-lab.eu)", 
208             default=pl_host, type="str")
209     parser.add_option("-k", "--ssh-key", dest="pl_ssh_key", 
210             help="Path to private ssh key used for PlanetLab authentication", 
211             default=pl_ssh_key, type="str")
212     parser.add_option("-u", "--pl-user", dest="pl_user", 
213             help="PlanetLab account user (i.e. Registration email address)", 
214             default=pl_user, type="str")
215     parser.add_option("-p", "--pl-pwd", dest="pl_pwd", 
216             help="PlanetLab account password", default=pl_pwd, type="str")
217     parser.add_option("-m", "--movie", dest="movie", 
218             help="Stream movie", type="str")
219     parser.add_option("-r", "--results", dest="results_dir", default = "/tmp", 
220             help="Path to directory to store results", type="str")
221     parser.add_option("-l", "--label", dest="exp_label", default = exp_label, 
222             help="Label to identify experiment results", type="str")
223     parser.add_option("-t", "--time", dest="time_to_run", default = 2, 
224             help="Time to run the experiment in hours", type="float")
225
226     (options, args) = parser.parse_args()
227
228     if not options.movie:
229         parser.error("movie is a required argument")
230
231     return (options.slicename, options.pl_host, options.pl_user, 
232             options.pl_pwd, options.pl_ssh_key, options.movie,
233             options.results_dir, options.exp_label, options.time_to_run)
234
235 if __name__ == '__main__':
236     root_dir = tempfile.mkdtemp()
237     (pl_slice, 
238             pl_host, 
239             pl_user, 
240             pl_pwd, 
241             pl_ssh_key, 
242             movie, 
243             results_dir,
244             exp_label,
245             time_to_run) = get_options()
246
247     # list to store information on monitoring apps per node
248     monitors = []
249     
250     # Create the experiment description object
251     exp_desc = ExperimentDescription()
252
253     # Create slice
254     slice_desc = create_slice(exp_desc, pl_slice, pl_host, pl_user, pl_pwd,
255         pl_ssh_key, root_dir)
256    
257     # Create the Internet box object
258     pl_inet = slice_desc.create("Internet")
259
260     # Create root node
261     hostname = "ple6.ipv6.lip6.fr"
262     (root_node, root_iface) = create_node(hostname, pl_inet, slice_desc)
263
264     # Create monitor info object for root node
265     root_mon = MonitorInfo(hostname, MonitorInfo.TYPE_ROOT)
266     monitors.append(root_mon)
267
268     # Add VLC service
269     create_vlc_server(movie, root_node, slice_desc)
270     
271     # Add memory and cpu monitoring for root node
272     root_mon.cpumem_monitor = create_cpumem_monitor(root_node, slice_desc)
273
274     # Create leaf nodes
275     cli_apps = []
276     cli_ifaces = []
277
278     """
279     hostnames = ["planetlab1.rd.tut.fi", 
280             "planetlab1.s3.kth.se", 
281             "planetlab1.tlm.unavarra.es", 
282             "planet1.servers.ua.pt", 
283             "onelab3.warsaw.rd.tp.pl", 
284             "gschembra3.diit.unict.it", 
285             "iraplab1.iralab.uni-karlsruhe.de", 
286             "host3-plb.loria.fr", 
287             "kostis.di.uoa.gr", 
288             "planetlab04.cnds.unibe.ch"]
289     """
290
291     hostnames = ["planetlab1.rd.tut.fi",
292             "planetlab-2.research.netlab.hut.fi",
293             "planetlab2.willab.fi",
294             "planetlab3.hiit.fi",
295             "planetlab4.hiit.fi",
296             "planetlab1.s3.kth.se", 
297             "itchy.comlab.bth.se",
298             "planetlab-1.ida.liu.se",
299             "scratchy.comlab.bth.se",
300             "planetlab2.s3.kth.se", 
301             "planetlab1.tlm.unavarra.es", 
302             "planetlab2.uc3m.es",
303             "planetlab2.upc.es",
304             "ait21.us.es",
305             "planetlab3.upc.es",
306             "planet1.servers.ua.pt",
307             "planetlab2.fct.ualg.pt",
308             "planetlab-1.tagus.ist.utl.pt",
309             "planetlab1.di.fct.unl.pt",
310             "planetlab1.fct.ualg.pt",
311             "onelab3.warsaw.rd.tp.pl",
312             "onelab1.warsaw.rd.tp.pl",
313             "prata.mimuw.edu.pl",
314             "onelab2.warsaw.rd.tp.pl",
315             "prometeusz.we.po.opole.pl",
316             "gschembra3.diit.unict.it",
317             "onelab6.iet.unipi.it",
318             "planetlab1.science.unitn.it",
319             "planetlab-1.ing.unimo.it",
320             "gschembra4.diit.unict.it",
321             "iraplab1.iralab.uni-karlsruhe.de", 
322             "planetlab-1.fokus.fraunhofer.de",
323             "iraplab2.iralab.uni-karlsruhe.de",
324             "planet2.zib.de",
325             "planet2.inf.tu-dresden.de",
326             "host3-plb.loria.fr",
327             "inriarennes1.irisa.fr",
328             "inriarennes2.irisa.fr",
329             "peeramide.irisa.fr",
330             "pl1.bell-labs.fr", 
331             "kostis.di.uoa.gr",
332             "pl001.ece.upatras.gr",
333             "planetlab1.ionio.gr",
334             "planetlab2.ionio.gr",
335             "planetlab2.cs.uoi.gr", 
336             "planetlab04.cnds.unibe.ch",
337             "lsirextpc01.epfl.ch",
338             "planetlab2.csg.uzh.ch",
339             "lsirextpc02.epfl.ch",
340             "planetlab1.unineuchatel.ch"]
341
342     for hostname in hostnames:
343         pl_node, pl_iface = create_node(hostname, pl_inet, slice_desc)
344         cli_ifaces.append(pl_iface)
345
346         # Create monitor info object for root node
347         node_mon = MonitorInfo(hostname, MonitorInfo.TYPE_LEAF)
348         monitors.append(node_mon)
349       
350         # Add memory and cpu monitoring for all nodes
351         node_mon.cpumem_monitor = create_cpumem_monitor(pl_node, slice_desc)
352
353         # Add network monitoring for all nodes
354         node_mon.net_monitor = create_net_monitor(pl_node, slice_desc, [root_iface])
355
356         # Add VLC clients
357         app = create_vlc_client(root_node, pl_node, slice_desc)
358         cli_apps.append(app)
359
360     # Add network monitoring for root node
361     root_mon.net_monitor = create_net_monitor(root_node, slice_desc, cli_ifaces)
362
363     xml = exp_desc.to_xml()
364    
365     controller = ExperimentController(xml, root_dir)
366     controller.start()
367
368     start_time = time.time()
369     duration = time_to_run * 3600 # in seconds
370     while not TERMINATE:
371         time.sleep(1)
372         if (time.time() - start_time) > duration: # elapsed time
373             TERMINATE.append(None)
374
375     controller.stop()
376  
377     # store results in results dir
378     store_results(controller, monitors, results_dir, exp_label)
379    
380     controller.shutdown()
381