3 # Copyright (c) 2012 The Chromium Authors. All rights reserved.
4 # Use of this source code is governed by a BSD-style license that can be
5 # found in the LICENSE file.
7 """Saves logcats from all connected devices.
9 Usage: adb_logcat_monitor.py <base_dir> [<adb_binary_path>]
11 This script will repeatedly poll adb for new devices and save logcats
12 inside the <base_dir> directory, which it attempts to create. The
13 script will run until killed by an external signal. To test, run the
14 script in a shell and <Ctrl>-C it after a while. It should be
15 resilient across phone disconnects and reconnects and start the logcat
16 early enough to not miss anything.
28 # Map from device_id -> (process, logcat_num)
32 class TimeoutException(Exception):
33 """Exception used to signal a timeout."""
37 class SigtermError(Exception):
38 """Exception used to catch a sigterm."""
42 def StartLogcatIfNecessary(device_id, adb_cmd, base_dir):
43 """Spawns a adb logcat process if one is not currently running."""
44 process, logcat_num = devices[device_id]
46 if process.poll() is None:
47 # Logcat process is still happily running
50 logging.info('Logcat for device %s has died', device_id)
51 error_filter = re.compile('- waiting for device -')
52 for line in process.stderr:
53 if not error_filter.match(line):
54 logging.error(device_id + ': ' + line)
56 logging.info('Starting logcat %d for device %s', logcat_num,
58 logcat_filename = 'logcat_%s_%03d' % (device_id, logcat_num)
59 logcat_file = open(os.path.join(base_dir, logcat_filename), 'w')
60 process = subprocess.Popen([adb_cmd, '-s', device_id,
61 'logcat', '-v', 'threadtime'],
63 stderr=subprocess.PIPE)
64 devices[device_id] = (process, logcat_num + 1)
67 def GetAttachedDevices(adb_cmd):
68 """Gets the device list from adb.
70 We use an alarm in this function to avoid deadlocking from an external
74 adb_cmd: binary to run adb
77 list of devices or an empty list on timeout
81 out, err = subprocess.Popen([adb_cmd, 'devices'],
82 stdout=subprocess.PIPE,
83 stderr=subprocess.PIPE).communicate()
85 logging.warning('adb device error %s', err.strip())
86 return re.findall('^(\w+)\tdevice$', out, re.MULTILINE)
87 except TimeoutException:
88 logging.warning('"adb devices" command timed out')
90 except (IOError, OSError):
91 logging.exception('Exception from "adb devices"')
97 def main(base_dir, adb_cmd='adb'):
98 """Monitor adb forever. Expects a SIGINT (Ctrl-C) to kill."""
99 # We create the directory to ensure 'run once' semantics
100 if os.path.exists(base_dir):
101 print 'adb_logcat_monitor: %s already exists? Cleaning' % base_dir
102 shutil.rmtree(base_dir, ignore_errors=True)
104 os.makedirs(base_dir)
105 logging.basicConfig(filename=os.path.join(base_dir, 'eventlog'),
107 format='%(asctime)-2s %(levelname)-8s %(message)s')
109 # Set up the alarm for calling 'adb devices'. This is to ensure
110 # our script doesn't get stuck waiting for a process response
111 def TimeoutHandler(_, unused_frame):
112 raise TimeoutException()
113 signal.signal(signal.SIGALRM, TimeoutHandler)
115 # Handle SIGTERMs to ensure clean shutdown
116 def SigtermHandler(_, unused_frame):
118 signal.signal(signal.SIGTERM, SigtermHandler)
120 logging.info('Started with pid %d', os.getpid())
121 pid_file_path = os.path.join(base_dir, 'LOGCAT_MONITOR_PID')
124 with open(pid_file_path, 'w') as f:
125 f.write(str(os.getpid()))
127 for device_id in GetAttachedDevices(adb_cmd):
128 if not device_id in devices:
129 subprocess.call([adb_cmd, '-s', device_id, 'logcat', '-c'])
130 devices[device_id] = (None, 0)
132 for device in devices:
133 # This will spawn logcat watchers for any device ever detected
134 StartLogcatIfNecessary(device, adb_cmd, base_dir)
138 logging.info('Received SIGTERM, shutting down')
140 logging.exception('Unexpected exception in main.')
142 for process, _ in devices.itervalues():
148 os.remove(pid_file_path)
151 if __name__ == '__main__':
152 if 2 <= len(sys.argv) <= 3:
153 print 'adb_logcat_monitor: Initializing'
154 sys.exit(main(*sys.argv[1:3]))
156 print 'Usage: %s <base_dir> [<adb_binary_path>]' % sys.argv[0]