|
1 #!/usr/bin/env python |
|
2 |
|
3 import argparse |
|
4 import collections |
|
5 import ConfigParser |
|
6 import multiprocessing |
|
7 import time |
|
8 |
|
9 ProcessNode = collections.namedtuple('ProcessNode', ['maxtime', 'children']) |
|
10 |
|
11 class ProcessLauncher(object): |
|
12 |
|
13 """ Create and Launch process trees specified by a '.ini' file |
|
14 |
|
15 Typical .ini file accepted by this class : |
|
16 |
|
17 [main] |
|
18 children=c1, 1*c2, 4*c3 |
|
19 maxtime=10 |
|
20 |
|
21 [c1] |
|
22 children= 2*c2, c3 |
|
23 maxtime=20 |
|
24 |
|
25 [c2] |
|
26 children=3*c3 |
|
27 maxtime=5 |
|
28 |
|
29 [c3] |
|
30 maxtime=3 |
|
31 |
|
32 This generates a process tree of the form: |
|
33 [main] |
|
34 |---[c1] |
|
35 | |---[c2] |
|
36 | | |---[c3] |
|
37 | | |---[c3] |
|
38 | | |---[c3] |
|
39 | | |
|
40 | |---[c2] |
|
41 | | |---[c3] |
|
42 | | |---[c3] |
|
43 | | |---[c3] |
|
44 | | |
|
45 | |---[c3] |
|
46 | |
|
47 |---[c2] |
|
48 | |---[c3] |
|
49 | |---[c3] |
|
50 | |---[c3] |
|
51 | |
|
52 |---[c3] |
|
53 |---[c3] |
|
54 |---[c3] |
|
55 |
|
56 Caveat: The section names cannot contain a '*'(asterisk) or a ','(comma) |
|
57 character as these are used as delimiters for parsing. |
|
58 """ |
|
59 |
|
60 # Unit time for processes in seconds |
|
61 UNIT_TIME = 1 |
|
62 |
|
63 def __init__(self, manifest, verbose=False): |
|
64 """ |
|
65 Parses the manifest and stores the information about the process tree |
|
66 in a format usable by the class. |
|
67 |
|
68 Raises IOError if : |
|
69 - The path does not exist |
|
70 - The file cannot be read |
|
71 Raises ConfigParser.*Error if: |
|
72 - Files does not contain section headers |
|
73 - File cannot be parsed because of incorrect specification |
|
74 |
|
75 :param manifest: Path to the manifest file that contains the |
|
76 configuration for the process tree to be launched |
|
77 :verbose: Print the process start and end information. |
|
78 Genrates a lot of output. Disabled by default. |
|
79 """ |
|
80 |
|
81 self.verbose=verbose |
|
82 |
|
83 # Children is a dictionary used to store information from the, |
|
84 # Configuration file in a more usable format. |
|
85 # Key : string contain the name of child process |
|
86 # Value : A Named tuple of the form (max_time, (list of child processes of Key)) |
|
87 # Where each child process is a list of type: [count to run, name of child] |
|
88 self.children = {} |
|
89 |
|
90 |
|
91 cfgparser = ConfigParser.ConfigParser() |
|
92 |
|
93 if not cfgparser.read(manifest): |
|
94 raise IOError('The manifest %s could not be found/opened', manifest) |
|
95 |
|
96 sections = cfgparser.sections() |
|
97 for section in sections: |
|
98 # Maxtime is a mandatory option |
|
99 # ConfigParser.NoOptionError is raised if maxtime does not exist |
|
100 if '*' in section or ',' in section: |
|
101 raise ConfigParser.ParsingError('%s is not a valid section name. Section names cannot contain a \'*\' or \',\'.' % section) |
|
102 m_time = cfgparser.get(section, 'maxtime') |
|
103 try: |
|
104 m_time = int(m_time) |
|
105 except ValueError: |
|
106 raise ValueError('Expected maxtime to be an integer, specified %s' % m_time) |
|
107 |
|
108 # No children option implies there are no further children |
|
109 # Leaving the children option blank is an error. |
|
110 try: |
|
111 c = cfgparser.get(section, 'children') |
|
112 if not c: |
|
113 # If children is an empty field, assume no children |
|
114 children = None |
|
115 |
|
116 else: |
|
117 # Tokenize chilren field, ignore empty strings |
|
118 children = [[y.strip() for y in x.strip().split('*', 1)] |
|
119 for x in c.split(',') if x] |
|
120 try: |
|
121 for i, child in enumerate(children): |
|
122 # No multiplicate factor infront of a process implies 1 |
|
123 if len(child) == 1: |
|
124 children[i] = [1, child[0]] |
|
125 else: |
|
126 children[i][0] = int(child[0]) |
|
127 |
|
128 if children[i][1] not in sections: |
|
129 raise ConfigParser.ParsingError('No section corresponding to child %s' % child[1]) |
|
130 except ValueError: |
|
131 raise ValueError('Expected process count to be an integer, specified %s' % child[0]) |
|
132 |
|
133 except ConfigParser.NoOptionError: |
|
134 children = None |
|
135 pn = ProcessNode(maxtime=m_time, |
|
136 children=children) |
|
137 self.children[section] = pn |
|
138 |
|
139 def run(self): |
|
140 """ |
|
141 This function launches the process tree. |
|
142 """ |
|
143 self._run('main', 0) |
|
144 |
|
145 def _run(self, proc_name, level): |
|
146 """ |
|
147 Runs the process specified by the section-name `proc_name` in the manifest file. |
|
148 Then makes calls to launch the child processes of `proc_name` |
|
149 |
|
150 :param proc_name: File name of the manifest as a string. |
|
151 :param level: Depth of the current process in the tree. |
|
152 """ |
|
153 if proc_name not in self.children.keys(): |
|
154 raise IOError("%s is not a valid process" % proc_name) |
|
155 |
|
156 maxtime = self.children[proc_name].maxtime |
|
157 if self.verbose: |
|
158 print "%sLaunching %s for %d*%d seconds" % (" "*level, proc_name, maxtime, self.UNIT_TIME) |
|
159 |
|
160 while self.children[proc_name].children: |
|
161 child = self.children[proc_name].children.pop() |
|
162 |
|
163 count, child_proc = child |
|
164 for i in range(count): |
|
165 p = multiprocessing.Process(target=self._run, args=(child[1], level+1)) |
|
166 p.start() |
|
167 |
|
168 self._launch(maxtime) |
|
169 if self.verbose: |
|
170 print "%sFinished %s" % (" "*level, proc_name) |
|
171 |
|
172 def _launch(self, running_time): |
|
173 """ |
|
174 Create and launch a process and idles for the time specified by |
|
175 `running_time` |
|
176 |
|
177 :param running_time: Running time of the process in seconds. |
|
178 """ |
|
179 elapsed_time = 0 |
|
180 |
|
181 while elapsed_time < running_time: |
|
182 time.sleep(self.UNIT_TIME) |
|
183 elapsed_time += self.UNIT_TIME |
|
184 |
|
185 if __name__ == '__main__': |
|
186 |
|
187 parser = argparse.ArgumentParser() |
|
188 parser.add_argument("manifest", help="Specify the configuration .ini file") |
|
189 args = parser.parse_args() |
|
190 |
|
191 proclaunch = ProcessLauncher(args.manifest) |
|
192 proclaunch.run() |