proclaunch.py (6870B)
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 12 class ProcessLauncher: 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 cfgparser = configparser.ConfigParser() 91 92 if not cfgparser.read(manifest): 93 raise OSError("The manifest %s could not be found/opened", manifest) 94 95 sections = cfgparser.sections() 96 for section in sections: 97 # Maxtime is a mandatory option 98 # ConfigParser.NoOptionError is raised if maxtime does not exist 99 if "*" in section or "," in section: 100 raise configparser.ParsingError( 101 "%s is not a valid section name. " 102 "Section names cannot contain a '*' or ','." % section 103 ) 104 m_time = cfgparser.get(section, "maxtime") 105 try: 106 m_time = int(m_time) 107 except ValueError: 108 raise ValueError( 109 "Expected maxtime to be an integer, specified %s" % m_time 110 ) 111 112 # No children option implies there are no further children 113 # Leaving the children option blank is an error. 114 try: 115 c = cfgparser.get(section, "children") 116 if not c: 117 # If children is an empty field, assume no children 118 children = None 119 120 else: 121 # Tokenize chilren field, ignore empty strings 122 children = [ 123 [y.strip() for y in x.strip().split("*", 1)] 124 for x in c.split(",") 125 if x 126 ] 127 try: 128 for i, child in enumerate(children): 129 # No multiplicate factor infront of a process implies 1 130 if len(child) == 1: 131 children[i] = [1, child[0]] 132 else: 133 children[i][0] = int(child[0]) 134 135 if children[i][1] not in sections: 136 raise configparser.ParsingError( 137 "No section corresponding to child %s" % child[1] 138 ) 139 except ValueError: 140 raise ValueError( 141 "Expected process count to be an integer, specified %s" 142 % child[0] 143 ) 144 145 except configparser.NoOptionError: 146 children = None 147 pn = ProcessNode(maxtime=m_time, children=children) 148 self.children[section] = pn 149 150 def run(self): 151 """ 152 This function launches the process tree. 153 """ 154 self._run("main", 0) 155 156 def _run(self, proc_name, level): 157 """ 158 Runs the process specified by the section-name `proc_name` in the manifest file. 159 Then makes calls to launch the child processes of `proc_name` 160 161 :param proc_name: File name of the manifest as a string. 162 :param level: Depth of the current process in the tree. 163 """ 164 if proc_name not in self.children: 165 raise OSError("%s is not a valid process" % proc_name) 166 167 maxtime = self.children[proc_name].maxtime 168 if self.verbose: 169 print( 170 "%sLaunching %s for %d*%d seconds" 171 % (" " * level, proc_name, maxtime, self.UNIT_TIME) 172 ) 173 174 while self.children[proc_name].children: 175 child = self.children[proc_name].children.pop() 176 177 count, child_proc = child 178 for i in range(count): 179 p = multiprocessing.Process( 180 target=self._run, args=(child[1], level + 1) 181 ) 182 p.start() 183 184 self._launch(maxtime) 185 if self.verbose: 186 print("%sFinished %s" % (" " * level, proc_name)) 187 188 def _launch(self, running_time): 189 """ 190 Create and launch a process and idles for the time specified by 191 `running_time` 192 193 :param running_time: Running time of the process in seconds. 194 """ 195 elapsed_time = 0 196 197 while elapsed_time < running_time: 198 time.sleep(self.UNIT_TIME) 199 elapsed_time += self.UNIT_TIME 200 201 202 if __name__ == "__main__": 203 parser = argparse.ArgumentParser() 204 parser.add_argument("manifest", help="Specify the configuration .ini file") 205 args = parser.parse_args() 206 207 proclaunch = ProcessLauncher(args.manifest) 208 proclaunch.run()