Index: runtime/third_party/binary_size/src/run_binary_size_analysis.py |
diff --git a/runtime/third_party/binary_size/src/run_binary_size_analysis.py b/runtime/third_party/binary_size/src/run_binary_size_analysis.py |
new file mode 100755 |
index 0000000000000000000000000000000000000000..d97858d45399bca61438c7930dadf164b2af706a |
--- /dev/null |
+++ b/runtime/third_party/binary_size/src/run_binary_size_analysis.py |
@@ -0,0 +1,666 @@ |
+#!/usr/bin/env python |
+# 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. |
+ |
+"""Generate a spatial analysis against an arbitrary library. |
+ |
+To use, build the 'binary_size_tool' target. Then run this tool, passing |
+in the location of the library to be analyzed along with any other options |
+you desire. |
+""" |
+ |
+import collections |
+import json |
+import logging |
+import multiprocessing |
+import optparse |
+import os |
+import re |
+import shutil |
+import struct |
+import subprocess |
+import sys |
+import tempfile |
+import time |
+ |
+import binary_size_utils |
+import elf_symbolizer |
+ |
+# Node dictionary keys. These are output in json read by the webapp so |
+# keep them short to save file size. |
+# Note: If these change, the webapp must also change. |
+NODE_TYPE_KEY = 'k' |
+NODE_NAME_KEY = 'n' |
+NODE_CHILDREN_KEY = 'children' |
+NODE_SYMBOL_TYPE_KEY = 't' |
+NODE_SYMBOL_SIZE_KEY = 'value' |
+NODE_MAX_DEPTH_KEY = 'maxDepth' |
+NODE_LAST_PATH_ELEMENT_KEY = 'lastPathElement' |
+ |
+# The display name of the bucket where we put symbols without path. |
+NAME_NO_PATH_BUCKET = '(No Path)' |
+ |
+# Try to keep data buckets smaller than this to avoid killing the |
+# graphing lib. |
+BIG_BUCKET_LIMIT = 3000 |
+ |
+ |
+def _MkChild(node, name): |
+ child = node[NODE_CHILDREN_KEY].get(name) |
+ if child is None: |
+ child = {NODE_NAME_KEY: name, |
+ NODE_CHILDREN_KEY: {}} |
+ node[NODE_CHILDREN_KEY][name] = child |
+ return child |
+ |
+ |
+ |
+def SplitNoPathBucket(node): |
+ """NAME_NO_PATH_BUCKET can be too large for the graphing lib to |
+ handle. Split it into sub-buckets in that case.""" |
+ root_children = node[NODE_CHILDREN_KEY] |
+ if NAME_NO_PATH_BUCKET in root_children: |
+ no_path_bucket = root_children[NAME_NO_PATH_BUCKET] |
+ old_children = no_path_bucket[NODE_CHILDREN_KEY] |
+ count = 0 |
+ for symbol_type, symbol_bucket in old_children.iteritems(): |
+ count += len(symbol_bucket[NODE_CHILDREN_KEY]) |
+ if count > BIG_BUCKET_LIMIT: |
+ new_children = {} |
+ no_path_bucket[NODE_CHILDREN_KEY] = new_children |
+ current_bucket = None |
+ index = 0 |
+ for symbol_type, symbol_bucket in old_children.iteritems(): |
+ for symbol_name, value in symbol_bucket[NODE_CHILDREN_KEY].iteritems(): |
+ if index % BIG_BUCKET_LIMIT == 0: |
+ group_no = (index / BIG_BUCKET_LIMIT) + 1 |
+ current_bucket = _MkChild(no_path_bucket, |
+ '%s subgroup %d' % (NAME_NO_PATH_BUCKET, |
+ group_no)) |
+ assert not NODE_TYPE_KEY in node or node[NODE_TYPE_KEY] == 'p' |
+ node[NODE_TYPE_KEY] = 'p' # p for path |
+ index += 1 |
+ symbol_size = value[NODE_SYMBOL_SIZE_KEY] |
+ AddSymbolIntoFileNode(current_bucket, symbol_type, |
+ symbol_name, symbol_size) |
+ |
+ |
+def MakeChildrenDictsIntoLists(node): |
+ largest_list_len = 0 |
+ if NODE_CHILDREN_KEY in node: |
+ largest_list_len = len(node[NODE_CHILDREN_KEY]) |
+ child_list = [] |
+ for child in node[NODE_CHILDREN_KEY].itervalues(): |
+ child_largest_list_len = MakeChildrenDictsIntoLists(child) |
+ if child_largest_list_len > largest_list_len: |
+ largest_list_len = child_largest_list_len |
+ child_list.append(child) |
+ node[NODE_CHILDREN_KEY] = child_list |
+ |
+ return largest_list_len |
+ |
+ |
+def AddSymbolIntoFileNode(node, symbol_type, symbol_name, symbol_size): |
+ """Puts symbol into the file path node |node|. |
+ Returns the number of added levels in tree. I.e. returns 2.""" |
+ |
+ # 'node' is the file node and first step is to find its symbol-type bucket. |
+ node[NODE_LAST_PATH_ELEMENT_KEY] = True |
+ node = _MkChild(node, symbol_type) |
+ assert not NODE_TYPE_KEY in node or node[NODE_TYPE_KEY] == 'b' |
+ node[NODE_SYMBOL_TYPE_KEY] = symbol_type |
+ node[NODE_TYPE_KEY] = 'b' # b for bucket |
+ |
+ # 'node' is now the symbol-type bucket. Make the child entry. |
+ node = _MkChild(node, symbol_name) |
+ if NODE_CHILDREN_KEY in node: |
+ if node[NODE_CHILDREN_KEY]: |
+ logging.warning('A container node used as symbol for %s.' % symbol_name) |
+ # This is going to be used as a leaf so no use for child list. |
+ del node[NODE_CHILDREN_KEY] |
+ node[NODE_SYMBOL_SIZE_KEY] = symbol_size |
+ node[NODE_SYMBOL_TYPE_KEY] = symbol_type |
+ node[NODE_TYPE_KEY] = 's' # s for symbol |
+ |
+ return 2 # Depth of the added subtree. |
+ |
+ |
+def MakeCompactTree(symbols, symbol_path_origin_dir): |
+ result = {NODE_NAME_KEY: '/', |
+ NODE_CHILDREN_KEY: {}, |
+ NODE_TYPE_KEY: 'p', |
+ NODE_MAX_DEPTH_KEY: 0} |
+ seen_symbol_with_path = False |
+ cwd = os.path.abspath(os.getcwd()) |
+ for symbol_name, symbol_type, symbol_size, file_path, _address 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 and file_path != "??": |
+ file_path = os.path.abspath(os.path.join(symbol_path_origin_dir, |
+ file_path)) |
+ # Let the output structure be relative to $CWD if inside $CWD, |
+ # otherwise relative to the disk root. This is to avoid |
+ # unnecessary click-through levels in the output. |
+ if file_path.startswith(cwd + os.sep): |
+ file_path = file_path[len(cwd):] |
+ if file_path.startswith('/'): |
+ file_path = file_path[1:] |
+ seen_symbol_with_path = True |
+ else: |
+ file_path = NAME_NO_PATH_BUCKET |
+ |
+ 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) |
+ assert not NODE_TYPE_KEY in node or node[NODE_TYPE_KEY] == 'p' |
+ node[NODE_TYPE_KEY] = 'p' # p for path |
+ |
+ depth += AddSymbolIntoFileNode(node, symbol_type, symbol_name, symbol_size) |
+ result[NODE_MAX_DEPTH_KEY] = max(result[NODE_MAX_DEPTH_KEY], depth) |
+ |
+ if not seen_symbol_with_path: |
+ logging.warning('Symbols lack paths. Data will not be structured.') |
+ |
+ # The (no path) bucket can be extremely large if we failed to get |
+ # path information. Split it into subgroups if needed. |
+ SplitNoPathBucket(result) |
+ |
+ largest_list_len = MakeChildrenDictsIntoLists(result) |
+ |
+ if largest_list_len > BIG_BUCKET_LIMIT: |
+ logging.warning('There are sections with %d nodes. ' |
+ 'Results might be unusable.' % largest_list_len) |
+ return result |
+ |
+ |
+def DumpCompactTree(symbols, symbol_path_origin_dir, outfile): |
+ tree_root = MakeCompactTree(symbols, symbol_path_origin_dir) |
+ with open(outfile, 'w') as out: |
+ out.write('var tree_data=') |
+ # Use separators without whitespace to get a smaller file. |
+ json.dump(tree_root, out, separators=(',', ':')) |
+ print('Writing %d bytes json' % os.path.getsize(outfile)) |
+ |
+ |
+def MakeSourceMap(symbols): |
+ sources = {} |
+ for _sym, _symbol_type, size, path, _address in symbols: |
+ key = None |
+ if path: |
+ key = os.path.normpath(path) |
+ else: |
+ key = '[no path]' |
+ if key not in sources: |
+ sources[key] = {'path': path, 'symbol_count': 0, 'size': 0} |
+ record = sources[key] |
+ record['size'] += size |
+ record['symbol_count'] += 1 |
+ return sources |
+ |
+ |
+# Regex for parsing "nm" output. A sample line looks like this: |
+# 0167b39c 00000018 t ACCESS_DESCRIPTION_free /path/file.c:95 |
+# |
+# The fields are: address, size, type, name, source location |
+# Regular expression explained ( see also: https://xkcd.com/208 ): |
+# ([0-9a-f]{8,}+) The address |
+# [\s]+ Whitespace separator |
+# ([0-9a-f]{8,}+) The size. From here on out it's all optional. |
+# [\s]+ Whitespace separator |
+# (\S?) The symbol type, which is any non-whitespace char |
+# [\s*] Whitespace separator |
+# ([^\t]*) Symbol name, any non-tab character (spaces ok!) |
+# [\t]? Tab separator |
+# (.*) The location (filename[:linennum|?][ (discriminator n)] |
+sNmPattern = re.compile( |
+ r'([0-9a-f]{8,})[\s]+([0-9a-f]{8,})[\s]*(\S?)[\s*]([^\t]*)[\t]?(.*)') |
+ |
+class Progress(): |
+ def __init__(self): |
+ self.count = 0 |
+ self.skip_count = 0 |
+ self.collisions = 0 |
+ self.time_last_output = time.time() |
+ self.count_last_output = 0 |
+ self.disambiguations = 0 |
+ self.was_ambiguous = 0 |
+ |
+ |
+def RunElfSymbolizer(outfile, library, addr2line_binary, nm_binary, jobs, |
+ disambiguate, src_path): |
+ nm_output = RunNm(library, nm_binary) |
+ nm_output_lines = nm_output.splitlines() |
+ nm_output_lines_len = len(nm_output_lines) |
+ address_symbol = {} |
+ progress = Progress() |
+ def map_address_symbol(symbol, addr): |
+ progress.count += 1 |
+ if addr in address_symbol: |
+ # 'Collision between %s and %s.' % (str(symbol.name), |
+ # str(address_symbol[addr].name)) |
+ progress.collisions += 1 |
+ else: |
+ if symbol.disambiguated: |
+ progress.disambiguations += 1 |
+ if symbol.was_ambiguous: |
+ progress.was_ambiguous += 1 |
+ |
+ address_symbol[addr] = symbol |
+ |
+ progress_output() |
+ |
+ def progress_output(): |
+ progress_chunk = 100 |
+ if progress.count % progress_chunk == 0: |
+ time_now = time.time() |
+ time_spent = time_now - progress.time_last_output |
+ if time_spent > 1.0: |
+ # Only output at most once per second. |
+ progress.time_last_output = time_now |
+ chunk_size = progress.count - progress.count_last_output |
+ progress.count_last_output = progress.count |
+ if time_spent > 0: |
+ speed = chunk_size / time_spent |
+ else: |
+ speed = 0 |
+ progress_percent = (100.0 * (progress.count + progress.skip_count) / |
+ nm_output_lines_len) |
+ disambiguation_percent = 0 |
+ if progress.disambiguations != 0: |
+ disambiguation_percent = (100.0 * progress.disambiguations / |
+ progress.was_ambiguous) |
+ |
+ sys.stdout.write('\r%.1f%%: Looked up %d symbols (%d collisions, ' |
+ '%d disambiguations where %.1f%% succeeded)' |
+ ' - %.1f lookups/s.' % |
+ (progress_percent, progress.count, progress.collisions, |
+ progress.disambiguations, disambiguation_percent, speed)) |
+ |
+ # In case disambiguation was disabled, we remove the source path (which upon |
+ # being set signals the symbolizer to enable disambiguation) |
+ if not disambiguate: |
+ src_path = None |
+ symbolizer = elf_symbolizer.ELFSymbolizer(library, addr2line_binary, |
+ map_address_symbol, |
+ max_concurrent_jobs=jobs, |
+ source_root_path=src_path) |
+ user_interrupted = False |
+ try: |
+ for line in nm_output_lines: |
+ match = sNmPattern.match(line) |
+ if match: |
+ location = match.group(5) |
+ if not location: |
+ addr = int(match.group(1), 16) |
+ size = int(match.group(2), 16) |
+ if addr in address_symbol: # Already looked up, shortcut |
+ # ELFSymbolizer. |
+ map_address_symbol(address_symbol[addr], addr) |
+ continue |
+ elif size == 0: |
+ # Save time by not looking up empty symbols (do they even exist?) |
+ print('Empty symbol: ' + line) |
+ else: |
+ symbolizer.SymbolizeAsync(addr, addr) |
+ continue |
+ |
+ progress.skip_count += 1 |
+ except KeyboardInterrupt: |
+ user_interrupted = True |
+ print('Interrupting - killing subprocesses. Please wait.') |
+ |
+ try: |
+ symbolizer.Join() |
+ except KeyboardInterrupt: |
+ # Don't want to abort here since we will be finished in a few seconds. |
+ user_interrupted = True |
+ print('Patience you must have my young padawan.') |
+ |
+ print '' |
+ |
+ if user_interrupted: |
+ print('Skipping the rest of the file mapping. ' |
+ 'Output will not be fully classified.') |
+ |
+ symbol_path_origin_dir = os.path.dirname(os.path.abspath(library)) |
+ |
+ with open(outfile, 'w') as out: |
+ for line in nm_output_lines: |
+ match = sNmPattern.match(line) |
+ if match: |
+ location = match.group(5) |
+ if not location: |
+ addr = int(match.group(1), 16) |
+ symbol = address_symbol.get(addr) |
+ if symbol is not None: |
+ path = '??' |
+ if symbol.source_path is not None: |
+ path = os.path.abspath(os.path.join(symbol_path_origin_dir, |
+ symbol.source_path)) |
+ line_number = 0 |
+ if symbol.source_line is not None: |
+ line_number = symbol.source_line |
+ out.write('%s\t%s:%d\n' % (line, path, line_number)) |
+ continue |
+ |
+ out.write('%s\n' % line) |
+ |
+ print('%d symbols in the results.' % len(address_symbol)) |
+ |
+ |
+def RunNm(binary, nm_binary): |
+ cmd = [nm_binary, '-C', '--print-size', '--size-sort', '--reverse-sort', |
+ binary] |
+ nm_process = subprocess.Popen(cmd, |
+ stdout=subprocess.PIPE, |
+ stderr=subprocess.PIPE) |
+ (process_output, err_output) = nm_process.communicate() |
+ |
+ if nm_process.returncode != 0: |
+ if err_output: |
+ raise Exception, err_output |
+ else: |
+ raise Exception, process_output |
+ |
+ return process_output |
+ |
+ |
+def GetNmSymbols(nm_infile, outfile, library, jobs, verbose, |
+ addr2line_binary, nm_binary, disambiguate, src_path): |
+ if nm_infile is None: |
+ if outfile is None: |
+ outfile = tempfile.NamedTemporaryFile(delete=False).name |
+ |
+ if verbose: |
+ print 'Running parallel addr2line, dumping symbols to ' + outfile |
+ RunElfSymbolizer(outfile, library, addr2line_binary, nm_binary, jobs, |
+ disambiguate, src_path) |
+ |
+ nm_infile = outfile |
+ |
+ elif verbose: |
+ print 'Using nm input from ' + nm_infile |
+ with file(nm_infile, 'r') as infile: |
+ return list(binary_size_utils.ParseNm(infile)) |
+ |
+ |
+PAK_RESOURCE_ID_TO_STRING = { "inited": False } |
+ |
+def LoadPakIdsFromResourceFile(filename): |
+ """Given a file name, it loads everything that looks like a resource id |
+ into PAK_RESOURCE_ID_TO_STRING.""" |
+ with open(filename) as resource_header: |
+ for line in resource_header: |
+ if line.startswith("#define "): |
+ line_data = line.split() |
+ if len(line_data) == 3: |
+ try: |
+ resource_number = int(line_data[2]) |
+ resource_name = line_data[1] |
+ PAK_RESOURCE_ID_TO_STRING[resource_number] = resource_name |
+ except ValueError: |
+ pass |
+ |
+def GetReadablePakResourceName(pak_file, resource_id): |
+ """Pak resources have a numeric identifier. It is not helpful when |
+ trying to locate where footprint is generated. This does its best to |
+ map the number to a usable string.""" |
+ if not PAK_RESOURCE_ID_TO_STRING['inited']: |
+ # Try to find resource header files generated by grit when |
+ # building the pak file. We'll look for files named *resources.h" |
+ # and lines of the type: |
+ # #define MY_RESOURCE_JS 1234 |
+ PAK_RESOURCE_ID_TO_STRING['inited'] = True |
+ gen_dir = os.path.join(os.path.dirname(pak_file), 'gen') |
+ if os.path.isdir(gen_dir): |
+ for dirname, _dirs, files in os.walk(gen_dir): |
+ for filename in files: |
+ if filename.endswith('resources.h'): |
+ LoadPakIdsFromResourceFile(os.path.join(dirname, filename)) |
+ return PAK_RESOURCE_ID_TO_STRING.get(resource_id, |
+ 'Pak Resource %d' % resource_id) |
+ |
+def AddPakData(symbols, pak_file): |
+ """Adds pseudo-symbols from a pak file.""" |
+ pak_file = os.path.abspath(pak_file) |
+ with open(pak_file, 'rb') as pak: |
+ data = pak.read() |
+ |
+ PAK_FILE_VERSION = 4 |
+ HEADER_LENGTH = 2 * 4 + 1 # Two uint32s. (file version, number of entries) |
+ # and one uint8 (encoding of text resources) |
+ INDEX_ENTRY_SIZE = 2 + 4 # Each entry is a uint16 and a uint32. |
+ version, num_entries, _encoding = struct.unpack('<IIB', data[:HEADER_LENGTH]) |
+ assert version == PAK_FILE_VERSION, ('Unsupported pak file ' |
+ 'version (%d) in %s. Only ' |
+ 'support version %d' % |
+ (version, pak_file, PAK_FILE_VERSION)) |
+ if num_entries > 0: |
+ # Read the index and data. |
+ data = data[HEADER_LENGTH:] |
+ for _ in range(num_entries): |
+ resource_id, offset = struct.unpack('<HI', data[:INDEX_ENTRY_SIZE]) |
+ data = data[INDEX_ENTRY_SIZE:] |
+ _next_id, next_offset = struct.unpack('<HI', data[:INDEX_ENTRY_SIZE]) |
+ resource_size = next_offset - offset |
+ |
+ symbol_name = GetReadablePakResourceName(pak_file, resource_id) |
+ symbol_path = pak_file |
+ symbol_type = 'd' # Data. Approximation. |
+ symbol_size = resource_size |
+ symbols.append((symbol_name, symbol_type, symbol_size, symbol_path)) |
+ |
+def _find_in_system_path(binary): |
+ """Locate the full path to binary in the system path or return None |
+ if not found.""" |
+ system_path = os.environ["PATH"].split(os.pathsep) |
+ for path in system_path: |
+ binary_path = os.path.join(path, binary) |
+ if os.path.isfile(binary_path): |
+ return binary_path |
+ return None |
+ |
+def CheckDebugFormatSupport(library, addr2line_binary): |
+ """Kills the program if debug data is in an unsupported format. |
+ |
+ There are two common versions of the DWARF debug formats and |
+ since we are right now transitioning from DWARF2 to newer formats, |
+ it's possible to have a mix of tools that are not compatible. Detect |
+ that and abort rather than produce meaningless output.""" |
+ tool_output = subprocess.check_output([addr2line_binary, '--version']) |
+ version_re = re.compile(r'^GNU [^ ]+ .* (\d+).(\d+).*?$', re.M) |
+ parsed_output = version_re.match(tool_output) |
+ major = int(parsed_output.group(1)) |
+ minor = int(parsed_output.group(2)) |
+ supports_dwarf4 = major > 2 or major == 2 and minor > 22 |
+ |
+ if supports_dwarf4: |
+ return |
+ |
+ print('Checking version of debug information in %s.' % library) |
+ debug_info = subprocess.check_output(['readelf', '--debug-dump=info', |
+ '--dwarf-depth=1', library]) |
+ dwarf_version_re = re.compile(r'^\s+Version:\s+(\d+)$', re.M) |
+ parsed_dwarf_format_output = dwarf_version_re.search(debug_info) |
+ version = int(parsed_dwarf_format_output.group(1)) |
+ if version > 2: |
+ print('The supplied tools only support DWARF2 debug data but the binary\n' + |
+ 'uses DWARF%d. Update the tools or compile the binary\n' % version + |
+ 'with -gdwarf-2.') |
+ sys.exit(1) |
+ |
+ |
+def main(): |
+ usage = """%prog [options] |
+ |
+ Runs a spatial analysis on a given library, looking up the source locations |
+ of its symbols and calculating how much space each directory, source file, |
+ and so on is taking. The result is a report that can be used to pinpoint |
+ sources of large portions of the binary, etceteras. |
+ |
+ Under normal circumstances, you only need to pass two arguments, thusly: |
+ |
+ %prog --library /path/to/library --destdir /path/to/output |
+ |
+ In this mode, the program will dump the symbols from the specified library |
+ and map those symbols back to source locations, producing a web-based |
+ report in the specified output directory. |
+ |
+ Other options are available via '--help'. |
+ """ |
+ parser = optparse.OptionParser(usage=usage) |
+ parser.add_option('--nm-in', metavar='PATH', |
+ help='if specified, use nm input from <path> instead of ' |
+ 'generating it. Note that source locations should be ' |
+ 'present in the file; i.e., no addr2line symbol lookups ' |
+ 'will be performed when this option is specified. ' |
+ 'Mutually exclusive with --library.') |
+ parser.add_option('--destdir', metavar='PATH', |
+ help='write output to the specified directory. An HTML ' |
+ 'report is generated here along with supporting files; ' |
+ 'any existing report will be overwritten.') |
+ parser.add_option('--library', metavar='PATH', |
+ help='if specified, process symbols in the library at ' |
+ 'the specified path. Mutually exclusive with --nm-in.') |
+ parser.add_option('--pak', metavar='PATH', |
+ help='if specified, includes the contents of the ' |
+ 'specified *.pak file in the output.') |
+ parser.add_option('--nm-binary', |
+ help='use the specified nm binary to analyze library. ' |
+ 'This is to be used when the nm in the path is not for ' |
+ 'the right architecture or of the right version.') |
+ parser.add_option('--addr2line-binary', |
+ help='use the specified addr2line binary to analyze ' |
+ 'library. This is to be used when the addr2line in ' |
+ 'the path is not for the right architecture or ' |
+ 'of the right version.') |
+ parser.add_option('--jobs', type='int', |
+ help='number of jobs to use for the parallel ' |
+ 'addr2line processing pool; defaults to 1. More ' |
+ 'jobs greatly improve throughput but eat RAM like ' |
+ 'popcorn, and take several gigabytes each. Start low ' |
+ 'and ramp this number up until your machine begins to ' |
+ 'struggle with RAM. ' |
+ 'This argument is only valid when using --library.') |
+ parser.add_option('-v', '--verbose', dest='verbose', action='store_true', |
+ help='be verbose, printing lots of status information.') |
+ parser.add_option('--nm-out', metavar='PATH', |
+ help='(deprecated) No-op. nm.out is stored in --destdir.') |
+ parser.add_option('--no-nm-out', action='store_true', |
+ help='do not keep the nm output file. This file is useful ' |
+ 'if you want to see the fully processed nm output after ' |
+ 'the symbols have been mapped to source locations, or if ' |
+ 'you plan to run explain_binary_size_delta.py. By default ' |
+ 'the file \'nm.out\' is placed alongside the generated ' |
+ 'report. The nm.out file is only created when using ' |
+ '--library.') |
+ parser.add_option('--disable-disambiguation', action='store_true', |
+ help='disables the disambiguation process altogether,' |
+ ' NOTE: this may, depending on your toolchain, produce' |
+ ' output with some symbols at the top layer if addr2line' |
+ ' could not get the entire source path.') |
+ parser.add_option('--source-path', default='./', |
+ help='the path to the source code of the output binary, ' |
+ 'default set to current directory. Used in the' |
+ ' disambiguation process.') |
+ opts, _args = parser.parse_args() |
+ |
+ if ((not opts.library) and (not opts.nm_in)) or (opts.library and opts.nm_in): |
+ parser.error('exactly one of --library or --nm-in is required') |
+ if opts.nm_out: |
+ print >> sys.stderr, ('WARNING: --nm-out is deprecated and has no effect.') |
+ if (opts.nm_in): |
+ if opts.jobs: |
+ print >> sys.stderr, ('WARNING: --jobs has no effect ' |
+ 'when used with --nm-in') |
+ if not opts.destdir: |
+ parser.error('--destdir is a required argument') |
+ if not opts.jobs: |
+ # Use the number of processors but cap between 2 and 4 since raw |
+ # CPU power isn't the limiting factor. It's I/O limited, memory |
+ # bus limited and available-memory-limited. Too many processes and |
+ # the computer will run out of memory and it will be slow. |
+ opts.jobs = max(2, min(4, str(multiprocessing.cpu_count()))) |
+ |
+ if opts.addr2line_binary: |
+ assert os.path.isfile(opts.addr2line_binary) |
+ addr2line_binary = opts.addr2line_binary |
+ else: |
+ addr2line_binary = _find_in_system_path('addr2line') |
+ assert addr2line_binary, 'Unable to find addr2line in the path. '\ |
+ 'Use --addr2line-binary to specify location.' |
+ |
+ if opts.nm_binary: |
+ assert os.path.isfile(opts.nm_binary) |
+ nm_binary = opts.nm_binary |
+ else: |
+ nm_binary = _find_in_system_path('nm') |
+ assert nm_binary, 'Unable to find nm in the path. Use --nm-binary '\ |
+ 'to specify location.' |
+ |
+ if opts.pak: |
+ assert os.path.isfile(opts.pak), 'Could not find ' % opts.pak |
+ |
+ print('addr2line: %s' % addr2line_binary) |
+ print('nm: %s' % nm_binary) |
+ |
+ if opts.library: |
+ CheckDebugFormatSupport(opts.library, addr2line_binary) |
+ |
+ # Prepare output directory and report guts |
+ if not os.path.exists(opts.destdir): |
+ os.makedirs(opts.destdir, 0755) |
+ nm_out = os.path.join(opts.destdir, 'nm.out') |
+ if opts.no_nm_out: |
+ nm_out = None |
+ |
+ # Copy report boilerplate into output directory. This also proves that the |
+ # output directory is safe for writing, so there should be no problems writing |
+ # the nm.out file later. |
+ data_js_file_name = 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(os.path.dirname(__file__), |
+ '..', |
+ '..', |
+ 'd3', 'src') |
+ template_src = os.path.join(os.path.dirname(__file__), |
+ '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) |
+ |
+ # Run nm and/or addr2line to gather the data |
+ symbols = GetNmSymbols(opts.nm_in, nm_out, opts.library, |
+ opts.jobs, opts.verbose is True, |
+ addr2line_binary, nm_binary, |
+ opts.disable_disambiguation is None, |
+ opts.source_path) |
+ |
+ # Post-processing |
+ if opts.pak: |
+ AddPakData(symbols, opts.pak) |
+ if opts.library: |
+ symbol_path_origin_dir = os.path.dirname(os.path.abspath(opts.library)) |
+ else: |
+ # Just a guess. Hopefully all paths in the input file are absolute. |
+ symbol_path_origin_dir = os.path.abspath(os.getcwd()) |
+ # Dump JSON for the HTML report. |
+ DumpCompactTree(symbols, symbol_path_origin_dir, data_js_file_name) |
+ print 'Report saved to ' + opts.destdir + '/index.html' |
+ |
+if __name__ == '__main__': |
+ sys.exit(main()) |