diff --git a/pydumpling/cli.py b/pydumpling/cli.py index d342e29..6c25e7a 100644 --- a/pydumpling/cli.py +++ b/pydumpling/cli.py @@ -3,7 +3,7 @@ from .debug_dumpling import debug_dumpling, load_dumpling from .helpers import print_traceback_and_except, validate_file_name from .rpdb import r_post_mortem - +from .vis import generate_vis parser = argparse.ArgumentParser( description="pydumpling cli tools", @@ -32,6 +32,10 @@ help="enter rpdb debugging interface" ) +pydumpling_cli_action_group.add_argument( + "--vis", action="store_true", help="visualize the traceback" +) + parser.add_argument( "filename", type=validate_file_name, @@ -49,3 +53,5 @@ def main() -> None: debug_dumpling(file_name) elif args.rdebug: r_post_mortem(file_name) + elif args.vis: + generate_vis(file_name) diff --git a/pydumpling/fake_types.py b/pydumpling/fake_types.py index 4cc1e18..e35a7d0 100644 --- a/pydumpling/fake_types.py +++ b/pydumpling/fake_types.py @@ -70,6 +70,13 @@ def __init__(self, traceback=None): traceback.tb_next) if traceback and traceback.tb_next else None self.tb_lasti = traceback.tb_lasti if traceback else 0 + def serialize(self): + return { + 'tb_frame': self.tb_frame.serialize() if self.tb_frame else None, + 'tb_lineno': self.tb_lineno, + 'tb_next': self.tb_next.serialize() if self.tb_next else None, + } + class FakeFrame(FakeType): def __init__(self, frame): @@ -83,6 +90,12 @@ def __init__(self, frame): self.f_lasti = frame.f_lasti self.f_builtins = frame.f_builtins + def serialize(self): + return { + 'f_code': self.f_code.serialize(), + 'f_lineno': self.f_lineno, + } + class FakeClass(FakeType): @@ -116,3 +129,9 @@ def __init__(self, code): def co_lines(self): return iter(self._co_lines) + + def serialize(self): + return { + 'co_filename': self.co_filename, + 'co_name': self.co_name, + } \ No newline at end of file diff --git a/pydumpling/templates/traceback.html b/pydumpling/templates/traceback.html new file mode 100644 index 0000000..4ead394 --- /dev/null +++ b/pydumpling/templates/traceback.html @@ -0,0 +1,161 @@ + + + + + Network + + + + + + +
+ +
+
+ current dump file: {{dump_file_name}} +
+
+ exc_type: {{exc_type}} +
+
+ exc_value: {{exc_value}} +
+
+ +
+
+ + + + \ No newline at end of file diff --git a/pydumpling/vis.py b/pydumpling/vis.py new file mode 100644 index 0000000..23f76bb --- /dev/null +++ b/pydumpling/vis.py @@ -0,0 +1,60 @@ +from linecache import getline +from os import system +from uuid import uuid4 +from os.path import splitext, abspath, dirname, join as pjoin +from json import dumps + +from pydumpling import load_dumpling + +__dir__ = dirname(abspath(__file__)) + +def _wrap_code(raw_code: str) -> str: + return f'{raw_code}' + +def _generate_label(f_code,lineno) -> str: + return f'line: {lineno} \n{_wrap_code("func: " + f_code["co_name"])} \n{_wrap_code(getline(f_code["co_filename"], lineno).strip())}' + + +def extract_frame(traceback: dict) -> list[dict]: + frames = [] + for key, value in traceback.items(): + if key == 'tb_frame': + f_code = value['f_code'] + frames.append( + { + 'id': str(uuid4()), + 'label': _generate_label(f_code, value["f_lineno"]), + 'extra': { + 'co_filename': f_code['co_filename'], + 'co_type': f_code['co_name'] == '' and 'module' or 'function', + }, + } + ) + if isinstance(value, dict): + frames.extend(extract_frame(value)) + + return frames + +def generate_vis(dump_file: str) -> None: + dumpling_result = load_dumpling(dump_file) + traceback = dumpling_result['traceback'].serialize() + raw_frames = extract_frame(traceback) + + res = dumps(raw_frames) + vis_html_file = splitext(dump_file)[0] + '.html' + with open(f'{__dir__}/templates/traceback.html', mode='r', encoding='utf-8') as handler: + template = handler.read() + traceback_html = template.replace('{{traceback}}', res) + traceback_html = traceback_html.replace('{{dump_file_name}}', dump_file) + traceback_html = traceback_html.replace( + '{{exc_type}}', repr(dumpling_result['exc_extra']['exc_type']).replace('<', '').replace('>', '') + ) + traceback_html = traceback_html.replace( + '{{exc_value}}', repr(dumpling_result['exc_extra']['exc_value']) + ) + + + with open(vis_html_file, mode="w", encoding="utf-8") as f: + f.write(traceback_html) + + system(f'start {vis_html_file}') \ No newline at end of file diff --git a/pyproject.toml b/pyproject.toml index dd140c3..e12c090 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -8,7 +8,7 @@ docs_export = { shell = "pdm export -G doc -o docs/requirements.txt --without-ha docs_preview = {shell = 'python -m http.server -d docs\build\html'} [tool.pdm.build] -includes = ["pydumpling/*.py"] +includes = ["pydumpling/*.py", "pydumpling/templates/*.html"] [tool.pdm.dev-dependencies] test = [ diff --git a/static/visualize_traceback.png b/static/visualize_traceback.png new file mode 100644 index 0000000..d61d6ba Binary files /dev/null and b/static/visualize_traceback.png differ