blob: af2e409abd54f39feaf977e77616eacf71a89662 [file] [log] [blame]
#!/usr/bin/env python
# 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
parentMap = {}
# For a given filepath "foo/bar/baz.cpp", `addPath` outputs rows to the data table
# 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],
def addPath(path):
if not path in parentMap:
head = os.path.split(path)[0]
if not head:
parentMap[path] = "ROOT"
else:
addPath(head)
parentMap[path] = head
print("['" + path + "', '" + parentMap[path] + "', 0],")
# 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() {
var data = google.visualization.arrayToDataTable([
['Name', 'Parent', 'Size'],
['ROOT', null, 0],""")
allSymbols = {}
# 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)
sys.exit(1)
(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
filepath = filepath.removeprefix('../../')
# Files in third_party sometimes have absolute paths. Strip those:
if filepath.startswith('/'):
relPathStart = filepath.find('third_party')
if relPathStart >= 0:
filepath = filepath[relPathStart:]
else:
print("ERROR: Unexpected absolute path:\n" + filepath)
sys.exit(1)
# It's rare, but symbols can contain double-quotes (it's a valid C++ operator)
symbol = symbol.replace('"', '\\"')
# Ensure that we've added intermediate nodes for all portions of this file path
addPath(filepath)
# Ensure that our final symbol name is unique
while symbol in allSymbols:
symbol += '_x'
allSymbols[symbol] = True
# Append another row for our sanitized data
print('["' + symbol + '", "' + filepath + '", ' + filesize + '],')
# HTML/script footer
print(""" ]);
tree = new google.visualization.TreeMap(document.getElementById("chart_div"));
tree.draw(data, {
generateTooltip: showTooltip
});
function showTooltip(row, size, value) {
var escapedLabel = data.getValue(row, 0)
.replace('&', '&amp;')
.replace('<', '&lt;')
.replace('>', '&gt;')
return '<div style="background:#fd9; padding:10px; border-style:solid">' +
'<span style="font-family:Courier">' + escapedLabel + '<br>' +
'Size: ' + size + '</div>';
}
}
</script>
</head>
<body>
<div id="chart_div" style="width: 100%; height: 100%;"></div>
</body>
</html>""")