diff --git a/README.md b/README.md index 0c4d82a1768f6184170ec6cc5712ca66c919ac1c..f8f19d403be02c7cbafc05a7869f3beebd3d06fa 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ ## Dependencies -The runner script requires the Python packages `PyYaml` and `click`. +The runner script requires the Python packages `PyYaml`, `structlog`, and `click`. On Debian they can be install via the package manager as follows. ``` -# apt install python3-yaml python3-click +# apt install python3-yaml python3-structlog python3-click ``` diff --git a/run-exp.py b/run-exp.py index 6eda8734591930306b37e129f245dfb01fa8df8a..9b1a1b0e825c07576b91e0e955b4ee6324ae1530 100755 --- a/run-exp.py +++ b/run-exp.py @@ -1,15 +1,19 @@ #!/usr/bin/env python3 -import itertools +import json import pathlib import subprocess from abc import ABC, abstractmethod import click +import structlog import yaml +logger = structlog.get_logger() + + class Variable(ABC): def __init__(self, name): self._name = name @@ -87,7 +91,19 @@ class Task: def work_dir(self): return self._work_dir + def __repr__(self): + return json.dumps({ + 'name': self._name, + 'work-dir': str(self._work_dir), + 'base-command': self._base_command, + 'parameters': self._parameters, + 'timeout': self._timeout, + }) + def run(self, variables, global_timeout, dry_run): + log = logger.bind(task = self, variables = variables) + log.info("Preparing task") + if dry_run: args = ["echo"] else: @@ -115,6 +131,7 @@ class Task: args.append(p["value"].format(**variables)) + log = log.bind(args = args) # print() # print(f"pushd {self._work_dir}") # print(" ".join(args)) @@ -125,6 +142,9 @@ class Task: elif global_timeout is not None: timeout = global_timeout + log = log.bind(timeout = timeout) + log.info("Running task") + try: subprocess.run( " ".join(args), @@ -135,9 +155,7 @@ class Task: return False except subprocess.TimeoutExpired: - print() - print("Task timed out!") - print() + log.error("Task timed out!") return True @@ -185,6 +203,14 @@ class Config: def tasks(self): return self._tasks + def __repr__(self): + return json.dumps({ + 'name': self._name, + 'repetitions': self._repetitions, + 'variables': [repr(v) for v in self._variables], + 'tasks': [repr(t) for t in self._tasks], + }) + @staticmethod def parse_var_definition(name, value_config): if isinstance(value_config, list): @@ -235,11 +261,10 @@ class Config: def main(config_file, global_timeout, dry_run): c = Config.from_file(config_file) - print(f"> Running experiment \"{c.name}\"") - print(f"> with {len(c.variables)} variables") - print(f"> and {len(c.tasks)} tasks") - print(f"> and {c.repetitions} repetitions") - print() + logger.info( + f"Running experiment \"{c.name}\"", + config = c, + ) # we precompute all variable values to be able to abort early in case of a problem, # for instance, if a file does not exist @@ -259,19 +284,12 @@ def main(config_file, global_timeout, dry_run): variable_maps = updated_variable_maps for variables in variable_maps: - print() - print("Running tasks with variable map: ") - print(variables) - print() - for rep in range(c.repetitions): - print(f"Repetition {rep}") + # print(f"Repetition {rep}") timed_out = False for task in c.tasks: - print(f"Running task {task.name}") timed_out = task.run(variables, global_timeout, dry_run) - print() if timed_out: # repetitions are useless if tasks time out break