| 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())
|
|
|