Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
9552d34
Check point on threaded local script adapter
jwhite242 Apr 10, 2020
9f41c29
Merge branch 'develop' into parallel_local
jwhite242 Apr 10, 2020
0fe1b35
Fixes and hacks to get demo spec running.
jwhite242 Apr 11, 2020
92709b1
Update future callbacks, exclude threadpool from pickling
jwhite242 Apr 14, 2020
5c04a33
Some dirty hacks to deal with hidden pending state.
jwhite242 Apr 15, 2020
04906cd
Remove unneeded hooks for command line local_procs parameter
jwhite242 Apr 20, 2020
b9892a9
correctly set avail_procs and total_procs for local parallel adapter
jagreenough Apr 24, 2020
6daa109
Enable cancellation of processes, fix cancel status marking
jwhite242 Apr 24, 2020
7c8e849
Merge branch 'parallel_local' of https://wci-git.llnl.gov/maestro/mae…
jwhite242 Apr 24, 2020
579d629
Update jobid to use uuid, fix handling of future completion and job
jwhite242 May 5, 2020
e1eaa80
Avoid popping ready steps until sure it will run, add early exit of
jwhite242 May 6, 2020
2db2510
Check point on threaded local script adapter
jwhite242 Apr 10, 2020
492dc11
Fixes and hacks to get demo spec running.
jwhite242 Apr 11, 2020
c121010
Update future callbacks, exclude threadpool from pickling
jwhite242 Apr 14, 2020
9232b7b
Some dirty hacks to deal with hidden pending state.
jwhite242 Apr 15, 2020
2d32a51
Remove unneeded hooks for command line local_procs parameter
jwhite242 Apr 20, 2020
1383c37
Enable cancellation of processes, fix cancel status marking
jwhite242 Apr 24, 2020
83739e2
correctly set avail_procs and total_procs for local parallel adapter
jagreenough Apr 24, 2020
7ba9ed3
Update jobid to use uuid, fix handling of future completion and job
jwhite242 May 5, 2020
bdc2d0b
Avoid popping ready steps until sure it will run, add early exit of
jwhite242 May 6, 2020
fecc7a7
Removal of stray local_procs variables
May 14, 2020
1544696
Removal of more stray local_procs
May 14, 2020
be4ced0
Porting of some missed variables in rebase.
May 14, 2020
2114730
Merge branch 'parallel_local' into 'rebase/parallel_local'
May 14, 2020
1eae4d5
Fix previous broken rebase...
jwhite242 Jun 12, 2020
529764f
Merge branch 'develop' into parallel_local
jwhite242 Feb 2, 2022
b2c51af
Fix incorrect variable
jwhite242 Feb 2, 2022
a4bbd4a
Initial pass hooking up launcher token replacement, fix up broken inh…
jwhite242 Feb 8, 2022
696bf07
Add rich repr to studystep for better debugging
jwhite242 Feb 28, 2022
e863bca
Initial test of script writer
jwhite242 Feb 28, 2022
517d851
Hook up slurm parallelize cmd and enable test of its use in local par…
jwhite242 Feb 28, 2022
5be3ae7
Merge branch 'develop' into parallel_local
jwhite242 Oct 17, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions maestrowf/abstracts/interfaces/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,8 @@
"""

from maestrowf.abstracts.interfaces.schedulerscriptadapter import \
SchedulerScriptAdapter
SchedulerScriptAdapter, ParallelizeCmd
from maestrowf.abstracts.interfaces.scriptadapter import ScriptAdapter


__all__ = ("SchedulerScriptAdapter", "ScriptAdapter")
__all__ = ("SchedulerScriptAdapter", "ScriptAdapter", "ParallelizeCmd")
33 changes: 33 additions & 0 deletions maestrowf/abstracts/interfaces/schedulerscriptadapter.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,21 @@
LOGGER = logging.getLogger(__name__)


@six.add_metaclass(ABCMeta)
class ParallelizeCmd():
"""
Callable object which generates the concrete scheduler task
launch commands.
"""
def __init__(self, cmd_flags=None, unsupported=None):
self._cmd_flags = {}
self._unsupported = {}

@abstractmethod
def __call__(self, procs, nodes, **kwargs):
pass


@six.add_metaclass(ABCMeta)
class SchedulerScriptAdapter(ScriptAdapter):
"""
Expand Down Expand Up @@ -82,6 +97,9 @@ def __init__(self, **kwargs):
super(SchedulerScriptAdapter, self).__init__(**kwargs)
self._batch = {}

# Store launcher -> concrete parallel command mappings
self._cmd_flags = {}

def add_batch_parameter(self, name, value):
"""
Add a parameter to the ScriptAdapter instance.
Expand All @@ -92,6 +110,17 @@ def add_batch_parameter(self, name, value):
"""
self._batch[name] = value

def add_cmd_flags(self, name, value):
"""
Add command flags to the ScriptAdapter instance. Command flags
map the launcher tokens to specific parallel invocations
"""
if name in self._cmd_flags:
LOGGER.info(f"Overwriting '{name}' value ('{self._cmd_flags[name]}"
f"') in cmd_flags with '{value}'")

self._cmd_flags[name] = value

@abstractmethod
def get_header(self, step):
"""
Expand All @@ -103,6 +132,10 @@ def get_header(self, step):
"""
pass

def register_parallelize_command(self, parallelize_func):
# Note do some validation here -> callable types only
self._parallelize_func = parallelize_func()

@abstractmethod
def get_parallelize_command(self, procs, nodes, **kwargs):
"""
Expand Down
61 changes: 57 additions & 4 deletions maestrowf/datastructures/core/executiongraph.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
import tempfile
from time import sleep
from filelock import FileLock, Timeout
from concurrent import futures

from maestrowf.abstracts import PickleInterface
from maestrowf.abstracts.enums import JobStatusCode, State, SubmissionCode, \
Expand Down Expand Up @@ -362,6 +363,7 @@ def __init__(self, submission_attempts=1, submission_throttle=0,
self._submission_throttle = submission_throttle
self.dry_run = dry_run


# A map that tracks the dependencies of a step.
# NOTE: I don't know how performant the Python dict structure is, but
# we'll use it for now. I think this may want to be changed to an AVL
Expand Down Expand Up @@ -453,6 +455,7 @@ def set_adapter(self, adapter):
msg = "'{}' adapter must be specfied in ScriptAdapterFactory." \
.format(adapter)
LOGGER.error(msg)
LOGGER.error("Valid adapters: {}".format(ScriptAdapterFactory.get_valid_adapters()))
raise TypeError(msg)

self._adapter = adapter
Expand Down Expand Up @@ -534,7 +537,9 @@ def generate_scripts(self):

# Set up the adapter.
LOGGER.info("Generating scripts...")
adapter = ScriptAdapterFactory.get_adapter(self._adapter["type"])
# adapter = ScriptAdapterFactory.get_adapter(self._adapter["type"],
# self._adapter.get('parallel_type')
adapter = ScriptAdapterFactory.get_adapter(self._adapter["type"])
adapter = adapter(**self._adapter)

self._check_tmp_dir()
Expand Down Expand Up @@ -893,9 +898,19 @@ def execute_ready_steps(self):

# We now have a collection of ready steps. Execute.
# If we don't have a submission limit, go ahead and submit all.
# Check requested resources -> nprocs
# nthreads = 1
# if self._local_procs > 0:
# nthreads = self._local_procs

# if adapter.total_procs != nthreads:
# adapter.total_procs = nthreads
# adapter.avail_procs = nthreads

if self._submission_throttle == 0:
LOGGER.info("Launching all ready steps...")
_available = len(self.ready_steps)
LOGGER.info("Ready steps: {}".format(self.ready_steps))
# Else, we have a limit -- adhere to it.
else:
# Compute the number of available slots we have for execution.
Expand All @@ -909,9 +924,23 @@ def execute_ready_steps(self):
_available = min(_available, len(self.ready_steps))
LOGGER.info("Found %d available slots...", _available)

# for i in range(0, _available):
# # Pop the record and execute using the helper method.
# _record = self.values[self.ready_steps.popleft()]

# # If we get to this point and we've cancelled, cancel the record.
# if self.is_canceled:
# logger.info("Cancelling '%s' -- continuing.", _record.name)
# _record.mark_end(State.CANCELLED)
# self.cancelled_steps.add(_record.name)
# continue

# logger.debug("Launching job %d -- %s", i, _record.name)
# self._execute_record(_record, adapter)

for i in range(0, _available):
# Pop the record and execute using the helper method.
_record = self.values[self.ready_steps.popleft()]
_record = self.values[self.ready_steps[0]]

# If we get to this point and we've cancelled, cancel the record.
if self.is_canceled:
Expand All @@ -920,8 +949,32 @@ def execute_ready_steps(self):
self.cancelled_steps.add(_record.name)
continue

LOGGER.debug("Launching job %d -- %s", i, _record.name)
self._execute_record(_record, adapter)
# NOTE: verify this actually updates avail_procs on the fly, thus allowing the
# available tasks to be fully consumed before going back to sleep
LOGGER.debug("Attempting to submit step {} with total procs = {}, available procs = {}".format(_record.step.name, adapter.total_procs, adapter.avail_procs))
avail_procs = adapter.avail_procs
LOGGER.debug("avail_procs from the adapter = %d", avail_procs)
LOGGER.debug("total_procs from the adapter = %d", adapter.total_procs)
step_procs = _record.step.run.get("procs")
if not step_procs:
step_procs = 1
else:
try:
step_procs = int(step_procs)
except ValueError:
step_procs = 1
LOGGER.error("Setting step {} with no 'procs' attribute to use 1 processor.".format(_record.step.name))

# NOTE: better place to set this default, and do type conversions?
if step_procs <= avail_procs:
LOGGER.debug("Launching job %d -- %s", i, _record.name)
self.ready_steps.popleft() # remove key now that it's sure to run
self._execute_record(_record, adapter)
else:
LOGGER.debug("step_procs thought > avail_procs: %d : %d", step_procs, avail_procs)
if avail_procs == 0:
break # exit loop early to avoid needless churn


# check the status of the study upon finishing this round of execution
completion_status = self._check_study_completion()
Expand Down
13 changes: 13 additions & 0 deletions maestrowf/datastructures/core/study.py
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,19 @@ def __ne__(self, other):
"""
return not self.__eq__(other)

def __rich_repr__(self):
"""
Unique representation of this StudyStep object structure for
pretty printing with the Rich library

NOTE: does this account for parameter expanded steps?
"""
yield 'name', self.name
yield 'description', self.description
yield 'nickname', self.nickname
yield 'run', self.run



class Study(DAG, PickleInterface):
"""
Expand Down
55 changes: 52 additions & 3 deletions maestrowf/interfaces/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,7 @@
import logging
import pkgutil
import inspect
from maestrowf.abstracts.interfaces import ScriptAdapter

from maestrowf.abstracts.interfaces import ScriptAdapter, ParallelizeCmd

__all__ = ("ScriptAdapterFactory",)
LOGGER = logging.getLogger(__name__)
Expand All @@ -52,6 +51,7 @@ def iter_adapters():
mods = [(name, ispkg) for finder, name, ispkg in pkgutil.iter_modules(
loader.load_module('maestrowf.interfaces.script').__path__,
loader.load_module('maestrowf.interfaces.script').__name__ + ".")]

cs = []
for name, _ in mods:
# get loader for every module
Expand All @@ -61,6 +61,42 @@ def iter_adapters():
if isinstance(cls, type) and issubclass(cls, ScriptAdapter) and \
not inspect.isabstract(cls):
cs.append(cls)
print("FOUND CLASS '{}' with key '{}'".format(cls.__name__, cls.key))
if isinstance(cls, type) and issubclass(cls, ScriptAdapter):
print("FOUND CLASS '{}'".format(cls.__name__))
LOGGER.debug("Found class '{}'".format(cls.__name__))

return cs


def iter_parallel_cmds():
"""
Based off of packaging.python.org loop over a namespace and find the
modules. This has been adapted for this particular use case of loading
all classes implementing ParallelizeCmd loaded from all modules in
maestrowf.interfaces.script.
:return: an iterable of the classes existing in the namespace
"""
# get loader for the script adapter package
loader = pkgutil.get_loader('maestrowf.interfaces.script')
# get all of the modules in the package
mods = [(name, ispkg) for finder, name, ispkg in pkgutil.iter_modules(
loader.load_module('maestrowf.interfaces.script').__path__,
loader.load_module('maestrowf.interfaces.script').__name__ + ".")]

cs = []
for name, _ in mods:
# get loader for every module
m = pkgutil.get_loader(name).load_module(name)
# get all classes that implement ParallelizeCmd and are not abstract
for n, cls in m.__dict__.items():
if isinstance(cls, type) and issubclass(cls, ParallelizeCmd) and \
not inspect.isabstract(cls):
cs.append(cls)
print("FOUND CLASS '{}' with key '{}'".format(cls.__name__, cls.key))
if isinstance(cls, type) and issubclass(cls, ParallelizeCmd):
print("FOUND CLASS '{}'".format(cls.__name__))
LOGGER.debug("Found class '{}'".format(cls.__name__))

return cs

Expand All @@ -70,15 +106,28 @@ class ScriptAdapterFactory(object):
adapter.key: adapter for adapter in iter_adapters()
}

parallel_cmd_factories = {
parallel_cmd.key: parallel_cmd for parallel_cmd in iter_parallel_cmds()
}

@classmethod
def get_adapter(cls, adapter_id):
def get_adapter(cls, adapter_id, parallel_type=None):
if adapter_id.lower() not in cls.factories:
msg = "Adapter '{0}' not found. Specify an adapter that exists " \
"or implement a new one mapping to the '{0}'" \
.format(str(adapter_id))
LOGGER.error(msg)
raise Exception(msg)

# adapter = cls.factories[adapter_id]

# # Attach any specified parallelize cmd generators
# if parallel_type and adapter_id in cls.parallel_cmd_factories:
# adapter.register_parallelize_command(
# cls.parallel_cmd_factories[adapter_id]
# )

print(f"Returning: {cls.factories[adapter_id]}")
return cls.factories[adapter_id]

@classmethod
Expand Down
Loading