Skip to content

Commit 91360e5

Browse files
committed
util/experiments: Extend with callbacks and parallel SW builds
1 parent b2e56bb commit 91360e5

File tree

4 files changed

+102
-29
lines changed

4 files changed

+102
-29
lines changed

experiments/frep/experiments.py

Lines changed: 3 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77

88
from copy import deepcopy
99
from snitch.target.SimResults import SimRegion
10-
from snitch.target.experiment_utils import ExperimentManager
10+
import snitch.target.experiment_utils as eu
1111
import random
1212

1313
from mako.template import Template
@@ -57,11 +57,10 @@
5757
'zonl64dobu': str(Path.cwd() / 'hw/zonl64dobu/bin/snitch_cluster.vsim'),
5858
'zonl48dobu': str(Path.cwd() / 'hw/zonl48dobu/bin/snitch_cluster.vsim'),
5959
}
60-
DATA_DIR = Path('data').absolute()
6160
VERIFY_PY = Path('../../../../sw/blas/gemm/scripts/verify.py').absolute()
6261

6362

64-
class FrepExperimentManager(ExperimentManager):
63+
class FrepExperimentManager(eu.ExperimentManager):
6564

6665
def derive_axes(self, experiment):
6766
return {
@@ -72,16 +71,7 @@ def derive_axes(self, experiment):
7271
}
7372

7473
def derive_data_cfg(self, experiment):
75-
# Create parent directory for configuration file
76-
cfg_path = DATA_DIR / experiment['name'] / 'cfg.json'
77-
cfg_path.parent.mkdir(parents=True, exist_ok=True)
78-
79-
# Fill in configuration template and write configuration file
80-
with open('cfg.json.tpl') as f:
81-
cfg = Template(f.read()).render(experiment=experiment)
82-
with open(cfg_path, 'w') as f:
83-
f.write(cfg)
84-
return cfg_path
74+
return eu.derive_data_cfg_from_template(experiment)
8575

8676
def derive_hw_cfg(self, experiment):
8777
return Path.cwd() / 'cfg' / f'{experiment["hw"]}.json'

util/experiments/build.py

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ def parser():
6060

6161

6262
# Build software target with a specific data configuration
63-
def build(target, build_dir, data_cfg=None, defines=None, hw_cfg=None):
63+
def build(target=None, build_dir=None, data_cfg=None, defines=None, hw_cfg=None):
6464
# Define variables for build system
6565
vars = {
6666
'DEBUG': 'ON',
@@ -69,16 +69,13 @@ def build(target, build_dir, data_cfg=None, defines=None, hw_cfg=None):
6969
if data_cfg is not None:
7070
vars[f'{target}_DATA_CFG'] = data_cfg
7171
if defines:
72-
cflags = ' '.join([f'-D{name}={value}' for name, value in defines.items()])
72+
cflags = common.join_cdefines(defines)
7373
vars[f'{target}_RISCV_CFLAGS'] = cflags
7474
if hw_cfg is not None:
7575
vars['CFG_OVERRIDE'] = hw_cfg
7676

77-
# Build software
78-
print(colored('Build app', 'black', attrs=['bold']), colored(target, 'cyan', attrs=['bold']),
79-
colored('in', 'black', attrs=['bold']), colored(build_dir, 'cyan', attrs=['bold']))
8077
env = common.extend_environment(vars)
81-
common.make(target, env=env)
78+
return common.make(target, env=env, sync=False)
8279

8380

8481
# Create test specification for a specific configuration

util/experiments/common.py

Lines changed: 31 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,30 @@
44
#
55
# Luca Colagrande <colluca@iis.ee.ethz.ch>
66

7+
import ctypes
78
from pathlib import Path
89
import os
10+
import signal
911
import subprocess
12+
import sys
13+
from termcolor import colored
1014

1115
MK_DIR = Path(__file__).resolve().parent / '../../'
1216

1317

18+
# Set the parent-death signal of the current process to `sig`.
19+
def _set_pdeathsig(sig=signal.SIGTERM):
20+
libc = ctypes.CDLL("libc.so.6", use_errno=True)
21+
PR_SET_PDEATHSIG = 1
22+
if libc.prctl(PR_SET_PDEATHSIG, sig) != 0:
23+
e = ctypes.get_errno()
24+
raise OSError(e, "prctl(PR_SET_PDEATHSIG) failed")
25+
26+
27+
def join_cdefines(defines):
28+
return ' '.join([f'-D{key}={value}' for key, value in defines.items()])
29+
30+
1431
def extend_environment(vars, env=None):
1532
if env is None:
1633
env = os.environ.copy()
@@ -25,9 +42,9 @@ def run(cmd, env=None, dry_run=False, sync=True):
2542
return None
2643
else:
2744
if sync:
28-
return subprocess.run(cmd, env=env)
45+
return subprocess.run(cmd, env=env, preexec_fn=_set_pdeathsig)
2946
else:
30-
return subprocess.Popen(cmd, env=env)
47+
return subprocess.Popen(cmd, env=env, preexec_fn=_set_pdeathsig)
3148

3249

3350
def make(target, vars={}, flags=[], dir=MK_DIR, env=None, dry_run=False, sync=True):
@@ -37,3 +54,15 @@ def make(target, vars={}, flags=[], dir=MK_DIR, env=None, dry_run=False, sync=Tr
3754
cmd.extend(['-C', dir])
3855
cmd.extend(flags)
3956
return run(cmd, env=env, dry_run=dry_run, sync=sync)
57+
58+
59+
def wait_processes(processes, dry_run=False):
60+
if not dry_run:
61+
for i, p in enumerate(processes):
62+
retcode = p.wait()
63+
if retcode != 0:
64+
print(
65+
colored(f'Process failed with exit code {retcode}:\n', 'red', attrs=['bold']),
66+
colored(f'{" ".join(p.args)}', 'black')
67+
)
68+
sys.exit(1)

util/experiments/experiment_utils.py

Lines changed: 65 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@
3939

4040
class ExperimentManager:
4141

42-
def __init__(self, experiments=None, actions=None, args=None):
42+
def __init__(self, experiments=None, actions=None, args=None, callbacks={}):
4343
"""Initializes the class from the command-line arguments."""
4444
if args is not None:
4545
self.args = args
@@ -50,6 +50,9 @@ def __init__(self, experiments=None, actions=None, args=None):
5050
else:
5151
self.actions = self.args.actions
5252

53+
# Save callbacks
54+
self.callbacks = callbacks
55+
5356
# Save directory
5457
self.dir = Path.cwd()
5558
self.run_dir = self.dir / self.args.run_dir
@@ -146,12 +149,27 @@ def run(self):
146149

147150
# Build software
148151
if 'sw' in self.actions or 'all' in self.actions:
152+
processes = []
149153
for experiment in experiments:
154+
target = experiment['app']
155+
build_dir = experiment['elf'].parent
150156
defines = self.derive_cdefines(experiment)
151157
data_cfg = self.derive_data_cfg(experiment)
152158
hw_cfg = self.derive_hw_cfg(experiment)
153-
build.build(experiment['app'], experiment['elf'].parent, defines=defines,
154-
data_cfg=data_cfg, hw_cfg=hw_cfg)
159+
if 'sw' in self.callbacks:
160+
func = self.callbacks['sw']
161+
else:
162+
func = build.build
163+
print(colored('Build app', 'black', attrs=['bold']),
164+
colored(target, 'cyan', attrs=['bold']),
165+
colored('in', 'black', attrs=['bold']),
166+
colored(build_dir, 'cyan', attrs=['bold']))
167+
process = func(
168+
target=target, build_dir=build_dir, defines=defines,
169+
data_cfg=data_cfg, hw_cfg=hw_cfg, dry_run=dry_run
170+
)
171+
processes.append(process)
172+
common.wait_processes(processes, dry_run=dry_run)
155173

156174
# Run experiments
157175
if 'run' in self.actions or 'all' in self.actions:
@@ -195,11 +213,7 @@ def run(self):
195213
process = common.make('perf', vars, flags=flags, sync=False)
196214
processes.append(process)
197215

198-
# Wait for all processes to complete
199-
for i, process in enumerate(processes):
200-
return_code = process.wait()
201-
if return_code != 0:
202-
raise Exception(f'Failed to generate performance dump for experiment {i}')
216+
common.wait_processes(processes)
203217

204218
# Build visual traces
205219
if 'visual-trace' in self.actions or 'all' in self.actions:
@@ -361,3 +375,46 @@ def get_area_results(self):
361375
df = pd.concat(columns, axis=1)
362376

363377
return df
378+
379+
380+
def derive_axes_from_keys(experiment, keys):
381+
"""Designate some keys in the experiment as the experiment axes."""
382+
return {key: experiment[key] for key in keys}
383+
384+
385+
def derive_data_cfg_from_template(experiment, template_path="cfg.json.tpl",
386+
root_cfg_dir=Path('data').absolute()):
387+
"""Derive the data configuration file from a template.
388+
389+
Fills the template file using the experiment dictionary.
390+
Writes it to a file under the root data configuration directory, creating
391+
a directory hierarchy defined by the experiment name.
392+
The output filename is derived from the template's, stripped of the '.tpl'
393+
suffix, when this suffix exists.
394+
395+
Args:
396+
experiment: Experiment dictionary.
397+
template_path: Path to the template file.
398+
root_cfg_dir: Root data configuration directory.
399+
"""
400+
# Read the template file
401+
with open(template_path, 'r') as f:
402+
template = mako.template.Template(f.read())
403+
404+
# Fill in (render) the template with the experiment parameters
405+
cfg = template.render(experiment=experiment)
406+
407+
# Derive output filename from template basename by stripping a trailing '.tpl'
408+
cfg_name = template_path.stem if template_path.suffix == '.tpl' else template_path.name
409+
410+
# Compose destination path from the root data configuration directory,
411+
# following a directory hierarchy defined by the experiment name
412+
cfg_path = root_cfg_dir / experiment["name"] / cfg_name
413+
cfg_path.parent.mkdir(parents=True, exist_ok=True)
414+
415+
# Write the rendered template to file
416+
with open(cfg_path, 'w') as f:
417+
f.write(cfg)
418+
419+
# Return the path to the rendered configuration file
420+
return cfg_path

0 commit comments

Comments
 (0)