1""" 2Finds and prints the crash report associated with a specific (binary filename, process id). 3Waits (max_wait_time/attempts_remaining) between retries. 4By default, max_wait_time=5 and retry_count=10, which results in a total wait time of ~15s 5Errors if the report cannot be found after `retry_count` retries. 6""" 7import sys, os, argparse, re, glob, shutil, time 8 9def main(): 10 parser = argparse.ArgumentParser() 11 parser.add_argument('--pid', type=str, required=True, help='The process id of the process that crashed') 12 parser.add_argument('--binary-filename', type=str, required=True, help='The name of the file that crashed') 13 parser.add_argument('--retry-count', type=int, nargs='?', default=10, help='The number of retries to make') 14 parser.add_argument('--max-wait-time', type=float, nargs='?', default=5.0, help='The max amount of seconds to wait between tries') 15 16 parser.add_argument('--dir', nargs='?', type=str, default="~/Library/Logs/DiagnosticReports", help='The directory to look for the crash report') 17 parser.add_argument('--outfile', nargs='?', type=argparse.FileType('r'), default=sys.stdout, help='Where to write the result') 18 args = parser.parse_args() 19 20 assert args.pid, "pid can't be empty" 21 assert args.binary_filename, "binary-filename can't be empty" 22 23 os.chdir(os.path.expanduser(args.dir)) 24 output_report_with_retries(args.outfile, args.pid.strip(), args.binary_filename, args.retry_count, args.max_wait_time) 25 26def output_report_with_retries(outfile, pid, filename, attempts_remaining, max_wait_time): 27 report_name = find_report_in_cur_dir(pid, filename) 28 if report_name: 29 with open(report_name, "r") as f: 30 shutil.copyfileobj(f, outfile) 31 return 32 elif(attempts_remaining > 0): 33 # As the number of attempts remaining decreases, increase the number of seconds waited 34 # if the max wait time is 2s and there are 10 attempts remaining, wait .2 seconds. 35 # if the max wait time is 2s and there are 2 attempts remaining, wait 1 second. 36 time.sleep(max_wait_time / attempts_remaining) 37 output_report_with_retries(outfile, pid, filename, attempts_remaining - 1, max_wait_time) 38 else: 39 raise RuntimeError("Report not found for ({}, {}).".format(filename, pid)) 40 41def find_report_in_cur_dir(pid, filename): 42 for report_name in sorted(glob.glob("{}_*.crash".format(filename)), reverse=True): 43 # parse out pid from first line of report 44 # `Process: filename [pid]`` 45 with open(report_name) as cur_report: 46 pattern = re.compile(r'Process: *{} \[([0-9]*)\]'.format(filename)) 47 cur_report_pid = pattern.search(cur_report.readline()).group(1) 48 49 assert cur_report_pid and cur_report_pid.isdigit() 50 if cur_report_pid == pid: 51 return report_name 52 53 # did not find the crash report 54 return None 55 56 57if __name__ == '__main__': 58 main() 59