| """ |
| Finds and prints the crash report associated with a specific (binary filename, process id). |
| Waits (max_wait_time/attempts_remaining) between retries. |
| By default, max_wait_time=5 and retry_count=10, which results in a total wait time of ~15s |
| Errors if the report cannot be found after `retry_count` retries. |
| """ |
| import sys, os, argparse, re, glob, shutil, time |
| |
| |
| def main(): |
| parser = argparse.ArgumentParser() |
| parser.add_argument( |
| "--pid", |
| type=str, |
| required=True, |
| help="The process id of the process that crashed", |
| ) |
| parser.add_argument( |
| "--binary-filename", |
| type=str, |
| required=True, |
| help="The name of the file that crashed", |
| ) |
| parser.add_argument( |
| "--retry-count", |
| type=int, |
| nargs="?", |
| default=10, |
| help="The number of retries to make", |
| ) |
| parser.add_argument( |
| "--max-wait-time", |
| type=float, |
| nargs="?", |
| default=5.0, |
| help="The max amount of seconds to wait between tries", |
| ) |
| |
| parser.add_argument( |
| "--dir", |
| nargs="?", |
| type=str, |
| default="~/Library/Logs/DiagnosticReports", |
| help="The directory to look for the crash report", |
| ) |
| parser.add_argument( |
| "--outfile", |
| nargs="?", |
| type=argparse.FileType("r"), |
| default=sys.stdout, |
| help="Where to write the result", |
| ) |
| args = parser.parse_args() |
| |
| assert args.pid, "pid can't be empty" |
| assert args.binary_filename, "binary-filename can't be empty" |
| |
| os.chdir(os.path.expanduser(args.dir)) |
| output_report_with_retries( |
| args.outfile, |
| args.pid.strip(), |
| args.binary_filename, |
| args.retry_count, |
| args.max_wait_time, |
| ) |
| |
| |
| def output_report_with_retries( |
| outfile, pid, filename, attempts_remaining, max_wait_time |
| ): |
| report_name = find_report_in_cur_dir(pid, filename) |
| if report_name: |
| with open(report_name, "r") as f: |
| shutil.copyfileobj(f, outfile) |
| return |
| elif attempts_remaining > 0: |
| # As the number of attempts remaining decreases, increase the number of seconds waited |
| # if the max wait time is 2s and there are 10 attempts remaining, wait .2 seconds. |
| # if the max wait time is 2s and there are 2 attempts remaining, wait 1 second. |
| time.sleep(max_wait_time / attempts_remaining) |
| output_report_with_retries( |
| outfile, pid, filename, attempts_remaining - 1, max_wait_time |
| ) |
| else: |
| raise RuntimeError("Report not found for ({}, {}).".format(filename, pid)) |
| |
| |
| def find_report_in_cur_dir(pid, filename): |
| for report_name in sorted(glob.glob("{}_*.crash".format(filename)), reverse=True): |
| # parse out pid from first line of report |
| # `Process: filename [pid]`` |
| with open(report_name) as cur_report: |
| pattern = re.compile(r"Process: *{} \[([0-9]*)\]".format(filename)) |
| cur_report_pid = pattern.search(cur_report.readline()).group(1) |
| |
| assert cur_report_pid and cur_report_pid.isdigit() |
| if cur_report_pid == pid: |
| return report_name |
| |
| # did not find the crash report |
| return None |
| |
| |
| if __name__ == "__main__": |
| main() |