#!/usr/bin/env vpython3
#
# Copyright 2016 The Chromium Authors
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.


import argparse
import os
import re
import sys
import tempfile

if __name__ == '__main__':
    sys.path.append(os.path.join(os.path.dirname(__file__), '..'))
from pylib.constants import host_paths

if host_paths.DEVIL_PATH not in sys.path:
    sys.path.append(host_paths.DEVIL_PATH)
from devil.utils import cmd_helper


_MICRODUMP_BEGIN = re.compile(
    '.*google-breakpad: -----BEGIN BREAKPAD MICRODUMP-----'
)
_MICRODUMP_END = re.compile(
    '.*google-breakpad: -----END BREAKPAD MICRODUMP-----'
)

""" Example Microdump
<timestamp>  6270  6131 F google-breakpad: -----BEGIN BREAKPAD MICRODUMP-----
<timestamp>  6270  6131 F google-breakpad: V Chrome_Android:54.0.2790.0
...
<timestamp>  6270  6131 F google-breakpad: -----END BREAKPAD MICRODUMP-----

"""


def GetMicroDumps(dump_path):
    """Returns all microdumps found in given log file

    Args:
      dump_path: Path to the log file.

    Returns:
      List of all microdumps as lists of lines.
    """
    with open(dump_path, 'r') as d:
        data = d.read()
    all_dumps = []
    current_dump = None
    for line in data.splitlines():
        if current_dump is not None:
            if _MICRODUMP_END.match(line):
                current_dump.append(line)
                all_dumps.append(current_dump)
                current_dump = None
            else:
                current_dump.append(line)
        elif _MICRODUMP_BEGIN.match(line):
            current_dump = []
            current_dump.append(line)
    return all_dumps


def SymbolizeMicroDump(stackwalker_binary_path, dump, symbols_path):
    """Runs stackwalker on microdump.

    Runs the stackwalker binary at stackwalker_binary_path on a given microdump
    using the symbols at symbols_path.

    Args:
      stackwalker_binary_path: Path to the stackwalker binary.
      dump: The microdump to run the stackwalker on.
      symbols_path: Path the the symbols file to use.

    Returns:
      Output from stackwalker tool.
    """
    with tempfile.NamedTemporaryFile() as tf:
        for l in dump:
            tf.write('%s\n' % l)
        cmd = [stackwalker_binary_path, tf.name, symbols_path]
        return cmd_helper.GetCmdOutput(cmd)


def AddArguments(parser):
    parser.add_argument(
        '--stackwalker-binary-path',
        required=True,
        help='Path to stackwalker binary.',
    )
    parser.add_argument(
        '--stack-trace-path',
        required=True,
        help='Path to stacktrace containing microdump.',
    )
    parser.add_argument(
        '--symbols-path', required=True, help='Path to symbols file.'
    )
    parser.add_argument(
        '--output-file', help='Path to dump stacktrace output to'
    )


def _PrintAndLog(line, fp):
    if fp:
        fp.write('%s\n' % line)
    print(line)


def main():
    parser = argparse.ArgumentParser()
    AddArguments(parser)
    args = parser.parse_args()

    micro_dumps = GetMicroDumps(args.stack_trace_path)
    if not micro_dumps:
        print('No microdump found. Exiting.')
        return 0

    symbolized_dumps = []
    for micro_dump in micro_dumps:
        symbolized_dumps.append(
            SymbolizeMicroDump(
                args.stackwalker_binary_path, micro_dump, args.symbols_path
            )
        )

    try:
        fp = open(args.output_file, 'w') if args.output_file else None
        _PrintAndLog('%d microdumps found.' % len(micro_dumps), fp)
        _PrintAndLog('---------- Start output from stackwalker ----------', fp)
        for index, symbolized_dump in list(enumerate(symbolized_dumps)):
            _PrintAndLog(
                '------------------ Start dump %d ------------------' % index,
                fp,
            )
            _PrintAndLog(symbolized_dump, fp)
            _PrintAndLog(
                '------------------- End dump %d -------------------' % index,
                fp,
            )
        _PrintAndLog('----------- End output from stackwalker -----------', fp)
    except Exception:
        if fp:
            fp.close()
        raise
    return 0


if __name__ == '__main__':
    sys.exit(main())
