tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

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()