Chromium Code Reviews| Index: build/android/pylib/device/logcat_monitor.py |
| diff --git a/build/android/pylib/device/logcat_monitor.py b/build/android/pylib/device/logcat_monitor.py |
| new file mode 100644 |
| index 0000000000000000000000000000000000000000..fe53f64050228a3f79cb74303a65c6f11dfc3504 |
| --- /dev/null |
| +++ b/build/android/pylib/device/logcat_monitor.py |
| @@ -0,0 +1,131 @@ |
| +# Copyright 2015 The Chromium Authors. All rights reserved. |
| +# Use of this source code is governed by a BSD-style license that can be |
| +# found in the LICENSE file. |
| + |
| +# pylint: disable=unused-argument |
| + |
| +import collections |
| +import itertools |
| +import logging |
| +import subprocess |
| +import tempfile |
| +import time |
| +import re |
| + |
| +from pylib.device import adb_wrapper |
| +from pylib.device import decorators |
| +from pylib.device import device_errors |
| + |
| + |
| +class LogcatMonitor(object): |
| + |
| + # Format: <DATE> <TIME> <PID> <TID> <LEVEL> <COMPONENT>: <MESSAGE> |
| + _THREADTIME_RE_FORMAT = r'\S* +\S* +(%s) +(%s) +(%s) +(%s): +(%s)$' |
| + |
| + def __init__(self, adb, clear=True, filters=None): |
| + if isinstance(adb, adb_wrapper.AdbWrapper): |
| + self._adb = adb |
| + else: |
| + raise ValueError('Unsupported type passed for argument "device"') |
| + self._clear = clear |
| + self._filters = filters |
| + self._logcat_out = None |
| + self._logcat_out_file = None |
| + self._logcat_proc = None |
| + |
| + @decorators.WithTimeoutAndRetriesDefaults(10, 0) |
| + def WaitFor(self, success_regex, failure_regex, timeout=None, retries=None): |
| + """Waits for a logcat line matching the provided regular expression. |
|
klundberg
2015/02/02 20:00:40
As discussed offline, it might be good to update t
jbudorick
2015/02/02 21:47:38
Clarified the docstring and added an implementatio
|
| + |
| + 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. |
| + |
| + Args: |
| + success_regex: The regular expression to search for. |
| + failure_regex: An optional regular expression that, if hit, causes this |
| + to stop looking for a match. Can be None. |
| + timeout: timeout in seconds |
| + retries: number of retries |
| + |
| + Returns: |
| + A match object if |success_regex| matches a part of a logcat line, or |
| + None if |failure_regex| matches a part of a logcat line. |
| + Raises: |
| + CommandFailedError on logcat failure (NOT on a |failure_regex| match) |
|
klundberg
2015/02/02 20:00:40
minor nit: perioad at the end of the sentence.
jbudorick
2015/02/02 21:47:38
Done.
|
| + CommandTimeoutError if a logcat line matching |success_regex| is not |
| + found in |timeout| seconds. |
| + DeviceUnreachableError if the device becomes unreachable. |
| + """ |
| + if isinstance(success_regex, basestring): |
| + success_regex = re.compile(success_regex) |
| + if isinstance(failure_regex, basestring): |
| + failure_regex = re.compile(failure_regex) |
| + |
| + logging.debug('Waiting %d seconds for "%s"', timeout, success_regex.pattern) |
| + |
| + for l in self._adb.Logcat(): |
| + m = success_regex.search(l) |
| + if m: |
| + return m |
| + if failure_regex and failure_regex.search(l): |
| + return None |
| + |
| + 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. |
| + |
| + Args: |
| + message_regex: The regular expression that the <message> section must |
| + match. |
| + proc_id: The process ID to match. If None, matches any process ID. |
| + thread_id: The thread ID to match. If None, matches any thread ID. |
| + log_level: The log level to match. If None, matches any log level. |
| + component: The component to match. If None, matches any component. |
| + |
| + Returns: |
| + An iterable containing objects with five attributes: |
| + |proc_id|: the process ID |
| + |thread_id|: the thread ID |
| + |log_level|: the log level |
| + |component|: the component |
| + |message|: the logcat message |
| + """ |
| + LogcatLine = collections.namedtuple( |
| + 'LogcatLine', |
| + ['proc_id', 'thread_id', 'log_level', 'component', 'message']) |
| + |
| + if proc_id is None: |
| + proc_id = r'\d+' |
| + if thread_id is None: |
| + thread_id = r'\d+' |
| + if log_level is None: |
| + log_level = r'[VDIWEF]' |
| + if component is None: |
| + component = r'[^\s:]+' |
| + threadtime_re = re.compile( |
| + type(self)._THREADTIME_RE_FORMAT % ( |
| + proc_id, thread_id, log_level, component, message_regex)) |
| + |
| + regexed_lines = ( |
| + re.match(threadtime_re, l) |
| + for l in self._adb.Logcat(dump=True, logcat_format='threadtime')) |
| + only_matches = (m for m in regexed_lines if m) |
| + return (LogcatLine(*m.group(1, 2, 3, 4, 5)) for m in only_matches) |
| + |
| + def Start(self): |
| + """Starts the logcat monitor.""" |
| + if self._clear: |
| + self._adb.Logcat(clear=True) |
| + |
| + def Stop(self): |
| + """Stops the logcat monitor.""" |
| + pass |
| + |
| + def __enter__(self): |
| + """Starts the logcat monitor.""" |
| + self.Start() |
| + return self |
| + |
| + def __exit__(self, exc_type, exc_val, exc_tb): |
| + """Stops the logcat monitor.""" |
| + self.Stop() |