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

Reply via email to