1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/media/webrtc/trunk/build/android/adb_logcat_monitor.py Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,155 @@ 1.4 +#!/usr/bin/env python 1.5 +# 1.6 +# Copyright (c) 2012 The Chromium Authors. All rights reserved. 1.7 +# Use of this source code is governed by a BSD-style license that can be 1.8 +# found in the LICENSE file. 1.9 + 1.10 +"""Saves logcats from all connected devices. 1.11 + 1.12 +Usage: adb_logcat_monitor.py <base_dir> [<adb_binary_path>] 1.13 + 1.14 +This script will repeatedly poll adb for new devices and save logcats 1.15 +inside the <base_dir> directory, which it attempts to create. The 1.16 +script will run until killed by an external signal. To test, run the 1.17 +script in a shell and <Ctrl>-C it after a while. It should be 1.18 +resilient across phone disconnects and reconnects and start the logcat 1.19 +early enough to not miss anything. 1.20 +""" 1.21 + 1.22 +import logging 1.23 +import os 1.24 +import re 1.25 +import shutil 1.26 +import signal 1.27 +import subprocess 1.28 +import sys 1.29 +import time 1.30 + 1.31 +# Map from device_id -> (process, logcat_num) 1.32 +devices = {} 1.33 + 1.34 + 1.35 +class TimeoutException(Exception): 1.36 + """Exception used to signal a timeout.""" 1.37 + pass 1.38 + 1.39 + 1.40 +class SigtermError(Exception): 1.41 + """Exception used to catch a sigterm.""" 1.42 + pass 1.43 + 1.44 + 1.45 +def StartLogcatIfNecessary(device_id, adb_cmd, base_dir): 1.46 + """Spawns a adb logcat process if one is not currently running.""" 1.47 + process, logcat_num = devices[device_id] 1.48 + if process: 1.49 + if process.poll() is None: 1.50 + # Logcat process is still happily running 1.51 + return 1.52 + else: 1.53 + logging.info('Logcat for device %s has died', device_id) 1.54 + error_filter = re.compile('- waiting for device -') 1.55 + for line in process.stderr: 1.56 + if not error_filter.match(line): 1.57 + logging.error(device_id + ': ' + line) 1.58 + 1.59 + logging.info('Starting logcat %d for device %s', logcat_num, 1.60 + device_id) 1.61 + logcat_filename = 'logcat_%s_%03d' % (device_id, logcat_num) 1.62 + logcat_file = open(os.path.join(base_dir, logcat_filename), 'w') 1.63 + process = subprocess.Popen([adb_cmd, '-s', device_id, 1.64 + 'logcat', '-v', 'threadtime'], 1.65 + stdout=logcat_file, 1.66 + stderr=subprocess.PIPE) 1.67 + devices[device_id] = (process, logcat_num + 1) 1.68 + 1.69 + 1.70 +def GetAttachedDevices(adb_cmd): 1.71 + """Gets the device list from adb. 1.72 + 1.73 + We use an alarm in this function to avoid deadlocking from an external 1.74 + dependency. 1.75 + 1.76 + Args: 1.77 + adb_cmd: binary to run adb 1.78 + 1.79 + Returns: 1.80 + list of devices or an empty list on timeout 1.81 + """ 1.82 + signal.alarm(2) 1.83 + try: 1.84 + out, err = subprocess.Popen([adb_cmd, 'devices'], 1.85 + stdout=subprocess.PIPE, 1.86 + stderr=subprocess.PIPE).communicate() 1.87 + if err: 1.88 + logging.warning('adb device error %s', err.strip()) 1.89 + return re.findall('^(\w+)\tdevice$', out, re.MULTILINE) 1.90 + except TimeoutException: 1.91 + logging.warning('"adb devices" command timed out') 1.92 + return [] 1.93 + except (IOError, OSError): 1.94 + logging.exception('Exception from "adb devices"') 1.95 + return [] 1.96 + finally: 1.97 + signal.alarm(0) 1.98 + 1.99 + 1.100 +def main(base_dir, adb_cmd='adb'): 1.101 + """Monitor adb forever. Expects a SIGINT (Ctrl-C) to kill.""" 1.102 + # We create the directory to ensure 'run once' semantics 1.103 + if os.path.exists(base_dir): 1.104 + print 'adb_logcat_monitor: %s already exists? Cleaning' % base_dir 1.105 + shutil.rmtree(base_dir, ignore_errors=True) 1.106 + 1.107 + os.makedirs(base_dir) 1.108 + logging.basicConfig(filename=os.path.join(base_dir, 'eventlog'), 1.109 + level=logging.INFO, 1.110 + format='%(asctime)-2s %(levelname)-8s %(message)s') 1.111 + 1.112 + # Set up the alarm for calling 'adb devices'. This is to ensure 1.113 + # our script doesn't get stuck waiting for a process response 1.114 + def TimeoutHandler(_, unused_frame): 1.115 + raise TimeoutException() 1.116 + signal.signal(signal.SIGALRM, TimeoutHandler) 1.117 + 1.118 + # Handle SIGTERMs to ensure clean shutdown 1.119 + def SigtermHandler(_, unused_frame): 1.120 + raise SigtermError() 1.121 + signal.signal(signal.SIGTERM, SigtermHandler) 1.122 + 1.123 + logging.info('Started with pid %d', os.getpid()) 1.124 + pid_file_path = os.path.join(base_dir, 'LOGCAT_MONITOR_PID') 1.125 + 1.126 + try: 1.127 + with open(pid_file_path, 'w') as f: 1.128 + f.write(str(os.getpid())) 1.129 + while True: 1.130 + for device_id in GetAttachedDevices(adb_cmd): 1.131 + if not device_id in devices: 1.132 + devices[device_id] = (None, 0) 1.133 + 1.134 + for device in devices: 1.135 + # This will spawn logcat watchers for any device ever detected 1.136 + StartLogcatIfNecessary(device, adb_cmd, base_dir) 1.137 + 1.138 + time.sleep(5) 1.139 + except SigtermError: 1.140 + logging.info('Received SIGTERM, shutting down') 1.141 + except: 1.142 + logging.exception('Unexpected exception in main.') 1.143 + finally: 1.144 + for process, _ in devices.itervalues(): 1.145 + if process: 1.146 + try: 1.147 + process.terminate() 1.148 + except OSError: 1.149 + pass 1.150 + os.remove(pid_file_path) 1.151 + 1.152 + 1.153 +if __name__ == '__main__': 1.154 + if 2 <= len(sys.argv) <= 3: 1.155 + print 'adb_logcat_monitor: Initializing' 1.156 + sys.exit(main(*sys.argv[1:3])) 1.157 + 1.158 + print 'Usage: %s <base_dir> [<adb_binary_path>]' % sys.argv[0]