Skip to content

Commit 5be1d35

Browse files
committed
Added multiprocessing for running experiments
1 parent 64d3b6b commit 5be1d35

11 files changed

+354
-135
lines changed

fabfile.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ def console():
88
FrameworkConsole().cmdloop()
99

1010

11-
for name in [n for n, o in getmembers(modules[__name__], isfunction) if hasattr(o, 'cmd')]:
12-
short_name = name.split('_')[-1]
11+
for name in [n for n, o in getmembers(modules[__name__], isfunction) if hasattr(o, 'behavior')]:
12+
short_name = '_'.join(name.split('_')[1:])
1313
exec('{}.__name__ = "{}"'.format(name, short_name))
1414
exec('{} = task({})'.format(short_name, name))

lib/behaviors.py

+52
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
# -*- coding: utf8 -*-
2+
from datetime import datetime, timedelta
3+
4+
from .constants import TASK_EXPIRATION
5+
from .logconfig import logging
6+
7+
8+
class DefaultCommand(object):
9+
is_multiprocessed = False
10+
11+
12+
class MultiprocessedCommand(object):
13+
is_multiprocessed = True
14+
15+
def __init__(self, console, command, name):
16+
super(MultiprocessedCommand, self).__init__()
17+
self.pool = console.pool
18+
self.tasklist = console.tasklist
19+
self.command = command
20+
self.short = '_'.join(command.__name__.split('_')[1:])
21+
self.name = name
22+
self.tasklist[self] = {
23+
'name': name,
24+
'command': self.short,
25+
'status': 'INIT',
26+
'expires': None,
27+
'result': None,
28+
}
29+
30+
def __str__(self):
31+
return '{}[{}]'.format(self.name, self.short)
32+
33+
def __set_info(self, status, result=None, expires=True):
34+
logging.debug(' > Process {} is over.'.format(self))
35+
self.tasklist[self].update({'status': status, 'result': result})
36+
if expires:
37+
self.tasklist[self]['expires'] = datetime.now() + timedelta(seconds=TASK_EXPIRATION)
38+
39+
def callback(self, result):
40+
self.__set_info('SUCCESS' if result is not False else 'FAILED', result)
41+
42+
def error_callback(self, result):
43+
self.__set_info('FAIL', result)
44+
45+
def is_expired(self):
46+
return datetime.now() > (self.tasklist[self]['expires'] or datetime.now())
47+
48+
def run(self, *args, **kwargs):
49+
if self not in self.tasklist.keys() or self.tasklist[self]['status'] != 'PENDING':
50+
self.__set_info('PENDING', expires=False)
51+
self.pool.apply_async(self.command, args, kwargs,
52+
callback=self.callback, error_callback=self.error_callback)

lib/commands.py

+73-37
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,14 @@
22
from fabric.api import hide, lcd, local, settings, sudo
33
from inspect import getmembers, isfunction
44
from os import listdir
5-
from os.path import dirname, exists, expanduser, isdir, join
5+
from os.path import dirname, exists, expanduser, join, splitext
66
from sys import modules
77

8+
from .behaviors import MultiprocessedCommand
89
from .constants import CONTIKI_FOLDER, COOJA_FOLDER, EXPERIMENT_FOLDER, FRAMEWORK_FOLDER, TEMPLATES_FOLDER
910
from .decorators import command, expand_file, report_bad_input, stderr
10-
from .helpers import copy_files, copy_folder, move_folder, read_config, remove_files, remove_folder, \
11-
std_input, write_config
11+
from .helpers import copy_files, copy_folder, move_files, move_folder, remove_files, remove_folder, \
12+
std_input, read_config, write_config
1213
from .install import check_cooja, modify_cooja, register_new_path_in_profile, \
1314
update_cooja_build, update_cooja_user_properties
1415
from .logconfig import logging, HIDDEN_ALL
@@ -23,8 +24,8 @@ def get_commands():
2324

2425

2526
# ****************************** TASKS ON INDIVIDUAL EXPERIMENT ******************************
26-
@command(examples=["my-simulation"], autocomplete=lambda: list_experiments())
2727
@report_bad_input
28+
@command(examples=["my-simulation"], autocomplete=lambda: list_experiments())
2829
def do_clean(name, ask=True):
2930
"""
3031
Remove an experiment.
@@ -33,16 +34,16 @@ def do_clean(name, ask=True):
3334
:param ask: ask confirmation
3435
"""
3536
if not exists(join(EXPERIMENT_FOLDER, name)):
36-
logging.debug(" > Folder does not exist !")
37+
logging.warning(" > Folder does not exist !")
3738
elif ask and std_input() == "yes":
3839
logging.debug(" > Cleaning folder...")
3940
with hide(*HIDDEN_ALL):
4041
with lcd(EXPERIMENT_FOLDER):
4142
local("rm -rf {}".format(name))
4243

4344

44-
@command(examples=["my-simulation true"], autocomplete=lambda: list_experiments())
4545
@report_bad_input
46+
@command(examples=["my-simulation true"], autocomplete=lambda: list_experiments())
4647
def do_cooja(name, with_malicious=False):
4748
"""
4849
Start an experiment in Cooja with/without the malicious mote.
@@ -58,8 +59,8 @@ def do_cooja(name, with_malicious=False):
5859
local("make cooja-with{}-malicious".format("" if with_malicious else "out"))
5960

6061

61-
@command(examples=["my-simulation", "my-simulation target=z1 debug=true"], autocomplete=lambda: list_experiments())
6262
@report_bad_input
63+
@command(examples=["my-simulation", "my-simulation target=z1 debug=true"], autocomplete=lambda: list_experiments())
6364
def do_make(name, **kwargs):
6465
"""
6566
Make a new experiment.
@@ -69,9 +70,9 @@ def do_make(name, **kwargs):
6970
"""
7071
global reuse_bin_path
7172
if exists(join(EXPERIMENT_FOLDER, name)):
72-
logging.debug(" > Folder already exists !")
73+
logging.warning(" > Folder already exists !")
7374
if std_input("Proceed anyway ? (yes|no) [default: no] ") != "yes":
74-
return
75+
exit(0)
7576
logging.info("CREATING EXPERIMENT '{}'".format(name))
7677
logging.debug(" > Validating parameters...")
7778
params = validated_parameters(kwargs)
@@ -144,18 +145,21 @@ def do_make(name, **kwargs):
144145
remove_folder((path, 'obj_{}'.format(params["target"])))
145146

146147

147-
@command(examples=["my-simulation"], autocomplete=lambda: list_experiments())
148148
@report_bad_input
149+
@command(examples=["my-simulation"], autocomplete=lambda: list_experiments())
149150
def do_remake(name):
150151
"""
151152
Remake the malicious mote of an experiment.
152153
(meaning that it lets all simulation's files unchanged except ./motes/malicious.[target])
153154
154155
:param name: experiment name
155156
"""
157+
path = get_path(EXPERIMENT_FOLDER, name)
158+
if not exists(path):
159+
logging.error("Experiment '{}' does not exist !".format(name))
160+
exit(2)
156161
logging.info("REMAKING MALICIOUS MOTE FOR EXPERIMENT '{}'".format(name))
157162
logging.debug(" > Retrieving parameters...")
158-
path = get_path(EXPERIMENT_FOLDER, name)
159163
params = read_config(path)
160164
ext_lib = params.get("ext_lib")
161165
logging.debug(" > Recompiling malicious mote...")
@@ -194,29 +198,49 @@ def do_remake(name):
194198
remove_folder((path, 'obj_{}'.format(params["target"])))
195199

196200

197-
@command(examples=["my-simulation"], autocomplete=lambda: list_experiments())
198201
@report_bad_input
202+
@command(examples=["my-simulation"], autocomplete=lambda: list_experiments(), behavior=MultiprocessedCommand)
199203
def do_run(name):
200204
"""
201205
Run an experiment.
202206
203207
:param name: experiment name
204208
"""
205-
logging.info("PROCESSING EXPERIMENT '{}'".format(name))
206209
path = get_path(EXPERIMENT_FOLDER, name)
210+
if not exists(path):
211+
logging.error("Experiment '{}' does not exist !".format(name))
212+
return False
213+
logging.info("PROCESSING EXPERIMENT '{}'".format(name))
214+
check_structure(path, remove=True)
215+
data, results = join(path, 'data'), join(path, 'results')
207216
with hide(*HIDDEN_ALL):
208-
with lcd(path):
209-
logging.debug(" > Running both simulations (with and without the malicious mote)...")
210-
local("make run-without-malicious")
211-
local("make run-with-malicious")
212-
remove_files(path,
213-
'COOJA.log',
214-
'COOJA.testlog')
217+
for sim in ["without", "with"]:
218+
with lcd(path):
219+
logging.debug(" > Running simulation {} the malicious mote...".format(sim))
220+
local("make run-{}-malicious".format(sim), capture=True)
221+
remove_files(path,
222+
'COOJA.log',
223+
'COOJA.testlog')
224+
# once the execution is over, gather the screenshots into a single GIF and keep the first and
225+
# the last screenshots ; move these to the results folder
226+
with lcd(data):
227+
local('convert -delay 10 -loop 0 network*.png wsn-{}-malicious.gif'.format(sim))
228+
network_images = {int(fn.split('.')[0].split('_')[-1]): fn for fn in listdir(data) \
229+
if fn.startswith('network_')}
230+
move_files(data, results, 'wsn-{}-malicious.gif'.format(sim))
231+
net_start_old = network_images[min(network_images.keys())]
232+
net_start, ext = splitext(net_start_old)
233+
net_start_new = 'wsn-{}-malicious_start{}'.format(sim, ext)
234+
net_end_old = network_images[max(network_images.keys())]
235+
net_end, ext = splitext(net_end_old)
236+
net_end_new = 'wsn-{}-malicious_end{}'.format(sim, ext)
237+
move_files(data, results, (net_start_old, net_start_new), (net_end_old, net_end_new))
238+
remove_files(data, *network_images.values())
215239

216240

217241
# ****************************** COMMANDS ON SIMULATION CAMPAIGN ******************************
218-
@command(examples=["my-simulation-campaign"], autocomplete=lambda: list_campaigns())
219242
@expand_file(EXPERIMENT_FOLDER, 'json')
243+
@command(examples=["my-simulation-campaign"], autocomplete=lambda: list_campaigns())
220244
def do_drop(exp_file='experiments'):
221245
"""
222246
Remove a campaign of experiments.
@@ -229,8 +253,8 @@ def do_drop(exp_file='experiments'):
229253
remove_files(EXPERIMENT_FOLDER, exp_file)
230254

231255

232-
@command(examples=["my-simulation-campaign"], autocomplete=lambda: list_campaigns())
233256
@expand_file(EXPERIMENT_FOLDER, 'json')
257+
@command(examples=["my-simulation-campaign"], autocomplete=lambda: list_campaigns())
234258
def do_make_all(exp_file="experiments"):
235259
"""
236260
Make a campaign of experiments.
@@ -244,8 +268,8 @@ def do_make_all(exp_file="experiments"):
244268
do_make(name, **params)
245269

246270

247-
@command(examples=["my-simulation-campaign"], autocomplete=lambda: list_campaigns())
248271
@expand_file(EXPERIMENT_FOLDER, 'json')
272+
@command(examples=["my-simulation-campaign"], autocomplete=lambda: list_campaigns())
249273
def do_prepare(exp_file='experiments'):
250274
"""
251275
Create a campaign of experiments from a template.
@@ -257,8 +281,8 @@ def do_prepare(exp_file='experiments'):
257281
copy_files(TEMPLATES_FOLDER, dirname(exp_file), ('experiments.json', exp_file))
258282

259283

260-
@command(examples=["my-simulation-campaign"], autocomplete=lambda: list_campaigns())
261284
@expand_file(EXPERIMENT_FOLDER, 'json')
285+
@command(examples=["my-simulation-campaign"], autocomplete=lambda: list_campaigns())
262286
def do_run_all(exp_file="experiments"):
263287
"""
264288
Run a campaign of experiments.
@@ -271,7 +295,7 @@ def do_run_all(exp_file="experiments"):
271295

272296

273297
# ************************************** INFORMATION COMMANDS *************************************
274-
@command(examples=["experiments", "campaigns"], autocomplete=["campaigns", "experiments"])
298+
@command(examples=["experiments", "campaigns"], autocomplete=["campaigns", "experiments"], reexec_on_emptyline=True)
275299
def do_list(item_type=None):
276300
"""
277301
List all available items of a specified type.
@@ -302,7 +326,6 @@ def do_config(contiki_folder='~/contiki', experiments_folder='~/Experiments'):
302326
f.write('[RPL Attacks Framework Configuration]\n')
303327
f.write('contiki_folder = {}\n'.format(contiki_folder))
304328
f.write('experiments_folder = {}\n'.format(experiments_folder))
305-
do_config.description = "Create a configuration file at ~/.rpl-attacks.conf for RPL Attacks Framework."
306329

307330

308331
@command()
@@ -321,26 +344,39 @@ def do_setup():
321344
"""
322345
Setup the framework.
323346
"""
347+
logging.info("SETTING UP OF THE FRAMEWORK")
348+
recompile = False
324349
# install Cooja modifications
325350
if not check_cooja(COOJA_FOLDER):
326-
logging.info("INSTALLING COOJA ADD-ONS")
351+
logging.debug(" > Installing Cooja add-ons...")
327352
# modify Cooja.java and adapt build.xml and ~/.cooja.user.properties
328353
modify_cooja(COOJA_FOLDER)
329354
update_cooja_build(COOJA_FOLDER)
330355
update_cooja_user_properties()
331-
# install VisualizerScreenshot plugin in Cooja
332-
visualizer = join(COOJA_FOLDER, 'apps', 'visualizer_screenshot')
333-
if not exists(visualizer):
334-
logging.debug(" > Installing VisualizerScreenshot Cooja plugin...")
335-
copy_folder('src/visualizer_screenshot', visualizer)
336-
# recompile Cooja for making the changes take effect
356+
recompile = True
357+
# install VisualizerScreenshot plugin in Cooja
358+
visualizer = join(COOJA_FOLDER, 'apps', 'visualizer_screenshot')
359+
if not exists(visualizer):
360+
logging.debug(" > Installing VisualizerScreenshot Cooja plugin...")
361+
copy_folder('src/visualizer_screenshot', visualizer)
362+
recompile = True
363+
# recompile Cooja for making the changes take effect
364+
if recompile:
337365
with lcd(COOJA_FOLDER):
338366
logging.debug(" > Recompiling Cooja...")
339367
with settings(warn_only=True):
340368
local("ant clean")
341369
local("ant jar")
342370
else:
343-
logging.info("COOJA IS UP-TO-DATE")
371+
logging.debug(" > Cooja is up-to-date")
372+
# install imagemagick
373+
with hide(*HIDDEN_ALL):
374+
imagemagick_apt_output = local('apt-cache policy imagemagick', capture=True)
375+
if 'Unable to locate package' in imagemagick_apt_output:
376+
logging.debug(" > Installing imagemagick package...")
377+
sudo("apt-get install imagemagick -y &")
378+
else:
379+
logging.debug(" > Imagemagick is installed")
344380
# install msp430 (GCC) upgrade
345381
with hide(*HIDDEN_ALL):
346382
msp430_version_output = local('msp430-gcc --version', capture=True)
@@ -349,7 +385,7 @@ def do_setup():
349385
"Would you like to upgrade it now ? (yes|no) [default: no] "
350386
answer = std_input(txt)
351387
if answer == "yes":
352-
logging.info("UPGRADING msp430-gcc FROM VERSION 4.6.3 TO 4.7.0")
388+
logging.debug(" > Upgrading msp430-gcc from version 4.6.3 to 4.7.0...")
353389
logging.warning("If you encounter problems with this upgrade, please refer to:\n"
354390
"https://github.com/contiki-os/contiki/wiki/MSP430X")
355391
with lcd('src/'):
@@ -359,10 +395,10 @@ def do_setup():
359395
local('export PATH=/usr/local/msp430/bin:$PATH')
360396
register_new_path_in_profile()
361397
else:
362-
logging.info("UPGRADE OF LIBRARY msp430-gcc ABORTED")
398+
logging.warning("Upgrade of library msp430-gcc aborted")
363399
logging.warning("You may experience problems of mote memory size at compilation")
364400
else:
365-
logging.info("LIBRARY msp430-gcc IS UP-TO-DATE (4.7.0)")
401+
logging.debug(" > Library msp430-gcc is up-to-date (version 4.7.0)")
366402

367403

368404
# **************************************** MAGIC COMMAND ****************************************

0 commit comments

Comments
 (0)