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