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('&', '&') 131*c8dee2aaSAndroid Build Coastguard Worker .replace('<', '<') 132*c8dee2aaSAndroid Build Coastguard Worker .replace('>', '>') 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