lsst.ctrl.pool  14.0-1-ga2912ff+12
parallel.py
Go to the documentation of this file.
1 #!/usr/bin/env python
2 
3 from __future__ import print_function
4 from builtins import object
5 
6 import re
7 import os
8 import os.path
9 import stat
10 import sys
11 import pipes
12 import tempfile
13 import argparse
14 import traceback
15 import contextlib
16 from lsst.pipe.base import CmdLineTask, TaskRunner
17 from .pool import startPool, Pool, NODE, abortOnError, setBatchType
18 from . import log # register pickle functions for log
19 
20 __all__ = ["Batch", "PbsBatch", "SlurmBatch", "SmpBatch", "BATCH_TYPES", "BatchArgumentParser",
21  "BatchCmdLineTask", "BatchPoolTask", ]
22 
23 UMASK = 0o002 # umask to set
24 
25 # Functions to convert a list of arguments to a quoted shell command, provided by Dave Abrahams
26 # http://stackoverflow.com/questions/967443/python-module-to-shellquote-unshellquote
27 _quote_pos = re.compile('(?=[^-0-9a-zA-Z_./\n])')
28 
29 
30 def shQuote(arg):
31  r"""Quote the argument for the shell.
32 
33  >>> quote('\t')
34  '\\\t'
35  >>> quote('foo bar')
36  'foo\\ bar'
37  """
38  # This is the logic emacs uses
39  if arg:
40  return _quote_pos.sub('\\\\', arg).replace('\n', "'\n'")
41  else:
42  return "''"
43 
44 
46  """Convert a list of shell arguments to a shell command-line"""
47  return ' '.join([shQuote(a) for a in args])
48 
49 
51  """Collect Linux-specific process statistics
52 
53  Parses the /proc/self/status file (N.B. Linux-specific!) into a dict
54  which is returned.
55  """
56  result = {}
57  with open("/proc/self/status") as f:
58  for line in f:
59  key, _, value = line.partition(":")
60  result[key] = value.strip()
61  return result
62 
63 
65  """Print the process statistics to the log"""
66  from lsst.log import Log
67  log = Log.getDefaultLogger()
68  log.info("Process stats for %s: %s" % (NODE, processStats()))
69 
70 
71 class Batch(object):
72  """Base class for batch submission"""
73 
74  def __init__(self, outputDir=None, numNodes=0, numProcsPerNode=0, numCores=0, queue=None, jobName=None,
75  walltime=0.0, dryrun=False, doExec=False, mpiexec="", submit=None, options=None,
76  verbose=False):
77  """!Constructor
78 
79  @param outputDir: output directory, or None
80  @param numNodes: number of nodes
81  @param numProcsPerNode: number of processors per node
82  @param numCores: number of cores (Slurm, SMP only)
83  @param queue: name of queue, or None
84  @param jobName: name of job, or None
85  @param walltime: maximum wall clock time for job
86  @param dryrun: Dry run (only print actions that would be taken)?
87  @param doExec: exec the script instead of submitting to batch system?
88  @param mpiexec: options for mpiexec
89  @param submit: command-line options for batch submission (e.g., for qsub, sbatch)
90  @param options: options to append to script header (e.g., #PBS or #SBATCH)
91  @param verbose: produce verbose output?
92  """
93  if (numNodes <= 0 or numProcsPerNode <= 0) and numCores <= 0:
94  raise RuntimeError("Must specify numNodes+numProcs or numCores")
95 
96  self.outputDir = outputDir
97  self.numNodes = numNodes
98  self.numProcsPerNode = numProcsPerNode
99  self.numCores = numCores
100  self.queue = queue
101  self.jobName = jobName
102  self.walltime = walltime
103  self.dryrun = dryrun
104  self.doExec = doExec
105  self.mpiexec = mpiexec
106  self.submit = submit
107  self.options = options
108  self.verbose = verbose
109 
110  def shebang(self):
111  return "#!/bin/bash"
112 
113  def preamble(self, command, walltime=None):
114  """Return preamble string for script to be submitted
115 
116  Most batch systems allow you to embed submission options as comments here.
117  """
118  raise NotImplementedError("Not implemented for base class")
119 
120  def execution(self, command):
121  """Return execution string for script to be submitted"""
122  script = [exportEnv(),
123  "umask %03o" % UMASK,
124  "cd %s" % pipes.quote(os.getcwd()),
125  ]
126  if self.verbose:
127  script += ["echo \"mpiexec is at: $(which mpiexec)\"",
128  "ulimit -a",
129  "echo 'umask: ' $(umask)",
130  "eups list -s",
131  "export",
132  "date",
133  ]
134  script += ["mpiexec %s %s" % (self.mpiexec, command)]
135  if self.verbose:
136  script += ["date",
137  "echo Done.",
138  ]
139  return "\n".join(script)
140 
141  def createScript(self, command, walltime=None):
142  """!Create script to be submitted
143 
144  @param command: command to run
145  @param walltime: maximum wall clock time, overrides value to constructor
146  @return name of script on filesystem
147  """
148  fd, scriptName = tempfile.mkstemp()
149  with os.fdopen(fd, "w") as f:
150  f.write(self.shebang())
151  f.write('\n')
152  f.write(self.preamble(walltime))
153  f.write('\n')
154  f.write(self.execution(command))
155  f.write('\n')
156 
157  os.chmod(scriptName, stat.S_IRUSR | stat.S_IWUSR | stat.S_IXUSR)
158  return scriptName
159 
160  def submitCommand(self, scriptName):
161  """!Return command to submit script
162 
163  @param scriptName: name of script on filesystem
164  """
165  raise NotImplementedError("No implementation for base class")
166 
167  def run(self, command, walltime=None):
168  """!Run the batch system
169 
170  Creates and submits the script to execute the provided command
171 
172  @param command: command to run
173  @param walltime: maximum wall clock time, overrides value to constructor
174  @return name of script on filesystem
175  """
176  scriptName = self.createScript(command, walltime=walltime)
177  command = self.submitCommand(scriptName)
178  if self.dryrun:
179  print("Would run: %s" % command)
180  elif self.doExec:
181  os.execl(scriptName, scriptName)
182  else:
183  os.system(command)
184  return scriptName
185 
186 
188  """Batch submission with PBS"""
189 
190  def preamble(self, walltime=None):
191  if walltime is None:
192  walltime = self.walltime
193  if walltime <= 0:
194  raise RuntimeError("Non-positive walltime: %s (did you forget '--time'?)" % (walltime,))
195  if self.numNodes <= 0 or self.numProcsPerNode <= 0:
196  raise RuntimeError(
197  "Number of nodes (--nodes=%d) or number of processors per node (--procs=%d) not set" %
198  (self.numNodes, self.numProcsPerNode))
199  if self.numCores > 0:
200  raise RuntimeError("PBS does not support setting the number of cores")
201  return "\n".join([
202  "#PBS %s" % self.options if self.options is not None else "",
203  "#PBS -l nodes=%d:ppn=%d" % (self.numNodes, self.numProcsPerNode),
204  "#PBS -l walltime=%d" % walltime if walltime is not None else "",
205  "#PBS -o %s" % self.outputDir if self.outputDir is not None else "",
206  "#PBS -N %s" % self.jobName if self.jobName is not None else "",
207  "#PBS -q %s" % self.queue if self.queue is not None else "",
208  "#PBS -j oe",
209  "#PBS -W umask=%03o" % UMASK,
210  ])
211 
212  def submitCommand(self, scriptName):
213  return "qsub %s -V %s" % (self.submit if self.submit is not None else "", scriptName)
214 
215 
217  """Batch submission with Slurm"""
218 
219  def preamble(self, walltime=None):
220  if walltime is None:
221  walltime = self.walltime
222  if walltime <= 0:
223  raise RuntimeError("Non-positive walltime: %s (did you forget '--time'?)" % (walltime,))
224  if (self.numNodes <= 0 or self.numProcsPerNode <= 0) and self.numCores <= 0:
225  raise RuntimeError(
226  "Number of nodes (--nodes=%d) and number of processors per node (--procs=%d) not set OR "
227  "number of cores (--cores=%d) not set" % (self.numNodes, self.numProcsPerNode, self.numCores))
228  if self.numCores > 0 and (self.numNodes > 0 or self.numProcsPerNode > 0):
229  raise RuntimeError("Must set either --nodes,--procs or --cores: not both")
230 
231  outputDir = self.outputDir if self.outputDir is not None else os.getcwd()
232  filename = os.path.join(outputDir, (self.jobName if self.jobName is not None else "slurm") + ".o%j")
233  return "\n".join([("#SBATCH --nodes=%d" % self.numNodes) if self.numNodes > 0 else "",
234  ("#SBATCH --ntasks-per-node=%d" % self.numProcsPerNode) if
235  self.numProcsPerNode > 0 else "",
236  ("#SBATCH --ntasks=%d" % self.numCores) if self.numCores > 0 else "",
237  "#SBATCH --time=%d" % max(walltime/60.0 + 0.5, 1) if walltime is not None else "",
238  "#SBATCH --job-name=%s" % self.jobName if self.jobName is not None else "",
239  "#SBATCH -p %s" % self.queue if self.queue is not None else "",
240  "#SBATCH --output=%s" % filename,
241  "#SBATCH --error=%s" % filename,
242  "#SBATCH %s" % self.options if self.options is not None else "",
243  ])
244 
245  def submitCommand(self, scriptName):
246  return "sbatch %s %s" % (self.submit if self.submit is not None else "", scriptName)
247 
248 
250  """Not-really-Batch submission with multiple cores on the current node
251 
252  The job is run immediately.
253  """
254 
255  def __init__(self, *args, **kwargs):
256  super(SmpBatch, self).__init__(*args, **kwargs)
257  if self.numNodes in (0, 1) and self.numProcsPerNode > 0 and self.numCores == 0:
258  # --nodes=1 --procs=NN being used as a synonym for --cores=NN
259  self.numNodes = 0
260  self.numCores = self.numProcsPerNode
262  if self.numNodes > 0 or self.numProcsPerNode > 0:
263  raise RuntimeError("SMP does not support the --nodes and --procs command-line options; "
264  "use --cores to specify the number of cores to use")
265  if self.numCores > 1:
266  self.mpiexec = "%s -n %d" % (self.mpiexec if self.mpiexec is not None else "", self.numCores)
267  else:
268  self.mpiexec = ""
269 
270  def preamble(self, walltime=None):
271  return ""
272 
273  def submitCommand(self, scriptName):
274  return "exec %s" % scriptName
275 
276 
277 BATCH_TYPES = {'none' : None,
278  'None' : None,
279  'pbs': PbsBatch,
280  'slurm': SlurmBatch,
281  'smp': SmpBatch,
282  } # Mapping batch type --> Batch class
283 
284 
285 class BatchArgumentParser(argparse.ArgumentParser):
286  """An argument parser to get relevant parameters for batch submission
287 
288  We want to be able to display the help for a 'parent' ArgumentParser
289  along with the batch-specific options we introduce in this class, but
290  we don't want to swallow the parent (i.e., ArgumentParser(parents=[parent]))
291  because we want to save the list of arguments that this particular
292  BatchArgumentParser doesn't parse, so they can be passed on to a different
293  program (though we also want to parse them to check that they can be parsed).
294  """
295 
296  def __init__(self, parent=None, *args, **kwargs):
297  super(BatchArgumentParser, self).__init__(*args, **kwargs)
298  self._parent = parent
299  group = self.add_argument_group("Batch submission options")
300  group.add_argument("--queue", help="Queue name")
301  group.add_argument("--job", help="Job name")
302  group.add_argument("--nodes", type=int, default=0, help="Number of nodes")
303  group.add_argument("--procs", type=int, default=0, help="Number of processors per node")
304  group.add_argument("--cores", type=int, default=0, help="Number of cores (Slurm/SMP only)")
305  group.add_argument("--time", type=float, default=0,
306  help="Expected execution time per element (sec)")
307  group.add_argument("--batch-type", dest="batchType", choices=list(BATCH_TYPES.keys()), default="smp",
308  help="Batch system to use")
309  group.add_argument("--batch-verbose", dest="batchVerbose", action="store_true", default=False,
310  help=("Enable verbose output in batch script "
311  "(including system environment information at batch start)?"))
312  group.add_argument("--batch-output", dest="batchOutput", help="Output directory")
313  group.add_argument("--batch-submit", dest="batchSubmit", help="Batch submission command-line flags")
314  group.add_argument("--batch-options", dest="batchOptions", help="Header options for batch script")
315  group.add_argument("--batch-profile", dest="batchProfile", action="store_true", default=False,
316  help="Enable profiling on batch job?")
317  group.add_argument("--batch-stats", dest="batchStats", action="store_true", default=False,
318  help="Print process stats on completion (Linux only)?")
319  group.add_argument("--dry-run", dest="dryrun", default=False, action="store_true",
320  help="Dry run?")
321  group.add_argument("--do-exec", dest="doExec", default=False, action="store_true",
322  help="Exec script instead of submit to batch system?")
323  group.add_argument("--mpiexec", default="", help="mpiexec options")
324 
325  def parse_args(self, config=None, args=None, namespace=None, **kwargs):
326  args, leftover = super(BatchArgumentParser, self).parse_known_args(args=args, namespace=namespace)
327  args.parent = None
328  args.leftover = None
329  if len(leftover) > 0:
330  # Save any leftovers for the parent
331  if self._parent is None:
332  self.error("Unrecognised arguments: %s" % leftover)
333  args.parent = self._parent.parse_args(config, args=leftover, **kwargs)
334  args.leftover = leftover
335  args.batch = self.makeBatch(args)
336  return args
337 
338  def makeBatch(self, args):
339  """Create a Batch object from the command-line arguments"""
340  # argMapping is a dict that maps Batch init kwarg names to parsed arguments attribute *names*
341  argMapping = {'outputDir': 'batchOutput',
342  'numNodes': 'nodes',
343  'numProcsPerNode': 'procs',
344  'numCores': 'cores',
345  'walltime': 'time',
346  'queue': 'queue',
347  'jobName': 'job',
348  'dryrun': 'dryrun',
349  'doExec': 'doExec',
350  'mpiexec': 'mpiexec',
351  'submit': 'batchSubmit',
352  'options': 'batchOptions',
353  'verbose': 'batchVerbose',
354  }
355 
356  if BATCH_TYPES[args.batchType] is None:
357  return None
358 
359  # kwargs is a dict that maps Batch init kwarg names to parsed arguments attribute *values*
360  kwargs = {k: getattr(args, v) for k, v in argMapping.items()}
361  return BATCH_TYPES[args.batchType](**kwargs)
362 
363  def format_help(self):
364  text = """This is a script for queue submission of a wrapped script.
365 
366 Use this program name and ignore that for the wrapped script (it will be
367 passed on to the batch system). Arguments for *both* this wrapper script or the
368 wrapped script are valid (if it is required for the wrapped script, it
369 is required for the wrapper as well).
370 
371 *** Batch system submission wrapper:
372 
373 """
374  text += super(BatchArgumentParser, self).format_help()
375  if self._parent is not None:
376  text += """
377 
378 *** Wrapped script:
379 
380 """
381  text += self._parent.format_help()
382  return text
383 
384  def format_usage(self):
385  if self._parent is not None:
386  prog = self._parent.prog
387  self._parent.prog = self.prog
388  usage = self._parent.format_usage()
389  self._parent.prog = prog
390  return usage
391  return super(BatchArgumentParser, self).format_usage()
392 
393 
394 def exportEnv():
395  """Generate bash script to regenerate the current environment"""
396  output = ""
397  for key, val in os.environ.items():
398  if key in ("DISPLAY",):
399  continue
400  if val.startswith("() {"):
401  # This is a function.
402  # "Two parentheses, a single space, and a brace"
403  # is exactly the same criterion as bash uses.
404 
405  # From 2014-09-25, the function name is prefixed by 'BASH_FUNC_'
406  # and suffixed by '()', which we have to remove.
407  if key.startswith("BASH_FUNC_") and key.endswith("()"):
408  key = key[10:-2]
409 
410  output += "{key} {val}\nexport -f {key}\n".format(key=key, val=val)
411  else:
412  # This is a variable.
413  output += "export {key}='{val}'\n".format(key=key, val=val.replace("'", "'\"'\"'"))
414  return output
415 
416 
417 class BatchCmdLineTask(CmdLineTask):
418 
419  @classmethod
420  def parseAndSubmit(cls, args=None, **kwargs):
421  taskParser = cls._makeArgumentParser(doBatch=True, add_help=False)
422  batchParser = BatchArgumentParser(parent=taskParser)
423  batchArgs = batchParser.parse_args(config=cls.ConfigClass(), args=args, override=cls.applyOverrides,
424  **kwargs)
425 
426  if not cls.RunnerClass(cls, batchArgs.parent).precall(batchArgs.parent): # Write config, schema
427  taskParser.error("Error in task preparation")
428 
429  setBatchType(batchArgs.batch)
430 
431  if batchArgs.batch is None: # don't use a batch system
432  sys.argv = [sys.argv[0]] + batchArgs.leftover # Remove all batch arguments
433 
434  return cls.parseAndRun()
435  else:
436  numCores = batchArgs.cores if batchArgs.cores > 0 else batchArgs.nodes*batchArgs.procs
437  walltime = cls.batchWallTime(batchArgs.time, batchArgs.parent, numCores)
438 
439  command = cls.batchCommand(batchArgs)
440  batchArgs.batch.run(command, walltime=walltime)
441 
442  @classmethod
443  def batchWallTime(cls, time, parsedCmd, numCores):
444  """!Return walltime request for batch job
445 
446  Subclasses should override if the walltime should be calculated
447  differently (e.g., addition of some serial time).
448 
449  @param cls: Class
450  @param time: Requested time per iteration
451  @param parsedCmd: Results of argument parsing
452  @param numCores: Number of cores
453  """
454  numTargets = len(cls.RunnerClass.getTargetList(parsedCmd))
455  return time*numTargets/float(numCores)
456 
457  @classmethod
458  def batchCommand(cls, args):
459  """!Return command to run CmdLineTask
460 
461  @param cls: Class
462  @param args: Parsed batch job arguments (from BatchArgumentParser)
463  """
464  job = args.job if args.job is not None else "job"
465  module = cls.__module__
466  script = ("import os; os.umask(%#05o); " +
467  "import lsst.base; lsst.base.disableImplicitThreading(); " +
468  "import lsst.ctrl.pool.log; lsst.ctrl.pool.log.jobLog(\"%s\"); ") % (UMASK, job)
469 
470  if args.batchStats:
471  script += ("import lsst.ctrl.pool.parallel; import atexit; " +
472  "atexit.register(lsst.ctrl.pool.parallel.printProcessStats); ")
473 
474  script += "import %s; %s.%s.parseAndRun();" % (module, module, cls.__name__)
475 
476  profilePre = "import cProfile; import os; cProfile.run(\"\"\""
477  profilePost = "\"\"\", filename=\"profile-" + job + "-%s-%d.dat\" % (os.uname()[1], os.getpid()))"
478 
479  return ("python -c '" + (profilePre if args.batchProfile else "") + script +
480  (profilePost if args.batchProfile else "") + "' " + shCommandFromArgs(args.leftover) +
481  " --noExit")
482 
483  @contextlib.contextmanager
484  def logOperation(self, operation, catch=False, trace=True):
485  """!Provide a context manager for logging an operation
486 
487  @param operation: description of operation (string)
488  @param catch: Catch all exceptions?
489  @param trace: Log a traceback of caught exception?
490 
491  Note that if 'catch' is True, all exceptions are swallowed, but there may
492  be other side-effects such as undefined variables.
493  """
494  self.log.info("%s: Start %s" % (NODE, operation))
495  try:
496  yield
497  except:
498  if catch:
499  cls, e, _ = sys.exc_info()
500  self.log.warn("%s: Caught %s while %s: %s" % (NODE, cls.__name__, operation, e))
501  if trace:
502  self.log.info("%s: Traceback:\n%s" % (NODE, traceback.format_exc()))
503  return
504  raise
505  finally:
506  self.log.info("%s: Finished %s" % (NODE, operation))
507 
508 
510  """Starts a BatchCmdLineTask with an MPI process pool
511 
512  Use this subclass of BatchCmdLineTask if you want to use the Pool directly.
513  """
514  @classmethod
515  @abortOnError
516  def parseAndRun(cls, *args, **kwargs):
517  """Run with a MPI process pool"""
518  pool = startPool()
519  super(BatchPoolTask, cls).parseAndRun(*args, **kwargs)
520  pool.exit()
521 
522 
523 class BatchTaskRunner(TaskRunner):
524  """Run a Task individually on a list of inputs using the MPI process pool"""
525 
526  def __init__(self, *args, **kwargs):
527  """Constructor
528 
529  Warn if the user specified multiprocessing.
530  """
531  TaskRunner.__init__(self, *args, **kwargs)
532  if self.numProcesses > 1:
533  self.log.warn("Multiprocessing arguments (-j %d) ignored since using batch processing" %
534  self.numProcesses)
535  self.numProcesses = 1
536 
537  def run(self, parsedCmd):
538  """Run the task on all targets
539 
540  Sole input is the result of parsing the command-line with the ArgumentParser.
541 
542  Output is None if 'precall' failed; otherwise it is a list of calling ourself
543  on each element of the target list from the 'getTargetList' method.
544  """
545  resultList = None
546 
547  import multiprocessing
548  self.prepareForMultiProcessing()
549  pool = Pool()
550 
551  if self.precall(parsedCmd):
552  targetList = self.getTargetList(parsedCmd)
553  if len(targetList) > 0:
554  parsedCmd.log.info("Processing %d targets with a pool of %d processes..." %
555  (len(targetList), pool.size))
556  # Run the task using self.__call__
557  resultList = pool.map(self, targetList)
558  else:
559  parsedCmd.log.warn("Not running the task because there is no data to process; "
560  "you may preview data using \"--show data\"")
561  resultList = []
562 
563  return resultList
564 
565  @abortOnError
566  def __call__(self, cache, args):
567  """Run the Task on a single target
568 
569  Strips out the process pool 'cache' argument.
570 
571  'args' are those arguments provided by the getTargetList method.
572 
573  Brings down the entire job if an exception is not caught (i.e., --doraise).
574  """
575  return TaskRunner.__call__(self, args)
576 
577 
579  """Runs the BatchCmdLineTask in parallel
580 
581  Use this subclass of BatchCmdLineTask if you don't need to use the Pool
582  directly, but just want to iterate over many objects (like a multi-node
583  version of the '-j' command-line argument).
584  """
585  RunnerClass = BatchTaskRunner
586 
587  @classmethod
588  def _makeArgumentParser(cls, *args, **kwargs):
589  """Build an ArgumentParser
590 
591  Removes the batch-specific parts in order to delegate to the parent classes.
592  """
593  kwargs.pop("doBatch", False)
594  kwargs.pop("add_help", False)
595  return super(BatchCmdLineTask, cls)._makeArgumentParser(*args, **kwargs)
596 
597  @classmethod
598  def parseAndRun(cls, *args, **kwargs):
599  """Parse an argument list and run the command
600 
601  This is the entry point when we run in earnest, so start the process pool
602  so that the worker nodes don't go any further.
603  """
604  pool = startPool()
605  results = super(BatchParallelTask, cls).parseAndRun(*args, **kwargs)
606  pool.exit()
607  return results
def parseAndRun(cls, args, kwargs)
Definition: parallel.py:516
def __call__(self, cache, args)
Definition: parallel.py:566
def preamble(self, walltime=None)
Definition: parallel.py:270
def parseAndRun(cls, args, kwargs)
Definition: parallel.py:598
def batchCommand(cls, args)
Return command to run CmdLineTask.
Definition: parallel.py:458
def __init__(self, outputDir=None, numNodes=0, numProcsPerNode=0, numCores=0, queue=None, jobName=None, walltime=0.0, dryrun=False, doExec=False, mpiexec="", submit=None, options=None, verbose=False)
Constructor.
Definition: parallel.py:76
def __init__(self, parent=None, args, kwargs)
Definition: parallel.py:296
def preamble(self, walltime=None)
Definition: parallel.py:219
def submitCommand(self, scriptName)
Definition: parallel.py:273
def startPool(comm=None, root=0, killSlaves=True)
Start a process pool.
Definition: pool.py:1214
def parseAndSubmit(cls, args=None, kwargs)
Definition: parallel.py:420
def createScript(self, command, walltime=None)
Create script to be submitted.
Definition: parallel.py:141
def __init__(self, args, kwargs)
Definition: parallel.py:255
def preamble(self, walltime=None)
Definition: parallel.py:190
def submitCommand(self, scriptName)
Definition: parallel.py:245
def parse_args(self, config=None, args=None, namespace=None, kwargs)
Definition: parallel.py:325
def submitCommand(self, scriptName)
Return command to submit script.
Definition: parallel.py:160
def batchWallTime(cls, time, parsedCmd, numCores)
Return walltime request for batch job.
Definition: parallel.py:443
def preamble(self, command, walltime=None)
Definition: parallel.py:113
def submitCommand(self, scriptName)
Definition: parallel.py:212
def setBatchType(batchType)
Definition: pool.py:103
def logOperation(self, operation, catch=False, trace=True)
Provide a context manager for logging an operation.
Definition: parallel.py:484
def __init__(self, args, kwargs)
Definition: parallel.py:526
def run(self, command, walltime=None)
Run the batch system.
Definition: parallel.py:167
def execution(self, command)
Definition: parallel.py:120
def shCommandFromArgs(args)
Definition: parallel.py:45