blob: b739b682f9d106e3482ddf584f6d9df91b43aa7d [file] [log] [blame]
Owen Lin9d19b272013-11-28 12:13:24 +08001#!/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 Lind28cf0f2013-12-11 11:08:41 +08006import atexit
7import itertools
Owen Lin9d19b272013-11-28 12:13:24 +08008import logging
Owen Lind28cf0f2013-12-11 11:08:41 +08009import os
Owen Lin9d19b272013-11-28 12:13:24 +080010import pipes
Owen Lind28cf0f2013-12-11 11:08:41 +080011import select
Owen Lin9d19b272013-11-28 12:13:24 +080012import subprocess
Owen Lin0d65e8a2013-11-28 14:29:54 +080013import threading
14
Owen Lind28cf0f2013-12-11 11:08:41 +080015from autotest_lib.client.common_lib.utils import TEE_TO_LOGS
16
Owen Lin0d65e8a2013-11-28 14:29:54 +080017_popen_lock = threading.Lock()
Owen Lind28cf0f2013-12-11 11:08:41 +080018_logging_service = None
19_command_serial_number = itertools.count(1)
20
21_LOG_BUFSIZE = 4096
22_PIPE_CLOSED = -1
23
24class _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
36class _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
49class _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
106def 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 Lin9d19b272013-11-28 12:13:24 +0800112
113
Owen Lin6513db52013-12-11 11:06:46 +0800114def kill_or_log_returncode(*popens):
115 '''Kills all the processes of the given Popens or logs the return code.
Owen Lin7ab45a22013-11-19 17:26:33 +0800116
117 @param poopens: The Popens to be killed.
118 '''
119 for p in popens:
Owen Lin6513db52013-12-11 11:06:46 +0800120 if p.poll() is None:
Owen Lin7ab45a22013-11-19 17:26:33 +0800121 try:
122 p.kill()
Owen Lin6513db52013-12-11 11:06:46 +0800123 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 Lin7ab45a22013-11-19 17:26:33 +0800128
129
Owen Lin9d19b272013-11-28 12:13:24 +0800130def 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 Lin7ab45a22013-11-19 17:26:33 +0800134
135 @param popens: The Popens to be checked.
Owen Lin9d19b272013-11-28 12:13:24 +0800136 '''
Owen Lin7ab45a22013-11-19 17:26:33 +0800137 error_message = None
Owen Lin9d19b272013-11-28 12:13:24 +0800138 for p in popens:
139 if p.wait() != 0:
Owen Lin7ab45a22013-11-19 17:26:33 +0800140 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 Lin9d19b272013-11-28 12:13:24 +0800145
146
Owen Lind28cf0f2013-12-11 11:08:41 +0800147def execute(args, stdin=None, stdout=TEE_TO_LOGS, stderr=TEE_TO_LOGS):
Owen Lin9d19b272013-11-28 12:13:24 +0800148 '''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 Lind28cf0f2013-12-11 11:08:41 +0800157 ps = popen(args, stdin=stdin, stdout=stdout, stderr=stderr)
Owen Lin9d19b272013-11-28 12:13:24 +0800158 out = ps.communicate()[0] if stdout == subprocess.PIPE else None
159 wait_and_check_returncode(ps)
160 return out
161
162
Owen Lind28cf0f2013-12-11 11:08:41 +0800163def popen(args, stdin=None, stdout=TEE_TO_LOGS, stderr=TEE_TO_LOGS):
Owen Lin9d19b272013-11-28 12:13:24 +0800164 '''Returns a Popen object just as subprocess.Popen does but with the
165 executed command stored in Popen.command.
166 '''
Owen Lind28cf0f2013-12-11 11:08:41 +0800167 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 Lin0d65e8a2013-11-28 14:29:54 +0800178 # The lock is required for http://crbug.com/323843.
179 with _popen_lock:
Owen Lind28cf0f2013-12-11 11:08:41 +0800180 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 Lin6f3f0022013-12-04 17:57:47 +0800183 ps.command = command
Owen Lin9d19b272013-11-28 12:13:24 +0800184 return ps