summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--tools/binary_size/README.txt6
-rw-r--r--tools/binary_size/legacy_template/.gitignore (renamed from tools/binary_size/template/.gitignore)0
-rw-r--r--tools/binary_size/legacy_template/index.html190
-rwxr-xr-xtools/binary_size/run_binary_size_analysis.py140
-rw-r--r--tools/binary_size/template/D3SymbolTreeMap.js938
-rw-r--r--tools/binary_size/template/index.html683
-rw-r--r--tools/binary_size/template/test-data-generator.html157
7 files changed, 1911 insertions, 203 deletions
diff --git a/tools/binary_size/README.txt b/tools/binary_size/README.txt
index 1af78d9..a8d204f 100644
--- a/tools/binary_size/README.txt
+++ b/tools/binary_size/README.txt
@@ -116,8 +116,8 @@ The tool is not perfect and has several shortcomings:
Feature Requests and Bug Reports
--------------------------------------------------------------------------------
Please file bugs and feature requests here, making sure to use the label
-"Binary-Size-Tool":
- https://code.google.com/p/chromium/issues/entry?labels=Binary-Size-Tool
+"Tools-BinarySize":
+ https://code.google.com/p/chromium/issues/entry?labels=Tools-BinarySize
View all open issues here:
- https://code.google.com/p/chromium/issues/list?can=2&q=label:Binary-Size-Tool \ No newline at end of file
+ https://code.google.com/p/chromium/issues/list?can=2&q=label:Tools-BinarySize
diff --git a/tools/binary_size/template/.gitignore b/tools/binary_size/legacy_template/.gitignore
index a6c57f5..a6c57f5 100644
--- a/tools/binary_size/template/.gitignore
+++ b/tools/binary_size/legacy_template/.gitignore
diff --git a/tools/binary_size/legacy_template/index.html b/tools/binary_size/legacy_template/index.html
new file mode 100644
index 0000000..b73958a
--- /dev/null
+++ b/tools/binary_size/legacy_template/index.html
@@ -0,0 +1,190 @@
+<!DOCTYPE html>
+<!--
+ Copyright 2014 The Chromium Authors. All rights reserved.
+ Use of this source code is governed by a BSD-style license that can be
+ found in the LICENSE file.
+-->
+<html>
+<head>
+ <title>Binary Size Analysis</title>
+ <link rel='stylesheet' href='webtreemap/webtreemap.css'>
+ <style>
+ body { font-family: sans-serif; }
+ tt, pre { font-family: WebKitWorkaround, monospace; }
+ #map {
+ margin: 0 auto;
+ position: relative;
+ cursor: pointer;
+ -webkit-user-select: none;
+ }
+ #table {
+ border: 1px solid black;
+ }
+ .treemaplegend {
+ margin: 0 auto;
+ position: relative;
+ }
+ .webtreemap-symbol-vtable {
+ background: #FFFFAA;
+ }
+ .webtreemap-node:hover {
+ border-color: red;
+ background: linear-gradient(rgb(240,240,200), rgb(180,180,200));
+ }
+ </style>
+ <script src='webtreemap/webtreemap.js'></script>
+ <script src='treemap-dump.js'></script>
+ <script src='largest-symbols.js'></script>
+ <script src='largest-sources.js'></script>
+ <script src='largest-vtables.js'></script>
+</head>
+<body onload='show_report_treemap()'>
+<div style='text-align: center; margin-bottom: 2em;'>
+ <h1>Binary Size Analysis</h1>
+ <a href='#' onclick='show_report_treemap()'>Spatial Treemap</a>
+ ~
+ <a href='#' onclick='show_report_largest_sources()'>Largest Sources</a>
+ ~
+ <a href='#' onclick='show_report_largest_symbols()'>Largest Symbols</a>
+ ~
+ <a href='#' onclick='show_report_largest_vtables()'>Largest VTables</a>
+</div>
+<div id='report'></div>
+<script>
+function escape(str) {
+ return str.replace(/&/g, '&amp;')
+ .replace(/"/g, '&quot;')
+ .replace(/</g, '&lt;')
+ .replace(/>/g, '&gt;');
+}
+
+var treemap_width = 800;
+var treemap_height = 450;
+function show_report_treemap() {
+ console.log('displaying treemap')
+ var div = document.getElementById('report');
+ var w = window.treemap_width;
+ var h = window.treemap_height;
+ div.innerHTML = '<div style=\'text-align: center;\'>' +
+ '<button onclick=\'zoomInTreemap()\'>Bigger (More Detail)</button>' +
+ ', <button onclick=\'zoomOutTreemap()\'>Smaller (Less Detail)</button>' +
+ ' or resize to: ' +
+ '<input type=text size=5 id=treemap_width value=' + w + '>x' +
+ '<input type=text size=5 id=treemap_height value=' + h + '>' +
+ '<button onclick=\'resizeTreemap()\'>Go</button>' +
+ '<br><em>Click on a box to zoom in and reveal more detail. ' +
+ 'Click on the outermost box to zoom out.</em>' +
+ '<br>Legend: <table border=1 class=\'treemaplegend\' cellborder=1><tr>' +
+ '<td class=\'webtreemap-symbol-bss\'>BSS</td>' +
+ '<td class=\'webtreemap-symbol-data\'>Data</td>' +
+ '<td class=\'webtreemap-symbol-code\'>Code</td>' +
+ '<td class=\'webtreemap-symbol-read-only_data\'>RO Data</td>' +
+ '<td class=\'webtreemap-symbol-weak_symbol\'>Weak</td>' +
+ '<td class=\'webtreemap-symbol-vtable\'>VTable</td>' +
+ '</tr></table>' +
+ '<br>' +
+ '<div id=\'map\' ' +
+ 'style=\'width: ' + w + 'px; height: ' + h + 'px;\'>' +
+ '</div></div>';
+ var map = document.getElementById('map');
+ appendTreemap(map, kTree);
+}
+
+function zoomInTreemap() {
+ window.treemap_width = Math.round(window.treemap_width * 1.25);
+ window.treemap_height = Math.round(window.treemap_height * 1.25);
+ show_report_treemap();
+}
+
+function zoomOutTreemap() {
+ window.treemap_width = Math.round(window.treemap_width / 1.25);
+ window.treemap_height = Math.round(window.treemap_height / 1.25);
+ show_report_treemap();
+}
+
+function resizeTreemap() {
+ window.treemap_width = document.getElementById('treemap_width').value;
+ window.treemap_height = document.getElementById('treemap_height').value;
+ show_report_treemap();
+}
+
+function show_report_largest_symbols() {
+ console.log('displaying largest-symbols report')
+ var div = document.getElementById('report');
+ div.innerHTML = '<div><table id=\'list\' border=1><tr>' +
+ '<th>Rank</th><th>Size</th><th>Type</th><th>Source</th>' +
+ '</tr></table>';
+ var list = document.getElementById('list');
+ for (var i = 0; i < largestSymbols.length; i++) {
+ var record = largestSymbols[i];
+ var link;
+ if (record.location.indexOf('out') == 0) {
+ link = record.location;
+ } else {
+ link = '<a href="https://code.google.com/p/chromium/codesearch#chromium/src/'
+ + record.location + '">' + escape(record.location) + '</a>';
+ }
+ list.innerHTML += '<tr>'
+ + '<td>' + (i+1) + '</td>'
+ + '<td>' + escape(record.size) + '</td>'
+ + '<td style=\'white-space: nowrap;\'>' + escape(record.type) + '</td>'
+ + '<td>' + link + ':<br>'
+ + escape(record.symbol) + '</td>'
+ + '</tr>';
+ }
+}
+
+function show_report_largest_sources() {
+ console.log('displaying largest-sources report')
+ var div = document.getElementById('report');
+ div.innerHTML = '<div><table id=\'list\' border=1><tr>' +
+ '<th>Rank</th><th>Size</th><th>Symbol Count</th><th>Source</th>' +
+ '</tr></table>';
+ var list = document.getElementById('list');
+ for (var i = 0; i < largestSources.length; i++) {
+ var record = largestSources[i];
+ var link;
+ if (record.location.indexOf('out') == 0) {
+ link = record.location;
+ } else {
+ link = '<a href="https://code.google.com/p/chromium/codesearch#chromium/src/'
+ + record.location + '">' + escape(record.location) + '</a>';
+ }
+
+ list.innerHTML += '<tr>'
+ + '<td>' + (i+1) + '</td>'
+ + '<td>' + escape(record.size) + '</td>'
+ + '<td>' + escape(record.symbol_count) + '</td>'
+ + '<td>' + link + '</td>'
+ + '</tr>';
+ }
+}
+
+function show_report_largest_vtables() {
+ console.log('displaying largest-vtables report')
+ var div = document.getElementById('report');
+ div.innerHTML = '<div><table id=\'list\' border=1><tr>' +
+ '<th>Rank</th><th>Size</th><th>Symbol</th><th>Source</th>' +
+ '</tr></table>';
+ var list = document.getElementById('list');
+ for (var i = 0; i < largestVTables.length; i++) {
+ var record = largestVTables[i];
+ var link;
+ if (record.location.indexOf('out') == 0) {
+ link = record.location;
+ } else {
+ link = '<a href="https://code.google.com/p/chromium/codesearch#chromium/src/'
+ + record.location + '">' + escape(record.location) + '</a>';
+ }
+
+ list.innerHTML += '<tr>'
+ + '<td>' + (i+1) + '</td>'
+ + '<td>' + escape(record.size) + '</td>'
+ + '<td>' + escape(record.symbol) + '</td>'
+ + '<td>' + link + '</td>'
+ + '</tr>';
+ }
+}
+</script>
+</body>
+</html> \ No newline at end of file
diff --git a/tools/binary_size/run_binary_size_analysis.py b/tools/binary_size/run_binary_size_analysis.py
index 37c8254..2a75faf 100755
--- a/tools/binary_size/run_binary_size_analysis.py
+++ b/tools/binary_size/run_binary_size_analysis.py
@@ -23,6 +23,7 @@ import sys
import tempfile
+# TODO(andrewhayden): Only used for legacy reports. Delete.
def FormatBytes(bytes):
"""Pretty-print a number of bytes."""
if bytes > 1e6:
@@ -34,6 +35,7 @@ def FormatBytes(bytes):
return str(bytes)
+# TODO(andrewhayden): Only used for legacy reports. Delete.
def SymbolTypeToHuman(type):
"""Convert a symbol type as printed by nm into a human-readable name."""
return {'b': 'bss',
@@ -70,10 +72,7 @@ def ParseNm(input):
if match:
size, type, sym = match.groups()[0:3]
size = int(size, 16)
- type = type.lower()
- if type == 'v':
- type = 'w' # just call them all weak
- if type == 'b':
+ if type.lower() == 'b':
continue # skip all BSS for now
path = match.group(4)
yield sym, type, size, path
@@ -93,6 +92,66 @@ def ParseNm(input):
print >>sys.stderr, 'unparsed:', repr(line)
+def _MkChild(node, name):
+ child = None
+ for test in node['children']:
+ if test['n'] == name:
+ child = test
+ break
+ if not child:
+ child = {'n': name, 'children': []}
+ node['children'].append(child)
+ return child
+
+
+def MakeCompactTree(symbols):
+ result = {'n': '/', 'children': [], 'k': 'p', 'maxDepth': 0}
+ for symbol_name, symbol_type, symbol_size, file_path in symbols:
+
+ if 'vtable for ' in symbol_name:
+ symbol_type = '@' # hack to categorize these separately
+ # Take path like '/foo/bar/baz', convert to ['foo', 'bar', 'baz']
+ if file_path:
+ file_path = os.path.normpath(file_path)
+ else:
+ file_path = '(No Path)'
+
+ if file_path.startswith('/'):
+ file_path = file_path[1:]
+ path_parts = file_path.split('/')
+
+ # Find pre-existing node in tree, or update if it already exists
+ node = result
+ depth = 0
+ while len(path_parts) > 0:
+ path_part = path_parts.pop(0)
+ if len(path_part) == 0:
+ continue
+ depth += 1
+ node = _MkChild(node, path_part);
+ node['k'] = 'p' # p for path
+
+ # 'node' is now the file node. Find the symbol-type bucket.
+ node['lastPathElement'] = True
+ node = _MkChild(node, symbol_type)
+ node['t'] = symbol_type
+ node['k'] = 'b' # b for bucket
+ depth += 1
+
+ # 'node' is now the symbol-type bucket. Make the child entry.
+ node = _MkChild(node, symbol_name)
+ if 'children' in node: # Only possible if we're adding duplicate entries!!!
+ del node['children']
+ node['value'] = symbol_size
+ node['t'] = symbol_type
+ node['k'] = 's' # s for symbol
+ depth += 1
+ result['maxDepth'] = max(result['maxDepth'], depth);
+
+ return result
+
+
+# TODO(andrewhayden): Only used for legacy reports. Delete.
def TreeifySymbols(symbols):
"""Convert symbols into a path-based tree, calculating size information
along the way.
@@ -188,6 +247,7 @@ def TreeifySymbols(symbols):
return dirs
+# TODO(andrewhayden): Only used for legacy reports. Delete.
def JsonifyTree(tree, name):
"""Convert TreeifySymbols output to a JSON treemap.
@@ -224,7 +284,16 @@ def JsonifyTree(tree, name):
'data': { '$area': tree['size'] },
'children': children }
+def DumpCompactTree(symbols, outfile):
+ out = open(outfile, 'w')
+ try:
+ out.write('var tree_data = ' + json.dumps(MakeCompactTree(symbols)))
+ finally:
+ out.flush()
+ out.close()
+
+# TODO(andrewhayden): Only used for legacy reports. Delete.
def DumpTreemap(symbols, outfile):
dirs = TreeifySymbols(symbols)
out = open(outfile, 'w')
@@ -235,6 +304,7 @@ def DumpTreemap(symbols, outfile):
out.close()
+# TODO(andrewhayden): Only used for legacy reports. Delete.
def DumpLargestSymbols(symbols, outfile, n):
# a list of (sym, type, size, path); sort by size.
symbols = sorted(symbols, key=lambda x: -x[2])
@@ -278,6 +348,7 @@ def MakeSourceMap(symbols):
return sources
+# TODO(andrewhayden): Only used for legacy reports. Delete.
def DumpLargestSources(symbols, outfile, n):
map = MakeSourceMap(symbols)
sources = sorted(map.values(), key=lambda x: -x['size'])
@@ -300,6 +371,7 @@ def DumpLargestSources(symbols, outfile, n):
out.close()
+# TODO(andrewhayden): Only used for legacy reports. Delete.
def DumpLargestVTables(symbols, outfile, n):
vtables = []
for symbol, type, size, path in symbols:
@@ -325,6 +397,7 @@ def DumpLargestVTables(symbols, outfile, n):
out.close()
+# TODO(andrewhayden): Switch to Primiano's python-based version.
def RunParallelAddress2Line(outfile, library, arch, jobs, verbose):
"""Run a parallel addr2line processing engine to dump and resolve symbols."""
out_dir = os.getenv('CHROMIUM_OUT_DIR', 'out')
@@ -342,15 +415,15 @@ def RunParallelAddress2Line(outfile, library, arch, jobs, verbose):
cmd.append('--verbose')
prefix = os.path.join('third_party', 'android_tools', 'ndk', 'toolchains')
if arch == 'android-arm':
- prefix = os.path.join(prefix, 'arm-linux-androideabi-4.7', 'prebuilt',
+ prefix = os.path.join(prefix, 'arm-linux-androideabi-4.8', 'prebuilt',
'linux-x86_64', 'bin', 'arm-linux-androideabi-')
cmd.extend(['--nm', prefix + 'nm', '--addr2line', prefix + 'addr2line'])
elif arch == 'android-mips':
- prefix = os.path.join(prefix, 'mipsel-linux-android-4.7', 'prebuilt',
+ prefix = os.path.join(prefix, 'mipsel-linux-android-4.8', 'prebuilt',
'linux-x86_64', 'bin', 'mipsel-linux-android-')
cmd.extend(['--nm', prefix + 'nm', '--addr2line', prefix + 'addr2line'])
elif arch == 'android-x86':
- prefix = os.path.join(prefix, 'x86-4.7', 'prebuilt',
+ prefix = os.path.join(prefix, 'x86-4.8', 'prebuilt',
'linux-x86_64', 'bin', 'i686-linux-android-')
cmd.extend(['--nm', prefix + 'nm', '--addr2line', prefix + 'addr2line'])
# else, use whatever is in PATH (don't pass --nm or --addr2line)
@@ -441,6 +514,8 @@ def main():
'mapped to source locations. By default, a tempfile is '
'used and is deleted when the program terminates.'
'This argument is only valid when using --library.')
+ parser.add_option('--legacy', action='store_true',
+ help='emit legacy binary size report instead of modern')
opts, args = parser.parse_args()
if ((not opts.library) and (not opts.nm_in)) or (opts.library and opts.nm_in):
@@ -464,27 +539,40 @@ def main():
if not os.path.exists(opts.destdir):
os.makedirs(opts.destdir, 0755)
- DumpTreemap(symbols, os.path.join(opts.destdir, 'treemap-dump.js'))
- DumpLargestSymbols(symbols,
- os.path.join(opts.destdir, 'largest-symbols.js'), 100)
- DumpLargestSources(symbols,
- os.path.join(opts.destdir, 'largest-sources.js'), 100)
- DumpLargestVTables(symbols,
- os.path.join(opts.destdir, 'largest-vtables.js'), 100)
-
- # TODO(andrewhayden): Switch to D3 for greater flexibility
- treemap_out = os.path.join(opts.destdir, 'webtreemap')
- if not os.path.exists(treemap_out):
- os.makedirs(treemap_out, 0755)
- treemap_src = os.path.join('third_party', 'webtreemap', 'src')
- shutil.copy(os.path.join(treemap_src, 'COPYING'), treemap_out)
- shutil.copy(os.path.join(treemap_src, 'webtreemap.js'), treemap_out)
- shutil.copy(os.path.join(treemap_src, 'webtreemap.css'), treemap_out)
- shutil.copy(os.path.join('tools', 'binary_size', 'template', 'index.html'),
- opts.destdir)
+
+ if opts.legacy: # legacy report
+ DumpTreemap(symbols, os.path.join(opts.destdir, 'treemap-dump.js'))
+ DumpLargestSymbols(symbols,
+ os.path.join(opts.destdir, 'largest-symbols.js'), 100)
+ DumpLargestSources(symbols,
+ os.path.join(opts.destdir, 'largest-sources.js'), 100)
+ DumpLargestVTables(symbols,
+ os.path.join(opts.destdir, 'largest-vtables.js'), 100)
+ treemap_out = os.path.join(opts.destdir, 'webtreemap')
+ if not os.path.exists(treemap_out):
+ os.makedirs(treemap_out, 0755)
+ treemap_src = os.path.join('third_party', 'webtreemap', 'src')
+ shutil.copy(os.path.join(treemap_src, 'COPYING'), treemap_out)
+ shutil.copy(os.path.join(treemap_src, 'webtreemap.js'), treemap_out)
+ shutil.copy(os.path.join(treemap_src, 'webtreemap.css'), treemap_out)
+ shutil.copy(os.path.join('tools', 'binary_size', 'legacy_template',
+ 'index.html'), opts.destdir)
+ else: # modern report
+ DumpCompactTree(symbols, os.path.join(opts.destdir, 'data.js'))
+ d3_out = os.path.join(opts.destdir, 'd3')
+ if not os.path.exists(d3_out):
+ os.makedirs(d3_out, 0755)
+ d3_src = os.path.join('third_party', 'd3', 'src')
+ template_src = os.path.join('tools', 'binary_size',
+ 'template')
+ shutil.copy(os.path.join(d3_src, 'LICENSE'), d3_out)
+ shutil.copy(os.path.join(d3_src, 'd3.js'), d3_out)
+ shutil.copy(os.path.join(template_src, 'index.html'), opts.destdir)
+ shutil.copy(os.path.join(template_src, 'D3SymbolTreeMap.js'), opts.destdir)
+
if opts.verbose:
print 'Report saved to ' + opts.destdir + '/index.html'
if __name__ == '__main__':
- sys.exit(main()) \ No newline at end of file
+ sys.exit(main())
diff --git a/tools/binary_size/template/D3SymbolTreeMap.js b/tools/binary_size/template/D3SymbolTreeMap.js
new file mode 100644
index 0000000..4bbe82f
--- /dev/null
+++ b/tools/binary_size/template/D3SymbolTreeMap.js
@@ -0,0 +1,938 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// TODO:
+// 1. Visibility functions: base on boxPadding.t, not 15
+// 2. Track a maxDisplayDepth that is user-settable:
+// maxDepth == currentRoot.depth + maxDisplayDepth
+function D3SymbolTreeMap(mapWidth, mapHeight, levelsToShow) {
+ this._mapContainer = undefined;
+ this._mapWidth = mapWidth;
+ this._mapHeight = mapHeight;
+ this.boxPadding = {'l': 5, 'r': 5, 't': 20, 'b': 5};
+ this.infobox = undefined;
+ this._maskContainer = undefined;
+ this._highlightContainer = undefined;
+ // Transition in this order:
+ // 1. Exiting items go away.
+ // 2. Updated items move.
+ // 3. New items enter.
+ this._exitDuration=500;
+ this._updateDuration=500;
+ this._enterDuration=500;
+ this._firstTransition=true;
+ this._layout = undefined;
+ this._currentRoot = undefined;
+ this._currentNodes = undefined;
+ this._treeData = undefined;
+ this._maxLevelsToShow = levelsToShow;
+ this._currentMaxDepth = this._maxLevelsToShow;
+}
+
+/**
+ * Make a number pretty, with comma separators.
+ */
+D3SymbolTreeMap._pretty = function(num) {
+ var asString = String(num);
+ var result = '';
+ var counter = 0;
+ for (var x = asString.length - 1; x >= 0; x--) {
+ counter++;
+ if (counter === 4) {
+ result = ',' + result;
+ counter = 1;
+ }
+ result = asString.charAt(x) + result;
+ }
+ return result;
+}
+
+/**
+ * Express a number in terms of KiB, MiB, GiB, etc.
+ * Note that these are powers of 2, not of 10.
+ */
+D3SymbolTreeMap._byteify = function(num) {
+ var suffix;
+ if (num >= 1024) {
+ if (num >= 1024 * 1024 * 1024) {
+ suffix = 'GiB';
+ num = num / (1024 * 1024 * 1024);
+ } else if (num >= 1024 * 1024) {
+ suffix = 'MiB';
+ num = num / (1024 * 1024);
+ } else if (num >= 1024) {
+ suffix = 'KiB'
+ num = num / 1024;
+ }
+ return num.toFixed(2) + ' ' + suffix;
+ }
+ return num + ' B';
+}
+
+D3SymbolTreeMap._NM_SYMBOL_TYPE_DESCRIPTIONS = {
+ // Definitions concisely derived from the nm 'man' page
+ 'A': 'Global absolute (A)',
+ 'B': 'Global uninitialized data (B)',
+ 'b': 'Local uninitialized data (b)',
+ 'C': 'Global uninitialized common (C)',
+ 'D': 'Global initialized data (D)',
+ 'd': 'Local initialized data (d)',
+ 'G': 'Global small initialized data (G)',
+ 'g': 'Local small initialized data (g)',
+ 'i': 'Indirect function (i)',
+ 'N': 'Debugging (N)',
+ 'p': 'Stack unwind (p)',
+ 'R': 'Global read-only data (R)',
+ 'r': 'Local read-only data (r)',
+ 'S': 'Global small uninitialized data (S)',
+ 's': 'Local small uninitialized data (s)',
+ 'T': 'Global code (T)',
+ 't': 'Local code (t)',
+ 'U': 'Undefined (U)',
+ 'u': 'Unique (u)',
+ 'V': 'Global weak object (V)',
+ 'v': 'Local weak object (v)',
+ 'W': 'Global weak symbol (W)',
+ 'w': 'Local weak symbol (w)',
+ '@': 'Vtable entry (@)', // non-standard, hack.
+ '-': 'STABS debugging (-)',
+ '?': 'Unrecognized (?)',
+};
+D3SymbolTreeMap._NM_SYMBOL_TYPES = '';
+for (var symbol_type in D3SymbolTreeMap._NM_SYMBOL_TYPE_DESCRIPTIONS) {
+ D3SymbolTreeMap._NM_SYMBOL_TYPES += symbol_type;
+}
+
+/**
+ * Given a symbol type code, look up and return a human-readable description
+ * of that symbol type. If the symbol type does not match one of the known
+ * types, the unrecognized description (corresponding to symbol type '?') is
+ * returned instead of null or undefined.
+ */
+D3SymbolTreeMap._getSymbolDescription = function(type) {
+ var result = D3SymbolTreeMap._NM_SYMBOL_TYPE_DESCRIPTIONS[type];
+ if (result === undefined) {
+ result = D3SymbolTreeMap._NM_SYMBOL_TYPE_DESCRIPTIONS['?'];
+ }
+ return result;
+}
+
+// Qualitative 12-value pastel Brewer palette.
+D3SymbolTreeMap._colorArray = [
+ 'rgb(141,211,199)',
+ 'rgb(255,255,179)',
+ 'rgb(190,186,218)',
+ 'rgb(251,128,114)',
+ 'rgb(128,177,211)',
+ 'rgb(253,180,98)',
+ 'rgb(179,222,105)',
+ 'rgb(252,205,229)',
+ 'rgb(217,217,217)',
+ 'rgb(188,128,189)',
+ 'rgb(204,235,197)',
+ 'rgb(255,237,111)'];
+
+D3SymbolTreeMap._initColorMap = function() {
+ var map = {};
+ var numColors = D3SymbolTreeMap._colorArray.length;
+ var count = 0;
+ for (var key in D3SymbolTreeMap._NM_SYMBOL_TYPE_DESCRIPTIONS) {
+ var index = count++ % numColors;
+ map[key] = d3.rgb(D3SymbolTreeMap._colorArray[index]);
+ }
+ D3SymbolTreeMap._colorMap = map;
+}
+D3SymbolTreeMap._initColorMap();
+
+D3SymbolTreeMap.getColorForType = function(type) {
+ var result = D3SymbolTreeMap._colorMap[type];
+ if (result === undefined) return d3.rgb('rgb(255,255,255)');
+ return result;
+}
+
+D3SymbolTreeMap.prototype.init = function() {
+ this.infobox = this._createInfoBox();
+ this._mapContainer = d3.select('body').append('div')
+ .style('position', 'relative')
+ .style('width', this._mapWidth)
+ .style('height', this._mapHeight)
+ .style('padding', 0)
+ .style('margin', 0)
+ .style('box-shadow', '5px 5px 5px #888');
+ this._layout = this._createTreeMapLayout();
+ this._setData(tree_data); // TODO: Don't use global 'tree_data'
+}
+
+/**
+ * Sets the data displayed by the treemap and layint out the map.
+ */
+D3SymbolTreeMap.prototype._setData = function(data) {
+ this._treeData = data;
+ console.time('_crunchStats');
+ this._crunchStats(data);
+ console.timeEnd('_crunchStats');
+ this._currentRoot = this._treeData;
+ this._currentNodes = this._layout.nodes(this._currentRoot);
+ this._currentMaxDepth = this._maxLevelsToShow;
+ this._doLayout();
+}
+
+/**
+ * Recursively traverses the entire tree starting from the specified node,
+ * computing statistics and recording metadata as it goes. Call this method
+ * only once per imported tree.
+ */
+D3SymbolTreeMap.prototype._crunchStats = function(node) {
+ var stack = [];
+ stack.idCounter = 0;
+ this._crunchStatsHelper(stack, node);
+}
+
+/**
+ * Invoke the specified visitor function on all data elements currently shown
+ * in the treemap including any and all of their children, starting at the
+ * currently-displayed root and descening recursively. The function will be
+ * passed the datum element representing each node. No traversal guarantees
+ * are made.
+ */
+D3SymbolTreeMap.prototype.visitFromDisplayedRoot = function(visitor) {
+ this._visit(this._currentRoot, visitor);
+}
+
+/**
+ * Helper function for visit functions.
+ */
+D3SymbolTreeMap.prototype._visit = function(datum, visitor) {
+ visitor.call(this, datum);
+ if (datum.children) for (var i = 0; i < datum.children.length; i++) {
+ this._visit(datum.children[i], visitor);
+ }
+}
+
+D3SymbolTreeMap.prototype._crunchStatsHelper = function(stack, node) {
+ // Only overwrite the node ID if it isn't already set.
+ // This allows stats to be crunched multiple times on subsets of data
+ // without breaking the data-to-ID bindings. New nodes get new IDs.
+ if (node.id === undefined) node.id = stack.idCounter++;
+ if (node.children === undefined) {
+ // Leaf node (symbol); accumulate stats.
+ for (var i = 0; i < stack.length; i++) {
+ var ancestor = stack[i];
+ if (!ancestor.symbol_stats) ancestor.symbol_stats = {};
+ if (ancestor.symbol_stats[node.t] === undefined) {
+ // New symbol type we haven't seen before, just record.
+ ancestor.symbol_stats[node.t] = {'count': 1,
+ 'size': node.value};
+ } else {
+ // Existing symbol type, increment.
+ ancestor.symbol_stats[node.t].count++;
+ ancestor.symbol_stats[node.t].size += node.value;
+ }
+ }
+ } else for (var i = 0; i < node.children.length; i++) {
+ stack.push(node);
+ this._crunchStatsHelper(stack, node.children[i]);
+ stack.pop();
+ }
+}
+
+D3SymbolTreeMap.prototype._createTreeMapLayout = function() {
+ var result = d3.layout.treemap()
+ .padding([this.boxPadding.t, this.boxPadding.r,
+ this.boxPadding.b, this.boxPadding.l])
+ .size([this._mapWidth, this._mapHeight]);
+ return result;
+}
+
+D3SymbolTreeMap.prototype.resize = function(width, height) {
+ this._mapWidth = width;
+ this._mapHeight = height;
+ this._mapContainer.style('width', width).style('height', height);
+ this._layout.size([this._mapWidth, this._mapHeight]);
+ this._currentNodes = this._layout.nodes(this._currentRoot);
+ this._doLayout();
+}
+
+D3SymbolTreeMap.prototype._zoomDatum = function(datum) {
+ if (this._currentRoot === datum) return; // already here
+ this._hideHighlight(datum);
+ this._hideInfoBox(datum);
+ this._currentRoot = datum;
+ this._currentNodes = this._layout.nodes(this._currentRoot);
+ this._currentMaxDepth = this._currentRoot.depth + this._maxLevelsToShow;
+ console.log('zooming into datum ' + this._currentRoot.n);
+ this._doLayout();
+}
+
+D3SymbolTreeMap.prototype.setMaxLevels = function(levelsToShow) {
+ this._maxLevelsToShow = levelsToShow;
+ this._currentNodes = this._layout.nodes(this._currentRoot);
+ this._currentMaxDepth = this._currentRoot.depth + this._maxLevelsToShow;
+ console.log('setting max levels to show: ' + this._maxLevelsToShow);
+ this._doLayout();
+}
+
+/**
+ * Clone the specified tree, returning an independent copy of the data.
+ * Only the original attributes expected to exist prior to invoking
+ * _crunchStatsHelper are retained, with the exception of the 'id' attribute
+ * (which must be retained for proper transitions).
+ * If the optional filter parameter is provided, it will be called with 'this'
+ * set to this treemap instance and passed the 'datum' object as an argument.
+ * When specified, the copy will retain only the data for which the filter
+ * function returns true.
+ */
+D3SymbolTreeMap.prototype._clone = function(datum, filter) {
+ var trackingStats = false;
+ if (this.__cloneState === undefined) {
+ console.time('_clone');
+ trackingStats = true;
+ this.__cloneState = {'accepted': 0, 'rejected': 0,
+ 'forced': 0, 'pruned': 0};
+ }
+
+ // Must go depth-first. All parents of children that are accepted by the
+ // filter must be preserved!
+ var copy = {'n': datum.n, 'k': datum.k};
+ var childAccepted = false;
+ if (datum.children !== undefined) {
+ for (var i = 0; i < datum.children.length; i++) {
+ var copiedChild = this._clone(datum.children[i], filter);
+ if (copiedChild !== undefined) {
+ childAccepted = true; // parent must also be accepted.
+ if (copy.children === undefined) copy.children = [];
+ copy.children.push(copiedChild);
+ }
+ }
+ }
+
+ // Ignore nodes that don't match the filter, when present.
+ var accept = false;
+ if (childAccepted) {
+ // Parent of an accepted child must also be accepted.
+ this.__cloneState.forced++;
+ accept = true;
+ } else if (filter !== undefined && filter.call(this, datum) !== true) {
+ this.__cloneState.rejected++;
+ } else if (datum.children === undefined) {
+ // Accept leaf nodes that passed the filter
+ this.__cloneState.accepted++;
+ accept = true;
+ } else {
+ // Non-leaf node. If no children are accepted, prune it.
+ this.__cloneState.pruned++;
+ }
+
+ if (accept) {
+ if (datum.id !== undefined) copy.id = datum.id;
+ if (datum.lastPathElement !== undefined) {
+ copy.lastPathElement = datum.lastPathElement;
+ }
+ if (datum.t !== undefined) copy.t = datum.t;
+ if (datum.value !== undefined && datum.children === undefined) {
+ copy.value = datum.value;
+ }
+ } else {
+ // Discard the copy we were going to return
+ copy = undefined;
+ }
+
+ if (trackingStats === true) {
+ // We are the fist call in the recursive chain.
+ console.timeEnd('_clone');
+ var totalAccepted = this.__cloneState.accepted +
+ this.__cloneState.forced;
+ console.log(
+ totalAccepted + ' nodes retained (' +
+ this.__cloneState.forced + ' forced by accepted children, ' +
+ this.__cloneState.accepted + ' accepted on their own merits), ' +
+ this.__cloneState.rejected + ' nodes (and their children) ' +
+ 'filtered out,' +
+ this.__cloneState.pruned + ' nodes pruned because because no ' +
+ 'children remained.');
+ delete this.__cloneState;
+ }
+ return copy;
+}
+
+D3SymbolTreeMap.prototype.filter = function(filter) {
+ // Ensure we have a copy of the original root.
+ if (this._backupTree === undefined) this._backupTree = this._treeData;
+ this._mapContainer.selectAll('div').remove();
+ this._setData(this._clone(this._backupTree, filter));
+}
+
+D3SymbolTreeMap.prototype._doLayout = function() {
+ console.time('_doLayout');
+ this._handleInodes();
+ this._handleLeaves();
+ this._firstTransition = false;
+ console.timeEnd('_doLayout');
+}
+
+D3SymbolTreeMap.prototype._highlightElement = function(datum, selection) {
+ this._showHighlight(datum, selection);
+}
+
+D3SymbolTreeMap.prototype._unhighlightElement = function(datum, selection) {
+ this._hideHighlight(datum, selection);
+}
+
+D3SymbolTreeMap.prototype._handleInodes = function() {
+ console.time('_handleInodes');
+ var thisTreeMap = this;
+ var inodes = this._currentNodes.filter(function(datum){
+ return (datum.depth <= thisTreeMap._currentMaxDepth) &&
+ datum.children !== undefined;
+ });
+ var cellsEnter = this._mapContainer.selectAll('div.inode')
+ .data(inodes, function(datum) { return datum.id; })
+ .enter()
+ .append('div').attr('class', 'inode').attr('id', function(datum){
+ return 'node-' + datum.id;});
+
+
+ // Define enter/update/exit for inodes
+ cellsEnter
+ .append('div')
+ .attr('class', 'rect inode_rect_entering')
+ .style('z-index', function(datum) { return datum.id * 2; })
+ .style('position', 'absolute')
+ .style('left', function(datum) { return datum.x; })
+ .style('top', function(datum){ return datum.y; })
+ .style('width', function(datum){ return datum.dx; })
+ .style('height', function(datum){ return datum.dy; })
+ .style('opacity', '0')
+ .style('border', '1px solid black')
+ .style('background-image', function(datum) {
+ return thisTreeMap._makeSymbolBucketBackgroundImage.call(
+ thisTreeMap, datum);
+ })
+ .style('background-color', function(datum) {
+ if (datum.t === undefined) return 'rgb(220,220,220)';
+ return D3SymbolTreeMap.getColorForType(datum.t).toString();
+ })
+ .on('mouseover', function(datum){
+ thisTreeMap._highlightElement.call(
+ thisTreeMap, datum, d3.select(this));
+ thisTreeMap._showInfoBox.call(thisTreeMap, datum);
+ })
+ .on('mouseout', function(datum){
+ thisTreeMap._unhighlightElement.call(
+ thisTreeMap, datum, d3.select(this));
+ thisTreeMap._hideInfoBox.call(thisTreeMap, datum);
+ })
+ .on('mousemove', function(){
+ thisTreeMap._moveInfoBox.call(thisTreeMap, event);
+ })
+ .on('dblclick', function(datum){
+ if (datum !== thisTreeMap._currentRoot) {
+ // Zoom into the selection
+ thisTreeMap._zoomDatum(datum);
+ } else if (datum.parent) {
+ console.log('event.shiftKey=' + event.shiftKey);
+ if (event.shiftKey === true) {
+ // Back to root
+ thisTreeMap._zoomDatum(thisTreeMap._treeData);
+ } else {
+ // Zoom out of the selection
+ thisTreeMap._zoomDatum(datum.parent);
+ }
+ }
+ });
+ cellsEnter
+ .append('div')
+ .attr('class', 'label inode_label_entering')
+ .style('z-index', function(datum) { return (datum.id * 2) + 1; })
+ .style('position', 'absolute')
+ .style('left', function(datum){ return datum.x; })
+ .style('top', function(datum){ return datum.y; })
+ .style('width', function(datum) { return datum.dx; })
+ .style('height', function(datum) { return thisTreeMap.boxPadding.t; })
+ .style('opacity', '0')
+ .style('pointer-events', 'none')
+ .style('-webkit-user-select', 'none')
+ .style('overflow', 'hidden') // required for ellipsis
+ .style('white-space', 'nowrap') // required for ellipsis
+ .style('text-overflow', 'ellipsis')
+ .style('text-align', 'center')
+ .style('vertical-align', 'top')
+ .style('visibility', function(datum) {
+ return (datum.dx < 15 || datum.dy < 15) ? 'hidden' : 'visible';
+ })
+ .text(function(datum) {
+ var sizeish = ' [' + D3SymbolTreeMap._byteify(datum.value) + ']'
+ var text;
+ if (datum.k === 'b') { // bucket
+ if (datum === thisTreeMap._currentRoot) {
+ text = thisTreeMap.pathFor(datum) + ': '
+ + D3SymbolTreeMap._getSymbolDescription(datum.t)
+ } else {
+ text = D3SymbolTreeMap._getSymbolDescription(datum.t);
+ }
+ } else if (datum === thisTreeMap._currentRoot) {
+ // The top-most level should always show the complete path
+ text = thisTreeMap.pathFor(datum);
+ } else {
+ // Anything that isn't a bucket or a leaf (symbol) or the
+ // current root should just show its name.
+ text = datum.n;
+ }
+ return text + sizeish;
+ }
+ );
+
+ // Complicated transition logic:
+ // For nodes that are entering, we want to fade them in in-place AFTER
+ // any adjusting nodes have resized and moved around. That way, new nodes
+ // seamlessly appear in the right spot after their containers have resized
+ // and moved around.
+ // To do this we do some trickery:
+ // 1. Define a '_entering' class on the entering elements
+ // 2. Use this to select only the entering elements and apply the opacity
+ // transition.
+ // 3. Use the same transition to drop the '_entering' suffix, so that they
+ // will correctly update in later zoom/resize/whatever operations.
+ // 4. The update transition is achieved by selecting the elements without
+ // the '_entering_' suffix and applying movement and resizing transition
+ // effects.
+ this._mapContainer.selectAll('div.inode_rect_entering').transition()
+ .duration(thisTreeMap._enterDuration).delay(
+ this._firstTransition ? 0 : thisTreeMap._exitDuration +
+ thisTreeMap._updateDuration)
+ .attr('class', 'rect inode_rect')
+ .style('opacity', '1')
+ this._mapContainer.selectAll('div.inode_label_entering').transition()
+ .duration(thisTreeMap._enterDuration).delay(
+ this._firstTransition ? 0 : thisTreeMap._exitDuration +
+ thisTreeMap._updateDuration)
+ .attr('class', 'label inode_label')
+ .style('opacity', '1')
+ this._mapContainer.selectAll('div.inode_rect').transition()
+ .duration(thisTreeMap._updateDuration).delay(thisTreeMap._exitDuration)
+ .style('opacity', '1')
+ .style('background-image', function(datum) {
+ return thisTreeMap._makeSymbolBucketBackgroundImage.call(
+ thisTreeMap, datum);
+ })
+ .style('left', function(datum) { return datum.x; })
+ .style('top', function(datum){ return datum.y; })
+ .style('width', function(datum){ return datum.dx; })
+ .style('height', function(datum){ return datum.dy; });
+ this._mapContainer.selectAll('div.inode_label').transition()
+ .duration(thisTreeMap._updateDuration).delay(thisTreeMap._exitDuration)
+ .style('opacity', '1')
+ .style('visibility', function(datum) {
+ return (datum.dx < 15 || datum.dy < 15) ? 'hidden' : 'visible';
+ })
+ .style('left', function(datum){ return datum.x; })
+ .style('top', function(datum){ return datum.y; })
+ .style('width', function(datum) { return datum.dx; })
+ .style('height', function(datum) { return thisTreeMap.boxPadding.t; })
+ .text(function(datum) {
+ var sizeish = ' [' + D3SymbolTreeMap._byteify(datum.value) + ']'
+ var text;
+ if (datum.k === 'b') {
+ if (datum === thisTreeMap._currentRoot) {
+ text = thisTreeMap.pathFor(datum) + ': ' +
+ D3SymbolTreeMap._getSymbolDescription(datum.t)
+ } else {
+ text = D3SymbolTreeMap._getSymbolDescription(datum.t);
+ }
+ } else if (datum === thisTreeMap._currentRoot) {
+ // The top-most level should always show the complete path
+ text = thisTreeMap.pathFor(datum);
+ } else {
+ // Anything that isn't a bucket or a leaf (symbol) or the
+ // current root should just show its name.
+ text = datum.n;
+ }
+ return text + sizeish;
+ });
+ var exit = this._mapContainer.selectAll('div.inode')
+ .data(inodes, function(datum) { return 'inode-' + datum.id; })
+ .exit();
+ exit.selectAll('div.inode_rect').transition().duration(
+ thisTreeMap._exitDuration).style('opacity', 0);
+ exit.selectAll('div.inode_label').transition().duration(
+ thisTreeMap._exitDuration).style('opacity', 0);
+ exit.transition().delay(thisTreeMap._exitDuration + 1).remove();
+
+ console.log(inodes.length + ' inodes layed out.');
+ console.timeEnd('_handleInodes');
+}
+
+D3SymbolTreeMap.prototype._handleLeaves = function() {
+ console.time('_handleLeaves');
+ var color_fn = d3.scale.category10();
+ var thisTreeMap = this;
+ var leaves = this._currentNodes.filter(function(datum){
+ return (datum.depth <= thisTreeMap._currentMaxDepth) &&
+ datum.children === undefined; });
+ var cellsEnter = this._mapContainer.selectAll('div.leaf')
+ .data(leaves, function(datum) { return datum.id; })
+ .enter()
+ .append('div').attr('class', 'leaf').attr('id', function(datum){
+ return 'node-' + datum.id;
+ });
+
+ // Define enter/update/exit for leaves
+ cellsEnter
+ .append('div')
+ .attr('class', 'rect leaf_rect_entering')
+ .style('z-index', function(datum) { return datum.id * 2; })
+ .style('position', 'absolute')
+ .style('left', function(datum){ return datum.x; })
+ .style('top', function(datum){ return datum.y; })
+ .style('width', function(datum){ return datum.dx; })
+ .style('height', function(datum){ return datum.dy; })
+ .style('opacity', '0')
+ .style('background-color', function(datum) {
+ if (datum.t === undefined) return 'rgb(220,220,220)';
+ return D3SymbolTreeMap.getColorForType(datum.t)
+ .darker(0.3).toString();
+ })
+ .style('border', '1px solid black')
+ .on('mouseover', function(datum){
+ thisTreeMap._highlightElement.call(
+ thisTreeMap, datum, d3.select(this));
+ thisTreeMap._showInfoBox.call(thisTreeMap, datum);
+ })
+ .on('mouseout', function(datum){
+ thisTreeMap._unhighlightElement.call(
+ thisTreeMap, datum, d3.select(this));
+ thisTreeMap._hideInfoBox.call(thisTreeMap, datum);
+ })
+ .on('mousemove', function(){ thisTreeMap._moveInfoBox.call(
+ thisTreeMap, event);
+ });
+ cellsEnter
+ .append('div')
+ .attr('class', 'label leaf_label_entering')
+ .style('z-index', function(datum) { return (datum.id * 2) + 1; })
+ .style('position', 'absolute')
+ .style('left', function(datum){ return datum.x; })
+ .style('top', function(datum){ return datum.y; })
+ .style('width', function(datum) { return datum.dx; })
+ .style('height', function(datum) { return datum.dy; })
+ .style('opacity', '0')
+ .style('pointer-events', 'none')
+ .style('-webkit-user-select', 'none')
+ .style('overflow', 'hidden') // required for ellipsis
+ .style('white-space', 'nowrap') // required for ellipsis
+ .style('text-overflow', 'ellipsis')
+ .style('text-align', 'center')
+ .style('vertical-align', 'middle')
+ .style('visibility', function(datum) {
+ return (datum.dx < 15 || datum.dy < 15) ? 'hidden' : 'visible';
+ })
+ .text(function(datum) { return datum.n; });
+
+ // Complicated transition logic: See note in _handleInodes()
+ this._mapContainer.selectAll('div.leaf_rect_entering').transition()
+ .duration(thisTreeMap._enterDuration).delay(
+ this._firstTransition ? 0 : thisTreeMap._exitDuration +
+ thisTreeMap._updateDuration)
+ .attr('class', 'rect leaf_rect')
+ .style('opacity', '1')
+ this._mapContainer.selectAll('div.leaf_label_entering').transition()
+ .duration(thisTreeMap._enterDuration).delay(
+ this._firstTransition ? 0 : thisTreeMap._exitDuration +
+ thisTreeMap._updateDuration)
+ .attr('class', 'label leaf_label')
+ .style('opacity', '1')
+ this._mapContainer.selectAll('div.leaf_rect').transition()
+ .duration(thisTreeMap._updateDuration).delay(thisTreeMap._exitDuration)
+ .style('opacity', '1')
+ .style('left', function(datum){ return datum.x; })
+ .style('top', function(datum){ return datum.y; })
+ .style('width', function(datum){ return datum.dx; })
+ .style('height', function(datum){ return datum.dy; });
+ this._mapContainer.selectAll('div.leaf_label').transition()
+ .duration(thisTreeMap._updateDuration).delay(thisTreeMap._exitDuration)
+ .style('opacity', '1')
+ .style('visibility', function(datum) {
+ return (datum.dx < 15 || datum.dy < 15) ? 'hidden' : 'visible';
+ })
+ .style('left', function(datum){ return datum.x; })
+ .style('top', function(datum){ return datum.y; })
+ .style('width', function(datum) { return datum.dx; })
+ .style('height', function(datum) { return datum.dy; });
+ var exit = this._mapContainer.selectAll('div.leaf')
+ .data(leaves, function(datum) { return 'leaf-' + datum.id; })
+ .exit();
+ exit.selectAll('div.leaf_rect').transition()
+ .duration(thisTreeMap._exitDuration)
+ .style('opacity', 0);
+ exit.selectAll('div.leaf_label').transition()
+ .duration(thisTreeMap._exitDuration)
+ .style('opacity', 0);
+ exit.transition().delay(thisTreeMap._exitDuration + 1).remove();
+
+ console.log(leaves.length + ' leaves layed out.');
+ console.timeEnd('_handleLeaves');
+}
+
+D3SymbolTreeMap.prototype._makeSymbolBucketBackgroundImage = function(datum) {
+ if (!(datum.t === undefined && datum.depth == this._currentMaxDepth)) {
+ return 'none';
+ }
+ var text = '';
+ var lastStop = 0;
+ for (var x = 0; x < D3SymbolTreeMap._NM_SYMBOL_TYPES.length; x++) {
+ symbol_type = D3SymbolTreeMap._NM_SYMBOL_TYPES.charAt(x);
+ var stats = datum.symbol_stats[symbol_type];
+ if (stats !== undefined) {
+ if (text.length !== 0) {
+ text += ', ';
+ }
+ var percent = 100 * (stats.size / datum.value);
+ var nowStop = lastStop + percent;
+ var tempcolor = D3SymbolTreeMap.getColorForType(symbol_type);
+ var color = d3.rgb(tempcolor).toString();
+ text += color + ' ' + lastStop + '%, ' + color + ' ' +
+ nowStop + '%';
+ lastStop = nowStop;
+ }
+ }
+ return 'linear-gradient(' + (datum.dx > datum.dy ? 'to right' :
+ 'to bottom') + ', ' + text + ')';
+}
+
+D3SymbolTreeMap.prototype.pathFor = function(datum) {
+ if (datum.__path) return datum.__path;
+ parts=[];
+ node = datum;
+ while (node) {
+ if (node.k === 'p') { // path node
+ if(node.n !== '/') parts.unshift(node.n);
+ }
+ node = node.parent;
+ }
+ datum.__path = '/' + parts.join('/');
+ return datum.__path;
+}
+
+D3SymbolTreeMap.prototype._createHighlight = function(datum, selection) {
+ var x = parseInt(selection.style('left'));
+ var y = parseInt(selection.style('top'));
+ var w = parseInt(selection.style('width'));
+ var h = parseInt(selection.style('height'));
+ datum.highlight = this._mapContainer.append('div')
+ .attr('id', 'h-' + datum.id)
+ .attr('class', 'highlight')
+ .style('pointer-events', 'none')
+ .style('-webkit-user-select', 'none')
+ .style('z-index', '999999')
+ .style('position', 'absolute')
+ .style('top', y-2)
+ .style('left', x-2)
+ .style('width', w+4)
+ .style('height', h+4)
+ .style('margin', 0)
+ .style('padding', 0)
+ .style('border', '4px outset rgba(250,40,200,0.9)')
+ .style('box-sizing', 'border-box')
+ .style('opacity', 0.0);
+}
+
+D3SymbolTreeMap.prototype._showHighlight = function(datum, selection) {
+ if (datum === this._currentRoot) return;
+ if (datum.highlight === undefined) {
+ this._createHighlight(datum, selection);
+ }
+ datum.highlight.transition().duration(200).style('opacity', 1.0);
+}
+
+D3SymbolTreeMap.prototype._hideHighlight = function(datum, selection) {
+ if (datum.highlight === undefined) return;
+ datum.highlight.transition().duration(750)
+ .style('opacity', 0)
+ .each('end', function(){
+ if (datum.highlight) datum.highlight.remove();
+ delete datum.highlight;
+ });
+}
+
+D3SymbolTreeMap.prototype._createInfoBox = function() {
+ return d3.select('body')
+ .append('div')
+ .attr('id', 'infobox')
+ .style('z-index', '2147483647') // (2^31) - 1: Hopefully safe :)
+ .style('position', 'absolute')
+ .style('visibility', 'hidden')
+ .style('background-color', 'rgba(255,255,255, 0.9)')
+ .style('border', '1px solid black')
+ .style('padding', '10px')
+ .style('-webkit-user-select', 'none')
+ .style('box-shadow', '3px 3px rgba(70,70,70,0.5)')
+ .style('border-radius', '10px')
+ .style('white-space', 'nowrap');
+}
+
+D3SymbolTreeMap.prototype._showInfoBox = function(datum) {
+ this.infobox.text('');
+ var numSymbols = 0;
+ var sizeish = D3SymbolTreeMap._pretty(datum.value) + ' bytes (' +
+ D3SymbolTreeMap._byteify(datum.value) + ')';
+ if (datum.k === 'p' || datum.k === 'b') { // path or bucket
+ if (datum.symbol_stats) { // can be empty if filters are applied
+ for (var x = 0; x < D3SymbolTreeMap._NM_SYMBOL_TYPES.length; x++) {
+ symbol_type = D3SymbolTreeMap._NM_SYMBOL_TYPES.charAt(x);
+ var stats = datum.symbol_stats[symbol_type];
+ if (stats !== undefined) numSymbols += stats.count;
+ }
+ }
+ } else if (datum.k === 's') { // symbol
+ numSymbols = 1;
+ }
+
+ if (datum.k === 'p' && !datum.lastPathElement) {
+ this.infobox.append('div').text('Directory: ' + this.pathFor(datum))
+ this.infobox.append('div').text('Size: ' + sizeish);
+ } else {
+ if (datum.k === 'p') { // path
+ this.infobox.append('div').text('File: ' + this.pathFor(datum))
+ this.infobox.append('div').text('Size: ' + sizeish);
+ } else if (datum.k === 'b') { // bucket
+ this.infobox.append('div').text('Symbol Bucket: ' +
+ D3SymbolTreeMap._getSymbolDescription(datum.t));
+ this.infobox.append('div').text('Count: ' + numSymbols);
+ this.infobox.append('div').text('Size: ' + sizeish);
+ this.infobox.append('div').text('Location: ' + this.pathFor(datum))
+ } else if (datum.k === 's') { // symbol
+ this.infobox.append('div').text('Symbol: ' + datum.n);
+ this.infobox.append('div').text('Type: ' +
+ D3SymbolTreeMap._getSymbolDescription(datum.t));
+ this.infobox.append('div').text('Size: ' + sizeish);
+ this.infobox.append('div').text('Location: ' + this.pathFor(datum))
+ }
+ }
+ if (datum.k === 'p') {
+ this.infobox.append('div')
+ .text('Number of symbols: ' + D3SymbolTreeMap._pretty(numSymbols));
+ if (datum.symbol_stats) { // can be empty if filters are applied
+ var table = this.infobox.append('table')
+ .attr('border', 1).append('tbody');
+ var header = table.append('tr');
+ header.append('th').text('Type');
+ header.append('th').text('Count');
+ header.append('th')
+ .style('white-space', 'nowrap')
+ .text('Total Size (Bytes)');
+ for (var x = 0; x < D3SymbolTreeMap._NM_SYMBOL_TYPES.length; x++) {
+ symbol_type = D3SymbolTreeMap._NM_SYMBOL_TYPES.charAt(x);
+ var stats = datum.symbol_stats[symbol_type];
+ if (stats !== undefined) {
+ var tr = table.append('tr');
+ tr.append('td')
+ .style('white-space', 'nowrap')
+ .text(D3SymbolTreeMap._getSymbolDescription(
+ symbol_type));
+ tr.append('td').text(D3SymbolTreeMap._pretty(stats.count));
+ tr.append('td').text(D3SymbolTreeMap._pretty(stats.size));
+ }
+ }
+ }
+ }
+ this.infobox.style('visibility', 'visible');
+}
+
+D3SymbolTreeMap.prototype._hideInfoBox = function(datum) {
+ this.infobox.style('visibility', 'hidden');
+}
+
+D3SymbolTreeMap.prototype._moveInfoBox = function(event) {
+ var element = document.getElementById('infobox');
+ var w = element.offsetWidth;
+ var h = element.offsetHeight;
+ var offsetLeft = 10;
+ var offsetTop = 10;
+
+ var rightLimit = window.innerWidth;
+ var rightEdge = event.pageX + offsetLeft + w;
+ if (rightEdge > rightLimit) {
+ // Too close to screen edge, reflect around the cursor
+ offsetLeft = -1 * (w + offsetLeft);
+ }
+
+ var bottomLimit = window.innerHeight;
+ var bottomEdge = event.pageY + offsetTop + h;
+ if (bottomEdge > bottomLimit) {
+ // Too close ot screen edge, reflect around the cursor
+ offsetTop = -1 * (h + offsetTop);
+ }
+
+ this.infobox.style('top', (event.pageY + offsetTop) + 'px')
+ .style('left', (event.pageX + offsetLeft) + 'px');
+}
+
+D3SymbolTreeMap.prototype.biggestSymbols = function(maxRecords) {
+ var result = undefined;
+ var smallest = undefined;
+ var sortFunction = function(a,b) {
+ var result = b.value - a.value;
+ if (result !== 0) return result; // sort by size
+ var pathA = treemap.pathFor(a); // sort by path
+ var pathB = treemap.pathFor(b);
+ if (pathA > pathB) return 1;
+ if (pathB > pathA) return -1;
+ return a.n - b.n; // sort by symbol name
+ };
+ this.visitFromDisplayedRoot(function(datum) {
+ if (datum.children) return; // ignore non-leaves
+ if (!result) { // first element
+ result = [datum];
+ smallest = datum.value;
+ return;
+ }
+ if (result.length < maxRecords) { // filling the array
+ result.push(datum);
+ return;
+ }
+ if (datum.value > smallest) { // array is already full
+ result.push(datum);
+ result.sort(sortFunction);
+ result.pop(); // get rid of smallest element
+ smallest = result[maxRecords - 1].value; // new threshold for entry
+ }
+ });
+ result.sort(sortFunction);
+ return result;
+}
+
+D3SymbolTreeMap.prototype.biggestPaths = function(maxRecords) {
+ var result = undefined;
+ var smallest = undefined;
+ var sortFunction = function(a,b) {
+ var result = b.value - a.value;
+ if (result !== 0) return result; // sort by size
+ var pathA = treemap.pathFor(a); // sort by path
+ var pathB = treemap.pathFor(b);
+ if (pathA > pathB) return 1;
+ if (pathB > pathA) return -1;
+ console.log('warning, multiple entries for the same path: ' + pathA);
+ return 0; // should be impossible
+ };
+ this.visitFromDisplayedRoot(function(datum) {
+ if (!datum.lastPathElement) return; // ignore non-files
+ if (!result) { // first element
+ result = [datum];
+ smallest = datum.value;
+ return;
+ }
+ if (result.length < maxRecords) { // filling the array
+ result.push(datum);
+ return;
+ }
+ if (datum.value > smallest) { // array is already full
+ result.push(datum);
+ result.sort(sortFunction);
+ result.pop(); // get rid of smallest element
+ smallest = result[maxRecords - 1].value; // new threshold for entry
+ }
+ });
+ result.sort(sortFunction);
+ return result;
+}
diff --git a/tools/binary_size/template/index.html b/tools/binary_size/template/index.html
index b73958a..b3a86ad 100644
--- a/tools/binary_size/template/index.html
+++ b/tools/binary_size/template/index.html
@@ -1,4 +1,3 @@
-<!DOCTYPE html>
<!--
Copyright 2014 The Chromium Authors. All rights reserved.
Use of this source code is governed by a BSD-style license that can be
@@ -6,185 +5,521 @@
-->
<html>
<head>
- <title>Binary Size Analysis</title>
- <link rel='stylesheet' href='webtreemap/webtreemap.css'>
- <style>
- body { font-family: sans-serif; }
- tt, pre { font-family: WebKitWorkaround, monospace; }
- #map {
- margin: 0 auto;
- position: relative;
- cursor: pointer;
- -webkit-user-select: none;
- }
- #table {
- border: 1px solid black;
- }
- .treemaplegend {
- margin: 0 auto;
- position: relative;
- }
- .webtreemap-symbol-vtable {
- background: #FFFFAA;
- }
- .webtreemap-node:hover {
- border-color: red;
- background: linear-gradient(rgb(240,240,200), rgb(180,180,200));
- }
- </style>
- <script src='webtreemap/webtreemap.js'></script>
- <script src='treemap-dump.js'></script>
- <script src='largest-symbols.js'></script>
- <script src='largest-sources.js'></script>
- <script src='largest-vtables.js'></script>
-</head>
-<body onload='show_report_treemap()'>
-<div style='text-align: center; margin-bottom: 2em;'>
- <h1>Binary Size Analysis</h1>
- <a href='#' onclick='show_report_treemap()'>Spatial Treemap</a>
- ~
- <a href='#' onclick='show_report_largest_sources()'>Largest Sources</a>
- ~
- <a href='#' onclick='show_report_largest_symbols()'>Largest Symbols</a>
- ~
- <a href='#' onclick='show_report_largest_vtables()'>Largest VTables</a>
-</div>
-<div id='report'></div>
+<title>Binary Size Analysis</title>
+<script src="d3/d3.js" charset="utf-8"></script>
+<script src="D3SymbolTreeMap.js" charset="utf-8"></script>
+<script src="data.js" charset="utf-8"></script>
+<style>
+body {
+ margin: 0px;
+ padding: 5px;
+}
+.swatch {
+ border: 1px solid rgb(100,100,100);
+ -webkit-user-select: none;
+ cursor: default;
+}
+</style>
<script>
-function escape(str) {
- return str.replace(/&/g, '&amp;')
- .replace(/"/g, '&quot;')
- .replace(/</g, '&lt;')
- .replace(/>/g, '&gt;');
-}
-
-var treemap_width = 800;
-var treemap_height = 450;
-function show_report_treemap() {
- console.log('displaying treemap')
- var div = document.getElementById('report');
- var w = window.treemap_width;
- var h = window.treemap_height;
- div.innerHTML = '<div style=\'text-align: center;\'>' +
- '<button onclick=\'zoomInTreemap()\'>Bigger (More Detail)</button>' +
- ', <button onclick=\'zoomOutTreemap()\'>Smaller (Less Detail)</button>' +
- ' or resize to: ' +
- '<input type=text size=5 id=treemap_width value=' + w + '>x' +
- '<input type=text size=5 id=treemap_height value=' + h + '>' +
- '<button onclick=\'resizeTreemap()\'>Go</button>' +
- '<br><em>Click on a box to zoom in and reveal more detail. ' +
- 'Click on the outermost box to zoom out.</em>' +
- '<br>Legend: <table border=1 class=\'treemaplegend\' cellborder=1><tr>' +
- '<td class=\'webtreemap-symbol-bss\'>BSS</td>' +
- '<td class=\'webtreemap-symbol-data\'>Data</td>' +
- '<td class=\'webtreemap-symbol-code\'>Code</td>' +
- '<td class=\'webtreemap-symbol-read-only_data\'>RO Data</td>' +
- '<td class=\'webtreemap-symbol-weak_symbol\'>Weak</td>' +
- '<td class=\'webtreemap-symbol-vtable\'>VTable</td>' +
- '</tr></table>' +
- '<br>' +
- '<div id=\'map\' ' +
- 'style=\'width: ' + w + 'px; height: ' + h + 'px;\'>' +
- '</div></div>';
- var map = document.getElementById('map');
- appendTreemap(map, kTree);
-}
-
-function zoomInTreemap() {
- window.treemap_width = Math.round(window.treemap_width * 1.25);
- window.treemap_height = Math.round(window.treemap_height * 1.25);
- show_report_treemap();
-}
-
-function zoomOutTreemap() {
- window.treemap_width = Math.round(window.treemap_width / 1.25);
- window.treemap_height = Math.round(window.treemap_height / 1.25);
- show_report_treemap();
-}
-
-function resizeTreemap() {
- window.treemap_width = document.getElementById('treemap_width').value;
- window.treemap_height = document.getElementById('treemap_height').value;
- show_report_treemap();
-}
-
-function show_report_largest_symbols() {
- console.log('displaying largest-symbols report')
- var div = document.getElementById('report');
- div.innerHTML = '<div><table id=\'list\' border=1><tr>' +
- '<th>Rank</th><th>Size</th><th>Type</th><th>Source</th>' +
- '</tr></table>';
- var list = document.getElementById('list');
- for (var i = 0; i < largestSymbols.length; i++) {
- var record = largestSymbols[i];
- var link;
- if (record.location.indexOf('out') == 0) {
- link = record.location;
+var treemap;
+var filterChanging = false;
+var savedSettings = {};
+
+function init() {
+ if (window.metadata !== undefined && window.metadata.subtitle) {
+ document.getElementById('subtitle').innerHTML = ': ' + escape(metadata.subtitle);
+ }
+ initFilterOptions();
+ treemap = new D3SymbolTreeMap(
+ savedSettings.width,
+ savedSettings.height,
+ savedSettings.maxLevels);
+ treemap.init();
+}
+
+function getIdealSizes() {
+ var width = window.innerWidth - 20;
+ var height = window.innerHeight - 70;
+ return {'width': width, 'height': height};
+}
+
+function showReport(title, data, headers, dataFunction, styleFunction) {
+ var div = d3.select('body').append('div')
+ .style('margin', '0')
+ .style('padding', '5px')
+ .style('position', 'absolute')
+ .style('top', '10%')
+ .style('left', '10%')
+ .style('background-color', 'rgba(255,255,255,0.9)')
+ .style('width', '80%')
+ .style('height', '80%')
+ .style('z-index', '2147483647')
+ .style('border', '3px ridge grey')
+ .style('box-shadow', '10px 10px 5px rgba(80,80,80,0.7)')
+ .style('text-align', 'center')
+ .style('border-radius', '10px');
+ var titlebar = div.append('div')
+ .style('margin', '0')
+ .style('padding', '5px')
+ .style('position', 'absolute')
+ .style('top', '0%')
+ .style('left', '0%')
+ .style('width', '100%')
+ .style('height', '10%')
+ .style('font-size', 'x-large');
+ titlebar.text(title);
+ var controls = div.append('div')
+ .style('margin', '0')
+ .style('padding', '5px')
+ .style('position', 'absolute')
+ .style('top', '90%')
+ .style('left', '0%')
+ .style('width', '100%')
+ .style('height', '10%');
+ controls.append('input').attr('type', 'button')
+ .attr('value', 'Dismiss')
+ .on('click', function(){div.remove();});
+
+ var tableDiv = div.append('div')
+ .style('overflow', 'auto')
+ .style('position', 'absolute')
+ .style('top', '10%')
+ .style('left', '0%')
+ .style('width', '100%')
+ .style('height', '80%')
+ .style('border-top', '1px solid rgb(230,230,230)')
+ .style('border-bottom', '1px solid rgb(230,230,230)');
+ var table = tableDiv.append('table')
+ .attr('border', '1')
+ .attr('cellspacing', '0')
+ .attr('cellpadding', '2')
+ .style('margin-left', 'auto')
+ .style('margin-right', 'auto');
+ var header = table.append('tr');
+ for (var i = 0; i < headers.length; i++) {
+ header.append('th').text(headers[i]);
+ }
+
+ for (var i = 0; i < data.length; i++) {
+ var row = table.append('tr');
+ for (j = 0; j < headers.length; j++) {
+ var td = row.append('td');
+ if (styleFunction) {
+ styleFunction.call(this, td, j);
+ }
+ dataFunction.call(this, data[i], j, td);
+ }
+ }
+}
+
+function bigSymbolsReport() {
+ var list = treemap.biggestSymbols(100);
+ var headers = ['Rank', 'Size (Bytes)', 'Type', 'Location'];
+ var styleFunction = function(selection, index) {
+ if (index === 3) {
+ selection.style('font-family', 'monospace');
+ }
+ };
+ var recordIndex = 1;
+ var dataFunction = function(record, index, cell) {
+ if (index === 0) {
+ cell.text(recordIndex++);
+ } else if (index === 1) {
+ cell.text(D3SymbolTreeMap._pretty(record.value));
+ } else if (index === 2) {
+ cell.text(record.t);
+ } else {
+ if (treemap.pathFor(record).indexOf('/out') == 0) {
+ cell.append('span').text(treemap.pathFor(record));
+ cell.append('br');
+ cell.append('span').text('Symbol: ');
+ cell.append('span').text(escape(record.n));
+ } else {
+ var href = 'https://code.google.com/p/chromium/codesearch#chromium/src'
+ + treemap.pathFor(record)
+ + '&q='
+ + escape(record.n);
+ cell.append('a')
+ .attr('href', href)
+ .attr('target', '_blank')
+ .text(escape(treemap.pathFor(record)));
+ cell.append('br');
+ cell.append('span').text('Symbol: ');
+ cell.append('span').text(escape(record.n));
+ }
+ }
+ };
+ showReport('100 Largest Symbols', list, headers, dataFunction, styleFunction);
+}
+
+function bigPathsReport() {
+ var list = treemap.biggestPaths(100);
+ var headers = ['Rank', 'Size (Bytes)', 'Location'];
+ var styleFunction = function(selection, index) {
+ if (index === 2) {
+ selection.style('font-family', 'monospace');
+ }
+ };
+ var recordIndex = 1;
+ var dataFunction = function(record, index, cell) {
+ if (index === 0) {
+ cell.text(recordIndex++);
+ } else if (index === 1) {
+ cell.text(D3SymbolTreeMap._pretty(record.value));
+ } else if (index === 2) {
+ if (treemap.pathFor(record).indexOf('/out') == 0) {
+ cell.text(treemap.pathFor(record));
+ } else {
+ var href = 'https://code.google.com/p/chromium/codesearch#chromium/src' + treemap.pathFor(record);
+ cell.append('a')
+ .attr('href', href)
+ .attr('target', '_blank')
+ .text(escape(treemap.pathFor(record)));
+ }
+
+ }
+ };
+ showReport('100 Largest Paths', list, headers, dataFunction, styleFunction);
+}
+
+function symbolFilterTextChanged() {
+ if (filterChanging) return true;
+ filterChanging = true;
+ var enabled = document.getElementById('symbol_types_filter').value;
+ for (var x=0; x<=25; x++) {
+ var checkBox = document.getElementById('check_' + x);
+ checkBox.checked = (enabled.indexOf(checkBox.value) != -1);
+ }
+ filterChanging = false;
+}
+
+function updateFilterText() {
+ if (filterChanging) return true;
+ filterChanging = true;
+ var text = '';
+ for (var x=0; x<=25; x++) {
+ var checkBox = document.getElementById('check_' + x);
+ if (checkBox.checked) {
+ text += checkBox.value;
+ }
+ }
+ document.getElementById('symbol_types_filter').value=text;
+ filterChanging = false;
+}
+
+function initFilterOptions() {
+ updateFilterText();
+ for (var x=0; x<=25; x++) {
+ var checkBox = document.getElementById('check_' + x);
+ checkBox.onchange=updateFilterText;
+ var swatch = document.getElementById('swatch_' + x);
+ swatch.style.backgroundColor = D3SymbolTreeMap.getColorForType(checkBox.value).toString();
+ }
+ var gteCheckbox = document.getElementById('check_gte');
+ gteCheckbox.onchange = function() {
+ document.getElementById('symbol_filter_gte').disabled = !gteCheckbox.checked;
+ }
+ var regexCheckbox = document.getElementById('check_regex');
+ regexCheckbox.onchange = function() {
+ document.getElementById('symbol_filter_regex').disabled = !regexCheckbox.checked;
+ }
+ var excludeRegexCheckbox = document.getElementById('check_exclude_regex');
+ excludeRegexCheckbox.onchange = function() {
+ document.getElementById('symbol_filter_exclude_regex').disabled = !excludeRegexCheckbox.checked;
+ }
+ var idealSizes = getIdealSizes();
+ document.getElementById('width').value = idealSizes.width;
+ document.getElementById('height').value = idealSizes.height;
+ saveFilterSettings();
+}
+
+function filterSetAll(enabled) {
+ for (var x=0; x<=25; x++) {
+ var checkBox = document.getElementById('check_' + x);
+ checkBox.checked = enabled;
+ }
+ updateFilterText();
+}
+
+function showOptions() {
+ loadFilterSettings();
+ var container = document.getElementById('options_container');
+ var w = container.offsetWidth;
+ var h = container.offsetHeight;
+ container.style.margin = '-' + (h/2) + 'px 0 0 -' + (w/2) + 'px';
+ container.style.visibility = 'visible';
+}
+
+function hideOptions() {
+ var container = document.getElementById('options_container');
+ container.style.visibility = 'hidden';
+}
+
+function applySettings() {
+ hideOptions();
+ var oldWidth = savedSettings.width;
+ var oldHeight = savedSettings.height;
+ var oldSymbols = savedSettings.symbolTypes;
+ var oldRegex = savedSettings.regex;
+ var oldExcludeRegex = savedSettings.excludeRegex;
+ var oldGte = savedSettings.gte;
+ var oldMaxLevels = savedSettings.maxLevels;
+ saveFilterSettings();
+ var resizeNeeded = oldWidth !== savedSettings.width || oldHeight !== savedSettings.height;
+ var regexChanged = oldRegex !== savedSettings.regex;
+ var excludeRegexChanged = oldExcludeRegex !== savedSettings.excludeRegex;
+ var symbolsChanged = oldSymbols !== savedSettings.symbolTypes;
+ var gteChanged = oldGte !== savedSettings.gte;
+ var filterChanged = regexChanged || excludeRegexChanged || symbolsChanged || gteChanged;
+ var maxLevelsChanged = oldMaxLevels !== savedSettings.maxLevels;
+
+ if (filterChanged) {
+ // Type filters
+ typeFilter = function(datum) {
+ if (datum.depth === 0) return true; // root node
+ if (datum.t === undefined) return true;
+ return savedSettings.symbolTypes !== undefined &&
+ savedSettings.symbolTypes.indexOf(datum.t) !== -1;
+ }
+
+ // Regex filter
+ var regexFilter = undefined;
+ if (savedSettings.regex !== undefined && savedSettings.regex.length > 0) {
+ console.log('filter: regex is "' + savedSettings.regex + '"');
+ var regex = new RegExp(savedSettings.regex);
+ regexFilter = function(datum) {
+ if (datum.depth === 0) return true; // root node
+ var fullName = this.pathFor(datum);
+ if (datum.children === undefined) { // it is a leaf node (symbol)
+ fullName += ':' + datum.n;
+ }
+ return regex.test(fullName);
+ }
+ }
+
+ // Exclude regex filter
+ var excludeRegexFilter = undefined;
+ if (savedSettings.excludeRegex !== undefined && savedSettings.excludeRegex.length > 0) {
+ console.log('filter: exclude-regex is "' + savedSettings.excludeRegex + '"');
+ var excludeRegex = new RegExp(savedSettings.excludeRegex);
+ excludeRegexFilter = function(datum) {
+ if (datum.depth === 0) return true; // root node
+ var fullName = this.pathFor(datum);
+ if (datum.children === undefined) { // it is a leaf node (symbol)
+ fullName += ':' + datum.n;
+ }
+ return !excludeRegex.test(fullName);
+ }
+ }
+
+ // Size filter
+ var sizeFilter = undefined;
+ if (savedSettings.gte !== undefined) {
+ console.log('filter: minimum size is ' + savedSettings.gte + ' bytes');
+ sizeFilter = function(datum) {
+ if (datum.children !== undefined) return true; // non-leaf
+ if (datum.value === undefined) console.log('whoops');
+ return datum.value >= savedSettings.gte;
+ }
+ }
+
+ // Make a filter to apply to the tree
+ var filter = function(datum) {
+ if (typeFilter && !typeFilter.call(this, datum)) return false;
+ if (regexFilter && !regexFilter.call(this, datum)) return false;
+ if (excludeRegexFilter && !excludeRegexFilter.call(this, datum)) return false;
+ if (sizeFilter && !sizeFilter.call(this, datum)) return false;
+ return true;
+ };
+ treemap.filter(filter);
+ }
+
+ // Adjust levels if needed.
+ if (maxLevelsChanged) {
+ treemap.setMaxLevels(savedSettings.maxLevels);
+ }
+
+ // Resize map if necessary.
+ if (resizeNeeded) {
+ console.log('desired treemap dimensions have changed, requesting resize');
+ treemap.resize(savedSettings.width, savedSettings.height);
+ }
+}
+
+function cancelSettings() {
+ hideOptions();
+ loadFilterSettings();
+}
+
+function saveFilterSettings() {
+ savedSettings.symbolTypes = document.getElementById('symbol_types_filter').value;
+ if (document.getElementById('check_regex').checked) {
+ savedSettings.regex = document.getElementById('symbol_filter_regex').value;
} else {
- link = '<a href="https://code.google.com/p/chromium/codesearch#chromium/src/'
- + record.location + '">' + escape(record.location) + '</a>';
- }
- list.innerHTML += '<tr>'
- + '<td>' + (i+1) + '</td>'
- + '<td>' + escape(record.size) + '</td>'
- + '<td style=\'white-space: nowrap;\'>' + escape(record.type) + '</td>'
- + '<td>' + link + ':<br>'
- + escape(record.symbol) + '</td>'
- + '</tr>';
- }
-}
-
-function show_report_largest_sources() {
- console.log('displaying largest-sources report')
- var div = document.getElementById('report');
- div.innerHTML = '<div><table id=\'list\' border=1><tr>' +
- '<th>Rank</th><th>Size</th><th>Symbol Count</th><th>Source</th>' +
- '</tr></table>';
- var list = document.getElementById('list');
- for (var i = 0; i < largestSources.length; i++) {
- var record = largestSources[i];
- var link;
- if (record.location.indexOf('out') == 0) {
- link = record.location;
+ savedSettings.regex = undefined;
+ }
+ if (document.getElementById('check_exclude_regex').checked) {
+ savedSettings.excludeRegex = document.getElementById('symbol_filter_exclude_regex').value;
} else {
- link = '<a href="https://code.google.com/p/chromium/codesearch#chromium/src/'
- + record.location + '">' + escape(record.location) + '</a>';
- }
-
- list.innerHTML += '<tr>'
- + '<td>' + (i+1) + '</td>'
- + '<td>' + escape(record.size) + '</td>'
- + '<td>' + escape(record.symbol_count) + '</td>'
- + '<td>' + link + '</td>'
- + '</tr>';
- }
-}
-
-function show_report_largest_vtables() {
- console.log('displaying largest-vtables report')
- var div = document.getElementById('report');
- div.innerHTML = '<div><table id=\'list\' border=1><tr>' +
- '<th>Rank</th><th>Size</th><th>Symbol</th><th>Source</th>' +
- '</tr></table>';
- var list = document.getElementById('list');
- for (var i = 0; i < largestVTables.length; i++) {
- var record = largestVTables[i];
- var link;
- if (record.location.indexOf('out') == 0) {
- link = record.location;
+ savedSettings.excludeRegex = undefined;
+ }
+ if (document.getElementById('check_gte').checked) {
+ savedSettings.gte = parseInt(document.getElementById('symbol_filter_gte').value);
} else {
- link = '<a href="https://code.google.com/p/chromium/codesearch#chromium/src/'
- + record.location + '">' + escape(record.location) + '</a>';
+ savedSettings.gte = undefined;
}
+ savedSettings.width = parseInt(document.getElementById('width').value);
+ savedSettings.height = parseInt(document.getElementById('height').value);
+ savedSettings.maxLevels = parseInt(document.getElementById('max_levels').value);
+}
- list.innerHTML += '<tr>'
- + '<td>' + (i+1) + '</td>'
- + '<td>' + escape(record.size) + '</td>'
- + '<td>' + escape(record.symbol) + '</td>'
- + '<td>' + link + '</td>'
- + '</tr>';
- }
+function loadFilterSettings() {
+ document.getElementById('symbol_types_filter').value = savedSettings.symbolTypes;
+ symbolFilterTextChanged();
+ if (savedSettings.regex !== undefined) {
+ document.getElementById('check_regex').checked = true;
+ document.getElementById('symbol_filter_regex').value = savedSettings.regex;
+ } else {
+ document.getElementById('check_regex').checked = false;
+ }
+ if (savedSettings.excludeRegex !== undefined) {
+ document.getElementById('check_exclude_regex').checked = true;
+ document.getElementById('symbol_filter_exclude_regex').value = savedSettings.excludeRegex;
+ } else {
+ document.getElementById('check_exclude_regex').checked = false;
+ }
+ if (savedSettings.gte !== undefined) {
+ document.getElementById('check_gte').checked = true;
+ document.getElementById('symbol_filter_gte').value = savedSettings.gte;
+ } else {
+ document.getElementById('check_gte').checked = false;
+ }
+ document.getElementById('width').value = savedSettings.width;
+ document.getElementById('height').value = savedSettings.height;
+ document.getElementById('max_levels').value = savedSettings.maxLevels;
+}
+
+function escape(str) {
+ return str.replace(/&/g, '&amp;')
+ .replace(/"/g, '&quot;')
+ .replace(/</g, '&lt;')
+ .replace(/>/g, '&gt;');
}
</script>
+</head>
+<body onload='init()'>
+<div style='position: absolute; top: 5px; left: 5px;'>
+ <input type='button' onclick='showOptions()' value='Options &amp; Legend...'>
+ <span style='-webkit-user-select: none; cursor: help;' title='Click to view the symbol legend or to configure filters and options for the treemap'>[?]</span>
+</div>
+<div style='position: absolute; right: 5px; top: 5px; white-space: nowrap;'>
+ Reports:
+ <input type='button' onclick='bigSymbolsReport()' value='Large Symbols' title='Click to view a report of the largest 100 symbols that are with the bounds of the treemap that is currently displayed.'>
+ <input type='button' onclick='bigPathsReport()' value='Large Files' title='Click to view a report of the largest 100 source files that are with the bounds of the treemap that is currently displayed.'>
+</div>
+<div style='text-align: center; margin-bottom: 5px;'>
+ <span style='font-size: x-large; font-weight: bold; font-variant: small-caps'>Binary Size Analysis<span id='subtitle'></span></span>
+ <br><span style='font-size: small; font-style: italic;'>Double-click a box to zoom in, double-click outermost title to zoom out.</span>
+</div>
+<table id='options_container' style='visibility: hidden; border: 3px ridge grey; padding: 0px; top: 50%; left: 50%; position: fixed; z-index: 2147483646; overflow: auto; background-color: rgba(255,255,255,0.9); border-radius: 10px; box-shadow: 10px 10px 5px rgba(80,80,80,0.7);'><tr><td style='vertical-align: top'>
+ <table cellspacing=0 cellborder=0 style='width:100%'>
+ <tr><th colspan=3 style='padding-bottom: .25em; text-decoration: underline;'>Symbol Types To Show</th></tr>
+ <tr>
+ <td style='width: 33%; white-space: nowrap; vertical-align: top;'>
+ <span class='swatch' id='swatch_0'>&nbsp;&nbsp;&nbsp;</span><input checked type='checkbox' id='check_0' value='A'>Global absolute (A)
+ <br><span class='swatch' id='swatch_1'>&nbsp;&nbsp;&nbsp;</span><input checked type='checkbox' id='check_1' value='B'>Global uninitialized data (B)
+ <br><span class='swatch' id='swatch_2'>&nbsp;&nbsp;&nbsp;</span><input checked type='checkbox' id='check_2' value='b'>Local uninitialized data (b)
+ <br><span class='swatch' id='swatch_3'>&nbsp;&nbsp;&nbsp;</span><input checked type='checkbox' id='check_3' value='C'>Global uninitialized common (C)
+ <br><span class='swatch' id='swatch_4'>&nbsp;&nbsp;&nbsp;</span><input checked type='checkbox' id='check_4' value='D'>Global initialized data (D)
+ <br><span class='swatch' id='swatch_5'>&nbsp;&nbsp;&nbsp;</span><input checked type='checkbox' id='check_5' value='d'>Local initialized data (d)
+ <br><span class='swatch' id='swatch_6'>&nbsp;&nbsp;&nbsp;</span><input checked type='checkbox' id='check_6' value='G'>Global small initialized data (G)
+ <br><span class='swatch' id='swatch_7'>&nbsp;&nbsp;&nbsp;</span><input checked type='checkbox' id='check_7' value='g'>Local small initialized data (g)
+ <br><span class='swatch' id='swatch_8'>&nbsp;&nbsp;&nbsp;</span><input checked type='checkbox' id='check_8' value='i'>Indirect function (i)
+ </td>
+ <td style='width: 33%; white-space: nowrap; vertical-align: top;'>
+ <span class='swatch' id='swatch_9'>&nbsp;&nbsp;&nbsp;</span><input checked type='checkbox' id='check_9' value='N'>Debugging (N)
+ <br><span class='swatch' id='swatch_10'>&nbsp;&nbsp;&nbsp;</span><input checked type='checkbox' id='check_10' value='p'>Stack unwind (p)
+ <br><span class='swatch' id='swatch_11'>&nbsp;&nbsp;&nbsp;</span><input checked type='checkbox' id='check_11' value='R'>Global read-only data (R)
+ <br><span class='swatch' id='swatch_12'>&nbsp;&nbsp;&nbsp;</span><input checked type='checkbox' id='check_12' value='r'>Local read-only data (r)
+ <br><span class='swatch' id='swatch_13'>&nbsp;&nbsp;&nbsp;</span><input checked type='checkbox' id='check_13' value='S'>Global small uninitialized data (S)
+ <br><span class='swatch' id='swatch_14'>&nbsp;&nbsp;&nbsp;</span><input checked type='checkbox' id='check_14' value='s'>Local small uninitialized data (s)
+ <br><span class='swatch' id='swatch_15'>&nbsp;&nbsp;&nbsp;</span><input checked type='checkbox' id='check_15' value='T'>Global code (T)
+ <br><span class='swatch' id='swatch_16'>&nbsp;&nbsp;&nbsp;</span><input checked type='checkbox' id='check_16' value='t'>Local code (t)
+ <br><span class='swatch' id='swatch_17'>&nbsp;&nbsp;&nbsp;</span><input checked type='checkbox' id='check_17' value='U'>Undefined (U)
+ </td>
+ <td style='width: 33%; white-space: nowrap; vertical-align: top;'>
+ <span class='swatch' id='swatch_18'>&nbsp;&nbsp;&nbsp;</span><input checked type='checkbox' id='check_18' value='u'>Unique (u)
+ <br><span class='swatch' id='swatch_19'>&nbsp;&nbsp;&nbsp;</span><input checked type='checkbox' id='check_19' value='V'>Global weak object (V)
+ <br><span class='swatch' id='swatch_20'>&nbsp;&nbsp;&nbsp;</span><input checked type='checkbox' id='check_20' value='v'>Local weak object (v)
+ <br><span class='swatch' id='swatch_21'>&nbsp;&nbsp;&nbsp;</span><input checked type='checkbox' id='check_21' value='W'>Global weak symbol (W)
+ <br><span class='swatch' id='swatch_22'>&nbsp;&nbsp;&nbsp;</span><input checked type='checkbox' id='check_22' value='w'>Local weak symbol (w)
+ <br><span class='swatch' id='swatch_23'>&nbsp;&nbsp;&nbsp;</span><input checked type='checkbox' id='check_23' value='@'>Vtable entry (@)
+ <br><span class='swatch' id='swatch_24'>&nbsp;&nbsp;&nbsp;</span><input checked type='checkbox' id='check_24' value='-'>STABS debugging (-)
+ <br><span class='swatch' id='swatch_25'>&nbsp;&nbsp;&nbsp;</span><input checked type='checkbox' id='check_25' value='?'>Unrecognized (?)
+ </td>
+ </tr>
+ <tr><td colspan=3 style='text-align: center; white-space: nowrap; padding-top: 1em;'>
+ Select <input type='button' onclick='filterSetAll(true)' value='All'>,
+ <input type='button' onclick='filterSetAll(false)' value='None'>,
+ or type a string: <input id='symbol_types_filter' size=30 value='' onkeyup='symbolFilterTextChanged()' onblur='updateFilterText()'>
+ <span style='-webkit-user-select: none; cursor: help;' title='Enter codes from the list above for the symbols you want to see. The checkboxes will update automatically to match the string that you enter.'>[?]</span>
+ </td></tr>
+ </table>
+</td></tr><tr><td style='vertical-align: top; padding-top: 10px; border-top: 1px solid grey;'>
+ <table cellspacing=0 cellborder=0 style='width: 100%'>
+ <tr><th colspan=2 style='padding-bottom: .25em; text-decoration: underline;'>Advanced Options</th></tr>
+ <tr>
+ <td style='white-space: nowrap; vertical-align: top;'>
+ <input type='checkbox' id='check_regex'>
+ Only include symbols matching this regex:
+ </td>
+ <td style='text-align: right; vertical-align: top;'>
+ <input disabled id='symbol_filter_regex' size=30 value='' style='text-align: right;'>
+ <span style='-webkit-user-select: none; cursor: help;' title='Enter a javascript regex. Only symbols that match this regex will be shown. This filter applies before any exclusion regex specified below. The format of each symbol is [path]:[symbol_name]'>[?]</span>
+ </td>
+ </tr>
+ <tr>
+ <td style='white-space: nowrap; vertical-align: top;'>
+ <input type='checkbox' id='check_exclude_regex'>
+ Exclude all symbols matching this regex:
+ </td>
+ <td style='text-align: right; vertical-align: top;'>
+ <input disabled id='symbol_filter_exclude_regex' size=30 value='' style='text-align: right;'>
+ <span style='-webkit-user-select: none; cursor: help;' title='Enter a javascript regex. Symbols that match this tegex will not be shown. This filter applies after any inclusion filter specified above. The format of each symbol is [path]:[symbol_name]'>[?]</span>
+ </td>
+ </tr>
+ <tr>
+ <td style='white-space: nowrap; vertical-align: top;'>
+ <input type='checkbox' id='check_gte'>
+ Only include symbols that are at least <span style='font-style: italic;'>n</span> bytes:
+ </td>
+ <td style='text-align: right; vertical-align: top;'>
+ <input disabled id='symbol_filter_gte' size=8 value='' style='text-align: right;'>
+ <span style='-webkit-user-select: none; cursor: help;' title='Symbols whose size is less than this value will be hidden.'>[?]</span>
+ </td>
+ </tr>
+ <tr>
+ <td style='white-space: nowrap vertical-align: top;;'>
+ Show at most <span style='font-style: italic;'>n</span> levels of detail at a time:
+ </td>
+ <td style='text-align: right; vertical-align: top;'>
+ <input id='max_levels' size=4 value='2' style='text-align: right;'><span style='-webkit-user-select: none; cursor: help;' title='Increasing this value shows more detail without the need to zoom, but uses more computing power.'>[?]</span>
+ </td>
+ </tr>
+ <tr>
+ <td style='white-space: nowrap vertical-align: top;;'>
+ Set the size of the treemap to <span style='font-style: italic;'>W x H</span> pixels:
+ </td>
+ <td style='text-align: right; vertical-align: top;'>
+ <input id='width' size=4 value='' style='text-align: right;'>
+ &nbsp;x&nbsp;<input id='height' size=4 value='' style='text-align: right;'>
+ </td>
+ </tr>
+ </table>
+</td></tr>
+<tr><td style='padding-top: 10px; text-align: right; border-top: 1px solid grey'>
+ <input type='button' value='Apply' onclick='applySettings()'>
+ <input type='button' value='Cancel' onclick='cancelSettings()'>
+</td></tr></table>
</body>
-</html> \ No newline at end of file
+</html>
diff --git a/tools/binary_size/template/test-data-generator.html b/tools/binary_size/template/test-data-generator.html
new file mode 100644
index 0000000..9c6790a
--- /dev/null
+++ b/tools/binary_size/template/test-data-generator.html
@@ -0,0 +1,157 @@
+<!DOCTYPE html>
+<!--
+ Copyright 2014 The Chromium Authors. All rights reserved.
+ Use of this source code is governed by a BSD-style license that can be
+ found in the LICENSE file.
+-->
+<html>
+<head>
+<script>
+function rnd(max) {
+ return Math.round(Math.random()*max);
+}
+
+function gen() {
+ var dirs1=['usr1', 'etc1', 'var1'];
+ var dirs2=['aaa2', 'bbb2', 'ccc2', 'ddd2', 'eee2', 'fff2', 'ggg2', 'hhh2',
+ 'frobozz2', 'kazaam2', 'shazam2'];
+ var dirs3=['iii3', 'jjj3', 'kkk3', 'lll3', 'mmm3', 'nnn3', 'ooo3', 'ppp3',
+ 'wonderllama3', 'excelsior3', 'src3'];
+ var filenames=['awesome.cc', 'rad.h', 'tubular.cxx', 'cool.cc', 'groovy.h',
+ 'excellent.c', 'gnarly.h', 'green.C', 'articulate.cc'];
+ //All possible types (we only see a subset in practice): 'ABbCDdGgiNpRrSsTtUuVvWw-?';
+ var nm_symbol_types = 'trd';
+ var minSize = 4;
+ var maxSize = 10000;
+ var numGen = 300000;
+ var text = 'var nm_data=[\n';
+ var vtablePercent = 5;
+ for (var x=0; x<numGen; x++) {
+ var path = '/' +
+ dirs1[rnd(dirs1.length - 1)] + '/' +
+ dirs2[rnd(dirs2.length - 1)] + '/' +
+ dirs3[rnd(dirs3.length - 1)] + '/' +
+ filenames[rnd(filenames.length - 1)];
+ var isVtable = Math.floor((Math.random()*100)+1) <= vtablePercent;
+ var size = rnd(maxSize);
+ var symbol_name;
+ var type;
+ if (!isVtable) {
+ symbol_name = 'sym' + x.toString(16);
+ type = nm_symbol_types.charAt(rnd(nm_symbol_types.length - 1));
+ } else {
+ symbol_name = 'vtable for ' + x.toString(16);
+ type = '@'
+ }
+ text = text + "{'n': '" + symbol_name +
+ "', 't': '" + type +
+ "', 's': " + size +
+ ", 'p': '" + path + "'},\n";
+ }
+ text += '];';
+
+ eval(text);
+ var treeified = to_d3_tree(nm_data);
+ generateDownloadLink('tree_data=' + JSON.stringify(treeified));
+}
+
+function generateDownloadLink(content) {
+ var blob = new Blob([content], {type: 'text/plain'});
+ var link = document.createElement('a');
+ link.download = 'generated-content.txt';
+ link.href = window.URL.createObjectURL(blob);
+ link.textContent = 'Download ready, click here.';
+ link.dataset.downloadurl = ['text/plain', link.download, link.href].join(':');
+ link.onclick = function(e) {
+ if ('disabled' in this.dataset) { return false; }
+ link.dataset.disabled = true;
+ setTimeout(function() { window.URL.revokeObjectURL(link.href); }, 1500);
+ };
+ document.getElementById('linkcontainer').innerHTML = '';
+ document.getElementById('linkcontainer').appendChild(link);
+}
+
+/**
+ * This function takes in an array of nm records and converts them into a
+ * hierarchical data structure suitable for use in a d3-base treemap layout.
+ * Leaves are individual symbols. The parents of the leaves are logical
+ * groupings by common symbol-type (for BSS, read-only data, code, etc).
+ * Above this, each node represents part of a filesystem path relative
+ * to the parent node. The root node has the name '/', and represents
+ * a root (though not necessarily THE root) of a file system traversal.
+ * The root node also has a special property, 'maxDepth', to which is bound
+ * the deepest level of nesting that was found during conversion: for the
+ * record at path /a/b/c/d.foo, the maxDepth will be 6; the file 'd.foo'
+ * is at depth 4, the type-bucket is depth 5 and the symbols are depth 6.
+ */
+function to_d3_tree(records) {
+ var result = {'n': '/', 'children': [], 'k': 'p'};
+ var maxDepth = 0;
+ //{'n': 'symbol1', 't': 'b', 's': 1000, 'p': '/usr/local/foo/foo.cc'},
+ for (index in records) {
+ var record = records[index];
+ var parts = record.p.split("/");
+ var node = result;
+ var depth = 0;
+ // Walk the tree and find the file that is named by the "location"
+ // field of the record. We create any intermediate nodes required.
+ // This is directly analogous to "mkdir -p".
+ while(parts.length > 0) {
+ var part = parts.shift();
+ if (part.length == 0) continue;
+ depth++;
+ node = _mk_child(node, part, record.s);
+ node.k = 'p'; // p for path
+ }
+ node.lastPathElement = true;
+
+ // 'node' is now the file node. Find the symbol-type bucket.
+ node = _mk_child(node, record.t, record.s);
+ node.t = record.t;
+ node.k = 'b'; // b for bucket
+ depth++;
+ // 'node' is now the symbol-type bucket. Make the child entry.
+ node = _mk_child(node, record.n, record.s);
+ delete node.children;
+ node.value = record.s;
+ node.t = record.t;
+ node.k = 's'; // s for symbol
+ depth++;
+
+ maxDepth = Math.max(maxDepth, depth);
+ }
+ result.maxDepth = maxDepth;
+ return result;
+}
+
+/**
+ * Given a node and a name, return the child within node.children whose
+ * name matches the specified name. If necessary, a new child node is
+ * created and appended to node.children.
+ * If this method creates a new node, the 'name' attribute is set to the
+ * specified name and the 'children' attribute is an empty array, and
+ * total_size is the specified size. Otherwise, the existing node is
+ * returned and its total_size value is incremented by the specified size.
+ */
+function _mk_child(node, name, size) {
+ var child = undefined;
+ for (child_index in node.children) {
+ if (node.children[child_index].n == name) {
+ child = node.children[child_index];
+ }
+ }
+ if (child === undefined) {
+ child = {'n': name, 'children': []};
+ node.children.push(child);
+ }
+ return child;
+}
+</script>
+</head>
+<body style='white-space: pre; font-family: monospace;'>
+This script generates sample data for use in D3SymbolTreeMap, and can be used
+for testing.
+<input type=button onclick='gen();' value='Generate data'></input>
+<div id='linkcontainer'></div>
+</body>
+</html>