utils/android: Add LogcatMonitor
diff --git a/devlib/utils/android.py b/devlib/utils/android.py
index 45bff20..48d6cf5 100644
--- a/devlib/utils/android.py
+++ b/devlib/utils/android.py
@@ -24,6 +24,9 @@
import subprocess
import logging
import re
+import threading
+import tempfile
+import Queue
from collections import defaultdict
from devlib.exception import TargetError, HostError, DevlibError
@@ -508,3 +511,129 @@
platform_tools = _env.platform_tools
adb = _env.adb
aapt = _env.aapt
+
+class LogcatMonitor(threading.Thread):
+
+ FLUSH_SIZE = 1000
+
+ @property
+ def logfile(self):
+ return self._logfile
+
+ def __init__(self, target, regexps=None):
+ super(LogcatMonitor, self).__init__()
+
+ self.target = target
+
+ self._stopped = threading.Event()
+ self._match_found = threading.Event()
+
+ self._sought = None
+ self._found = None
+
+ self._lines = Queue.Queue()
+ self._datalock = threading.Lock()
+ self._regexps = regexps
+
+ def start(self, outfile=None):
+ if outfile:
+ self._logfile = outfile
+ else:
+ fd, self._logfile = tempfile.mkstemp()
+ os.close(fd)
+ logger.debug('Logging to {}'.format(self._logfile))
+
+ super(LogcatMonitor, self).start()
+
+ def run(self):
+ self.target.clear_logcat()
+
+ logcat_cmd = 'logcat'
+
+ # Join all requested regexps with an 'or'
+ if self._regexps:
+ regexp = '{}'.format('|'.join(self._regexps))
+ if len(self._regexps) > 1:
+ regexp = '({})'.format(regexp)
+ logcat_cmd = '{} -e {}'.format(logcat_cmd, regexp)
+
+ logger.debug('logcat command ="{}"'.format(logcat_cmd))
+ self._logcat = self.target.background(logcat_cmd)
+
+ while not self._stopped.is_set():
+ line = self._logcat.stdout.readline(1024)
+ self._add_line(line)
+
+ def stop(self):
+ # Popen can be stuck on readline() so send it a SIGKILL
+ self._logcat.terminate()
+
+ self._stopped.set()
+ self.join()
+
+ self._flush_lines()
+
+ def _add_line(self, line):
+ self._lines.put(line)
+
+ if self._sought and re.match(self._sought, line):
+ self._found = line
+ self._match_found.set()
+
+ if self._lines.qsize() >= self.FLUSH_SIZE:
+ self._flush_lines()
+
+ def _flush_lines(self):
+ with self._datalock:
+ with open(self._logfile, 'a') as fh:
+ while not self._lines.empty():
+ fh.write(self._lines.get())
+
+ def clear_log(self):
+ with self._datalock:
+ while not self._lines.empty():
+ self._lines.get()
+
+ with open(self._logfile, 'w') as fh:
+ pass
+
+ def get_log(self):
+ """
+ Return the list of lines found by the monitor
+ """
+ self._flush_lines()
+
+ with self._datalock:
+ with open(self._logfile, 'r') as fh:
+ res = [line for line in fh]
+
+ return res
+
+ def search(self, regexp, timeout=30):
+ """
+ Search a line that matches a regexp in the logcat log
+ """
+ res = []
+
+ self._flush_lines()
+
+ with self._datalock:
+ with open(self._logfile, 'r') as fh:
+ for line in fh:
+ if re.match(regexp, line):
+ res.append(line)
+
+ # Found some matches, return them
+ if len(res) > 0:
+ return res
+
+ # Did not find any match, wait for one to pop up
+ self._sought = regexp
+ found = self._match_found.wait(timeout)
+ self._match_found.clear()
+ self._sought = None
+
+ if found:
+ return [self._found]
+ else:
+ raise RuntimeError('Logcat monitor timeout ({}s)'.format(timeout))