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 9 10def main(): 11 parser = argparse.ArgumentParser() 12 parser.add_argument( 13 "--pid", 14 type=str, 15 required=True, 16 help="The process id of the process that crashed", 17 ) 18 parser.add_argument( 19 "--binary-filename", 20 type=str, 21 required=True, 22 help="The name of the file that crashed", 23 ) 24 parser.add_argument( 25 "--retry-count", 26 type=int, 27 nargs="?", 28 default=10, 29 help="The number of retries to make", 30 ) 31 parser.add_argument( 32 "--max-wait-time", 33 type=float, 34 nargs="?", 35 default=5.0, 36 help="The max amount of seconds to wait between tries", 37 ) 38 39 parser.add_argument( 40 "--dir", 41 nargs="?", 42 type=str, 43 default="~/Library/Logs/DiagnosticReports", 44 help="The directory to look for the crash report", 45 ) 46 parser.add_argument( 47 "--outfile", 48 nargs="?", 49 type=argparse.FileType("r"), 50 default=sys.stdout, 51 help="Where to write the result", 52 ) 53 args = parser.parse_args() 54 55 assert args.pid, "pid can't be empty" 56 assert args.binary_filename, "binary-filename can't be empty" 57 58 os.chdir(os.path.expanduser(args.dir)) 59 output_report_with_retries( 60 args.outfile, 61 args.pid.strip(), 62 args.binary_filename, 63 args.retry_count, 64 args.max_wait_time, 65 ) 66 67 68def output_report_with_retries( 69 outfile, pid, filename, attempts_remaining, max_wait_time 70): 71 report_name = find_report_in_cur_dir(pid, filename) 72 if report_name: 73 with open(report_name, "r") as f: 74 shutil.copyfileobj(f, outfile) 75 return 76 elif attempts_remaining > 0: 77 # As the number of attempts remaining decreases, increase the number of seconds waited 78 # if the max wait time is 2s and there are 10 attempts remaining, wait .2 seconds. 79 # if the max wait time is 2s and there are 2 attempts remaining, wait 1 second. 80 time.sleep(max_wait_time / attempts_remaining) 81 output_report_with_retries( 82 outfile, pid, filename, attempts_remaining - 1, max_wait_time 83 ) 84 else: 85 raise RuntimeError("Report not found for ({}, {}).".format(filename, pid)) 86 87 88def find_report_in_cur_dir(pid, filename): 89 for report_name in sorted(glob.glob("{}_*.crash".format(filename)), reverse=True): 90 # parse out pid from first line of report 91 # `Process: filename [pid]`` 92 with open(report_name) as cur_report: 93 pattern = re.compile(r"Process: *{} \[([0-9]*)\]".format(filename)) 94 cur_report_pid = pattern.search(cur_report.readline()).group(1) 95 96 assert cur_report_pid and cur_report_pid.isdigit() 97 if cur_report_pid == pid: 98 return report_name 99 100 # did not find the crash report 101 return None 102 103 104if __name__ == "__main__": 105 main() 106