llvm/compiler-rt/test/sanitizer_common/ios_commands/print_crashreport_for_pid.py

"""
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()