OLD | NEW |
| (Empty) |
1 #!/usr/bin/python | |
2 # | |
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. | |
6 | |
7 """Saves logcats from all connected devices. | |
8 | |
9 Usage: adb_logcat_monitor.py <base_dir> [<adb_binary_path>] | |
10 | |
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. | |
17 """ | |
18 | |
19 import logging | |
20 import os | |
21 import re | |
22 import shutil | |
23 import signal | |
24 import subprocess | |
25 import sys | |
26 import time | |
27 | |
28 # Map from device_id -> (process, logcat_num) | |
29 devices = {} | |
30 | |
31 | |
32 class TimeoutException(Exception): | |
33 """Exception used to signal a timeout.""" | |
34 pass | |
35 | |
36 | |
37 class SigtermError(Exception): | |
38 """Exception used to catch a sigterm.""" | |
39 pass | |
40 | |
41 | |
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] | |
45 if process: | |
46 if process.poll() is None: | |
47 # Logcat process is still happily running | |
48 return | |
49 else: | |
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) | |
55 | |
56 logging.info('Starting logcat %d for device %s', logcat_num, | |
57 device_id) | |
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'], | |
62 stdout=logcat_file, | |
63 stderr=subprocess.PIPE) | |
64 devices[device_id] = (process, logcat_num + 1) | |
65 | |
66 | |
67 def GetAttachedDevices(adb_cmd): | |
68 """Gets the device list from adb. | |
69 | |
70 We use an alarm in this function to avoid deadlocking from an external | |
71 dependency. | |
72 | |
73 Args: | |
74 adb_cmd: binary to run adb | |
75 | |
76 Returns: | |
77 list of devices or an empty list on timeout | |
78 """ | |
79 signal.alarm(2) | |
80 try: | |
81 out, err = subprocess.Popen([adb_cmd, 'devices'], | |
82 stdout=subprocess.PIPE, | |
83 stderr=subprocess.PIPE).communicate() | |
84 if err: | |
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') | |
89 return [] | |
90 except (IOError, OSError): | |
91 logging.exception('Exception from "adb devices"') | |
92 return [] | |
93 finally: | |
94 signal.alarm(0) | |
95 | |
96 | |
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) | |
103 | |
104 os.makedirs(base_dir) | |
105 logging.basicConfig(filename=os.path.join(base_dir, 'eventlog'), | |
106 level=logging.INFO, | |
107 format='%(asctime)-2s %(levelname)-8s %(message)s') | |
108 | |
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) | |
114 | |
115 # Handle SIGTERMs to ensure clean shutdown | |
116 def SigtermHandler(_, unused_frame): | |
117 raise SigtermError() | |
118 signal.signal(signal.SIGTERM, SigtermHandler) | |
119 | |
120 logging.info('Started with pid %d', os.getpid()) | |
121 pid_file_path = os.path.join(base_dir, 'LOGCAT_MONITOR_PID') | |
122 | |
123 try: | |
124 with open(pid_file_path, 'w') as f: | |
125 f.write(str(os.getpid())) | |
126 while True: | |
127 for device_id in GetAttachedDevices(adb_cmd): | |
128 if not device_id in devices: | |
129 devices[device_id] = (None, 0) | |
130 | |
131 for device in devices: | |
132 # This will spawn logcat watchers for any device ever detected | |
133 StartLogcatIfNecessary(device, adb_cmd, base_dir) | |
134 | |
135 time.sleep(5) | |
136 except SigtermError: | |
137 logging.info('Received SIGTERM, shutting down') | |
138 except: | |
139 logging.exception('Unexpected exception in main.') | |
140 finally: | |
141 for process, _ in devices.itervalues(): | |
142 if process: | |
143 try: | |
144 process.terminate() | |
145 except OSError: | |
146 pass | |
147 os.remove(pid_file_path) | |
148 | |
149 | |
150 if __name__ == '__main__': | |
151 if 2 <= len(sys.argv) <= 3: | |
152 print 'adb_logcat_monitor: Initializing' | |
153 sys.exit(main(*sys.argv[1:3])) | |
154 | |
155 print 'Usage: %s <base_dir> [<adb_binary_path>]' % sys.argv[0] | |
OLD | NEW |