People using autotest in systems with busybox might have trouble with the fact that busybox's tail doesn't support any of the required options. Given that tail's functionality can be emulated using a few lines of python, let's go ahead and do it.
The idea is to replace tail subprocesses watching the stdout file to sys.stdout and watching the stderr file to sys.stderr with threads that keep reading lines from those files and writing them to the respective streams. This code was lightly tested (only run autoserv in some target machines), so quite a bit of testing should go in the event people are OK with it. After all, the reasoning of avoiding calling out to external programs if we can do the same with a reasonable amount of standard python is sound, besides reducing client requirements. This was sent as pull request #73 https://github.com/autotest/autotest/pull/73 Signed-off-by: Lucas Meneghel Rodrigues <[email protected]> --- client/bin/autotestd_monitor | 85 ++++++++++++++++++++++++++++------------- 1 files changed, 58 insertions(+), 27 deletions(-) diff --git a/client/bin/autotestd_monitor b/client/bin/autotestd_monitor index a3217af..7aeee90 100755 --- a/client/bin/autotestd_monitor +++ b/client/bin/autotestd_monitor @@ -1,38 +1,72 @@ #!/usr/bin/python - -import common -import sys, os, signal, time, subprocess, fcntl +""" +Watches the execution of autotestd, printing the logfiles to stdout and stderr. +""" +import sys, os, signal, time, fcntl, threading logdir = sys.argv[1] stdout_start = int(sys.argv[2]) # number of bytes we can skip on stdout stderr_start = int(sys.argv[3]) # nubmer of bytes we can skip on stderr +autotestd_monitor_timeout = 30 # monitor timeout -# if any of our tail processes die, the monitor should die too -def kill_self(signum, frame): - os.kill(os.getpid(), signal.SIGTERM) -signal.signal(signal.SIGCHLD, kill_self) -devnull = open(os.devnull, 'w') +class AutotestdMonitorTimeoutError(Exception): + """ + Exception thrown when autotestd cannot start after a given timeout. + """ + def __init__(self, logdir, timeout): + self.logdir = logdir + self.timeout = timeout -# launch some tail processes to pump the std* streams -def launch_tail(filename, outstream, start): - path = os.path.join(logdir, filename) - argv = ['tail', '--retry', '--follow=name', '--bytes=+%d' % start, path] - # stdout=sys.stdout fails on pre-2.5 python (bug in subprocess module) - if outstream != subprocess.PIPE and outstream.fileno() == 1: - return subprocess.Popen(argv, stderr=devnull) - else: - return subprocess.Popen(argv, stdout=outstream, stderr=devnull) -stdout_pump = launch_tail('stdout', sys.stdout, stdout_start) -stderr_pump = launch_tail('stderr', sys.stderr, stderr_start) + def __str__(self): + return ("Autotestd monitor failed to start on %s after %d s" % + (self.logdir, self.timeout)) + + +def launch_tail(path, outstream, start): + """ + Watches the given path printing the result to outstream. + + @param path: Path that will be watched. + @param outstream: Output stream (sys.stdout, for example). + @param start: Skip [start] bytes from output. + """ + global _thread_termination_event + while not os.path.isfile(path): + time.sleep(0.1) + log_file = open(path, 'r') + log_file.seek(start) + while True: + where = log_file.tell() + line = log_file.readline() + if not line: + log_file.seek(where) + else: + outstream.write(line) + if _thread_termination_event.isSet(): + break + _thread_termination_event.wait(1) + + +stdout_file_path = os.path.join(logdir, 'stdout') +stderr_file_path = os.path.join(logdir, 'stderr') + +_thread_termination_event = threading.Event() +_stdout_thread = threading.Thread(target=launch_tail, + args=(stdout_file_path, sys.stdout, stdout_start)) +_stderr_thread = threading.Thread(target=launch_tail, + args=(stderr_file_path, sys.stderr, stderr_start)) +_stdout_thread.start() +_stderr_thread.start() # wait for logdir/started to exist to be sure autotestd is started start_time = time.time() started_file_path = os.path.join(logdir, 'started') while not os.path.exists(started_file_path): time.sleep(1) - if time.time() - start_time >= 30: - raise Exception("autotestd failed to start in %s" % logdir) + time_elapsed = time.time() - start_time + if time_elapsed >= autotestd_monitor_timeout: + raise AutotestdMonitorTimeoutError(logdir, autotestd_monitor_timeout) # watch the exit code file for an exit exit_code_file = open(os.path.join(logdir, 'exit_code')) @@ -47,12 +81,9 @@ finally: fcntl.flock(exit_code_file, fcntl.LOCK_UN) exit_code_file.close() -# tail runs in 1s polling loop, so give them a chance to finish -time.sleep(2) -# clear the SIGCHLD handler so that killing the tails doesn't kill us -signal.signal(signal.SIGCHLD, signal.SIG_DFL) -os.kill(stdout_pump.pid, signal.SIGTERM) -os.kill(stderr_pump.pid, signal.SIGTERM) +_thread_termination_event.set() +_stdout_thread.join(2) +_stderr_thread.join(2) # exit (with the same code as autotestd) sys.exit(exit_code) -- 1.7.7.1 _______________________________________________ Autotest mailing list [email protected] http://test.kernel.org/cgi-bin/mailman/listinfo/autotest
