blob: ee43e2d85ea4704312b574fa8fbf97be9f34a2d1 [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 Linad6610a2013-12-13 11:20:48 +080015from subprocess import PIPE
Owen Lind28cf0f2013-12-11 11:08:41 +080016from autotest_lib.client.common_lib.utils import TEE_TO_LOGS
17
Owen Lin0d65e8a2013-11-28 14:29:54 +080018_popen_lock = threading.Lock()
Owen Lind28cf0f2013-12-11 11:08:41 +080019_logging_service = None
20_command_serial_number = itertools.count(1)
21
22_LOG_BUFSIZE = 4096
23_PIPE_CLOSED = -1
24
25class _LoggerProxy(object):
26
27 def __init__(self, logger):
28 self._logger = logger
29
30 def fileno(self):
31 return self._logger._pipe[1]
32
33 def __del__(self):
34 self._logger.close()
35
36
37class _PipeLogger(object):
38
39 def __init__(self, level, prefix):
40 self._pipe = list(os.pipe())
41 self._level = level
42 self._prefix = prefix
43
44 def close(self):
45 if self._pipe[1] != _PIPE_CLOSED:
46 os.close(self._pipe[1])
47 self._pipe[1] = _PIPE_CLOSED
48
49
50class _LoggingService(object):
51
52 def __init__(self):
53 # Python's list is thread safe
54 self._loggers = []
55
56 # Change tuple to list so that we can change the value when
57 # closing the pipe.
58 self._pipe = list(os.pipe())
59 self._thread = threading.Thread(target=self._service_run)
60 self._thread.daemon = True
61 self._thread.start()
62
63
64 def _service_run(self):
65 terminate_loop = False
66 while not terminate_loop:
67 rlist = [l._pipe[0] for l in self._loggers]
68 rlist.append(self._pipe[0])
69 for r in select.select(rlist, [], [])[0]:
70 data = os.read(r, _LOG_BUFSIZE)
71 if r != self._pipe[0]:
72 self._output_logger_message(r, data)
73 elif len(data) == 0:
74 terminate_loop = True
75 # Release resources.
76 os.close(self._pipe[0])
77 for logger in self._loggers:
78 os.close(logger._pipe[0])
79
80
81 def _output_logger_message(self, r, data):
82 logger = next(l for l in self._loggers if l._pipe[0] == r)
83
84 if len(data) == 0:
85 os.close(logger._pipe[0])
86 self._loggers.remove(logger)
87 return
88
89 for line in data.split('\n'):
90 logging.log(logger._level, '%s%s', logger._prefix, line)
91
92
93 def create_logger(self, level=logging.DEBUG, prefix=''):
94 logger = _PipeLogger(level=level, prefix=prefix)
95 self._loggers.append(logger)
96 os.write(self._pipe[1], '\0')
97 return _LoggerProxy(logger)
98
99
100 def shutdown(self):
101 if self._pipe[1] != _PIPE_CLOSED:
102 os.close(self._pipe[1])
103 self._pipe[1] = _PIPE_CLOSED
104 self._thread.join()
105
106
107def create_logger(level=logging.DEBUG, prefix=''):
108 global _logging_service
109 if _logging_service is None:
110 _logging_service = _LoggingService()
111 atexit.register(_logging_service.shutdown)
112 return _logging_service.create_logger(level=level, prefix=prefix)
Owen Lin9d19b272013-11-28 12:13:24 +0800113
114
Owen Lin6513db52013-12-11 11:06:46 +0800115def kill_or_log_returncode(*popens):
116 '''Kills all the processes of the given Popens or logs the return code.
Owen Lin7ab45a22013-11-19 17:26:33 +0800117
118 @param poopens: The Popens to be killed.
119 '''
120 for p in popens:
Owen Lin6513db52013-12-11 11:06:46 +0800121 if p.poll() is None:
Owen Lin7ab45a22013-11-19 17:26:33 +0800122 try:
123 p.kill()
Owen Lin6513db52013-12-11 11:06:46 +0800124 except Exception as e:
125 logging.warning('failed to kill %d, %s', p.pid, e)
126 else:
127 logging.warning('command exit (pid=%d, rc=%d): %s',
128 p.pid, p.returncode, p.command)
Owen Lin7ab45a22013-11-19 17:26:33 +0800129
130
Owen Lin9d19b272013-11-28 12:13:24 +0800131def wait_and_check_returncode(*popens):
132 '''Wait for all the Popens and check the return code is 0.
133
134 If the return code is not 0, it raises an RuntimeError.
Owen Lin7ab45a22013-11-19 17:26:33 +0800135
136 @param popens: The Popens to be checked.
Owen Lin9d19b272013-11-28 12:13:24 +0800137 '''
Owen Lin7ab45a22013-11-19 17:26:33 +0800138 error_message = None
Owen Lin9d19b272013-11-28 12:13:24 +0800139 for p in popens:
140 if p.wait() != 0:
Owen Lin7ab45a22013-11-19 17:26:33 +0800141 error_message = ('Command failed(%d, %d): %s' %
142 (p.pid, p.returncode, p.command))
143 logging.error(error_message)
144 if error_message:
145 raise RuntimeError(error_message)
Owen Lin9d19b272013-11-28 12:13:24 +0800146
147
Owen Lind28cf0f2013-12-11 11:08:41 +0800148def execute(args, stdin=None, stdout=TEE_TO_LOGS, stderr=TEE_TO_LOGS):
Owen Lin9d19b272013-11-28 12:13:24 +0800149 '''Executes a child command and wait for it.
150
151 Returns the output from standard output if 'stdout' is subprocess.PIPE.
152 Raises RuntimeException if the return code of the child command is not 0.
153
154 @param args: the command to be executed
155 @param stdin: the executed program's standard input
156 @param stdout: the executed program's stdandrd output
157 '''
Owen Lind28cf0f2013-12-11 11:08:41 +0800158 ps = popen(args, stdin=stdin, stdout=stdout, stderr=stderr)
Owen Lin9d19b272013-11-28 12:13:24 +0800159 out = ps.communicate()[0] if stdout == subprocess.PIPE else None
160 wait_and_check_returncode(ps)
161 return out
162
163
Owen Lin820cc0c2014-12-05 15:47:25 +0800164def popen(args, stdin=None, stdout=TEE_TO_LOGS, stderr=TEE_TO_LOGS, env=None):
Owen Lin9d19b272013-11-28 12:13:24 +0800165 '''Returns a Popen object just as subprocess.Popen does but with the
166 executed command stored in Popen.command.
167 '''
Owen Lind28cf0f2013-12-11 11:08:41 +0800168 command_id = _command_serial_number.next()
169 prefix = '[%04d] ' % command_id
170
171 if stdout is TEE_TO_LOGS:
172 stdout = create_logger(level=logging.DEBUG, prefix=prefix)
173 if stderr is TEE_TO_LOGS:
174 stderr = create_logger(level=logging.ERROR, prefix=prefix)
175
176 command = ' '.join(pipes.quote(x) for x in args)
177 logging.info('%sRunning: %s', prefix, command)
178
Owen Lin0d65e8a2013-11-28 14:29:54 +0800179 # The lock is required for http://crbug.com/323843.
180 with _popen_lock:
Owen Lin820cc0c2014-12-05 15:47:25 +0800181 ps = subprocess.Popen(args, stdin=stdin, stdout=stdout, stderr=stderr,
182 env=env)
Owen Lind28cf0f2013-12-11 11:08:41 +0800183 logging.info('%spid is %d', prefix, ps.pid)
184 ps.command_id = command_id
Owen Lin6f3f0022013-12-04 17:57:47 +0800185 ps.command = command
Owen Lin9d19b272013-11-28 12:13:24 +0800186 return ps