Skip to content

Commit d2b4a39

Browse files
Add usage_filter support to executable modifiers
This commit allows executable_modifiers defined within modifiers to define usage filters. These can currently be set to only apply once, to apply once but only on the first MPI executable, to be applied to only MPI executables (but all of them), or to have no usage filters. Filters will constrain the number of times an executable modifier will be applied within a given experiment.
1 parent f0958c4 commit d2b4a39

File tree

3 files changed

+125
-12
lines changed

3 files changed

+125
-12
lines changed

lib/ramble/ramble/language/modifier_language.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -131,7 +131,7 @@ def _execute_variable_modification(mod):
131131

132132

133133
@modifier_directive("executable_modifiers")
134-
def executable_modifier(name, when=None, **kwargs):
134+
def executable_modifier(name, usage_filter=None, when=None, **kwargs):
135135
"""Register an executable modifier
136136
137137
Executable modifiers can modify various aspects of non-builtin application
@@ -166,6 +166,9 @@ def write_exec_name(self, executable_name, executable, app_inst=None):
166166
Args:
167167
name (str): Name of executable modifier to use. Should be the name of a
168168
class method.
169+
usage_filter (str): Filters the application of this executable modifier.
170+
Modifiers can register filters to select how to apply this.
171+
Valid default options include: None, "once", "first_mpi", "all_mpi"
169172
when (list | None): List of when conditions this executable modifier should apply in
170173
171174
Each executable modifier needs to return:
@@ -185,6 +188,7 @@ def _executable_modifier(mod):
185188
mod.executable_modifiers[when_set] = {}
186189

187190
mod.executable_modifiers[when_set][name] = {
191+
"usage_filter": usage_filter,
188192
"when": when_list,
189193
}
190194

var/ramble/repos/builtin/base_classes/modifier-base/base_class.py

Lines changed: 117 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,7 @@ def __init__(self, file_path):
7070
self.expander = None
7171
self._usage_mode = None
7272
self.app_inst = None
73+
self._executable_modification_applied = set()
7374

7475
self._mod_regex = re.compile(
7576
self._mod_prefix_builtin + f"{self.name}{NS_SEPARATOR}"
@@ -375,22 +376,130 @@ def applies_to_executable(self, executable):
375376

376377
return bool(self._mod_regex.match(executable))
377378

379+
def executable_modifier_applied(self, exec_mod):
380+
"""Check if an executable modifier has been applies alerady
381+
382+
383+
Returns:
384+
(bool): True if the executable modifier has been applied. False otherwise
385+
"""
386+
if exec_mod in self._executable_modification_applied:
387+
return True
388+
return False
389+
390+
def executable_modifier_usage_filter(filter_name: str):
391+
"""Decorator for registering a usage filter for executable modifiers"""
392+
393+
def _decorator(decorated_function):
394+
if not hasattr(decorated_function, "_ramble_attributes"):
395+
decorated_function._ramble_attributes = {}
396+
decorated_function._ramble_attributes["filter_name"] = filter_name
397+
return decorated_function
398+
399+
return _decorator
400+
401+
@executable_modifier_usage_filter("once")
402+
def filter_once(self, exec_mod, executable) -> bool:
403+
"""Usage filter for only allowing an executable modifier to be applied
404+
once in an experiment"""
405+
return not self.executable_modifier_applied(exec_mod)
406+
407+
@executable_modifier_usage_filter("first_mpi")
408+
def filter_first_mpi(self, exec_mod, executable) -> bool:
409+
"""Usage filter for only applying executable modifier to the first MPI
410+
executable in an experiment"""
411+
return executable.mpi and not self.executable_modifier_applied(
412+
exec_mod
413+
)
414+
415+
@executable_modifier_usage_filter("all_mpi")
416+
def filter_all_mpi(self, exec_mod, executable) -> bool:
417+
"""Usage filter for applying an executalbe modifier to only MPI
418+
executables in an experiment"""
419+
return executable.mpi
420+
421+
def get_executable_modifier_filter(self, filter_name):
422+
"""Get the filter function for a usage filter (by name)
423+
424+
Args:
425+
filter_name (str): Name of usage filter to extract for filtering executable modifier
426+
427+
Returns:
428+
Reference to function, if found. None otherwise"""
429+
430+
filter_names = set()
431+
for attr in dir(self):
432+
method = getattr(self, attr)
433+
434+
if callable(method):
435+
method_attributes = getattr(method, "_ramble_attributes", {})
436+
test_filter_name = None
437+
if "filter_name" in method_attributes:
438+
test_filter_name = method_attributes["filter_name"]
439+
filter_names.add(test_filter_name)
440+
if filter_name == test_filter_name:
441+
return method
442+
443+
if filter_name is not None and filter_name != "None":
444+
logger.die(
445+
f"When extracting a usage_filter for an executable_modifier "
446+
f"on modifier {self.name} "
447+
f"the filter {filter_name} does not exist. Registered filters are: \n"
448+
f"{filter_names}"
449+
)
450+
return None
451+
452+
def executable_modification_applies(
453+
self, exec_mod, filter_name, executable
454+
):
455+
"""Determine if an executable modifier applies to an executable or not
456+
457+
Args:
458+
exec_mod (str): Name of executable modifier
459+
filter_name (str): Name of usage filter to apply
460+
executable: CommandExecutable object to check if exec_mod applies to
461+
462+
"""
463+
apply = True
464+
465+
filter_func = self.get_executable_modifier_filter(filter_name)
466+
467+
if filter_func is not None:
468+
apply = filter_func(exec_mod, executable)
469+
470+
return apply
471+
378472
def apply_executable_modifiers(
379473
self, executable_name, executable, app_inst=None
380474
):
475+
"""Apply all executable modifiers to an executable
476+
477+
Args:
478+
executable_name (str): Name of executable
479+
executable: CommandExecutable object
480+
app_inst: Instance of application object
481+
482+
Returns
483+
(list, list): List of CommandExecutable objects that occur before
484+
and after (respectively) to the input executable.
485+
"""
381486
pre_execs = []
382487
post_execs = []
383488
for when_set, exec_mods in self.executable_modifiers.items():
384489
if self.expander.satisfies(when_set, self.experiment_variants()):
385-
for exec_mod in exec_mods:
386-
mod_func = getattr(self, exec_mod)
387-
388-
pre_exec, post_exec = mod_func(
389-
executable_name, executable, app_inst=app_inst
390-
)
490+
for exec_mod, mod_conf in exec_mods.items():
491+
if self.executable_modification_applies(
492+
exec_mod, mod_conf["usage_filter"], executable
493+
):
494+
self._executable_modification_applied.add(exec_mod)
495+
mod_func = getattr(self, exec_mod)
496+
497+
pre_exec, post_exec = mod_func(
498+
executable_name, executable, app_inst=app_inst
499+
)
391500

392-
pre_execs.extend(pre_exec)
393-
post_execs.extend(post_exec)
501+
pre_execs.extend(pre_exec)
502+
post_execs.extend(post_exec)
394503

395504
return pre_execs, post_execs
396505

var/ramble/repos/builtin/modifiers/intel-aps/modifier.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,7 @@ class IntelAps(BasicModifier):
8080

8181
modifier_variable(
8282
"apply_aps_exe_regex",
83-
default="",
83+
default=".*",
8484
description="apply aps to executables that match with the regex",
8585
mode="mpi",
8686
)
@@ -115,7 +115,7 @@ class IntelAps(BasicModifier):
115115

116116
required_package("intel-oneapi-vtune")
117117

118-
executable_modifier("aps_summary")
118+
executable_modifier("aps_summary", usage_filter="all_mpi")
119119

120120
def aps_summary(self, executable_name, executable, app_inst=None):
121121
from ramble.util.executable import CommandExecutable
@@ -125,7 +125,7 @@ def aps_summary(self, executable_name, executable, app_inst=None):
125125

126126
exe_regex = self.expander.expand_var_name("apply_aps_exe_regex")
127127
applicable = exe_regex and re.match(exe_regex, executable_name)
128-
if applicable or executable.mpi:
128+
if applicable:
129129
env_var_name = self.expander.expand_var_name(
130130
"external_aps_env_var"
131131
)

0 commit comments

Comments
 (0)