зеркало из https://github.com/mozilla/gecko-dev.git
193 строки
6.5 KiB
Python
193 строки
6.5 KiB
Python
#!/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()
|