Owen Lin | 9d19b27 | 2013-11-28 12:13:24 +0800 | [diff] [blame] | 1 | #!/usr/bin/python |
| 2 | # Copyright (c) 2013 The Chromium OS Authors. All rights reserved. |
| 3 | # Use of this source code is governed by a BSD-style license that can be |
| 4 | # found in the LICENSE file. |
| 5 | |
Owen Lin | d28cf0f | 2013-12-11 11:08:41 +0800 | [diff] [blame^] | 6 | import atexit |
| 7 | import itertools |
Owen Lin | 9d19b27 | 2013-11-28 12:13:24 +0800 | [diff] [blame] | 8 | import logging |
Owen Lin | d28cf0f | 2013-12-11 11:08:41 +0800 | [diff] [blame^] | 9 | import os |
Owen Lin | 9d19b27 | 2013-11-28 12:13:24 +0800 | [diff] [blame] | 10 | import pipes |
Owen Lin | d28cf0f | 2013-12-11 11:08:41 +0800 | [diff] [blame^] | 11 | import select |
Owen Lin | 9d19b27 | 2013-11-28 12:13:24 +0800 | [diff] [blame] | 12 | import subprocess |
Owen Lin | 0d65e8a | 2013-11-28 14:29:54 +0800 | [diff] [blame] | 13 | import threading |
| 14 | |
Owen Lin | d28cf0f | 2013-12-11 11:08:41 +0800 | [diff] [blame^] | 15 | from autotest_lib.client.common_lib.utils import TEE_TO_LOGS |
| 16 | |
Owen Lin | 0d65e8a | 2013-11-28 14:29:54 +0800 | [diff] [blame] | 17 | _popen_lock = threading.Lock() |
Owen Lin | d28cf0f | 2013-12-11 11:08:41 +0800 | [diff] [blame^] | 18 | _logging_service = None |
| 19 | _command_serial_number = itertools.count(1) |
| 20 | |
| 21 | _LOG_BUFSIZE = 4096 |
| 22 | _PIPE_CLOSED = -1 |
| 23 | |
| 24 | class _LoggerProxy(object): |
| 25 | |
| 26 | def __init__(self, logger): |
| 27 | self._logger = logger |
| 28 | |
| 29 | def fileno(self): |
| 30 | return self._logger._pipe[1] |
| 31 | |
| 32 | def __del__(self): |
| 33 | self._logger.close() |
| 34 | |
| 35 | |
| 36 | class _PipeLogger(object): |
| 37 | |
| 38 | def __init__(self, level, prefix): |
| 39 | self._pipe = list(os.pipe()) |
| 40 | self._level = level |
| 41 | self._prefix = prefix |
| 42 | |
| 43 | def close(self): |
| 44 | if self._pipe[1] != _PIPE_CLOSED: |
| 45 | os.close(self._pipe[1]) |
| 46 | self._pipe[1] = _PIPE_CLOSED |
| 47 | |
| 48 | |
| 49 | class _LoggingService(object): |
| 50 | |
| 51 | def __init__(self): |
| 52 | # Python's list is thread safe |
| 53 | self._loggers = [] |
| 54 | |
| 55 | # Change tuple to list so that we can change the value when |
| 56 | # closing the pipe. |
| 57 | self._pipe = list(os.pipe()) |
| 58 | self._thread = threading.Thread(target=self._service_run) |
| 59 | self._thread.daemon = True |
| 60 | self._thread.start() |
| 61 | |
| 62 | |
| 63 | def _service_run(self): |
| 64 | terminate_loop = False |
| 65 | while not terminate_loop: |
| 66 | rlist = [l._pipe[0] for l in self._loggers] |
| 67 | rlist.append(self._pipe[0]) |
| 68 | for r in select.select(rlist, [], [])[0]: |
| 69 | data = os.read(r, _LOG_BUFSIZE) |
| 70 | if r != self._pipe[0]: |
| 71 | self._output_logger_message(r, data) |
| 72 | elif len(data) == 0: |
| 73 | terminate_loop = True |
| 74 | # Release resources. |
| 75 | os.close(self._pipe[0]) |
| 76 | for logger in self._loggers: |
| 77 | os.close(logger._pipe[0]) |
| 78 | |
| 79 | |
| 80 | def _output_logger_message(self, r, data): |
| 81 | logger = next(l for l in self._loggers if l._pipe[0] == r) |
| 82 | |
| 83 | if len(data) == 0: |
| 84 | os.close(logger._pipe[0]) |
| 85 | self._loggers.remove(logger) |
| 86 | return |
| 87 | |
| 88 | for line in data.split('\n'): |
| 89 | logging.log(logger._level, '%s%s', logger._prefix, line) |
| 90 | |
| 91 | |
| 92 | def create_logger(self, level=logging.DEBUG, prefix=''): |
| 93 | logger = _PipeLogger(level=level, prefix=prefix) |
| 94 | self._loggers.append(logger) |
| 95 | os.write(self._pipe[1], '\0') |
| 96 | return _LoggerProxy(logger) |
| 97 | |
| 98 | |
| 99 | def shutdown(self): |
| 100 | if self._pipe[1] != _PIPE_CLOSED: |
| 101 | os.close(self._pipe[1]) |
| 102 | self._pipe[1] = _PIPE_CLOSED |
| 103 | self._thread.join() |
| 104 | |
| 105 | |
| 106 | def create_logger(level=logging.DEBUG, prefix=''): |
| 107 | global _logging_service |
| 108 | if _logging_service is None: |
| 109 | _logging_service = _LoggingService() |
| 110 | atexit.register(_logging_service.shutdown) |
| 111 | return _logging_service.create_logger(level=level, prefix=prefix) |
Owen Lin | 9d19b27 | 2013-11-28 12:13:24 +0800 | [diff] [blame] | 112 | |
| 113 | |
Owen Lin | 6513db5 | 2013-12-11 11:06:46 +0800 | [diff] [blame] | 114 | def kill_or_log_returncode(*popens): |
| 115 | '''Kills all the processes of the given Popens or logs the return code. |
Owen Lin | 7ab45a2 | 2013-11-19 17:26:33 +0800 | [diff] [blame] | 116 | |
| 117 | @param poopens: The Popens to be killed. |
| 118 | ''' |
| 119 | for p in popens: |
Owen Lin | 6513db5 | 2013-12-11 11:06:46 +0800 | [diff] [blame] | 120 | if p.poll() is None: |
Owen Lin | 7ab45a2 | 2013-11-19 17:26:33 +0800 | [diff] [blame] | 121 | try: |
| 122 | p.kill() |
Owen Lin | 6513db5 | 2013-12-11 11:06:46 +0800 | [diff] [blame] | 123 | except Exception as e: |
| 124 | logging.warning('failed to kill %d, %s', p.pid, e) |
| 125 | else: |
| 126 | logging.warning('command exit (pid=%d, rc=%d): %s', |
| 127 | p.pid, p.returncode, p.command) |
Owen Lin | 7ab45a2 | 2013-11-19 17:26:33 +0800 | [diff] [blame] | 128 | |
| 129 | |
Owen Lin | 9d19b27 | 2013-11-28 12:13:24 +0800 | [diff] [blame] | 130 | def wait_and_check_returncode(*popens): |
| 131 | '''Wait for all the Popens and check the return code is 0. |
| 132 | |
| 133 | If the return code is not 0, it raises an RuntimeError. |
Owen Lin | 7ab45a2 | 2013-11-19 17:26:33 +0800 | [diff] [blame] | 134 | |
| 135 | @param popens: The Popens to be checked. |
Owen Lin | 9d19b27 | 2013-11-28 12:13:24 +0800 | [diff] [blame] | 136 | ''' |
Owen Lin | 7ab45a2 | 2013-11-19 17:26:33 +0800 | [diff] [blame] | 137 | error_message = None |
Owen Lin | 9d19b27 | 2013-11-28 12:13:24 +0800 | [diff] [blame] | 138 | for p in popens: |
| 139 | if p.wait() != 0: |
Owen Lin | 7ab45a2 | 2013-11-19 17:26:33 +0800 | [diff] [blame] | 140 | error_message = ('Command failed(%d, %d): %s' % |
| 141 | (p.pid, p.returncode, p.command)) |
| 142 | logging.error(error_message) |
| 143 | if error_message: |
| 144 | raise RuntimeError(error_message) |
Owen Lin | 9d19b27 | 2013-11-28 12:13:24 +0800 | [diff] [blame] | 145 | |
| 146 | |
Owen Lin | d28cf0f | 2013-12-11 11:08:41 +0800 | [diff] [blame^] | 147 | def execute(args, stdin=None, stdout=TEE_TO_LOGS, stderr=TEE_TO_LOGS): |
Owen Lin | 9d19b27 | 2013-11-28 12:13:24 +0800 | [diff] [blame] | 148 | '''Executes a child command and wait for it. |
| 149 | |
| 150 | Returns the output from standard output if 'stdout' is subprocess.PIPE. |
| 151 | Raises RuntimeException if the return code of the child command is not 0. |
| 152 | |
| 153 | @param args: the command to be executed |
| 154 | @param stdin: the executed program's standard input |
| 155 | @param stdout: the executed program's stdandrd output |
| 156 | ''' |
Owen Lin | d28cf0f | 2013-12-11 11:08:41 +0800 | [diff] [blame^] | 157 | ps = popen(args, stdin=stdin, stdout=stdout, stderr=stderr) |
Owen Lin | 9d19b27 | 2013-11-28 12:13:24 +0800 | [diff] [blame] | 158 | out = ps.communicate()[0] if stdout == subprocess.PIPE else None |
| 159 | wait_and_check_returncode(ps) |
| 160 | return out |
| 161 | |
| 162 | |
Owen Lin | d28cf0f | 2013-12-11 11:08:41 +0800 | [diff] [blame^] | 163 | def popen(args, stdin=None, stdout=TEE_TO_LOGS, stderr=TEE_TO_LOGS): |
Owen Lin | 9d19b27 | 2013-11-28 12:13:24 +0800 | [diff] [blame] | 164 | '''Returns a Popen object just as subprocess.Popen does but with the |
| 165 | executed command stored in Popen.command. |
| 166 | ''' |
Owen Lin | d28cf0f | 2013-12-11 11:08:41 +0800 | [diff] [blame^] | 167 | command_id = _command_serial_number.next() |
| 168 | prefix = '[%04d] ' % command_id |
| 169 | |
| 170 | if stdout is TEE_TO_LOGS: |
| 171 | stdout = create_logger(level=logging.DEBUG, prefix=prefix) |
| 172 | if stderr is TEE_TO_LOGS: |
| 173 | stderr = create_logger(level=logging.ERROR, prefix=prefix) |
| 174 | |
| 175 | command = ' '.join(pipes.quote(x) for x in args) |
| 176 | logging.info('%sRunning: %s', prefix, command) |
| 177 | |
Owen Lin | 0d65e8a | 2013-11-28 14:29:54 +0800 | [diff] [blame] | 178 | # The lock is required for http://crbug.com/323843. |
| 179 | with _popen_lock: |
Owen Lin | d28cf0f | 2013-12-11 11:08:41 +0800 | [diff] [blame^] | 180 | ps = subprocess.Popen(args, stdin=stdin, stdout=stdout, stderr=stderr) |
| 181 | logging.info('%spid is %d', prefix, ps.pid) |
| 182 | ps.command_id = command_id |
Owen Lin | 6f3f002 | 2013-12-04 17:57:47 +0800 | [diff] [blame] | 183 | ps.command = command |
Owen Lin | 9d19b27 | 2013-11-28 12:13:24 +0800 | [diff] [blame] | 184 | return ps |