blob: b4fdf96559a081d4db8ce940227183b6be56f609 [file] [log] [blame]
#!/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()