"""Collect, archive, and analyze Chrome's binary size."""
import argparse
import atexit
import logging
import pathlib
import platform
import resource
import sys
import archive
import console
import diff
import dex_disassembly
import file_format
import models
import native_disassembly
import os
def _LogPeakRamUsage():
peak_ram_usage = resource.getrusage(resource.RUSAGE_SELF).ru_maxrss
peak_ram_usage += resource.getrusage(resource.RUSAGE_CHILDREN).ru_maxrss
logging.info('Peak RAM usage was %d MB.', peak_ram_usage // 1024)
def _AddCommonArguments(parser):
parser.add_argument('-v',
'--verbose',
default=0,
action='count',
help='Verbose level (multiple times for more)')
class _PathResolver:
def __init__(self, parent_path):
self._parent_path = pathlib.Path(parent_path)
def __call__(self, subpath):
candidates = list({
self._parent_path / subpath: 0,
self._parent_path / pathlib.PosixPath(subpath).name: 0,
})
for p in candidates:
if p.exists():
return p
raise Exception('Paths do not exist: ' +
', '.join(str(t) for t in candidates))
class _DiffAction:
@staticmethod
def AddArguments(parser):
parser.add_argument('before', help='Before-patch .size file.')
parser.add_argument('after', help='After-patch .size file.')
parser.add_argument('--all', action='store_true', help='Verbose diff')
@staticmethod
def Run(args, on_config_error):
args.output_directory = None
args.inputs = [args.before, args.after]
args.query = '\n'.join([
'd = Diff()',
'sis = canned_queries.StaticInitializers(d.symbols)',
'count = sis.CountsByDiffStatus()[models.DIFF_STATUS_ADDED]',
'count += sis.CountsByDiffStatus()[models.DIFF_STATUS_REMOVED]',
'if count > 0:',
' print("Static Initializers Diff:")',
' Print(sis, summarize=False)',
' print()',
' print("Full diff:")',
'Print(d, verbose=%s)' % bool(args.all),
])
console.Run(args, on_config_error)
class _SaveDiffAction:
@staticmethod
def AddArguments(parser):
parser.add_argument('before', help='Before-patch .size file.')
parser.add_argument('after', help='After-patch .size file.')
parser.add_argument(
'output_file',
help='Write generated data to the specified .sizediff file.')
parser.add_argument('--title',
help='Value for the "title" build_config entry.')
parser.add_argument('--url', help='Value for the "url" build_config entry.')
parser.add_argument(
'--save-disassembly',
help='Adds the disassembly for the top 10 changed symbols.',
action='store_true')
parser.add_argument(
'--before-directory',
help='Defaults to directory containing before-patch .size file.')
parser.add_argument(
'--after-directory',
help='Defaults to directory containing after-patch .size file.')
@staticmethod
def Run(args, on_config_error):
if not args.before.endswith('.size'):
on_config_error('Before input must end with ".size"')
if not args.after.endswith('.size'):
on_config_error('After input must end with ".size"')
if not args.output_file.endswith('.sizediff'):
on_config_error('Output must end with ".sizediff"')
if args.save_disassembly:
if not args.before_directory:
args.before_directory = os.path.dirname(args.before)
if not args.after_directory:
args.after_directory = os.path.dirname(args.after)
before_size_info = archive.LoadAndPostProcessSizeInfo(args.before)
after_size_info = archive.LoadAndPostProcessSizeInfo(args.after)
if args.title:
after_size_info.build_config[models.BUILD_CONFIG_TITLE] = args.title
if args.url:
after_size_info.build_config[models.BUILD_CONFIG_URL] = args.url
delta_size_info = diff.Diff(before_size_info, after_size_info)
if args.save_disassembly:
before_path_resolver = _PathResolver(args.before_directory)
after_path_resolver = _PathResolver(args.after_directory)
dex_disassembly.AddDisassembly(delta_size_info, before_path_resolver,
after_path_resolver)
native_disassembly.AddDisassembly(delta_size_info, before_path_resolver,
after_path_resolver)
file_format.SaveDeltaSizeInfo(delta_size_info, args.output_file)
def main():
parser = argparse.ArgumentParser(prog='supersize', description=__doc__)
sub_parsers = parser.add_subparsers()
actions = {}
actions['archive'] = (archive, 'Create a .size file')
actions['console'] = (
console,
'Starts an interactive Python console for analyzing .size files.')
actions['diff'] = (
_DiffAction(),
'Shorthand for console --query "Print(Diff())" (plus highlights static '
'initializers in diff)')
actions['save_diff'] = (
_SaveDiffAction(),
'Create a stand-alone .sizediff diff report from two .size files.')
for name, tup in actions.items():
sub_parser = sub_parsers.add_parser(name, help=tup[1])
_AddCommonArguments(sub_parser)
tup[0].AddArguments(sub_parser)
sub_parser.set_defaults(func=tup[0].Run)
if len(sys.argv) == 1:
parser.print_help()
sys.exit(1)
elif len(sys.argv) == 2 and sys.argv[1] in actions:
parser.parse_args(sys.argv[1:] + ['-h'])
sys.exit(1)
args = parser.parse_args()
logging.basicConfig(level=logging.WARNING - args.verbose * 10,
format='%(levelname).1s %(relativeCreated)6d %(message)s')
if logging.getLogger().isEnabledFor(logging.DEBUG):
atexit.register(_LogPeakRamUsage)
def on_config_error(*args):
parser.error(*args)
args.func(args, on_config_error)
if __name__ == '__main__':
main()