1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208
|
#!/usr/bin/env python
import argparse
import collections
import configparser
import multiprocessing
import time
ProcessNode = collections.namedtuple("ProcessNode", ["maxtime", "children"])
class ProcessLauncher:
"""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 OSError("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:
raise OSError("%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()
|