| #!/usr/bin/env vpython | |
| # Copyright 2016 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. | |
| """Wrapper for adding logdog streaming support to swarming tasks.""" | |
| import argparse | |
| import contextlib | |
| import logging | |
| import os | |
| import signal | |
| import subprocess | |
| import sys | |
| _SRC_PATH = os.path.abspath(os.path.join( | |
| os.path.dirname(__file__), '..', '..', '..')) | |
| sys.path.append(os.path.join(_SRC_PATH, 'third_party', 'catapult', 'devil')) | |
| sys.path.append(os.path.join(_SRC_PATH, 'third_party', 'catapult', 'common', | |
| 'py_utils')) | |
| from devil.utils import signal_handler | |
| from devil.utils import timeout_retry | |
| from py_utils import tempfile_ext | |
| PROJECT = 'chromium' | |
| OUTPUT = 'logdog' | |
| COORDINATOR_HOST = 'luci-logdog.appspot.com' | |
| SERVICE_ACCOUNT_JSON = ('/creds/service_accounts' | |
| '/service-account-luci-logdog-publisher.json') | |
| LOGDOG_TERMINATION_TIMEOUT = 30 | |
| def CommandParser(): | |
| # Parses the command line arguments being passed in | |
| parser = argparse.ArgumentParser() | |
| wrapped = parser.add_mutually_exclusive_group() | |
| wrapped.add_argument( | |
| '--target', | |
| help='The test target to be run. If neither target nor script are set,' | |
| ' any extra args passed to this script are assumed to be the' | |
| ' full test command to run.') | |
| wrapped.add_argument( | |
| '--script', | |
| help='The script target to be run. If neither target nor script are set,' | |
| ' any extra args passed to this script are assumed to be the' | |
| ' full test command to run.') | |
| parser.add_argument('--logdog-bin-cmd', required=True, | |
| help='The logdog bin cmd.') | |
| return parser | |
| def CreateStopTestsMethod(proc): | |
| def StopTests(signum, _frame): | |
| logging.error('Forwarding signal %s to test process', str(signum)) | |
| proc.send_signal(signum) | |
| return StopTests | |
| @contextlib.contextmanager | |
| def NoLeakingProcesses(popen): | |
| try: | |
| yield popen | |
| finally: | |
| if popen is not None: | |
| 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(): | |
| parser = CommandParser() | |
| args, extra_cmd_args = parser.parse_known_args(sys.argv[1:]) | |
| logging.basicConfig(level=logging.INFO) | |
| if args.target: | |
| test_cmd = [os.path.join('bin', 'run_%s' % args.target), '-v'] | |
| test_cmd += extra_cmd_args | |
| elif args.script: | |
| test_cmd = [args.script] | |
| test_cmd += extra_cmd_args | |
| else: | |
| test_cmd = extra_cmd_args | |
| test_env = dict(os.environ) | |
| logdog_cmd = [] | |
| with tempfile_ext.NamedTemporaryDirectory( | |
| prefix='tmp_android_logdog_wrapper') as temp_directory: | |
| if not os.path.exists(args.logdog_bin_cmd): | |
| logging.error( | |
| 'Logdog binary %s unavailable. Unable to create logdog client', | |
| args.logdog_bin_cmd) | |
| else: | |
| streamserver_uri = 'unix:%s' % os.path.join(temp_directory, | |
| 'butler.sock') | |
| prefix = os.path.join('android', 'swarming', 'logcats', | |
| os.environ.get('SWARMING_TASK_ID')) | |
| logdog_cmd = [ | |
| args.logdog_bin_cmd, | |
| '-project', PROJECT, | |
| '-output', OUTPUT, | |
| '-prefix', prefix, | |
| '--service-account-json', SERVICE_ACCOUNT_JSON, | |
| '-coordinator-host', COORDINATOR_HOST, | |
| 'serve', | |
| '-streamserver-uri', streamserver_uri] | |
| test_env.update({ | |
| 'LOGDOG_STREAM_PROJECT': PROJECT, | |
| 'LOGDOG_STREAM_PREFIX': prefix, | |
| 'LOGDOG_STREAM_SERVER_PATH': streamserver_uri, | |
| 'LOGDOG_COORDINATOR_HOST': COORDINATOR_HOST, | |
| }) | |
| logdog_proc = None | |
| if logdog_cmd: | |
| logdog_proc = subprocess.Popen(logdog_cmd) | |
| with NoLeakingProcesses(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() | |
| if logdog_proc: | |
| def logdog_stopped(): | |
| return logdog_proc.poll() is not None | |
| logdog_proc.terminate() | |
| timeout_retry.WaitFor(logdog_stopped, wait_period=1, | |
| max_tries=LOGDOG_TERMINATION_TIMEOUT) | |
| # If logdog_proc hasn't finished by this point, allow | |
| # NoLeakingProcesses to kill it. | |
| return result | |
| if __name__ == '__main__': | |
| sys.exit(main()) |