xref: /aosp_15_r20/external/skia/tools/bloaty_treemap.py (revision c8dee2aa9b3f27cf6c858bd81872bdeb2c07ed17)
1*c8dee2aaSAndroid Build Coastguard Worker#!/usr/bin/env python3
2*c8dee2aaSAndroid Build Coastguard Worker
3*c8dee2aaSAndroid Build Coastguard Worker# Copyright 2021 Google LLC
4*c8dee2aaSAndroid Build Coastguard Worker#
5*c8dee2aaSAndroid Build Coastguard Worker# Use of this source code is governed by a BSD-style license that can be
6*c8dee2aaSAndroid Build Coastguard Worker# found in the LICENSE file.
7*c8dee2aaSAndroid Build Coastguard Worker
8*c8dee2aaSAndroid Build Coastguard Worker
9*c8dee2aaSAndroid Build Coastguard Worker# This script is written to process the output from bloaty, read via stdin
10*c8dee2aaSAndroid Build Coastguard Worker# The easiest way to use the script:
11*c8dee2aaSAndroid Build Coastguard Worker#
12*c8dee2aaSAndroid Build Coastguard Worker#   bloaty <path_to_binary> -d compileunits,symbols -n 0 --tsv | bloaty_treemap.py > bloaty.html
13*c8dee2aaSAndroid Build Coastguard Worker#
14*c8dee2aaSAndroid Build Coastguard Worker# Open the resulting .html file in your browser.
15*c8dee2aaSAndroid Build Coastguard Worker
16*c8dee2aaSAndroid Build Coastguard Worker# TODO: Deal with symbols vs. fullsymbols, even both?
17*c8dee2aaSAndroid Build Coastguard Worker# TODO: Support aggregation by scope, rather than file (split C++ identifiers on '::')
18*c8dee2aaSAndroid Build Coastguard Worker# TODO: Deal with duplicate symbols better. These are actually good targets for optimization.
19*c8dee2aaSAndroid Build Coastguard Worker#       They are sometimes static functions in headers (so they appear in multiple .o files),
20*c8dee2aaSAndroid Build Coastguard Worker#       There are also symbols that appear multiple times due to inlining (eg, kNoCropRect).
21*c8dee2aaSAndroid Build Coastguard Worker# TODO: Figure out why some symbols are misattributed. Eg, Swizzle::Convert and ::Make are tied
22*c8dee2aaSAndroid Build Coastguard Worker#       to the header by nm, and then to one caller (at random) by bloaty. They're not inlined,
23*c8dee2aaSAndroid Build Coastguard Worker#       though. Unless LTO is doing something wacky here? Scope-aggregation may be the answer?
24*c8dee2aaSAndroid Build Coastguard Worker#       Ultimately, this seems like an issue with bloaty and/or debug information itself.
25*c8dee2aaSAndroid Build Coastguard Worker
26*c8dee2aaSAndroid Build Coastguard Workerimport os
27*c8dee2aaSAndroid Build Coastguard Workerimport sys
28*c8dee2aaSAndroid Build Coastguard Worker
29*c8dee2aaSAndroid Build Coastguard Workerparent_map = {}
30*c8dee2aaSAndroid Build Coastguard Worker
31*c8dee2aaSAndroid Build Coastguard Worker# For a given filepath "foo/bar/baz.cpp", `add_path` outputs rows to the data table
32*c8dee2aaSAndroid Build Coastguard Worker# establishing the node hierarchy, and ensures that each line is emitted exactly once:
33*c8dee2aaSAndroid Build Coastguard Worker#
34*c8dee2aaSAndroid Build Coastguard Worker#   ['foo/bar/baz.cpp', 'foo/bar', 0],
35*c8dee2aaSAndroid Build Coastguard Worker#   ['foo/bar',         'foo',     0],
36*c8dee2aaSAndroid Build Coastguard Worker#   ['foo',             'ROOT',    0],
37*c8dee2aaSAndroid Build Coastguard Workerdef add_path(path):
38*c8dee2aaSAndroid Build Coastguard Worker    if not path in parent_map:
39*c8dee2aaSAndroid Build Coastguard Worker        head = os.path.split(path)[0]
40*c8dee2aaSAndroid Build Coastguard Worker        if not head:
41*c8dee2aaSAndroid Build Coastguard Worker            parent_map[path] = "ROOT"
42*c8dee2aaSAndroid Build Coastguard Worker        else:
43*c8dee2aaSAndroid Build Coastguard Worker            add_path(head)
44*c8dee2aaSAndroid Build Coastguard Worker            parent_map[path] = head
45*c8dee2aaSAndroid Build Coastguard Worker
46*c8dee2aaSAndroid Build Coastguard Worker        # We add a suffix to paths to eliminate the chances of a path name colliding with a symbol
47*c8dee2aaSAndroid Build Coastguard Worker        # name. This is important because google.visualization.TreeMap requires node names to be
48*c8dee2aaSAndroid Build Coastguard Worker        # unique, and a file such as test/foo/bar.cpp would create a node named "test", which could
49*c8dee2aaSAndroid Build Coastguard Worker        # collide with a symbol named "test" defined in a C++ file.
50*c8dee2aaSAndroid Build Coastguard Worker        #
51*c8dee2aaSAndroid Build Coastguard Worker        # Assumptions made:
52*c8dee2aaSAndroid Build Coastguard Worker        #  - No C++ symbol ends with " (Path)".
53*c8dee2aaSAndroid Build Coastguard Worker        #  - No C++ symbol is named "ROOT".
54*c8dee2aaSAndroid Build Coastguard Worker        parent = parent_map[path]
55*c8dee2aaSAndroid Build Coastguard Worker        if parent != "ROOT": parent = "%s (Path)" % parent
56*c8dee2aaSAndroid Build Coastguard Worker        print("['%s (Path)', '%s', 0]," % (path, parent))
57*c8dee2aaSAndroid Build Coastguard Worker
58*c8dee2aaSAndroid Build Coastguard Workerdef main():
59*c8dee2aaSAndroid Build Coastguard Worker    # HTML/script header, plus the first two (fixed) rows of the data table
60*c8dee2aaSAndroid Build Coastguard Worker    print("""
61*c8dee2aaSAndroid Build Coastguard Worker    <html>
62*c8dee2aaSAndroid Build Coastguard Worker        <head>
63*c8dee2aaSAndroid Build Coastguard Worker            <script type="text/javascript" src="https://www.gstatic.com/charts/loader.js"></script>
64*c8dee2aaSAndroid Build Coastguard Worker            <script type="text/javascript">
65*c8dee2aaSAndroid Build Coastguard Worker                google.charts.load('current', {'packages':['treemap']});
66*c8dee2aaSAndroid Build Coastguard Worker                google.charts.setOnLoadCallback(drawChart);
67*c8dee2aaSAndroid Build Coastguard Worker                function drawChart() {
68*c8dee2aaSAndroid Build Coastguard Worker                    const data = google.visualization.arrayToDataTable([
69*c8dee2aaSAndroid Build Coastguard Worker                        ['Name', 'Parent', 'Size'],
70*c8dee2aaSAndroid Build Coastguard Worker                        ['ROOT', null, 0],""")
71*c8dee2aaSAndroid Build Coastguard Worker
72*c8dee2aaSAndroid Build Coastguard Worker    symbol_frequencies = {}
73*c8dee2aaSAndroid Build Coastguard Worker
74*c8dee2aaSAndroid Build Coastguard Worker    # Skip header row
75*c8dee2aaSAndroid Build Coastguard Worker    # TODO: In the future, we could use this to automatically detect the source columns
76*c8dee2aaSAndroid Build Coastguard Worker    next(sys.stdin)
77*c8dee2aaSAndroid Build Coastguard Worker
78*c8dee2aaSAndroid Build Coastguard Worker    for line in sys.stdin:
79*c8dee2aaSAndroid Build Coastguard Worker        vals = line.rstrip().split("\t")
80*c8dee2aaSAndroid Build Coastguard Worker        if len(vals) != 4:
81*c8dee2aaSAndroid Build Coastguard Worker            print("ERROR: Failed to match line\n" + line)
82*c8dee2aaSAndroid Build Coastguard Worker            sys.exit(1)
83*c8dee2aaSAndroid Build Coastguard Worker        (filepath, symbol, vmsize, filesize) = vals
84*c8dee2aaSAndroid Build Coastguard Worker
85*c8dee2aaSAndroid Build Coastguard Worker        # Skip any entry where the filepath or symbol starts with '['
86*c8dee2aaSAndroid Build Coastguard Worker        # These tend to be section meta-data and debug information
87*c8dee2aaSAndroid Build Coastguard Worker        if filepath.startswith("[") or symbol.startswith("["):
88*c8dee2aaSAndroid Build Coastguard Worker            continue
89*c8dee2aaSAndroid Build Coastguard Worker
90*c8dee2aaSAndroid Build Coastguard Worker        # Strip the leading ../../ from paths
91*c8dee2aaSAndroid Build Coastguard Worker        while filepath.startswith("../"):
92*c8dee2aaSAndroid Build Coastguard Worker            filepath = filepath[3:];
93*c8dee2aaSAndroid Build Coastguard Worker
94*c8dee2aaSAndroid Build Coastguard Worker        # Files in third_party sometimes have absolute paths. Strip those:
95*c8dee2aaSAndroid Build Coastguard Worker        if filepath.startswith("/"):
96*c8dee2aaSAndroid Build Coastguard Worker            rel_path_start = filepath.find("third_party")
97*c8dee2aaSAndroid Build Coastguard Worker            if rel_path_start >= 0:
98*c8dee2aaSAndroid Build Coastguard Worker                filepath = filepath[rel_path_start:]
99*c8dee2aaSAndroid Build Coastguard Worker            else:
100*c8dee2aaSAndroid Build Coastguard Worker                print("ERROR: Unexpected absolute path:\n" + filepath)
101*c8dee2aaSAndroid Build Coastguard Worker                sys.exit(1)
102*c8dee2aaSAndroid Build Coastguard Worker
103*c8dee2aaSAndroid Build Coastguard Worker        # Symbols involving C++ lambdas can contain single quotes
104*c8dee2aaSAndroid Build Coastguard Worker        symbol = symbol.replace("'", "\\'")
105*c8dee2aaSAndroid Build Coastguard Worker
106*c8dee2aaSAndroid Build Coastguard Worker        # Ensure that we've added intermediate nodes for all portions of this file path
107*c8dee2aaSAndroid Build Coastguard Worker        add_path(filepath)
108*c8dee2aaSAndroid Build Coastguard Worker
109*c8dee2aaSAndroid Build Coastguard Worker        # Ensure that our final symbol name is unique (a repeated "foo" symbol becomes "foo_1",
110*c8dee2aaSAndroid Build Coastguard Worker        # "foo_2", etc.)
111*c8dee2aaSAndroid Build Coastguard Worker        if symbol not in symbol_frequencies:
112*c8dee2aaSAndroid Build Coastguard Worker            symbol_frequencies[symbol] = 1
113*c8dee2aaSAndroid Build Coastguard Worker        else:
114*c8dee2aaSAndroid Build Coastguard Worker            freq = symbol_frequencies[symbol]
115*c8dee2aaSAndroid Build Coastguard Worker            symbol_frequencies[symbol] = freq + 1
116*c8dee2aaSAndroid Build Coastguard Worker            symbol += "_" + str(freq)
117*c8dee2aaSAndroid Build Coastguard Worker
118*c8dee2aaSAndroid Build Coastguard Worker        # Append another row for our sanitized data
119*c8dee2aaSAndroid Build Coastguard Worker        print("['%s', '%s (Path)', %d]," % (symbol, filepath, int(filesize)))
120*c8dee2aaSAndroid Build Coastguard Worker
121*c8dee2aaSAndroid Build Coastguard Worker    # HTML/script footer
122*c8dee2aaSAndroid Build Coastguard Worker    print("""       ]);
123*c8dee2aaSAndroid Build Coastguard Worker                    tree = new google.visualization.TreeMap(document.getElementById('chart_div'));
124*c8dee2aaSAndroid Build Coastguard Worker                    tree.draw(data, {
125*c8dee2aaSAndroid Build Coastguard Worker                        generateTooltip: showTooltip
126*c8dee2aaSAndroid Build Coastguard Worker                    });
127*c8dee2aaSAndroid Build Coastguard Worker
128*c8dee2aaSAndroid Build Coastguard Worker                    function showTooltip(row, size, value) {
129*c8dee2aaSAndroid Build Coastguard Worker                        const escapedLabel = data.getValue(row, 0)
130*c8dee2aaSAndroid Build Coastguard Worker                            .replace('&', '&amp;')
131*c8dee2aaSAndroid Build Coastguard Worker                            .replace('<', '&lt;')
132*c8dee2aaSAndroid Build Coastguard Worker                            .replace('>', '&gt;')
133*c8dee2aaSAndroid Build Coastguard Worker                        return `<div style="background:#fd9; padding:10px; border-style:solid">
134*c8dee2aaSAndroid Build Coastguard Worker                                <span style="font-family:Courier"> ${escapedLabel} <br>
135*c8dee2aaSAndroid Build Coastguard Worker                                Size: ${size} </div>`;
136*c8dee2aaSAndroid Build Coastguard Worker                    }
137*c8dee2aaSAndroid Build Coastguard Worker                }
138*c8dee2aaSAndroid Build Coastguard Worker            </script>
139*c8dee2aaSAndroid Build Coastguard Worker        </head>
140*c8dee2aaSAndroid Build Coastguard Worker        <body>
141*c8dee2aaSAndroid Build Coastguard Worker            <div id="chart_div" style="width: 100%; height: 100%;"></div>
142*c8dee2aaSAndroid Build Coastguard Worker        </body>
143*c8dee2aaSAndroid Build Coastguard Worker    </html>""")
144*c8dee2aaSAndroid Build Coastguard Worker
145*c8dee2aaSAndroid Build Coastguard Workerif __name__ == "__main__":
146*c8dee2aaSAndroid Build Coastguard Worker    main()
147