2021-07-19 13:41:47 +00:00
|
|
|
#!/usr/bin/env python3
|
2021-07-15 17:14:51 +00:00
|
|
|
|
|
|
|
# Copyright 2021 Google LLC
|
|
|
|
#
|
|
|
|
# Use of this source code is governed by a BSD-style license that can be
|
|
|
|
# found in the LICENSE file.
|
|
|
|
|
|
|
|
|
|
|
|
# This script is written to process the output from bloaty, read via stdin
|
|
|
|
# The easiest way to use the script:
|
|
|
|
#
|
|
|
|
# bloaty <path_to_binary> -d compileunits,symbols -n 0 --tsv | bloaty_treemap.py > bloaty.html
|
|
|
|
#
|
|
|
|
# Open the resulting .html file in your browser.
|
|
|
|
|
|
|
|
# TODO: Deal with symbols vs. fullsymbols, even both?
|
|
|
|
# TODO: Support aggregation by scope, rather than file (split C++ identifiers on '::')
|
|
|
|
# TODO: Deal with duplicate symbols better. These are actually good targets for optimization.
|
|
|
|
# They are sometimes static functions in headers (so they appear in multiple .o files),
|
|
|
|
# There are also symbols that appear multiple times due to inlining (eg, kNoCropRect).
|
|
|
|
# TODO: Figure out why some symbols are misattributed. Eg, Swizzle::Convert and ::Make are tied
|
|
|
|
# to the header by nm, and then to one caller (at random) by bloaty. They're not inlined,
|
|
|
|
# though. Unless LTO is doing something wacky here? Scope-aggregation may be the answer?
|
|
|
|
# Ultimately, this seems like an issue with bloaty and/or debug information itself.
|
|
|
|
|
|
|
|
import os
|
|
|
|
import sys
|
|
|
|
|
2021-07-19 13:41:47 +00:00
|
|
|
parent_map = {}
|
2021-07-15 17:14:51 +00:00
|
|
|
|
2021-07-19 13:41:47 +00:00
|
|
|
# For a given filepath "foo/bar/baz.cpp", `add_path` outputs rows to the data table
|
2021-07-15 17:14:51 +00:00
|
|
|
# establishing the node hierarchy, and ensures that each line is emitted exactly once:
|
|
|
|
#
|
|
|
|
# ['foo/bar/baz.cpp', 'foo/bar', 0],
|
|
|
|
# ['foo/bar', 'foo', 0],
|
|
|
|
# ['foo', 'ROOT', 0],
|
2021-07-19 13:41:47 +00:00
|
|
|
def add_path(path):
|
|
|
|
if not path in parent_map:
|
2021-07-15 17:14:51 +00:00
|
|
|
head = os.path.split(path)[0]
|
|
|
|
if not head:
|
2021-07-19 13:41:47 +00:00
|
|
|
parent_map[path] = "ROOT"
|
2021-07-15 17:14:51 +00:00
|
|
|
else:
|
2021-07-19 13:41:47 +00:00
|
|
|
add_path(head)
|
|
|
|
parent_map[path] = head
|
2021-12-01 21:03:27 +00:00
|
|
|
|
|
|
|
# We add a suffix to paths to eliminate the chances of a path name colliding with a symbol
|
|
|
|
# name. This is important because google.visualization.TreeMap requires node names to be
|
|
|
|
# unique, and a file such as test/foo/bar.cpp would create a node named "test", which could
|
|
|
|
# collide with a symbol named "test" defined in a C++ file.
|
|
|
|
#
|
|
|
|
# Assumptions made:
|
|
|
|
# - No C++ symbol ends with " (Path)".
|
|
|
|
# - No C++ symbol is named "ROOT".
|
|
|
|
parent = parent_map[path]
|
|
|
|
if parent != "ROOT": parent = "%s (Path)" % parent
|
|
|
|
print("['%s (Path)', '%s', 0]," % (path, parent))
|
2021-07-19 13:41:47 +00:00
|
|
|
|
|
|
|
def main():
|
|
|
|
# HTML/script header, plus the first two (fixed) rows of the data table
|
|
|
|
print("""
|
|
|
|
<html>
|
|
|
|
<head>
|
|
|
|
<script type="text/javascript" src="https://www.gstatic.com/charts/loader.js"></script>
|
|
|
|
<script type="text/javascript">
|
|
|
|
google.charts.load('current', {'packages':['treemap']});
|
|
|
|
google.charts.setOnLoadCallback(drawChart);
|
|
|
|
function drawChart() {
|
|
|
|
const data = google.visualization.arrayToDataTable([
|
|
|
|
['Name', 'Parent', 'Size'],
|
|
|
|
['ROOT', null, 0],""")
|
|
|
|
|
2021-12-01 21:03:27 +00:00
|
|
|
symbol_frequencies = {}
|
2021-07-19 13:41:47 +00:00
|
|
|
|
|
|
|
# Skip header row
|
|
|
|
# TODO: In the future, we could use this to automatically detect the source columns
|
|
|
|
next(sys.stdin)
|
|
|
|
|
|
|
|
for line in sys.stdin:
|
|
|
|
vals = line.rstrip().split("\t")
|
|
|
|
if len(vals) != 4:
|
|
|
|
print("ERROR: Failed to match line\n" + line)
|
2021-07-15 17:14:51 +00:00
|
|
|
sys.exit(1)
|
2021-07-19 13:41:47 +00:00
|
|
|
(filepath, symbol, vmsize, filesize) = vals
|
|
|
|
|
|
|
|
# Skip any entry where the filepath or symbol starts with '['
|
|
|
|
# These tend to be section meta-data and debug information
|
|
|
|
if filepath.startswith("[") or symbol.startswith("["):
|
|
|
|
continue
|
|
|
|
|
|
|
|
# Strip the leading ../../ from paths
|
|
|
|
while filepath.startswith("../"):
|
2021-09-13 16:00:35 +00:00
|
|
|
filepath = filepath[3:];
|
2021-07-19 13:41:47 +00:00
|
|
|
|
|
|
|
# Files in third_party sometimes have absolute paths. Strip those:
|
|
|
|
if filepath.startswith("/"):
|
|
|
|
rel_path_start = filepath.find("third_party")
|
|
|
|
if rel_path_start >= 0:
|
|
|
|
filepath = filepath[rel_path_start:]
|
|
|
|
else:
|
|
|
|
print("ERROR: Unexpected absolute path:\n" + filepath)
|
|
|
|
sys.exit(1)
|
|
|
|
|
2021-07-19 18:24:11 +00:00
|
|
|
# Symbols involving C++ lambdas can contain single quotes
|
|
|
|
symbol = symbol.replace("'", "\\'")
|
2021-07-19 13:41:47 +00:00
|
|
|
|
|
|
|
# Ensure that we've added intermediate nodes for all portions of this file path
|
|
|
|
add_path(filepath)
|
|
|
|
|
2021-12-01 21:03:27 +00:00
|
|
|
# Ensure that our final symbol name is unique (a repeated "foo" symbol becomes "foo_1",
|
|
|
|
# "foo_2", etc.)
|
|
|
|
if symbol not in symbol_frequencies:
|
|
|
|
symbol_frequencies[symbol] = 1
|
|
|
|
else:
|
|
|
|
freq = symbol_frequencies[symbol]
|
|
|
|
symbol_frequencies[symbol] = freq + 1
|
|
|
|
symbol += "_" + str(freq)
|
2021-07-19 13:41:47 +00:00
|
|
|
|
|
|
|
# Append another row for our sanitized data
|
2021-12-01 21:03:27 +00:00
|
|
|
print("['%s', '%s (Path)', %d]," % (symbol, filepath, int(filesize)))
|
2021-07-19 13:41:47 +00:00
|
|
|
|
|
|
|
# HTML/script footer
|
|
|
|
print(""" ]);
|
|
|
|
tree = new google.visualization.TreeMap(document.getElementById('chart_div'));
|
|
|
|
tree.draw(data, {
|
|
|
|
generateTooltip: showTooltip
|
|
|
|
});
|
|
|
|
|
|
|
|
function showTooltip(row, size, value) {
|
|
|
|
const escapedLabel = data.getValue(row, 0)
|
|
|
|
.replace('&', '&')
|
|
|
|
.replace('<', '<')
|
|
|
|
.replace('>', '>')
|
|
|
|
return `<div style="background:#fd9; padding:10px; border-style:solid">
|
|
|
|
<span style="font-family:Courier"> ${escapedLabel} <br>
|
|
|
|
Size: ${size} </div>`;
|
|
|
|
}
|
2021-07-15 17:14:51 +00:00
|
|
|
}
|
2021-07-19 13:41:47 +00:00
|
|
|
</script>
|
|
|
|
</head>
|
|
|
|
<body>
|
|
|
|
<div id="chart_div" style="width: 100%; height: 100%;"></div>
|
|
|
|
</body>
|
|
|
|
</html>""")
|
|
|
|
|
|
|
|
if __name__ == "__main__":
|
|
|
|
main()
|