#!/usr/bin/env dart // Copyright (c) 2022, the Dart project authors. Please see the AUTHORS file // for details. All rights reserved. Use of this source code is governed by a // BSD-style license that can be found in the LICENSE file. import "dart:io"; class Symbol { String? name; String? type; int? shallowSize; int? retainedSize; List children = []; Symbol compressTrivialPaths() { for (var i = 0; i < children.length; i++) { children[i] = children[i].compressTrivialPaths(); } if ((type == "path") && (children.length == 1)) { return children[0]; } return this; } int computeRetainedSize() { var s = shallowSize!; for (var child in children) { s += child.computeRetainedSize(); } retainedSize = s; return s; } writeJson(StringBuffer out) { out.write("{"); out.write('"name": "$name",'); out.write('"shallowSize": $shallowSize,'); out.write('"retainedSize": $retainedSize,'); out.write('"type": "$type",'); out.write('"children": ['); bool first = true; for (var child in children) { if (first) { first = false; } else { out.write(","); } child.writeJson(out); } out.write("]}"); } } const filteredPathComponents = [ "", ".", "..", "out", "xcodebuild", "src", "source", "third_party", "DebugX64", "ReleaseX64", "ProductX64", "DebugARM64", "ReleaseARM64", "ProductARM64", "DebugXARM64", "ReleaseXARM64", "ProductXARM64", ]; var cwd = Directory.current.path; String prettyPath(String path) { if (path.startsWith(cwd)) { path = path.substring(cwd.length); } return path .split("/") .where((component) => !filteredPathComponents.contains(component)) .join("/"); } main(List args) { if (args.isEmpty) { print("Usage: binary_size "); exit(1); } for (var arg in args) { analyze(arg); } } analyze(String binaryPath) { var nmExec = "nm"; var nmArgs = ["-a", "--demangle", "--line-numbers", "--print-size", binaryPath]; var nmResult = Process.runSync(nmExec, nmArgs); if (nmResult.exitCode != 0) { print("+ ${nmExec} ${nmArgs.join(' ')}"); print(nmResult.exitCode); print(nmResult.stdout); print(nmResult.stderr); exit(1); } var root = new Symbol(); root.name = binaryPath; root.type = "path"; root.shallowSize = 0; var paths = new Map(); paths[""] = root; addToPath(Symbol s, String path) { Symbol? p = paths[path]; if (p == null) { p = new Symbol(); p.name = path; p.type = "path"; p.shallowSize = 0; paths[path] = p; var i = path.lastIndexOf("/"); if (i != -1) { p.name = path.substring(i + 1); addToPath(p, path.substring(0, i)); } else { root.children.add(p); } } p.children.add(s); } var lines = nmResult.stdout.split("\n"); var regex = new RegExp("([0-9a-f]+) ([0-9a-f]+) ([a-zA-z]) (.*)"); for (var line in lines) { print(line); var match = regex.firstMatch(line); if (match == null) { continue; } var address = int.parse(match[1]!, radix: 16); var size = int.parse(match[2]!, radix: 16); var type = match[3]; if (type == "b" || type == "B") { // Uninitialized data does not contribute to file size. continue; } var nameAndPath = match[4]!.split("\t"); var name = nameAndPath[0].trim(); var path = nameAndPath.length == 1 ? "" : nameAndPath[1].trim(); var colon = path.lastIndexOf(":"); if (colon > 0) { path = path.substring(0, colon); } path = prettyPath(path); for (var prefix in const ["vtable for ", "typeinfo for ", "typeinfo name for "]) { if (name.startsWith(prefix)) { name = name.substring(prefix.length); path = prefix; } } var s = new Symbol(); s.name = name; s.type = type; s.shallowSize = size; addToPath(s, path); } root.compressTrivialPaths(); root.computeRetainedSize(); var json = new StringBuffer(); root.writeJson(json); var html = viewer.replaceAll("__DATA__", json.toString()); new File("${binaryPath}.html").writeAsStringSync(html); // This written as a URL instead of path because some terminals will // automatically recognize it and make it a link. var url = Directory.current.uri.resolve("${binaryPath}.html"); print("Wrote $url"); } var viewer = """ """;