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