| Index: tools/binary_size/console.py
|
| diff --git a/tools/binary_size/console.py b/tools/binary_size/console.py
|
| new file mode 100755
|
| index 0000000000000000000000000000000000000000..a2353a0c387afa31e3fccf3cd4ad7f81601932e3
|
| --- /dev/null
|
| +++ b/tools/binary_size/console.py
|
| @@ -0,0 +1,189 @@
|
| +#!/usr/bin/env python
|
| +# Copyright 2017 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.
|
| +
|
| +"""Tool for analyzing binary size of executables using nm or linker map files.
|
| +
|
| +Map files can be created by passing "-Map Foo.map" to the linker. If a map file
|
| +is unavailable, this tool can also be pointed at an unstripped executable, but
|
| +the information does not seem to be as accurate in this case.
|
| +
|
| +Inspired by SymbolSort for Windows:
|
| + https://github.com/adrianstone55/SymbolSort
|
| +"""
|
| +
|
| +import argparse
|
| +import atexit
|
| +import code
|
| +import contextlib
|
| +import itertools
|
| +import logging
|
| +import os
|
| +import readline
|
| +import subprocess
|
| +import sys
|
| +
|
| +import describe
|
| +import file_format
|
| +import helpers
|
| +import map2size
|
| +import models
|
| +
|
| +
|
| +# Number of lines before using less for Print().
|
| +_THRESHOLD_FOR_PAGER = 30
|
| +
|
| +
|
| +@contextlib.contextmanager
|
| +def _LessPipe():
|
| + """Output to `less`. Yields a file object to write to."""
|
| + try:
|
| + proc = subprocess.Popen(['less'], stdin=subprocess.PIPE, stdout=sys.stdout)
|
| + yield proc.stdin
|
| + proc.stdin.close()
|
| +
|
| + proc.wait()
|
| + except IOError:
|
| + pass # Happens when less is quit before all data is written.
|
| + except KeyboardInterrupt:
|
| + pass # Assume used to break out of less.
|
| +
|
| +
|
| +class _Session(object):
|
| + _readline_initialized = False
|
| +
|
| + def __init__(self, extra_vars):
|
| + self._variables = {
|
| + 'Print': self._PrintFunc,
|
| + 'Write': self._WriteFunc,
|
| + 'Diff': models.Diff,
|
| + }
|
| + self._variables.update(extra_vars)
|
| +
|
| + def _PrintFunc(self, obj, verbose=False, use_pager=None):
|
| + """Prints out the given Symbol / SymbolGroup / SymbolDiff / SizeInfo.
|
| +
|
| + Args:
|
| + obj: The object to be printed.
|
| + use_pager: Whether to pipe output through `less`. Ignored when |obj| is a
|
| + Symbol.
|
| + """
|
| + lines = describe.GenerateLines(obj, verbose=verbose)
|
| + if use_pager is None and sys.stdout.isatty():
|
| + # Does not take into account line-wrapping... Oh well.
|
| + first_lines = list(itertools.islice(lines, _THRESHOLD_FOR_PAGER))
|
| + if len(first_lines) == _THRESHOLD_FOR_PAGER:
|
| + use_pager = True
|
| + lines = itertools.chain(first_lines, lines)
|
| +
|
| + if use_pager:
|
| + with _LessPipe() as stdin:
|
| + describe.WriteLines(lines, stdin.write)
|
| + else:
|
| + describe.WriteLines(lines, sys.stdout.write)
|
| +
|
| +
|
| + def _WriteFunc(self, obj, path, verbose=False):
|
| + """Same as Print(), but writes to a file.
|
| +
|
| + Example: Write(Diff(size_info2, size_info1), 'output.txt')
|
| + """
|
| + parent_dir = os.path.dirname(path)
|
| + if parent_dir and not os.path.exists(parent_dir):
|
| + os.makedirs(parent_dir)
|
| + with file_format.OpenMaybeGz(path, 'w') as file_obj:
|
| + lines = describe.GenerateLines(obj, verbose=verbose)
|
| + describe.WriteLines(lines, file_obj.write)
|
| +
|
| +
|
| + def _CreateBanner(self):
|
| + symbol_info_keys = sorted(m for m in dir(models.SizeInfo) if m[0] != '_')
|
| + symbol_group_keys = sorted(m for m in dir(models.SymbolGroup)
|
| + if m[0] != '_')
|
| + symbol_diff_keys = sorted(m for m in dir(models.SymbolDiff)
|
| + if m[0] != '_' and m not in symbol_group_keys)
|
| + functions = sorted(k for k in self._variables if k[0].isupper())
|
| + variables = sorted(k for k in self._variables if k[0].islower())
|
| + return '\n'.join([
|
| + '*' * 80,
|
| + 'Entering interactive Python shell. Here is some inspiration:',
|
| + '',
|
| + '# Show pydoc for main types:',
|
| + 'import models',
|
| + 'help(models)',
|
| + '',
|
| + '# Show two levels of .text, grouped by first two subdirectories',
|
| + 'text_syms = size_info1.symbols.WhereInSection("t")',
|
| + 'by_path = text_syms.GroupByPath(depth=2)',
|
| + 'Print(by_path.WhereBiggerThan(1024))',
|
| + '',
|
| + '# Show all non-vtable generated symbols',
|
| + 'generated_syms = size_info1.symbols.WhereIsGenerated()',
|
| + 'Print(generated_syms.WhereNameMatches("vtable").Inverted())',
|
| + '',
|
| + '*' * 80,
|
| + 'Here is some quick reference:',
|
| + '',
|
| + 'SizeInfo: %s' % ', '.join(symbol_info_keys),
|
| + 'SymbolGroup: %s' % ', '.join(symbol_group_keys),
|
| + 'SymbolDiff (extends SymbolGroup): %s' % ', '.join(symbol_diff_keys),
|
| + '',
|
| + 'Functions: %s' % ', '.join('%s()' % f for f in functions),
|
| + 'Variables: %s' % ', '.join(variables),
|
| + '',
|
| + ])
|
| +
|
| + @classmethod
|
| + def _InitReadline(cls):
|
| + if cls._readline_initialized:
|
| + return
|
| + cls._readline_initialized = True
|
| + # Without initializing readline, arrow keys don't even work!
|
| + readline.parse_and_bind('tab: complete')
|
| + history_file = os.path.join(os.path.expanduser('~'),
|
| + '.binary_size_query_history')
|
| + if os.path.exists(history_file):
|
| + readline.read_history_file(history_file)
|
| + atexit.register(lambda: readline.write_history_file(history_file))
|
| +
|
| + def Eval(self, query):
|
| + eval_result = eval(query, self._variables)
|
| + if eval_result:
|
| + self._PrintFunc(eval_result)
|
| +
|
| + def GoInteractive(self):
|
| + _Session._InitReadline()
|
| + code.InteractiveConsole(self._variables).interact(self._CreateBanner())
|
| +
|
| +
|
| +def main(argv):
|
| + parser = argparse.ArgumentParser()
|
| + parser.add_argument('inputs', nargs='*',
|
| + help='Input .size/.map files to load. They will be '
|
| + 'mapped to variables as: size_info1, size_info2,'
|
| + ' etc.')
|
| + parser.add_argument('--query',
|
| + help='Print the result of the given snippet. Example: '
|
| + 'size_info1.symbols.WhereInSection("d").'
|
| + 'WhereBiggerThan(100)')
|
| + map2size.AddOptions(parser)
|
| + args = helpers.AddCommonOptionsAndParseArgs(parser, argv)
|
| +
|
| + info_variables = {}
|
| + for i, path in enumerate(args.inputs):
|
| + size_info = map2size.AnalyzeWithArgs(args, path)
|
| + info_variables['size_info%d' % (i + 1)] = size_info
|
| +
|
| + session = _Session(info_variables)
|
| +
|
| + if args.query:
|
| + logging.info('Running query from command-line.')
|
| + session.Eval(args.query)
|
| + else:
|
| + logging.info('Entering interactive console.')
|
| + session.GoInteractive()
|
| +
|
| +
|
| +if __name__ == '__main__':
|
| + sys.exit(main(sys.argv))
|
|
|