blob: 5194668ec6e80dcf800758b1b9670c3fe9167f62 [file] [log] [blame]
David Ghandehari9e5b5872016-07-28 09:50:04 -07001#!/usr/bin/env 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"""Shutdown adb_logcat_monitor and print accumulated logs.
8
9To test, call './adb_logcat_printer.py <base_dir>' where
10<base_dir> contains 'adb logcat -v threadtime' files named as
11logcat_<deviceID>_<sequenceNum>
12
13The script will print the files to out, and will combine multiple
14logcats from a single device if there is overlap.
15
16Additionally, if a <base_dir>/LOGCAT_MONITOR_PID exists, the script
17will attempt to terminate the contained PID by sending a SIGINT and
18monitoring for the deletion of the aforementioned file.
19"""
20
21import cStringIO
22import logging
23import os
24import re
25import signal
26import sys
27import time
28
29
30# Set this to debug for more verbose output
31LOG_LEVEL = logging.INFO
32
33
34def CombineLogFiles(list_of_lists, logger):
35 """Splices together multiple logcats from the same device.
36
37 Args:
38 list_of_lists: list of pairs (filename, list of timestamped lines)
39 logger: handler to log events
40
41 Returns:
42 list of lines with duplicates removed
43 """
44 cur_device_log = ['']
45 for cur_file, cur_file_lines in list_of_lists:
46 # Ignore files with just the logcat header
47 if len(cur_file_lines) < 2:
48 continue
49 common_index = 0
50 # Skip this step if list just has empty string
51 if len(cur_device_log) > 1:
52 try:
53 line = cur_device_log[-1]
54 # Used to make sure we only splice on a timestamped line
55 if re.match('^\d{2}-\d{2} \d{2}:\d{2}:\d{2}.\d{3} ', line):
56 common_index = cur_file_lines.index(line)
57 else:
58 logger.warning('splice error - no timestamp in "%s"?', line.strip())
59 except ValueError:
60 # The last line was valid but wasn't found in the next file
61 cur_device_log += ['***** POSSIBLE INCOMPLETE LOGCAT *****']
62 logger.info('Unable to splice %s. Incomplete logcat?', cur_file)
63
64 cur_device_log += ['*'*30 + ' %s' % cur_file]
65 cur_device_log.extend(cur_file_lines[common_index:])
66
67 return cur_device_log
68
69
70def FindLogFiles(base_dir):
71 """Search a directory for logcat files.
72
73 Args:
74 base_dir: directory to search
75
76 Returns:
77 Mapping of device_id to a sorted list of file paths for a given device
78 """
79 logcat_filter = re.compile('^logcat_(\w+)_(\d+)$')
80 # list of tuples (<device_id>, <seq num>, <full file path>)
81 filtered_list = []
82 for cur_file in os.listdir(base_dir):
83 matcher = logcat_filter.match(cur_file)
84 if matcher:
85 filtered_list += [(matcher.group(1), int(matcher.group(2)),
86 os.path.join(base_dir, cur_file))]
87 filtered_list.sort()
88 file_map = {}
89 for device_id, _, cur_file in filtered_list:
90 if not device_id in file_map:
91 file_map[device_id] = []
92
93 file_map[device_id] += [cur_file]
94 return file_map
95
96
97def GetDeviceLogs(log_filenames, logger):
98 """Read log files, combine and format.
99
100 Args:
101 log_filenames: mapping of device_id to sorted list of file paths
102 logger: logger handle for logging events
103
104 Returns:
105 list of formatted device logs, one for each device.
106 """
107 device_logs = []
108
109 for device, device_files in log_filenames.iteritems():
110 logger.debug('%s: %s', device, str(device_files))
111 device_file_lines = []
112 for cur_file in device_files:
113 with open(cur_file) as f:
114 device_file_lines += [(cur_file, f.read().splitlines())]
115 combined_lines = CombineLogFiles(device_file_lines, logger)
116 # Prepend each line with a short unique ID so it's easy to see
117 # when the device changes. We don't use the start of the device
118 # ID because it can be the same among devices. Example lines:
119 # AB324: foo
120 # AB324: blah
121 device_logs += [('\n' + device[-5:] + ': ').join(combined_lines)]
122 return device_logs
123
124
125def ShutdownLogcatMonitor(base_dir, logger):
126 """Attempts to shutdown adb_logcat_monitor and blocks while waiting."""
127 try:
128 monitor_pid_path = os.path.join(base_dir, 'LOGCAT_MONITOR_PID')
129 with open(monitor_pid_path) as f:
130 monitor_pid = int(f.readline())
131
132 logger.info('Sending SIGTERM to %d', monitor_pid)
133 os.kill(monitor_pid, signal.SIGTERM)
134 i = 0
135 while True:
136 time.sleep(.2)
137 if not os.path.exists(monitor_pid_path):
138 return
139 if not os.path.exists('/proc/%d' % monitor_pid):
140 logger.warning('Monitor (pid %d) terminated uncleanly?', monitor_pid)
141 return
142 logger.info('Waiting for logcat process to terminate.')
143 i += 1
144 if i >= 10:
145 logger.warning('Monitor pid did not terminate. Continuing anyway.')
146 return
147
148 except (ValueError, IOError, OSError):
149 logger.exception('Error signaling logcat monitor - continuing')
150
151
152def main(base_dir, output_file):
153 log_stringio = cStringIO.StringIO()
154 logger = logging.getLogger('LogcatPrinter')
155 logger.setLevel(LOG_LEVEL)
156 sh = logging.StreamHandler(log_stringio)
157 sh.setFormatter(logging.Formatter('%(asctime)-2s %(levelname)-8s'
158 ' %(message)s'))
159 logger.addHandler(sh)
160
161 try:
162 # Wait at least 5 seconds after base_dir is created before printing.
163 #
164 # The idea is that 'adb logcat > file' output consists of 2 phases:
165 # 1 Dump all the saved logs to the file
166 # 2 Stream log messages as they are generated
167 #
168 # We want to give enough time for phase 1 to complete. There's no
169 # good method to tell how long to wait, but it usually only takes a
170 # second. On most bots, this code path won't occur at all, since
171 # adb_logcat_monitor.py command will have spawned more than 5 seconds
172 # prior to called this shell script.
173 try:
174 sleep_time = 5 - (time.time() - os.path.getctime(base_dir))
175 except OSError:
176 sleep_time = 5
177 if sleep_time > 0:
178 logger.warning('Monitor just started? Sleeping %.1fs', sleep_time)
179 time.sleep(sleep_time)
180
181 assert os.path.exists(base_dir), '%s does not exist' % base_dir
182 ShutdownLogcatMonitor(base_dir, logger)
183 separator = '\n' + '*' * 80 + '\n\n'
184 for log in GetDeviceLogs(FindLogFiles(base_dir), logger):
185 output_file.write(log)
186 output_file.write(separator)
187 with open(os.path.join(base_dir, 'eventlog')) as f:
188 output_file.write('\nLogcat Monitor Event Log\n')
189 output_file.write(f.read())
190 except:
191 logger.exception('Unexpected exception')
192
193 logger.info('Done.')
194 sh.flush()
195 output_file.write('\nLogcat Printer Event Log\n')
196 output_file.write(log_stringio.getvalue())
197
198if __name__ == '__main__':
199 if len(sys.argv) == 1:
200 print 'Usage: %s <base_dir>' % sys.argv[0]
201 sys.exit(1)
202 sys.exit(main(sys.argv[1], sys.stdout))