testing/mozbase/mozprocess/tests/proclaunch.py

changeset 0
6474c204b198
     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()

mercurial