xref: /aosp_15_r20/external/executorch/util/python_profiler.py (revision 523fa7a60841cd1ecfb9cc4201f1ca8b03ed023a)
1*523fa7a6SAndroid Build Coastguard Worker# Copyright (c) Meta Platforms, Inc. and affiliates.
2*523fa7a6SAndroid Build Coastguard Worker# All rights reserved.
3*523fa7a6SAndroid Build Coastguard Worker#
4*523fa7a6SAndroid Build Coastguard Worker# This source code is licensed under the BSD-style license found in the
5*523fa7a6SAndroid Build Coastguard Worker# LICENSE file in the root directory of this source tree.
6*523fa7a6SAndroid Build Coastguard Worker
7*523fa7a6SAndroid Build Coastguard Workerimport cProfile
8*523fa7a6SAndroid Build Coastguard Workerimport io
9*523fa7a6SAndroid Build Coastguard Workerimport logging
10*523fa7a6SAndroid Build Coastguard Workerimport os
11*523fa7a6SAndroid Build Coastguard Workerimport pstats
12*523fa7a6SAndroid Build Coastguard Workerimport re
13*523fa7a6SAndroid Build Coastguard Workerfrom pstats import Stats
14*523fa7a6SAndroid Build Coastguard Worker
15*523fa7a6SAndroid Build Coastguard Workerfrom snakeviz.stats import json_stats, table_rows
16*523fa7a6SAndroid Build Coastguard Workerfrom tornado import template
17*523fa7a6SAndroid Build Coastguard Worker
18*523fa7a6SAndroid Build Coastguard Workermodule_found = True
19*523fa7a6SAndroid Build Coastguard Workertry:
20*523fa7a6SAndroid Build Coastguard Worker    import snakeviz
21*523fa7a6SAndroid Build Coastguard Workerexcept ImportError:
22*523fa7a6SAndroid Build Coastguard Worker    module_found = False
23*523fa7a6SAndroid Build Coastguard Worker
24*523fa7a6SAndroid Build Coastguard Workersnakeviz_dir = os.path.dirname(os.path.abspath(snakeviz.__file__))
25*523fa7a6SAndroid Build Coastguard Workersnakeviz_templates_dir = os.path.join(snakeviz_dir, "templates")
26*523fa7a6SAndroid Build Coastguard Worker
27*523fa7a6SAndroid Build Coastguard Worker
28*523fa7a6SAndroid Build Coastguard Workerdef _from_pstat_to_static_html(stats: Stats, html_filename: str):
29*523fa7a6SAndroid Build Coastguard Worker    """
30*523fa7a6SAndroid Build Coastguard Worker    Parses pstats data and populates viz.html template stored under templates dir.
31*523fa7a6SAndroid Build Coastguard Worker    This utility allows to export html file without kicking off webserver.
32*523fa7a6SAndroid Build Coastguard Worker
33*523fa7a6SAndroid Build Coastguard Worker    Note that it relies js scripts stored at rawgit cdn. This is not super
34*523fa7a6SAndroid Build Coastguard Worker    reliable, however it does allow one to not have to rely on webserver and
35*523fa7a6SAndroid Build Coastguard Worker    local rendering. On the other hand, for local rendering please follow
36*523fa7a6SAndroid Build Coastguard Worker    the main snakeviz tutorial
37*523fa7a6SAndroid Build Coastguard Worker
38*523fa7a6SAndroid Build Coastguard Worker    Inspiration for this util is from https://gist.github.com/jiffyclub/6b5e0f0f05ab487ff607.
39*523fa7a6SAndroid Build Coastguard Worker
40*523fa7a6SAndroid Build Coastguard Worker    Args:
41*523fa7a6SAndroid Build Coastguard Worker        stats: Stats generated from cProfile data
42*523fa7a6SAndroid Build Coastguard Worker        html_filename: Output filename in which populated template is rendered
43*523fa7a6SAndroid Build Coastguard Worker    """
44*523fa7a6SAndroid Build Coastguard Worker    RESTR = r'(?<!] \+ ")/static/'
45*523fa7a6SAndroid Build Coastguard Worker    REPLACE_WITH = "https://cdn.rawgit.com/jiffyclub/snakeviz/v0.4.2/snakeviz/static/"
46*523fa7a6SAndroid Build Coastguard Worker
47*523fa7a6SAndroid Build Coastguard Worker    if not isinstance(html_filename, str):
48*523fa7a6SAndroid Build Coastguard Worker        raise ValueError("A valid file name must be provided.")
49*523fa7a6SAndroid Build Coastguard Worker
50*523fa7a6SAndroid Build Coastguard Worker    viz_html_loader = template.Loader(snakeviz_templates_dir)
51*523fa7a6SAndroid Build Coastguard Worker    html_bytes_renderer = viz_html_loader.load("viz.html")
52*523fa7a6SAndroid Build Coastguard Worker    file_split = html_filename.split(".")
53*523fa7a6SAndroid Build Coastguard Worker    if len(file_split) < 2:
54*523fa7a6SAndroid Build Coastguard Worker        raise ValueError(
55*523fa7a6SAndroid Build Coastguard Worker            f"\033[0;32;40m Provided filename \033[0;31;47m {html_filename} \033[0;32;40m does not contain . separator."
56*523fa7a6SAndroid Build Coastguard Worker        )
57*523fa7a6SAndroid Build Coastguard Worker    profile_name = file_split[0]
58*523fa7a6SAndroid Build Coastguard Worker    html_bytes = html_bytes_renderer.generate(
59*523fa7a6SAndroid Build Coastguard Worker        profile_name=profile_name,
60*523fa7a6SAndroid Build Coastguard Worker        table_rows=table_rows(stats),
61*523fa7a6SAndroid Build Coastguard Worker        callees=json_stats(stats),
62*523fa7a6SAndroid Build Coastguard Worker    )
63*523fa7a6SAndroid Build Coastguard Worker    html_string = html_bytes.decode("utf-8")
64*523fa7a6SAndroid Build Coastguard Worker    html_string = re.sub(RESTR, REPLACE_WITH, html_string)
65*523fa7a6SAndroid Build Coastguard Worker    with open(html_filename, "w") as f:
66*523fa7a6SAndroid Build Coastguard Worker        f.write(html_string)
67*523fa7a6SAndroid Build Coastguard Worker
68*523fa7a6SAndroid Build Coastguard Worker
69*523fa7a6SAndroid Build Coastguard Workerclass CProfilerFlameGraph:
70*523fa7a6SAndroid Build Coastguard Worker    def __init__(self, filename: str):
71*523fa7a6SAndroid Build Coastguard Worker        if not module_found:
72*523fa7a6SAndroid Build Coastguard Worker            raise Exception(
73*523fa7a6SAndroid Build Coastguard Worker                "Please install snakeviz to use CProfilerFlameGraph. Follow cprofiler_flamegraph.md for more information."
74*523fa7a6SAndroid Build Coastguard Worker            )
75*523fa7a6SAndroid Build Coastguard Worker        self.filename = filename
76*523fa7a6SAndroid Build Coastguard Worker
77*523fa7a6SAndroid Build Coastguard Worker    def __enter__(self):
78*523fa7a6SAndroid Build Coastguard Worker        self.pr = cProfile.Profile()
79*523fa7a6SAndroid Build Coastguard Worker        self.pr.enable()
80*523fa7a6SAndroid Build Coastguard Worker
81*523fa7a6SAndroid Build Coastguard Worker    def __exit__(self, exc_type, exc_val, exc_tb):
82*523fa7a6SAndroid Build Coastguard Worker        if exc_type is not None:
83*523fa7a6SAndroid Build Coastguard Worker            logging.error("Exception occurred", exc_info=(exc_type, exc_val, exc_tb))
84*523fa7a6SAndroid Build Coastguard Worker
85*523fa7a6SAndroid Build Coastguard Worker        self.pr.disable()
86*523fa7a6SAndroid Build Coastguard Worker        s = io.StringIO()
87*523fa7a6SAndroid Build Coastguard Worker        ps = pstats.Stats(self.pr, stream=s)
88*523fa7a6SAndroid Build Coastguard Worker        _from_pstat_to_static_html(ps, self.filename)
89