Skip to content

Commit cb35bb8

Browse files
committed
feat: add logfile and errfile parameters to ensemble run for optional targeted stdout/stderr filenames for ensembles
1 parent a51fc87 commit cb35bb8

1 file changed

Lines changed: 64 additions & 12 deletions

File tree

ipsframework/services.py

Lines changed: 64 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -2279,15 +2279,40 @@ def submit_tasks(
22792279
submitted.
22802280
22812281
Optionally, dask can be used to schedule and run the task pool.
2282+
2283+
:param task_pool_name: name of task pool to submit
2284+
:param block: if True, return when all tasks have been launched.
2285+
If False, return when all tasks that can be launched immediately have
2286+
been launched.
2287+
:param use_dask: if True, use dask to schedule and run the task pool
2288+
:param dask_nodes: if using dask, number of nodes to use
2289+
:param dask_ppw: if using dask, number of processes per worker to use;
2290+
if None, use number of cores per node
2291+
:param launch_interval: number of seconds to wait between launching tasks
2292+
:param use_shifter: if True, use shifter to run tasks in a container
2293+
:param shifter_args: args for running under shifter
2294+
:param dask_worker_plugin: plugin class for Dask workers
2295+
:param dask_worker_per_gpu: how many Dask workers per GPU?
2296+
:param oversubscribe: if True, oversubscribe available resources
2297+
:param hwthreads: if True, use hardware threads as the basis for
2298+
resource allocation; if False, use physical cores as the basis for
2299+
resource allocation
2300+
:returns: task return value
22822301
"""
22832302
start_time = time.time()
2284-
self._send_monitor_event('IPS_TASK_POOL_BEGIN', 'task_pool = %s ' % task_pool_name)
2303+
self._send_monitor_event('IPS_TASK_POOL_BEGIN',
2304+
'task_pool = %s ' % task_pool_name)
22852305
task_pool: TaskPool = self.task_pools[task_pool_name]
22862306
retval = task_pool.submit_tasks(
2287-
block, use_dask, dask_nodes, dask_ppw, launch_interval, use_shifter, shifter_args, dask_worker_plugin, dask_worker_per_gpu, oversubscribe, hwthreads
2307+
block, use_dask, dask_nodes, dask_ppw, launch_interval,
2308+
use_shifter, shifter_args, dask_worker_plugin,
2309+
dask_worker_per_gpu, oversubscribe, hwthreads
22882310
)
22892311
elapsed_time = time.time() - start_time
2290-
self._send_monitor_event('IPS_TASK_POOL_END', 'task_pool = %s elapsed time = %.2f S' % (task_pool_name, elapsed_time), elapsed_time=elapsed_time)
2312+
self._send_monitor_event('IPS_TASK_POOL_END',
2313+
'task_pool = %s elapsed time = %.2f S' % (
2314+
task_pool_name, elapsed_time),
2315+
elapsed_time=elapsed_time)
22912316
return retval
22922317

22932318
def get_finished_tasks(self, task_pool_name: str):
@@ -2429,6 +2454,8 @@ def run_ensemble(
24292454
cores_per_instance: Optional[int] = None,
24302455
oversubscribe: bool = False,
24312456
hwthreads: bool = False,
2457+
logfile: Union[str, os.PathLike] = None,
2458+
errfile: Union[str, os.PathLike] = None,
24322459
):
24332460
"""Run ensemble of simulations given the template and variables.
24342461
@@ -2456,6 +2483,14 @@ def run_ensemble(
24562483
config file created from `template` with `?` variables replaced
24572484
with the values from `variables`.
24582485
2486+
.. seealso::
2487+
:py:func:`ipsutil.params_from_csv()` for a convenient way to generate
2488+
the `variables` dict from a csv file.)
2489+
2490+
.. note::
2491+
`logfile` and `errfile` will write to the working directory local
2492+
to the ensemble instance.
2493+
24592494
TODO be able to specify the number of cores per instance
24602495
24612496
:param template: configuration template file
@@ -2470,6 +2505,8 @@ def run_ensemble(
24702505
:param oversubscribe: Whether to allow oversubscription of nodes
24712506
when launching the ensemble runs. Default is False.
24722507
:param hwthreads: Whether to use hardware threads
2508+
:param logfile: Optional file name in which to write stdout
2509+
:param errfile: Optional file name in which to write stderr
24732510
:returns: a list of dicts mapping created subdirs to simulation names
24742511
and their parameters
24752512
"""
@@ -2737,13 +2774,20 @@ def send_ensemble_instance_to_portal(ensemble_name: str, data_path: Path) -> Non
27372774
# IPS run pointed to that config file.
27382775
args = [f'--simulation={simulation_filename}', f'--log={log_file}', f'--platform={platform_filename!s}']
27392776

2777+
kwargs = {} # optionally add logfile and errfile
2778+
if logfile:
2779+
kwargs['logfile'] = logfile
2780+
if errfile:
2781+
kwargs['errfile'] = errfile
2782+
2783+
27402784
if self.fwk.logger.getEffectiveLevel() == logging.DEBUG:
27412785
# If we're in debug mode, then also pass the debug flag.
27422786
# May as well pass in the --verbose, too.
27432787
args.insert(1, '--debug')
27442788
args.insert(1, '--verbose')
27452789

2746-
self.add_task(task_pool_name, instance[0], 1, working_dir, 'ips.py', *args)
2790+
self.add_task(task_pool_name, instance[0], 1, working_dir, 'ips.py', *args, keywords=kwargs)
27472791

27482792
try:
27492793
# Note that we *always* use Dask to run the ensemble tasks
@@ -3014,7 +3058,7 @@ def submit_dask_tasks(
30143058
dask_worker_plugin=None,
30153059
dask_worker_per_gpu=False,
30163060
oversubscribe=False,
3017-
hwthreads=False,
3061+
hwthreads=False
30183062
):
30193063
"""Launch tasks in *queued_tasks* using dask.
30203064
@@ -3310,7 +3354,7 @@ def submit_tasks(
33103354
dask_worker_plugin=None,
33113355
dask_worker_per_gpu=False,
33123356
oversubscribe=False,
3313-
hwthreads=False,
3357+
hwthreads=False
33143358
):
33153359
"""Launch tasks in *queued_tasks*. Finished tasks are handled before
33163360
launching new ones. If *block* is ``True``, the number of
@@ -3349,22 +3393,29 @@ def submit_tasks(
33493393
:param hwthreads: If True then use hardware threads when launching
33503394
tasks. Default is False.
33513395
:type hwthreads: bool
3396+
:returns:
33523397
"""
3353-
33543398
if use_dask:
33553399
if TaskPool.dask and TaskPool.distributed and self.serial_pool:
33563400
self.dask_pool = True
33573401
if use_shifter and not self.shifter:
3358-
self.services.error('Requested to run dask within shifter but shifter not available')
3402+
self.services.error('Requested to run dask within shifter '
3403+
'but shifter not available')
33593404
raise RuntimeError('shifter not found')
33603405
else:
33613406
return self.submit_dask_tasks(
3362-
block, dask_nodes, dask_ppw, use_shifter, shifter_args, dask_worker_plugin, dask_worker_per_gpu, oversubscribe, hwthreads
3407+
block, dask_nodes, dask_ppw, use_shifter,
3408+
shifter_args, dask_worker_plugin,
3409+
dask_worker_per_gpu, oversubscribe, hwthreads
33633410
)
33643411
elif not TaskPool.dask or not TaskPool.distributed:
3365-
raise RuntimeError('Requested use_dask but cannot because import dask or distributed failed')
3412+
raise RuntimeError(
3413+
'Requested use_dask but cannot because import dask or '
3414+
'distributed failed')
33663415
elif not self.serial_pool:
3367-
self.services.warning('Requested use_dask but cannot because multiple processors requested')
3416+
self.services.warning(
3417+
'Requested use_dask but cannot because multiple '
3418+
'processors requested')
33683419

33693420
submit_count = 0
33703421
# Make sure any finished tasks are handled before attempting to submit
@@ -3373,7 +3424,8 @@ def submit_tasks(
33733424
while True:
33743425
if len(self.queued_tasks) == 0:
33753426
break
3376-
active_tasks = self.services.launch_task_pool(self.name, launch_interval)
3427+
active_tasks = self.services.launch_task_pool(self.name,
3428+
launch_interval)
33773429
for task_name, task_id in active_tasks.items():
33783430
self.active_tasks[task_id] = self.queued_tasks.pop(task_name)
33793431
submit_count += 1

0 commit comments

Comments
 (0)