• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1# Copyright (c) 2014-2015, Intel Corporation
2# All rights reserved.
3#
4# Redistribution and use in source and binary forms, with or without modification,
5# are permitted provided that the following conditions are met:
6#
7# 1. Redistributions of source code must retain the above copyright notice, this
8# list of conditions and the following disclaimer.
9#
10# 2. Redistributions in binary form must reproduce the above copyright notice,
11# this list of conditions and the following disclaimer in the documentation and/or
12# other materials provided with the distribution.
13#
14# 3. Neither the name of the copyright holder nor the names of its contributors
15# may be used to endorse or promote products derived from this software without
16# specific prior written permission.
17#
18# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
19# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
20# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
21# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
22# ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
23# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
24# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
25# ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
26# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
27# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28
29import threading
30import subprocess
31import logging
32import signal
33import os
34
35
36class StreamLoggerThread(threading.Thread):
37
38    """ File-like object used to log Popen stdout and stderr streams """
39
40    def __init__(self, consoleLogger, level, name):
41        """
42            StreamLoggerThread Object initializer
43
44            :param consoleLogger: console log handler
45            :type consoleLogger: Handler
46            :param level: desired logger level of the stream
47            :type level: logging.level
48            :param name: Thread name
49            :type name: string
50        """
51        super().__init__()
52
53        # Pipe to interact with subprocess
54        self.__readFd, self.__writeFd = os.pipe()
55
56        self.__logger = logging.getLogger(__name__)
57        self.__logger.addHandler(consoleLogger)
58
59        self.__level = level
60
61        self.name = name
62
63        # Start stream logging
64        self.start()
65
66    def fileno(self):
67        """ Give Writing side of internal pipe to receive data """
68
69        return self.__writeFd
70
71    def run(self):
72        """ Read the reading side of the pipe until EOF """
73
74        with os.fdopen(self.__readFd) as stream:
75            for line in stream:
76                self.__logger.log(self.__level, line.strip('\n'))
77
78    def close(self):
79        """ Close writing pipe side """
80
81        os.close(self.__writeFd)
82
83
84class SubprocessLoggerThread(threading.Thread):
85
86    """ This class is here to log long process stdout and stderr """
87
88    # Event used to ask all SubprocessLoggerThread object to die
89    __closeEvent = threading.Event()
90
91    def __init__(self, cmd, consoleLogger):
92        """
93            SubprocessLoggerThread Object initializer
94
95            :param cmd: command to launch
96            :type cmd: list
97            :param consoleLogger: console log handler
98            :type consoleLogger: Handler
99        """
100
101        super().__init__()
102
103        self.__cmd = cmd
104        self.__subProc = None
105
106        self.name = "Thread : " + ' '.join(cmd)
107
108        self.__consoleLogger = consoleLogger
109
110        # Default logging level
111        self._stdOutLogLevel = logging.DEBUG
112
113    @classmethod
114    def closeAll(cls):
115        """ Set the closeEvent to ask the thread to die  """
116        cls.__closeEvent.set()
117
118    def __cleanup(self):
119        """
120            Close properly the child with SIGINT.
121
122            The signal is sended to all the group to kill
123            subprocess launched by Popen
124        """
125        os.killpg(self.__subProc.pid, signal.SIGINT)
126
127    def __subProcPreExec(self):
128        """
129            Make Popen object a Group leader.
130
131            Avoid subprocess to receive signal destinated
132            to the MainThread.
133        """
134        os.setpgrp()
135
136    def run(self):
137        """ Create Popen object and manage it """
138
139        # Logging threaded file-object
140        stdOutLogger = StreamLoggerThread(
141            self.__consoleLogger,
142            self._stdOutLogLevel,
143            self.name + "STDOUT")
144        stdErrLogger = StreamLoggerThread(
145            self.__consoleLogger,
146            logging.ERROR,
147            self.name + "STDERR")
148
149        # Logging stdout and stderr through objects
150        self.__subProc = subprocess.Popen(
151            [os.getenv("SHELL"), "-c", ' '.join(self.__cmd)],
152            bufsize=1,
153            stdout=stdOutLogger,
154            stderr=stdErrLogger,
155            preexec_fn=self.__subProcPreExec,
156            shell=False)
157
158        # Waiting process close or closing order
159        while True:
160            try:
161                # We end the thread if we are requested to do so
162                if SubprocessLoggerThread.__closeEvent.wait(0.01):
163                    self.__cleanup()
164                    break
165
166                # or if the subprocess is dead
167                if self.__subProc.poll() is not None:
168                    break
169            except KeyboardInterrupt:
170                continue
171
172        # Close pipes
173        if stdOutLogger.is_alive():
174            stdOutLogger.close()
175        if stdErrLogger.is_alive():
176            stdErrLogger.close()
177
178
179class ScriptLoggerThread(SubprocessLoggerThread):
180
181    """ This class is used to log script subprocess """
182
183    def __init__(self, cmd, consoleLogger):
184        """
185            ScriptLoggerThread Object initializer
186
187            :param cmd: command to launch
188            :type cmd: list
189            :param consoleLogger: console log handler
190            :type consoleLogger: Handler
191        """
192        super().__init__(cmd, consoleLogger)
193
194        # Script logging level
195        self._stdOutLogLevel = logging.INFO
196
197    @classmethod
198    def getRunningInstances(cls):
199        """
200            Running ScriptLoggerThread instances getter
201
202            :return: The list of running ScriptLoggerThread instances
203            :rtype: list
204        """
205        return [t for t in threading.enumerate() if isinstance(t, cls)]
206