Commit 897b0280 authored by John Budorick's avatar John Budorick Committed by Commit Bot

[android] Stop capturing full-suite logcats.

This also switches from running tests through the butler to
running tests alongside the butler s.t. we can independently
control process lifetimes.

Bug: 721889
Change-Id: I176b7340d9b3f097ad660b66ea64ffb4816287a8
Reviewed-on: https://chromium-review.googlesource.com/667808
Commit-Queue: John Budorick <jbudorick@chromium.org>
Reviewed-by: default avatarBenjamin Pastene <bpastene@chromium.org>
Cr-Commit-Position: refs/heads/master@{#502757}
parent b3170c39
...@@ -287,6 +287,7 @@ class GtestTestInstance(test_instance.TestInstance): ...@@ -287,6 +287,7 @@ class GtestTestInstance(test_instance.TestInstance):
self._extract_test_list_from_filter = args.extract_test_list_from_filter self._extract_test_list_from_filter = args.extract_test_list_from_filter
self._filter_tests_lock = threading.Lock() self._filter_tests_lock = threading.Lock()
self._shard_timeout = args.shard_timeout self._shard_timeout = args.shard_timeout
self._should_save_logcat = bool(args.json_results_file)
self._store_tombstones = args.store_tombstones self._store_tombstones = args.store_tombstones
self._total_external_shards = args.test_launcher_total_shards self._total_external_shards = args.test_launcher_total_shards
self._suite = args.suite_name[0] self._suite = args.suite_name[0]
...@@ -437,6 +438,12 @@ class GtestTestInstance(test_instance.TestInstance): ...@@ -437,6 +438,12 @@ class GtestTestInstance(test_instance.TestInstance):
def shard_timeout(self): def shard_timeout(self):
return self._shard_timeout return self._shard_timeout
# TODO(jbudorick): Remove this once mikecase lands
# https://codereview.chromium.org/2933993002/
@property
def should_save_logcat(self):
return self._should_save_logcat
@property @property
def store_tombstones(self): def store_tombstones(self):
return self._store_tombstones return self._store_tombstones
......
...@@ -2,6 +2,7 @@ ...@@ -2,6 +2,7 @@
# Use of this source code is governed by a BSD-style license that can be # Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file. # found in the LICENSE file.
import contextlib
import datetime import datetime
import functools import functools
import logging import logging
...@@ -9,6 +10,7 @@ import os ...@@ -9,6 +10,7 @@ import os
import shutil import shutil
import tempfile import tempfile
import threading import threading
import time
import devil_chromium import devil_chromium
from devil import base_error from devil import base_error
...@@ -21,9 +23,20 @@ from devil.android.sdk import adb_wrapper ...@@ -21,9 +23,20 @@ from devil.android.sdk import adb_wrapper
from devil.utils import file_utils from devil.utils import file_utils
from devil.utils import parallelizer from devil.utils import parallelizer
from pylib import constants from pylib import constants
from pylib.android import logdog_logcat_monitor
from pylib.base import environment from pylib.base import environment
from pylib.utils import instrumentation_tracing from pylib.utils import instrumentation_tracing
from py_trace_event import trace_event from py_trace_event import trace_event
from py_utils import contextlib_ext
LOGCAT_FILTERS = [
'*:e',
'chromium:v',
'cr_*:v',
'DEBUG:I',
'StrictMode:D',
]
def _DeviceCachePath(device): def _DeviceCachePath(device):
...@@ -75,6 +88,39 @@ def handle_shard_failures_with(on_failure): ...@@ -75,6 +88,39 @@ def handle_shard_failures_with(on_failure):
return decorator return decorator
# TODO(jbudorick): Reconcile this with the output manager logic in
# https://codereview.chromium.org/2933993002/ once that lands.
@contextlib.contextmanager
def OptionalPerTestLogcat(
device, test_name, condition, additional_filter_specs=None,
deobfuscate_func=None):
"""Conditionally capture logcat and stream it to logdog.
Args:
device: (DeviceUtils) the device from which logcat should be captured.
test_name: (str) the test name to use in the stream name.
condition: (bool) whether or not to capture the logcat.
additional_filter_specs: (list) additional logcat filters.
deobfuscate_func: (callable) an optional unary function that
deobfuscates logcat lines. The callable should take an iterable
of logcat lines and return a list of deobfuscated logcat lines.
Yields:
A LogdogLogcatMonitor instance whether condition is true or not,
though it may not be active.
"""
stream_name = 'logcat_%s_%s_%s' % (
test_name,
time.strftime('%Y%m%dT%H%M%S-UTC', time.gmtime()),
device.serial)
filter_specs = LOGCAT_FILTERS + (additional_filter_specs or [])
logmon = logdog_logcat_monitor.LogdogLogcatMonitor(
device.adb, stream_name, filter_specs=filter_specs,
deobfuscate_func=deobfuscate_func)
with contextlib_ext.Optional(logmon, condition):
yield logmon
class LocalDeviceEnvironment(environment.Environment): class LocalDeviceEnvironment(environment.Environment):
def __init__(self, args, _error_func): def __init__(self, args, _error_func):
......
...@@ -413,12 +413,15 @@ class LocalDeviceGtestRun(local_device_test_run.LocalDeviceTestRun): ...@@ -413,12 +413,15 @@ class LocalDeviceGtestRun(local_device_test_run.LocalDeviceTestRun):
for f in flags: for f in flags:
logging.info(' %s', f) logging.info(' %s', f)
with contextlib_ext.Optional( with local_device_environment.OptionalPerTestLogcat(
trace_event.trace(str(test)), device, hash(tuple(test)),
self._env.trace_output): self._test_instance.should_save_logcat) as logmon:
output = self._delegate.Run( with contextlib_ext.Optional(
test, device, flags=' '.join(flags), trace_event.trace(str(test)),
timeout=timeout, retries=0) self._env.trace_output):
output = self._delegate.Run(
test, device, flags=' '.join(flags),
timeout=timeout, retries=0)
if self._test_instance.enable_xml_result_parsing: if self._test_instance.enable_xml_result_parsing:
try: try:
...@@ -432,6 +435,8 @@ class LocalDeviceGtestRun(local_device_test_run.LocalDeviceTestRun): ...@@ -432,6 +435,8 @@ class LocalDeviceGtestRun(local_device_test_run.LocalDeviceTestRun):
str(e)) str(e))
gtest_xml = None gtest_xml = None
logcat_url = logmon.GetLogcatURL()
for s in self._servers[str(device)]: for s in self._servers[str(device)]:
s.Reset() s.Reset()
if self._test_instance.app_files: if self._test_instance.app_files:
...@@ -451,14 +456,14 @@ class LocalDeviceGtestRun(local_device_test_run.LocalDeviceTestRun): ...@@ -451,14 +456,14 @@ class LocalDeviceGtestRun(local_device_test_run.LocalDeviceTestRun):
results = gtest_test_instance.ParseGTestOutput( results = gtest_test_instance.ParseGTestOutput(
output, self._test_instance.symbolizer, device.product_cpu_abi) output, self._test_instance.symbolizer, device.product_cpu_abi)
# Check whether there are any crashed testcases. tombstones_url = None
self._crashes.update(r.GetName() for r in results for r in results:
if r.GetType() == base_test_result.ResultType.CRASH) if self._test_instance.should_save_logcat:
r.SetLink('logcat', logcat_url)
if self._test_instance.store_tombstones: if r.GetType() == base_test_result.ResultType.CRASH:
tombstones_url = None self._crashes.add(r.GetName())
for result in results: if self._test_instance.store_tombstones:
if result.GetType() == base_test_result.ResultType.CRASH:
if not tombstones_url: if not tombstones_url:
resolved_tombstones = tombstones.ResolveTombstones( resolved_tombstones = tombstones.ResolveTombstones(
device, device,
...@@ -470,7 +475,7 @@ class LocalDeviceGtestRun(local_device_test_run.LocalDeviceTestRun): ...@@ -470,7 +475,7 @@ class LocalDeviceGtestRun(local_device_test_run.LocalDeviceTestRun):
device.serial) device.serial)
tombstones_url = logdog_helper.text( tombstones_url = logdog_helper.text(
stream_name, '\n'.join(resolved_tombstones)) stream_name, '\n'.join(resolved_tombstones))
result.SetLink('tombstones', tombstones_url) r.SetLink('tombstones', tombstones_url)
tests_stripped_disabled_prefix = set() tests_stripped_disabled_prefix = set()
for t in test: for t in test:
......
...@@ -22,7 +22,6 @@ from devil.android.tools import system_app ...@@ -22,7 +22,6 @@ from devil.android.tools import system_app
from devil.utils import reraiser_thread from devil.utils import reraiser_thread
from incremental_install import installer from incremental_install import installer
from pylib import valgrind_tools from pylib import valgrind_tools
from pylib.android import logdog_logcat_monitor
from pylib.base import base_test_result from pylib.base import base_test_result
from pylib.constants import host_paths from pylib.constants import host_paths
from pylib.instrumentation import instrumentation_test_instance from pylib.instrumentation import instrumentation_test_instance
...@@ -440,16 +439,11 @@ class LocalDeviceInstrumentationTestRun( ...@@ -440,16 +439,11 @@ class LocalDeviceInstrumentationTestRun(
time_ms = lambda: int(time.time() * 1e3) time_ms = lambda: int(time.time() * 1e3)
start_ms = time_ms() start_ms = time_ms()
stream_name = 'logcat_%s_%s_%s' % ( with local_device_environment.OptionalPerTestLogcat(
test_name.replace('#', '.'), device, test_name.replace('#', '.'),
time.strftime('%Y%m%dT%H%M%S-UTC', time.gmtime()), self._test_instance.should_save_logcat,
device.serial) additional_filter_specs=['%s:I' % _TAG],
logmon = logdog_logcat_monitor.LogdogLogcatMonitor( deobfuscate_func=self._test_instance.MaybeDeobfuscateLines) as logmon:
device.adb, stream_name, filter_specs=LOGCAT_FILTERS,
deobfuscate_func=self._test_instance.MaybeDeobfuscateLines)
with contextlib_ext.Optional(
logmon, self._test_instance.should_save_logcat):
with _LogTestEndpoints(device, test_name): with _LogTestEndpoints(device, test_name):
with contextlib_ext.Optional( with contextlib_ext.Optional(
trace_event.trace(test_name), trace_event.trace(test_name),
...@@ -872,6 +866,7 @@ class LocalDeviceInstrumentationTestRun( ...@@ -872,6 +866,7 @@ class LocalDeviceInstrumentationTestRun(
return timeout return timeout
def _IsRenderTest(test): def _IsRenderTest(test):
"""Determines if a test or list of tests has a RenderTest amongst them.""" """Determines if a test or list of tests has a RenderTest amongst them."""
if not isinstance(test, list): if not isinstance(test, list):
......
...@@ -76,8 +76,9 @@ def AddTestLauncherOptions(parser): ...@@ -76,8 +76,9 @@ def AddTestLauncherOptions(parser):
'--test-launcher-summary-output', '--test-launcher-summary-output',
'--json-results-file', '--json-results-file',
dest='json_results_file', type=os.path.realpath, dest='json_results_file', type=os.path.realpath,
help='If set, will dump results in JSON form ' help='If set, will dump results in JSON form to the specified file. '
'to specified file.') 'Note that this will also trigger saving per-test logcats to '
'logdog.')
parser.add_argument( parser.add_argument(
'--test-launcher-shard-index', '--test-launcher-shard-index',
type=int, default=os.environ.get('GTEST_SHARD_INDEX', 0), type=int, default=os.environ.get('GTEST_SHARD_INDEX', 0),
......
...@@ -6,6 +6,7 @@ ...@@ -6,6 +6,7 @@
"""Wrapper for adding logdog streaming support to swarming tasks.""" """Wrapper for adding logdog streaming support to swarming tasks."""
import argparse import argparse
import contextlib
import logging import logging
import os import os
import signal import signal
...@@ -36,58 +37,82 @@ def CommandParser(): ...@@ -36,58 +37,82 @@ def CommandParser():
help='The logdog bin cmd.') help='The logdog bin cmd.')
parser.add_argument('--target-devices-file', required=False, parser.add_argument('--target-devices-file', required=False,
help='The target devices file.') help='The target devices file.')
parser.add_argument('--logcat-output-file',
help='The logcat output file.')
return parser return parser
def CreateStopTestsMethod(proc): def CreateStopTestsMethod(proc):
def StopTests(signum, _frame): def StopTests(signum, _frame):
logging.error('Forwarding signal %s to test process', str(signum)) logging.error('Forwarding signal %s to test process', str(signum))
proc.send_signal(signum) proc.send_signal(signum)
return StopTests return StopTests
@contextlib.contextmanager
def NoLeakingProcesses(popen):
try:
yield popen
finally:
try:
if popen.poll() is None:
popen.kill()
except OSError:
logging.warning('Failed to kill %s. Process may be leaked.',
str(popen.pid))
def main(): def main():
parser = CommandParser() parser = CommandParser()
args, extra_cmd_args = parser.parse_known_args(sys.argv[1:]) args, extra_cmd_args = parser.parse_known_args(sys.argv[1:])
logging.basicConfig(level=logging.INFO) logging.basicConfig(level=logging.INFO)
with tempfile_ext.NamedTemporaryDirectory() as logcat_output_dir: test_cmd = [
test_cmd = [ os.path.join('bin', 'run_%s' % args.target),
os.path.join('bin', 'run_%s' % args.target), '--target-devices-file', args.target_devices_file,
'--logcat-output-file', '-v']
(args.logcat_output_file if args.logcat_output_file test_env = dict(os.environ)
else os.path.join(logcat_output_dir, 'logcats')), logdog_cmd = []
'--target-devices-file', args.target_devices_file,
'-v'] with tempfile_ext.NamedTemporaryDirectory(
prefix='tmp_android_logdog_wrapper') as temp_directory:
with tempfile_ext.NamedTemporaryDirectory( if not os.path.exists(args.logdog_bin_cmd):
prefix='tmp_android_logdog_wrapper') as temp_directory: logging.error(
if not os.path.exists(args.logdog_bin_cmd): 'Logdog binary %s unavailable. Unable to create logdog client',
logging.error( args.logdog_bin_cmd)
'Logdog binary %s unavailable. Unable to create logdog client', else:
args.logdog_bin_cmd) streamserver_uri = 'unix:%s' % os.path.join(temp_directory,
else: 'butler.sock')
test_cmd += ['--upload-logcats-file'] prefix = os.path.join('android', 'swarming', 'logcats',
streamserver_uri = 'unix:%s' % os.path.join(temp_directory, os.environ.get('SWARMING_TASK_ID'))
'butler.sock')
prefix = os.path.join('android', 'swarming', 'logcats', logdog_cmd = [
os.environ.get('SWARMING_TASK_ID')) args.logdog_bin_cmd,
'-project', PROJECT,
# Call test_cmdline through logdog butler subcommand. '-output', OUTPUT,
test_cmd = [ '-prefix', prefix,
args.logdog_bin_cmd, '-project', PROJECT, '--service-account-json', SERVICE_ACCOUNT_JSON,
'-output', OUTPUT, '-coordinator-host', COORDINATOR_HOST,
'-prefix', prefix, 'serve',
'--service-account-json', SERVICE_ACCOUNT_JSON, '-streamserver-uri', streamserver_uri]
'-coordinator-host', COORDINATOR_HOST, test_env.update({
'run', '-streamserver-uri', streamserver_uri, '--'] + test_cmd 'LOGDOG_STREAM_PROJECT': PROJECT,
'LOGDOG_STREAM_PREFIX': prefix,
test_cmd += extra_cmd_args 'LOGDOG_STREAM_SERVER_PATH': streamserver_uri,
test_proc = subprocess.Popen(test_cmd) 'LOGDOG_COORDINATOR_HOST': COORDINATOR_HOST,
with signal_handler.SignalHandler(signal.SIGTERM, })
CreateStopTestsMethod(test_proc)):
result = test_proc.wait() test_cmd += extra_cmd_args
return result
with NoLeakingProcesses(subprocess.Popen(logdog_cmd)) as logdog_proc:
with NoLeakingProcesses(
subprocess.Popen(test_cmd, env=test_env)) as test_proc:
with signal_handler.SignalHandler(signal.SIGTERM,
CreateStopTestsMethod(test_proc)):
result = test_proc.wait()
logdog_proc.terminate()
logdog_proc.wait()
return result
if __name__ == '__main__': if __name__ == '__main__':
sys.exit(main()) sys.exit(main())
...@@ -1118,7 +1118,6 @@ class MetaBuildWrapper(object): ...@@ -1118,7 +1118,6 @@ class MetaBuildWrapper(object):
'--target', target, '--target', target,
'--target-devices-file', '${SWARMING_BOT_FILE}', '--target-devices-file', '${SWARMING_BOT_FILE}',
'--logdog-bin-cmd', '../../bin/logdog_butler', '--logdog-bin-cmd', '../../bin/logdog_butler',
'--logcat-output-file', '${ISOLATED_OUTDIR}/logcats',
'--store-tombstones'] '--store-tombstones']
elif is_fuchsia and test_type != 'script': elif is_fuchsia and test_type != 'script':
cmdline = [os.path.join('bin', 'run_%s' % target)] cmdline = [os.path.join('bin', 'run_%s' % target)]
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment