summaryrefslogtreecommitdiffstats
path: root/testing/mozbase/mozprocess/tests/proclaunch.py
blob: 3f7a1b6936eb197f5dde294e364c8b9d8cb3642b (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
#!/usr/bin/env python

from __future__ import absolute_import, print_function

import argparse
import collections
import multiprocessing

import time
from six.moves import configparser

ProcessNode = collections.namedtuple("ProcessNode", ["maxtime", "children"])


class ProcessLauncher(object):
    """Create and Launch process trees specified by a '.ini' file

    Typical .ini file accepted by this class :

    [main]
    children=c1, 1*c2, 4*c3
    maxtime=10

    [c1]
    children= 2*c2, c3
    maxtime=20

    [c2]
    children=3*c3
    maxtime=5

    [c3]
    maxtime=3

    This generates a process tree of the form:
        [main]
            |---[c1]
            |     |---[c2]
            |     |     |---[c3]
            |     |     |---[c3]
            |     |     |---[c3]
            |     |
            |     |---[c2]
            |     |     |---[c3]
            |     |     |---[c3]
            |     |     |---[c3]
            |     |
            |     |---[c3]
            |
            |---[c2]
            |     |---[c3]
            |     |---[c3]
            |     |---[c3]
            |
            |---[c3]
            |---[c3]
            |---[c3]

    Caveat: The section names cannot contain a '*'(asterisk) or a ','(comma)
    character as these are used as delimiters for parsing.
    """

    # Unit time for processes in seconds
    UNIT_TIME = 1

    def __init__(self, manifest, verbose=False):
        """
        Parses the manifest and stores the information about the process tree
        in a format usable by the class.

        Raises IOError if :
            - The path does not exist
            - The file cannot be read
        Raises ConfigParser.*Error if:
            - Files does not contain section headers
            - File cannot be parsed because of incorrect specification

        :param manifest: Path to the manifest file that contains the
        configuration for the process tree to be launched
        :verbose: Print the process start and end information.
        Genrates a lot of output. Disabled by default.
        """

        self.verbose = verbose

        # Children is a dictionary used to store information from the,
        # Configuration file in a more usable format.
        # Key : string contain the name of child process
        # Value : A Named tuple of the form (max_time, (list of child processes of Key))
        #   Where each child process is a list of type: [count to run, name of child]
        self.children = {}

        cfgparser = configparser.ConfigParser()

        if not cfgparser.read(manifest):
            raise IOError("The manifest %s could not be found/opened", manifest)

        sections = cfgparser.sections()
        for section in sections:
            # Maxtime is a mandatory option
            # ConfigParser.NoOptionError is raised if maxtime does not exist
            if "*" in section or "," in section:
                raise configparser.ParsingError(
                    "%s is not a valid section name. "
                    "Section names cannot contain a '*' or ','." % section
                )
            m_time = cfgparser.get(section, "maxtime")
            try:
                m_time = int(m_time)
            except ValueError:
                raise ValueError(
                    "Expected maxtime to be an integer, specified %s" % m_time
                )

            # No children option implies there are no further children
            # Leaving the children option blank is an error.
            try:
                c = cfgparser.get(section, "children")
                if not c:
                    # If children is an empty field, assume no children
                    children = None

                else:
                    # Tokenize chilren field, ignore empty strings
                    children = [
                        [y.strip() for y in x.strip().split("*", 1)]
                        for x in c.split(",")
                        if x
                    ]
                    try:
                        for i, child in enumerate(children):
                            # No multiplicate factor infront of a process implies 1
                            if len(child) == 1:
                                children[i] = [1, child[0]]
                            else:
                                children[i][0] = int(child[0])

                            if children[i][1] not in sections:
                                raise configparser.ParsingError(
                                    "No section corresponding to child %s" % child[1]
                                )
                    except ValueError:
                        raise ValueError(
                            "Expected process count to be an integer, specified %s"
                            % child[0]
                        )

            except configparser.NoOptionError:
                children = None
            pn = ProcessNode(maxtime=m_time, children=children)
            self.children[section] = pn

    def run(self):
        """
        This function launches the process tree.
        """
        self._run("main", 0)

    def _run(self, proc_name, level):
        """
        Runs the process specified by the section-name `proc_name` in the manifest file.
        Then makes calls to launch the child processes of `proc_name`

        :param proc_name: File name of the manifest as a string.
        :param level: Depth of the current process in the tree.
        """
        if proc_name not in self.children:
            raise IOError("%s is not a valid process" % proc_name)

        maxtime = self.children[proc_name].maxtime
        if self.verbose:
            print(
                "%sLaunching %s for %d*%d seconds"
                % (" " * level, proc_name, maxtime, self.UNIT_TIME)
            )

        while self.children[proc_name].children:
            child = self.children[proc_name].children.pop()

            count, child_proc = child
            for i in range(count):
                p = multiprocessing.Process(
                    target=self._run, args=(child[1], level + 1)
                )
                p.start()

        self._launch(maxtime)
        if self.verbose:
            print("%sFinished %s" % (" " * level, proc_name))

    def _launch(self, running_time):
        """
        Create and launch a process and idles for the time specified by
        `running_time`

        :param running_time: Running time of the process in seconds.
        """
        elapsed_time = 0

        while elapsed_time < running_time:
            time.sleep(self.UNIT_TIME)
            elapsed_time += self.UNIT_TIME


if __name__ == "__main__":
    parser = argparse.ArgumentParser()
    parser.add_argument("manifest", help="Specify the configuration .ini file")
    args = parser.parse_args()

    proclaunch = ProcessLauncher(args.manifest)
    proclaunch.run()