1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/testing/mozbase/mozprocess/tests/proclaunch.py Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,192 @@ 1.4 +#!/usr/bin/env python 1.5 + 1.6 +import argparse 1.7 +import collections 1.8 +import ConfigParser 1.9 +import multiprocessing 1.10 +import time 1.11 + 1.12 +ProcessNode = collections.namedtuple('ProcessNode', ['maxtime', 'children']) 1.13 + 1.14 +class ProcessLauncher(object): 1.15 + 1.16 + """ Create and Launch process trees specified by a '.ini' file 1.17 + 1.18 + Typical .ini file accepted by this class : 1.19 + 1.20 + [main] 1.21 + children=c1, 1*c2, 4*c3 1.22 + maxtime=10 1.23 + 1.24 + [c1] 1.25 + children= 2*c2, c3 1.26 + maxtime=20 1.27 + 1.28 + [c2] 1.29 + children=3*c3 1.30 + maxtime=5 1.31 + 1.32 + [c3] 1.33 + maxtime=3 1.34 + 1.35 + This generates a process tree of the form: 1.36 + [main] 1.37 + |---[c1] 1.38 + | |---[c2] 1.39 + | | |---[c3] 1.40 + | | |---[c3] 1.41 + | | |---[c3] 1.42 + | | 1.43 + | |---[c2] 1.44 + | | |---[c3] 1.45 + | | |---[c3] 1.46 + | | |---[c3] 1.47 + | | 1.48 + | |---[c3] 1.49 + | 1.50 + |---[c2] 1.51 + | |---[c3] 1.52 + | |---[c3] 1.53 + | |---[c3] 1.54 + | 1.55 + |---[c3] 1.56 + |---[c3] 1.57 + |---[c3] 1.58 + 1.59 + Caveat: The section names cannot contain a '*'(asterisk) or a ','(comma) 1.60 + character as these are used as delimiters for parsing. 1.61 + """ 1.62 + 1.63 + # Unit time for processes in seconds 1.64 + UNIT_TIME = 1 1.65 + 1.66 + def __init__(self, manifest, verbose=False): 1.67 + """ 1.68 + Parses the manifest and stores the information about the process tree 1.69 + in a format usable by the class. 1.70 + 1.71 + Raises IOError if : 1.72 + - The path does not exist 1.73 + - The file cannot be read 1.74 + Raises ConfigParser.*Error if: 1.75 + - Files does not contain section headers 1.76 + - File cannot be parsed because of incorrect specification 1.77 + 1.78 + :param manifest: Path to the manifest file that contains the 1.79 + configuration for the process tree to be launched 1.80 + :verbose: Print the process start and end information. 1.81 + Genrates a lot of output. Disabled by default. 1.82 + """ 1.83 + 1.84 + self.verbose=verbose 1.85 + 1.86 + # Children is a dictionary used to store information from the, 1.87 + # Configuration file in a more usable format. 1.88 + # Key : string contain the name of child process 1.89 + # Value : A Named tuple of the form (max_time, (list of child processes of Key)) 1.90 + # Where each child process is a list of type: [count to run, name of child] 1.91 + self.children = {} 1.92 + 1.93 + 1.94 + cfgparser = ConfigParser.ConfigParser() 1.95 + 1.96 + if not cfgparser.read(manifest): 1.97 + raise IOError('The manifest %s could not be found/opened', manifest) 1.98 + 1.99 + sections = cfgparser.sections() 1.100 + for section in sections: 1.101 + # Maxtime is a mandatory option 1.102 + # ConfigParser.NoOptionError is raised if maxtime does not exist 1.103 + if '*' in section or ',' in section: 1.104 + raise ConfigParser.ParsingError('%s is not a valid section name. Section names cannot contain a \'*\' or \',\'.' % section) 1.105 + m_time = cfgparser.get(section, 'maxtime') 1.106 + try: 1.107 + m_time = int(m_time) 1.108 + except ValueError: 1.109 + raise ValueError('Expected maxtime to be an integer, specified %s' % m_time) 1.110 + 1.111 + # No children option implies there are no further children 1.112 + # Leaving the children option blank is an error. 1.113 + try: 1.114 + c = cfgparser.get(section, 'children') 1.115 + if not c: 1.116 + # If children is an empty field, assume no children 1.117 + children = None 1.118 + 1.119 + else: 1.120 + # Tokenize chilren field, ignore empty strings 1.121 + children = [[y.strip() for y in x.strip().split('*', 1)] 1.122 + for x in c.split(',') if x] 1.123 + try: 1.124 + for i, child in enumerate(children): 1.125 + # No multiplicate factor infront of a process implies 1 1.126 + if len(child) == 1: 1.127 + children[i] = [1, child[0]] 1.128 + else: 1.129 + children[i][0] = int(child[0]) 1.130 + 1.131 + if children[i][1] not in sections: 1.132 + raise ConfigParser.ParsingError('No section corresponding to child %s' % child[1]) 1.133 + except ValueError: 1.134 + raise ValueError('Expected process count to be an integer, specified %s' % child[0]) 1.135 + 1.136 + except ConfigParser.NoOptionError: 1.137 + children = None 1.138 + pn = ProcessNode(maxtime=m_time, 1.139 + children=children) 1.140 + self.children[section] = pn 1.141 + 1.142 + def run(self): 1.143 + """ 1.144 + This function launches the process tree. 1.145 + """ 1.146 + self._run('main', 0) 1.147 + 1.148 + def _run(self, proc_name, level): 1.149 + """ 1.150 + Runs the process specified by the section-name `proc_name` in the manifest file. 1.151 + Then makes calls to launch the child processes of `proc_name` 1.152 + 1.153 + :param proc_name: File name of the manifest as a string. 1.154 + :param level: Depth of the current process in the tree. 1.155 + """ 1.156 + if proc_name not in self.children.keys(): 1.157 + raise IOError("%s is not a valid process" % proc_name) 1.158 + 1.159 + maxtime = self.children[proc_name].maxtime 1.160 + if self.verbose: 1.161 + print "%sLaunching %s for %d*%d seconds" % (" "*level, proc_name, maxtime, self.UNIT_TIME) 1.162 + 1.163 + while self.children[proc_name].children: 1.164 + child = self.children[proc_name].children.pop() 1.165 + 1.166 + count, child_proc = child 1.167 + for i in range(count): 1.168 + p = multiprocessing.Process(target=self._run, args=(child[1], level+1)) 1.169 + p.start() 1.170 + 1.171 + self._launch(maxtime) 1.172 + if self.verbose: 1.173 + print "%sFinished %s" % (" "*level, proc_name) 1.174 + 1.175 + def _launch(self, running_time): 1.176 + """ 1.177 + Create and launch a process and idles for the time specified by 1.178 + `running_time` 1.179 + 1.180 + :param running_time: Running time of the process in seconds. 1.181 + """ 1.182 + elapsed_time = 0 1.183 + 1.184 + while elapsed_time < running_time: 1.185 + time.sleep(self.UNIT_TIME) 1.186 + elapsed_time += self.UNIT_TIME 1.187 + 1.188 +if __name__ == '__main__': 1.189 + 1.190 + parser = argparse.ArgumentParser() 1.191 + parser.add_argument("manifest", help="Specify the configuration .ini file") 1.192 + args = parser.parse_args() 1.193 + 1.194 + proclaunch = ProcessLauncher(args.manifest) 1.195 + proclaunch.run()