#!/usr/bin/env python import argparse import collections import ConfigParser import multiprocessing import time ProcessNode = collections.namedtuple('ProcessNode', ['maxtime', 'children']) class ProcessLauncher(object): """ Create and Launch process trees specified by a '.ini' file Typical .ini file accepted by this class : [main] children=c1, 1*c2, 4*c3 maxtime=10 [c1] children= 2*c2, c3 maxtime=20 [c2] children=3*c3 maxtime=5 [c3] maxtime=3 This generates a process tree of the form: [main] |---[c1] | |---[c2] | | |---[c3] | | |---[c3] | | |---[c3] | | | |---[c2] | | |---[c3] | | |---[c3] | | |---[c3] | | | |---[c3] | |---[c2] | |---[c3] | |---[c3] | |---[c3] | |---[c3] |---[c3] |---[c3] Caveat: The section names cannot contain a '*'(asterisk) or a ','(comma) character as these are used as delimiters for parsing. """ # Unit time for processes in seconds UNIT_TIME = 1 def __init__(self, manifest, verbose=False): """ Parses the manifest and stores the information about the process tree in a format usable by the class. Raises IOError if : - The path does not exist - The file cannot be read Raises ConfigParser.*Error if: - Files does not contain section headers - File cannot be parsed because of incorrect specification :param manifest: Path to the manifest file that contains the configuration for the process tree to be launched :verbose: Print the process start and end information. Genrates a lot of output. Disabled by default. """ self.verbose=verbose # Children is a dictionary used to store information from the, # Configuration file in a more usable format. # Key : string contain the name of child process # Value : A Named tuple of the form (max_time, (list of child processes of Key)) # Where each child process is a list of type: [count to run, name of child] self.children = {} cfgparser = ConfigParser.ConfigParser() if not cfgparser.read(manifest): raise IOError('The manifest %s could not be found/opened', manifest) sections = cfgparser.sections() for section in sections: # Maxtime is a mandatory option # ConfigParser.NoOptionError is raised if maxtime does not exist if '*' in section or ',' in section: raise ConfigParser.ParsingError('%s is not a valid section name. Section names cannot contain a \'*\' or \',\'.' % section) m_time = cfgparser.get(section, 'maxtime') try: m_time = int(m_time) except ValueError: raise ValueError('Expected maxtime to be an integer, specified %s' % m_time) # No children option implies there are no further children # Leaving the children option blank is an error. try: c = cfgparser.get(section, 'children') if not c: # If children is an empty field, assume no children children = None else: # Tokenize chilren field, ignore empty strings children = [[y.strip() for y in x.strip().split('*', 1)] for x in c.split(',') if x] try: for i, child in enumerate(children): # No multiplicate factor infront of a process implies 1 if len(child) == 1: children[i] = [1, child[0]] else: children[i][0] = int(child[0]) if children[i][1] not in sections: raise ConfigParser.ParsingError('No section corresponding to child %s' % child[1]) except ValueError: raise ValueError('Expected process count to be an integer, specified %s' % child[0]) except ConfigParser.NoOptionError: children = None pn = ProcessNode(maxtime=m_time, children=children) self.children[section] = pn def run(self): """ This function launches the process tree. """ self._run('main', 0) def _run(self, proc_name, level): """ Runs the process specified by the section-name `proc_name` in the manifest file. Then makes calls to launch the child processes of `proc_name` :param proc_name: File name of the manifest as a string. :param level: Depth of the current process in the tree. """ if proc_name not in self.children.keys(): raise IOError("%s is not a valid process" % proc_name) maxtime = self.children[proc_name].maxtime if self.verbose: print "%sLaunching %s for %d*%d seconds" % (" "*level, proc_name, maxtime, self.UNIT_TIME) while self.children[proc_name].children: child = self.children[proc_name].children.pop() count, child_proc = child for i in range(count): p = multiprocessing.Process(target=self._run, args=(child[1], level+1)) p.start() self._launch(maxtime) if self.verbose: print "%sFinished %s" % (" "*level, proc_name) def _launch(self, running_time): """ Create and launch a process and idles for the time specified by `running_time` :param running_time: Running time of the process in seconds. """ elapsed_time = 0 while elapsed_time < running_time: time.sleep(self.UNIT_TIME) elapsed_time += self.UNIT_TIME if __name__ == '__main__': parser = argparse.ArgumentParser() parser.add_argument("manifest", help="Specify the configuration .ini file") args = parser.parse_args() proclaunch = ProcessLauncher(args.manifest) proclaunch.run()