| Index: build/android/devil/android/logcat_monitor.py
|
| diff --git a/build/android/devil/android/logcat_monitor.py b/build/android/devil/android/logcat_monitor.py
|
| index d1622309cec3327a6be4cb6752c37963a9a155c8..0177ecea5dad3b6f96e6360d16e4f5e79fb26159 100644
|
| --- a/build/android/devil/android/logcat_monitor.py
|
| +++ b/build/android/devil/android/logcat_monitor.py
|
| @@ -5,7 +5,12 @@
|
| # pylint: disable=unused-argument
|
|
|
| import logging
|
| +import os
|
| import re
|
| +import tempfile
|
| +import time
|
| +import threading
|
| +import shutil
|
|
|
| from devil.android import decorators
|
| from devil.android.sdk import adb_wrapper
|
| @@ -13,17 +18,19 @@ from devil.android.sdk import adb_wrapper
|
|
|
| class LogcatMonitor(object):
|
|
|
| + _WAIT_TIME = 0.2
|
| _THREADTIME_RE_FORMAT = (
|
| r'(?P<date>\S*) +(?P<time>\S*) +(?P<proc_id>%s) +(?P<thread_id>%s) +'
|
| r'(?P<log_level>%s) +(?P<component>%s) *: +(?P<message>%s)$')
|
|
|
| - def __init__(self, adb, clear=True, filter_specs=None):
|
| + def __init__(self, adb, clear=True, filter_specs=None, output_file=None):
|
| """Create a LogcatMonitor instance.
|
|
|
| Args:
|
| adb: An instance of adb_wrapper.AdbWrapper.
|
| clear: If True, clear the logcat when monitoring starts.
|
| filter_specs: An optional list of '<tag>[:priority]' strings.
|
| + output_file: File path to save recorded logcat.
|
| """
|
| if isinstance(adb, adb_wrapper.AdbWrapper):
|
| self._adb = adb
|
| @@ -31,9 +38,11 @@ class LogcatMonitor(object):
|
| raise ValueError('Unsupported type passed for argument "device"')
|
| self._clear = clear
|
| self._filter_specs = filter_specs
|
| - self._logcat_out = None
|
| - self._logcat_out_file = None
|
| - self._logcat_proc = None
|
| + self._output_file = output_file
|
| + self._record_file = None
|
| + self._record_thread = None
|
| + self._stop_recording_event = threading.Event()
|
| + self._flush_event = threading.Event()
|
|
|
| @decorators.WithTimeoutAndRetriesDefaults(10, 0)
|
| def WaitFor(self, success_regex, failure_regex=None, timeout=None,
|
| @@ -43,7 +52,8 @@ class LogcatMonitor(object):
|
| This will attempt to match lines in the logcat against both |success_regex|
|
| and |failure_regex| (if provided). Note that this calls re.search on each
|
| logcat line, not re.match, so the provided regular expressions don't have
|
| - to match an entire line.
|
| + to match an entire line. This will only look for logcat lines logged after
|
| + this function was called.
|
|
|
| Args:
|
| success_regex: The regular expression to search for.
|
| @@ -61,6 +71,11 @@ class LogcatMonitor(object):
|
| |failure_regex| is found in |timeout| seconds.
|
| DeviceUnreachableError if the device becomes unreachable.
|
| """
|
| + assert self._record_thread is not None, (
|
| + 'Must be currently recording logcat.')
|
| + self._FlushLogcat()
|
| + current_logcat_size = os.path.getsize(self._record_file.name)
|
| +
|
| if isinstance(success_regex, basestring):
|
| success_regex = re.compile(success_regex)
|
| if isinstance(failure_regex, basestring):
|
| @@ -73,17 +88,26 @@ class LogcatMonitor(object):
|
| # returned.
|
| # - failure_regex matches a line, in which case None is returned
|
| # - the timeout is hit, in which case a CommandTimeoutError is raised.
|
| - for l in self._adb.Logcat(filter_specs=self._filter_specs):
|
| - m = success_regex.search(l)
|
| - if m:
|
| - return m
|
| - if failure_regex and failure_regex.search(l):
|
| - return None
|
| + with open(self._record_file.name, 'r') as f:
|
| + f.seek(current_logcat_size)
|
| + while True:
|
| + line = f.readline()
|
| + if line:
|
| + m = success_regex.search(line)
|
| + if m:
|
| + return m
|
| + if failure_regex and failure_regex.search(line):
|
| + return None
|
| + else:
|
| + time.sleep(self._WAIT_TIME)
|
|
|
| def FindAll(self, message_regex, proc_id=None, thread_id=None, log_level=None,
|
| component=None):
|
| """Finds all lines in the logcat that match the provided constraints.
|
|
|
| + This function will look only at the state of the logcat at the time it was
|
| + called.
|
| +
|
| Args:
|
| message_regex: The regular expression that the <message> section must
|
| match.
|
| @@ -98,6 +122,11 @@ class LogcatMonitor(object):
|
| the following named groups: 'date', 'time', 'proc_id', 'thread_id',
|
| 'log_level', 'component', and 'message'.
|
| """
|
| + assert self._record_file is not None, (
|
| + 'Must have recorded or be recording logcat.')
|
| + self._FlushLogcat()
|
| + current_logcat_size = os.path.getsize(self._record_file.name)
|
| +
|
| if proc_id is None:
|
| proc_id = r'\d+'
|
| if thread_id is None:
|
| @@ -111,10 +140,55 @@ class LogcatMonitor(object):
|
| type(self)._THREADTIME_RE_FORMAT % (
|
| proc_id, thread_id, log_level, component, message_regex))
|
|
|
| - for line in self._adb.Logcat(dump=True, logcat_format='threadtime'):
|
| - m = re.match(threadtime_re, line)
|
| - if m:
|
| - yield m
|
| + with open(self._record_file.name, 'r') as f:
|
| + for line in f:
|
| + if f.tell() > current_logcat_size:
|
| + break
|
| + m = re.match(threadtime_re, line)
|
| + if m:
|
| + yield m
|
| +
|
| + def _FlushLogcat(self):
|
| + """Helper function that waits for logcat file to be flushed."""
|
| + if self._record_thread:
|
| + self._flush_event.clear()
|
| + while not self._flush_event.isSet():
|
| + self._flush_event.wait()
|
| +
|
| + def _StartRecording(self):
|
| + """Starts recording logcat to file.
|
| +
|
| + Function spawns a thread that records logcat to file and will not die
|
| + until |StopRecording| is called.
|
| + """
|
| + def record_to_file():
|
| + with open(self._record_file.name, 'a') as f:
|
| + for data in self._adb.Logcat(filter_specs=self._filter_specs,
|
| + logcat_format='threadtime'):
|
| + if self._stop_recording_event.isSet():
|
| + f.flush()
|
| + return
|
| + f.write(data)
|
| + if not self._flush_event.isSet():
|
| + f.flush()
|
| + self._flush_event.set()
|
| +
|
| + assert self._record_thread is None, (
|
| + 'Already started recording. Call |StopRecording| to stop.')
|
| + assert self._record_file is not None, (
|
| + 'Must call |Start| on the logcat monitor before you can record.')
|
| + if self._clear:
|
| + self._adb.Logcat(clear=True)
|
| + self._stop_recording_event.clear()
|
| + self._record_thread = threading.Thread(target=record_to_file)
|
| + self._record_thread.start()
|
| +
|
| + def _StopRecording(self):
|
| + """Finish recording logcat."""
|
| + if self._record_thread:
|
| + self._stop_recording_event.set()
|
| + self._record_thread.join()
|
| + self._record_thread = None
|
|
|
| def Start(self):
|
| """Starts the logcat monitor.
|
| @@ -123,6 +197,15 @@ class LogcatMonitor(object):
|
| """
|
| if self._clear:
|
| self._adb.Logcat(clear=True)
|
| + if not self._record_file:
|
| + self._record_file = tempfile.NamedTemporaryFile()
|
| + self._StartRecording()
|
| +
|
| + def Stop(self):
|
| + """Stops the logcat monitor."""
|
| + self._StopRecording()
|
| + if self._record_file and self._output_file:
|
| + shutil.copy(self._record_file.name, self._output_file)
|
|
|
| def __enter__(self):
|
| """Starts the logcat monitor."""
|
| @@ -131,4 +214,9 @@ class LogcatMonitor(object):
|
|
|
| def __exit__(self, exc_type, exc_val, exc_tb):
|
| """Stops the logcat monitor."""
|
| - pass
|
| + self.Stop()
|
| +
|
| + def __del__(self):
|
| + """Closes logcat recording file in case |Stop| was never called."""
|
| + if self._record_file:
|
| + self._record_file.close()
|
|
|