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