c976160e1e8d7629d6c47795bdb3f044d9c21926
[nepi.git] / examples / streaming / vlc_broadcast.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_in_monitor = None
41         self.net_out_monitor = None
42         self.vlc = None
43
44 def create_slice(exp_desc, slicename, plc_host, pl_user, pl_pwd, 
45         pl_ssh_key, root_dir):
46     pl_provider = FactoriesProvider("planetlab")
47     slice_desc = exp_desc.add_testbed_description(pl_provider)
48     slice_desc.set_attribute_value("homeDirectory", root_dir)
49     slice_desc.set_attribute_value("slice", slicename)
50     slice_desc.set_attribute_value("sliceSSHKey", pl_ssh_key)
51     slice_desc.set_attribute_value("authUser", pl_user)
52     slice_desc.set_attribute_value("authPass", pl_pwd)
53     slice_desc.set_attribute_value("plcHost", plc_host)
54     # Kills all running processes before starting the experiment
55     slice_desc.set_attribute_value("cleanProc", True)
56     # NOTICE: Setting 'cleanHome' to 'True' will erase all previous
57     # folders in the sliver Home directory, including result files!
58     slice_desc.set_attribute_value("cleanHome", True)
59     slice_desc.set_attribute_value("plLogLevel", "DEBUG")
60     return slice_desc
61  
62 def create_node(hostname, pl_inet, slice_desc):
63     pl_node = slice_desc.create("Node")
64     pl_node.set_attribute_value("hostname", hostname)
65     pl_node.set_attribute_value("label", "%d" % pl_node.guid)
66     pl_node.set_attribute_value("operatingSystem", "f12")
67     pl_iface = slice_desc.create("NodeInterface")
68     pl_iface.set_attribute_value("label", "iface_%d" % pl_node.guid)
69     pl_iface.connector("inet").connect(pl_inet.connector("devs"))
70     pl_node.connector("devs").connect(pl_iface.connector("node"))
71     return pl_node, pl_iface
72
73 def create_vlc_server(movie, pl_node, slice_desc):
74     mv = os.path.basename(movie)
75     pl_app = slice_desc.create("Application")
76     pl_app.set_attribute_value("rpmFusion", True)
77     pl_app.set_attribute_value("depends", "vlc")
78     pl_app.set_attribute_value("build", 
79     #    "echo -e 'new TEST vod enabled\\nsetup TEST input %s' > ${SOURCES}/VOD.vlm" % mv)
80        "echo -e 'new TEST broadcast enabled loop\\n"\
81        "setup TEST input %s\\n"\
82        "setup TEST output #rtp{mux=ts,sdp=rtsp://0.0.0.0:8554/TEST}\\n\\n"\
83        "new test_sched schedule enabled\\n"\
84        "setup test_sched append control TEST play' > ${SOURCES}/VOD.vlm" % mv)
85
86     pl_app.set_attribute_value("sources", "%s" % movie)
87     pl_app.set_attribute_value("command",
88         "sudo -S dbus-uuidgen --ensure ; vlc -vvv -I dummy --vlm-conf VOD.vlm")
89     pl_app.enable_trace("stdout")
90     pl_app.enable_trace("stderr")
91     pl_node.connector("apps").connect(pl_app.connector("node"))
92     return pl_app
93
94 def create_vlc_client(root_node, pl_node, slice_desc):
95     label = "%d_app" % pl_node.guid
96     hostname = root_node.get_attribute_value("hostname")
97     pl_app = slice_desc.create("Application")
98     pl_app.set_attribute_value("label", label)
99     pl_app.set_attribute_value("rpmFusion", True)
100     pl_app.set_attribute_value("depends", "vlc")
101     pl_app.set_attribute_value("command",
102        "sudo -S dbus-uuidgen --ensure ; sleep 5;" \
103        "vlc -I dummy rtsp://%s:8554/TEST --sout '#std{access=file,mux=ts,dst=/dev/null}'" % (hostname))
104     pl_app.enable_trace("stdout")
105     pl_app.enable_trace("stderr")
106     pl_node.connector("apps").connect(pl_app.connector("node"))
107     return pl_app
108
109 def create_cpumem_monitor(pl_node, slice_desc):
110     """ This function creates a monitoring application for the
111     utilization of node resources by the vlc application.
112
113     The format of the stdout trace file is the following:
114     'timestamp cpu(%) mem(%) time'
115     """
116     label = "%d_cpumem" % pl_node.guid
117     pl_app = slice_desc.create("Application")
118     pl_app.set_attribute_value("label", label)
119     pl_app.set_attribute_value("command", 
120             "while true ; do echo $(date +%Y%m%d%H%M%S%z) " \
121             " $(top -b -n 1 | grep 'vlc' | head -1 | sed 's/\s\s*/ /g' | sed 's/^\s//g' | cut -d' ' -f9,10,11)" \
122             "; sleep 1 ; done")
123     pl_app.enable_trace("stdout")
124     pl_app.enable_trace("stderr")
125     pl_node.connector("apps").connect(pl_app.connector("node"))
126     return pl_app
127
128 def create_net_monitor(pl_node, slice_desc, pl_ifaces, pcap=False):
129     """ This function creates a monitoring application for the
130     amount of bytes transmitted/received by the vlc application.
131
132     The format of the stdout trace file is the following:
133     'total-Mbytes total-time'
134     """
135     label = "%d_net" % pl_node.guid
136     hosts = " or ".join(map(lambda pl_iface: " ( host {#[%s].addr[0].[Address]#} ) " % 
137         pl_iface.get_attribute_value("label"), pl_ifaces))
138     pl_app = slice_desc.create("Application")
139     pl_app.set_attribute_value("label", label)
140     pl_app.set_attribute_value("rpmFusion", True)
141     pl_app.set_attribute_value("sudo", True)
142     pl_app.set_attribute_value("depends", "tcpdump pv")
143
144     output = "/dev/null"
145     if pcap:
146         output = "{#[%s].trace[output].[name]#}" % label
147
148     pl_app.set_attribute_value("command", 
149             "tcpdump -l -i eth0 -s 0 -f '(%s)' -w - | pv -fbt >%s 2>>{#[%s].trace[stdout].[name]#}" %
150             (hosts, output, label))
151
152     if pcap:
153         pl_app.enable_trace("output")
154     
155     pl_app.enable_trace("stdout")
156     pl_app.enable_trace("stderr")
157     pl_node.connector("apps").connect(pl_app.connector("node"))
158     return pl_app
159
160 def store_results(controller, monitors, results_dir, exp_label):
161     # create results directory for experiment
162     root_path = os.path.join(results_dir, exp_label)
163
164     print "STORING RESULTS in ", root_path
165
166     try:
167         os.makedirs(root_path)
168     except OSError:
169         pass
170
171     # collect information on nodes
172     hosts_info = ""
173
174     for mon in monitors:
175         hosts_info += "%s %s\n" % (mon.hostname, mon.type)
176
177         # create a subdir per hostname
178         node_path = os.path.join(root_path, mon.hostname)
179         try:
180             os.makedirs(node_path)
181         except OSError:
182             pass
183
184         # store monitoring results
185    
186         cpumem_out = controller.trace(mon.cpumem_monitor.guid, "stdout")
187
188         net_in = None
189         if mon.net_in_monitor:
190             net_in = controller.trace(mon.net_in_monitor.guid, "stdout")
191         
192         net_out = None
193         if mon.net_out_monitor:
194             net_out = controller.trace(mon.net_out_monitor.guid, "stdout")
195
196         vlc_err = controller.trace(mon.vlc.guid, "stderr")
197         vlc_out = controller.trace(mon.vlc.guid, "stdout")
198
199         results = dict({
200             "cpumem": cpumem_out, 
201             "net_in": net_in, 
202             "net_out": net_out, 
203             "vlc_out": vlc_out,
204             "vlc_err": vlc_err })
205
206         for name, result in results.iteritems():
207             if not result:
208                 continue
209
210             fpath = os.path.join(node_path, name)
211             f = open(fpath, "w")
212             f.write(result)
213             f.close()
214
215     # store node info file
216     fpath = os.path.join(root_path, "hosts")
217     f = open(fpath, "w")
218     f.write(hosts_info)
219     f.close()
220
221 def get_options():
222     slicename = os.environ.get("PL_SLICE")
223     pl_host = os.environ.get("PL_HOST", "www.planet-lab.eu")
224     pl_ssh_key = os.environ.get(
225         "PL_SSH_KEY",
226         "%s/.ssh/id_rsa_planetlab" % (os.environ['HOME'],) )
227     pl_user = os.environ.get('PL_USER')
228     pl_pwd = os.environ.get('PL_PASS')
229     exp_label = "%s" % uuid.uuid4()
230
231     usage = "usage: %prog -s <pl_slice> -H <pl_host> -k <ssh_key> -u <pl_user> \
232             -p <pl_password> -m <movie> -r <results-dir> -l <experiment-label>"
233
234     parser = OptionParser(usage=usage)
235     parser.add_option("-s", "--slicename", dest="slicename", 
236             help="PlanetLab slicename", default=slicename, type="str")
237     parser.add_option("-H", "--pl-host", dest="pl_host", 
238             help="PlanetLab site (e.g. www.planet-lab.eu)", 
239             default=pl_host, type="str")
240     parser.add_option("-k", "--ssh-key", dest="pl_ssh_key", 
241             help="Path to private ssh key used for PlanetLab authentication", 
242             default=pl_ssh_key, type="str")
243     parser.add_option("-u", "--pl-user", dest="pl_user", 
244             help="PlanetLab account user (i.e. Registration email address)", 
245             default=pl_user, type="str")
246     parser.add_option("-p", "--pl-pwd", dest="pl_pwd", 
247             help="PlanetLab account password", default=pl_pwd, type="str")
248     parser.add_option("-m", "--movie", dest="movie", 
249             help="Stream movie", type="str")
250     parser.add_option("-r", "--results", dest="results_dir", default = "/tmp", 
251             help="Path to directory to store results", type="str")
252     parser.add_option("-l", "--label", dest="exp_label", default = exp_label, 
253             help="Label to identify experiment results", type="str")
254     parser.add_option("-t", "--time", dest="time_to_run", default = 20, 
255             help="Time to run the experiment in minutes", type="float")
256
257     (options, args) = parser.parse_args()
258
259     if not options.movie:
260         parser.error("movie is a required argument")
261
262     return (options.slicename, options.pl_host, options.pl_user, 
263             options.pl_pwd, options.pl_ssh_key, options.movie,
264             options.results_dir, options.exp_label, options.time_to_run)
265
266 if __name__ == '__main__':
267     root_dir = tempfile.mkdtemp()
268     (pl_slice, 
269             pl_host, 
270             pl_user, 
271             pl_pwd, 
272             pl_ssh_key, 
273             movie, 
274             results_dir,
275             exp_label,
276             time_to_run) = get_options()
277
278     # list to store information on monitoring apps per node
279     monitors = []
280     
281     # Create the experiment description object
282     exp_desc = ExperimentDescription()
283
284     # Create slice
285     slice_desc = create_slice(exp_desc, pl_slice, pl_host, pl_user, pl_pwd,
286         pl_ssh_key, root_dir)
287    
288     # Create the Internet box object
289     pl_inet = slice_desc.create("Internet")
290
291     # Create root node
292     hostname = "ple6.ipv6.lip6.fr"
293     (root_node, root_iface) = create_node(hostname, pl_inet, slice_desc)
294
295     # Create monitor info object for root node
296     root_mon = MonitorInfo(hostname, MonitorInfo.TYPE_ROOT)
297     monitors.append(root_mon)
298
299     # Add VLC service
300     root_vlc = create_vlc_server(movie, root_node, slice_desc)
301     
302     # Add memory and cpu monitoring for root node
303     root_mon.cpumem_monitor = create_cpumem_monitor(root_node, slice_desc)
304
305     # Add reference to vlc app 
306     root_mon.vlc = root_vlc
307
308     # Create leaf nodes
309     cli_apps = []
310     cli_ifaces = []
311
312     hostnames = ["planetlab1.rd.tut.fi",
313              "planetlab-2.research.netlab.hut.fi",
314              "planetlab2.willab.fi",
315              "planetlab3.hiit.fi",
316              "planetlab4.hiit.fi",
317              "planetlab1.willab.fi",
318              "planetlab1.s3.kth.se",
319              "itchy.comlab.bth.se",
320              "planetlab-1.ida.liu.se",
321              "scratchy.comlab.bth.se",
322              "planetlab2.s3.kth.se",
323              "planetlab1.sics.se",
324              "planetlab1.tlm.unavarra.es",
325              "planetlab2.uc3m.es",
326              "planetlab2.upc.es",
327              "ait21.us.es",
328              "planetlab3.upc.es",
329              "planetlab1.uc3m.es",
330              "planetlab2.dit.upm.es",
331              "planetlab1.upc.es",
332              "planetlab2.um.es",
333              "planet1.servers.ua.pt",
334              "planetlab2.fct.ualg.pt",
335              "planetlab-1.tagus.ist.utl.pt",
336              "planetlab-2.tagus.ist.utl.pt",
337              "planetlab-um00.di.uminho.pt",
338              "planet2.servers.ua.pt",
339              "planetlab1.mini.pw.edu.pl",
340              "roti.mimuw.edu.pl",
341              "planetlab1.ci.pwr.wroc.pl",
342              "planetlab1.pjwstk.edu.pl",
343              "ple2.tu.koszalin.pl",
344              "planetlab2.ci.pwr.wroc.pl",
345              "planetlab2.cyfronet.pl",
346              "plab2.ple.silweb.pl",
347              "planetlab1.cyfronet.pl",
348              "plab4.ple.silweb.pl",
349              "ple2.dmcs.p.lodz.pl",
350              "planetlab2.pjwstk.edu.pl",
351              "ple1.dmcs.p.lodz.pl",
352              "pandora.we.po.opole.pl",
353              "gschembra3.diit.unict.it",
354              "onelab6.iet.unipi.it",
355              "planetlab1.science.unitn.it",
356              "planetlab-1.ing.unimo.it",
357              "gschembra4.diit.unict.it",
358              "iraplab1.iralab.uni-karlsruhe.de",
359              "planetlab-1.fokus.fraunhofer.de",
360              "iraplab2.iralab.uni-karlsruhe.de",
361              "planet2.zib.de",
362              "pl2.uni-rostock.de",
363              "onelab-1.fhi-fokus.de",
364              "planet2.l3s.uni-hannover.de",
365              "planetlab1.exp-math.uni-essen.de",
366              "planetlab-2.fokus.fraunhofer.de",
367              "planetlab02.tkn.tu-berlin.de",
368              "planetlab1.informatik.uni-goettingen.de",
369              "planetlab1.informatik.uni-erlangen.de",
370              "planetlab2.exp-math.uni-essen.de",
371              "planetlab2.lkn.ei.tum.de",
372              "planetlab1.wiwi.hu-berlin.de",
373              "planet1.l3s.uni-hannover.de",
374              "planetlab1.informatik.uni-wuerzburg.de",
375              "host3-plb.loria.fr",
376              "inriarennes1.irisa.fr",
377              "inriarennes2.irisa.fr",
378              "peeramide.irisa.fr",
379              "pl1.bell-labs.fr",
380              "pl2.bell-labs.fr",
381              "host4-plb.loria.fr",
382              "planetlab-1.imag.fr",
383              "planetlab-2.imag.fr",
384              "ple2.ipv6.lip6.fr",
385              "planetlab1.u-strasbg.fr",
386              "kostis.di.uoa.gr",
387              "planetlab1.ionio.gr",
388              "planetlab2.ionio.gr",
389              "planetlab2.cs.uoi.gr",
390              "stella.planetlab.ntua.gr",
391              "vicky.planetlab.ntua.gr",
392              "planetlab1.cs.uoi.gr",
393              "pl002.ece.upatras.gr",
394              "planetlab04.cnds.unibe.ch",
395              "lsirextpc01.epfl.ch",
396              "planetlab2.csg.uzh.ch",
397              "planetlab1.csg.uzh.ch",
398              "planetlab-2.cs.unibas.ch",
399              "planetlab-1.cs.unibas.ch",
400              "planetlab4.cs.st-andrews.ac.uk",
401              "planetlab-1.imperial.ac.uk",
402              "planetlab3.xeno.cl.cam.ac.uk",
403              "planetlab1.xeno.cl.cam.ac.uk",
404              "planetlab2.xeno.cl.cam.ac.uk",
405              "planetlab3.cs.st-andrews.ac.uk",
406              "planetlab1.aston.ac.uk",
407              "planetlab1.nrl.eecs.qmul.ac.uk",
408              "chimay.infonet.fundp.ac.be",
409              "orval.infonet.fundp.ac.be",
410              "rochefort.infonet.fundp.ac.be",
411              "planck227ple.test.ibbt.be",
412             ]
413
414
415     for hostname in hostnames:
416         pl_node, pl_iface = create_node(hostname, pl_inet, slice_desc)
417         cli_ifaces.append(pl_iface)
418
419         # Create monitor info object for root node
420         node_mon = MonitorInfo(hostname, MonitorInfo.TYPE_LEAF)
421         monitors.append(node_mon)
422       
423         # Add memory and cpu monitoring for all nodes
424         node_mon.cpumem_monitor = create_cpumem_monitor(pl_node, slice_desc)
425
426         # Add network monitoring for all nodes
427         node_mon.net_out_monitor = create_net_monitor(pl_node, slice_desc, [root_iface])
428
429         # Add VLC clients
430         vlc = create_vlc_client(root_node, pl_node, slice_desc)
431         cli_apps.append(vlc)
432
433         # Add reference to vlc app 
434         node_mon.vlc = vlc
435
436     # Add network monitoring for root node
437     #root_mon.net_monitor = create_net_monitor(root_node, slice_desc, cli_ifaces, pcap=True)
438     root_mon.net_out_monitor = create_net_monitor(root_node, slice_desc, cli_ifaces)
439
440     xml = exp_desc.to_xml()
441    
442     controller = ExperimentController(xml, root_dir)
443     controller.start()
444
445     start_time = time.time()
446     duration = time_to_run * 60 # in seconds
447     while not TERMINATE:
448         time.sleep(1)
449         if (time.time() - start_time) > duration: # elapsed time
450             TERMINATE.append(None)
451
452     controller.stop()
453  
454     # store results in results dir
455     store_results(controller, monitors, results_dir, exp_label)
456    
457     controller.shutdown()
458