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(_signum
, _unused_frame
):
112 raise TimeoutException()
113 signal
.signal(signal
.SIGALRM
, TimeoutHandler
)
115 # Handle SIGTERMs to ensure clean shutdown
116 def SigtermHandler(_signum
, _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]