| #!/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() |