diff --git a/tools/binary_size/README.md b/tools/binary_size/README.md index 4bc66ea..e058031 100644 --- a/tools/binary_size/README.md +++ b/tools/binary_size/README.md
@@ -48,8 +48,8 @@ ### Example Usage: - # Sync, build, and store MonochromePublic.apk for HEAD and HEAD^. - tools/binary_size/diagnose_apk_bloat.py -v + # Sync, build, and diff for HEAD and HEAD^. + tools/binary_size/diagnose_apk_bloat.py # Display detailed usage info (there are many options). tools/binary_size/diagnose_apk_bloat.py -h
diff --git a/tools/binary_size/diagnose_apk_bloat.py b/tools/binary_size/diagnose_apk_bloat.py index 847c305..da5bd40e 100755 --- a/tools/binary_size/diagnose_apk_bloat.py +++ b/tools/binary_size/diagnose_apk_bloat.py
@@ -9,18 +9,121 @@ """ import argparse -import logging +import collections +import itertools +import json import multiprocessing import os import shutil import subprocess import sys -import helpers - -_DEFAULT_OUT_DIR = os.path.join(helpers.SRC_ROOT, 'out', 'diagnose-apk-bloat') +_SRC_ROOT = os.path.abspath( + os.path.join(os.path.dirname(__file__), os.pardir, os.pardir)) +_DEFAULT_OUT_DIR = os.path.join(_SRC_ROOT, 'out', 'diagnose-apk-bloat') _DEFAULT_TARGET = 'monochrome_public_apk' -_DEFAULT_ARCHIVE_DIR = os.path.join(helpers.SRC_ROOT, 'binary-size-bloat') +_DEFAULT_ARCHIVE_DIR = os.path.join(_SRC_ROOT, 'binary-size-bloat') + +# Global variable for storing the initial branch before the script was launched +# so that it doesn't need to be passed everywhere in case we fail and exit. +_initial_branch = None + + +class BaseDiff(object): + """Base class capturing binary size diffs.""" + def __init__(self, name): + self.name = name + self.banner = '\n' + '*' * 30 + name + '*' * 30 + self.RunDiff() + + def AppendResults(self, logfile): + """Print and write diff results to an open |logfile|.""" + _PrintAndWriteToFile(logfile, self.banner) + _PrintAndWriteToFile(logfile, 'Summary:') + _PrintAndWriteToFile(logfile, self.Summary()) + _PrintAndWriteToFile(logfile, '\nDetails:') + for l in self.DetailedResults(): + _PrintAndWriteToFile(logfile, l) + + def Summary(self): + """A short description that summarizes the source of binary size bloat.""" + raise NotImplementedError() + + def DetailedResults(self): + """An iterable description of the cause of binary size bloat.""" + raise NotImplementedError() + + def ProduceDiff(self): + """Prepare a binary size diff with ready to print results.""" + raise NotImplementedError() + + def RunDiff(self): + _Print('Creating {}', self.name) + self.ProduceDiff() + + +_ResourceSizesDiffResult = collections.namedtuple( + 'ResourceSizesDiffResult', ['section', 'value', 'units']) + + +class ResourceSizesDiff(BaseDiff): + _RESOURCE_SIZES_PATH = os.path.join( + _SRC_ROOT, 'build', 'android', 'resource_sizes.py') + + def __init__(self, archive_dirs, apk_name, slow_options=False): + self._archive_dirs = archive_dirs + self._apk_name = apk_name + self._slow_options = slow_options + self._diff = None # Set by |ProduceDiff()| + super(ResourceSizesDiff, self).__init__('Resource Sizes Diff') + + def DetailedResults(self): + for section, value, units in self._diff: + yield '{:>+10,} {} {}'.format(value, units, section) + + def Summary(self): + for s in self._diff: + if 'normalized' in s.section: + return 'Normalized APK size: {:+,} {}'.format(s.value, s.units) + return '' + + def ProduceDiff(self): + chartjsons = self._RunResourceSizes() + diff = [] + with_patch = chartjsons[0]['charts'] + without_patch = chartjsons[1]['charts'] + for section, section_dict in with_patch.iteritems(): + for subsection, v in section_dict.iteritems(): + # Ignore entries when resource_sizes.py chartjson format has changed. + if (section not in without_patch or + subsection not in without_patch[section] or + v['units'] != without_patch[section][subsection]['units']): + _Print('Found differing dict structures for resource_sizes.py, ' + 'skipping {} {}', section, subsection) + else: + diff.append( + _ResourceSizesDiffResult( + '%s %s' % (section, subsection), + v['value'] - without_patch[section][subsection]['value'], + v['units'])) + self._diff = sorted(diff, key=lambda x: abs(x.value), reverse=True) + + def _RunResourceSizes(self): + chartjsons = [] + for archive_dir in self._archive_dirs: + apk_path = os.path.join(archive_dir, self._apk_name) + chartjson_file = os.path.join(archive_dir, 'results-chart.json') + cmd = [self._RESOURCE_SIZES_PATH, '--output-dir', archive_dir, + '--no-output-dir', + '--chartjson', apk_path] + if self._slow_options: + cmd += ['--estimate-patch-size'] + else: + cmd += ['--no-static-initializer-check'] + _RunCmd(cmd) + with open(chartjson_file) as f: + chartjsons.append(json.load(f)) + return chartjsons class _BuildHelper(object): @@ -62,26 +165,25 @@ return cmd def Build(self): - logging.info('Building %s. This may take a while (run with -vv for ' - 'detailed ninja output).', self.target) + _Print('Building: {}.', self.target) _RunCmd(self._GenGnCmd()) _RunCmd(self._GenNinjaCmd(), print_stdout=True) -def _GetLinkerMapPath(target_os, target): +def _GetMainLibPath(target_os, target): # TODO(estevenson): Get this from GN instead of hardcoding. if target_os == 'linux': - return 'chrome.map.gz' + return 'chrome' elif 'monochrome' in target: - return 'lib.unstripped/libmonochrome.so.map.gz' + return 'lib.unstripped/libmonochrome.so' else: - return 'lib.unstripped/libchrome.so.map.gz' + return 'lib.unstripped/libchrome.so' -def _ApkPathFromTarget(target): +def _ApkNameFromTarget(target): # Only works on apk targets that follow: my_great_apk naming convention. apk_name = ''.join(s.title() for s in target.split('_')[:-1]) + '.apk' - return os.path.join('apks', apk_name) + return apk_name.replace('Webview', 'WebView') def _RunCmd(cmd, print_stdout=False): @@ -90,89 +192,113 @@ Args: cmd: the command to run. print_stdout: if this is True, then the stdout of the process will be - printed (to stdout if log level is DEBUG otherwise to /dev/null). - If false, stdout will be returned. + printed, otherwise stdout will be returned. Returns: Command stdout if |print_stdout| is False otherwise ''. """ cmd_str = ' '.join(c for c in cmd) - logging.debug('Running: %s', cmd_str) - if not print_stdout: - proc_stdout = subprocess.PIPE - elif logging.getLogger().isEnabledFor(logging.DEBUG): + _Print('Running: {}', cmd_str) + if print_stdout: proc_stdout = sys.stdout else: - proc_stdout = open(os.devnull, 'wb') + proc_stdout = subprocess.PIPE proc = subprocess.Popen(cmd, stdout=proc_stdout, stderr=subprocess.PIPE) stdout, stderr = proc.communicate() if proc.returncode != 0: - logging.error('Command failed: %s\nstderr:\n%s' % (cmd_str, stderr)) - sys.exit(1) + _Die('command failed: {}\nstderr:\n{}', cmd_str, stderr) return stdout.strip() if stdout else '' def _GitCmd(args): - return _RunCmd(['git', '-C', helpers.SRC_ROOT] + args) + return _RunCmd(['git', '-C', _SRC_ROOT] + args) def _GclientSyncCmd(rev): cwd = os.getcwd() - os.chdir(helpers.SRC_ROOT) - logging.info('gclient sync to %s', rev) + os.chdir(_SRC_ROOT) _RunCmd(['gclient', 'sync', '-r', 'src@' + rev], print_stdout=True) os.chdir(cwd) def _ArchiveBuildResult(archive_dir, build_helper): - """Save resulting APK and mapping file.""" - def ArchiveFile(file_path): - file_path = os.path.join(build_helper.output_directory, file_path) - if os.path.exists(file_path): - if not os.path.exists(archive_dir): - os.makedirs(archive_dir) - shutil.copy(file_path, archive_dir) - else: - logging.error('Expected file: %s not found.' % file_path) - sys.exit(1) + """Save build artifacts necessary for diffing.""" + _Print('Saving build results to: {}', archive_dir) + if not os.path.exists(archive_dir): + os.makedirs(archive_dir) - logging.info('Saving build results to: %s', archive_dir) - ArchiveFile(_GetLinkerMapPath(build_helper.target_os, build_helper.target)) + def ArchiveFile(filename): + if not os.path.exists(filename): + _Die('missing expected file: {}', filename) + shutil.copy(filename, archive_dir) + + lib_path = os.path.join( + build_helper.output_directory, + _GetMainLibPath(build_helper.target_os, build_helper.target)) + ArchiveFile(lib_path) + + size_path = os.path.join( + archive_dir, os.path.splitext(os.path.basename(lib_path))[0] + '.size') + supersize_path = os.path.join(_SRC_ROOT, 'tools/binary_size/supersize') + _RunCmd([supersize_path, 'archive', size_path, '--output-directory', + build_helper.output_directory, '--elf-file', lib_path]) + if build_helper.target_os == 'android': - ArchiveFile(_ApkPathFromTarget(build_helper.target)) + apk_path = os.path.join(build_helper.output_directory, 'apks', + _ApkNameFromTarget(build_helper.target)) + ArchiveFile(apk_path) -def _SyncAndBuild(rev_with_patch, rev_without_patch, archive_dir, build_helper): - rev_with_patch = _GitCmd(['rev-parse', rev_with_patch]) - rev_without_patch = _GitCmd([ - 'rev-parse', rev_without_patch or rev_with_patch + '^']) - +def _SyncAndBuild(revs, archive_dirs, build_helper): # Move to a detached state since gclient sync doesn't work with local commits # on a branch. _GitCmd(['checkout', '--detach']) + for rev, archive_dir in itertools.izip(revs, archive_dirs): + _GclientSyncCmd(rev) + build_helper.Build() + _ArchiveBuildResult(archive_dir, build_helper) - _GclientSyncCmd(rev_with_patch) - build_helper.Build() - _ArchiveBuildResult( - os.path.join(archive_dir, 'with_patch_%s' % rev_with_patch), build_helper) - _GclientSyncCmd(rev_without_patch) - build_helper.Build() - _ArchiveBuildResult( - os.path.join(archive_dir, 'without_patch_%s' % rev_without_patch), - build_helper) +def _NormalizeRev(rev): + """Use actual revs instead of HEAD, HEAD^, etc.""" + return _GitCmd(['rev-parse', rev]) def _EnsureDirectoryClean(): - logging.info('Checking source directory') + _Print('Checking source directory') stdout = _GitCmd(['status', '--porcelain']) # Ignore untracked files. if stdout and stdout[:2] != '??': - logging.error('Failure: please ensure working directory is clean.') - sys.exit(1) + _Die('please ensure working directory is clean.') + + +def _SetInitialBranch(): + global _initial_branch + _initial_branch = _GitCmd(['rev-parse', '--abbrev-ref', 'HEAD']) + + +def _RestoreInitialBranch(): + if _initial_branch: + _GitCmd(['checkout', _initial_branch]) + + +def _Die(s, *args, **kwargs): + _Print('Failure: ' + s, *args, **kwargs) + _RestoreInitialBranch() + sys.exit(1) + + +def _Print(s, *args, **kwargs): + print s.format(*args, **kwargs) + + +def _PrintAndWriteToFile(logfile, s): + """Print |s| to |logfile| and stdout.""" + _Print(s) + logfile.write('%s\n' % s) def main(): @@ -188,6 +314,11 @@ parser.add_argument('--rev-without-patch', help='Older patch to diff against. If not supplied, ' 'the previous commit to rev_with_patch will be used.') + parser.add_argument('--include-slow-options', + action='store_true', + help='Run some extra steps that take longer to complete. ' + 'This includes apk-patch-size estimation and ' + 'static-initializer counting') build_group = parser.add_argument_group('ninja', 'Args to use with ninja/gn') build_group.add_argument('-j', @@ -215,13 +346,32 @@ build_group.add_argument('--target', default=_DEFAULT_TARGET, help='GN APK target to build.') - args = helpers.AddCommonOptionsAndParseArgs(parser, sys.argv, pypy_warn=False) + args = parser.parse_args() _EnsureDirectoryClean() + _SetInitialBranch() + revs = [args.rev_with_patch, + args.rev_without_patch or args.rev_with_patch + '^'] + revs = [_NormalizeRev(r) for r in revs] build_helper = _BuildHelper(args) - _SyncAndBuild(args.rev_with_patch, args.rev_without_patch, args.archive_dir, - build_helper) + archive_dirs = [os.path.join(args.archive_dir, '%d-%s' % (len(revs) - i, rev)) + for i, rev in enumerate(revs)] + _SyncAndBuild(revs, archive_dirs, build_helper) + _RestoreInitialBranch() + output_file = os.path.join(args.archive_dir, + 'diff_result_{}_{}.txt'.format(*revs)) + if os.path.exists(output_file): + os.remove(output_file) + diffs = [] + if build_helper.target_os == 'android': + diffs += [ + ResourceSizesDiff(archive_dirs, _ApkNameFromTarget(args.target), + slow_options=args.include_slow_options) + ] + with open(output_file, 'a') as logfile: + for d in diffs: + d.AppendResults(logfile) if __name__ == '__main__': sys.exit(main())
diff --git a/tools/clang/scripts/update.py b/tools/clang/scripts/update.py index c820517d..40051f6 100755 --- a/tools/clang/scripts/update.py +++ b/tools/clang/scripts/update.py
@@ -27,7 +27,7 @@ # Do NOT CHANGE this if you don't know what you're doing -- see # https://chromium.googlesource.com/chromium/src/+/master/docs/updating_clang.md # Reverting problematic clang rolls is safe, though. -CLANG_REVISION = '298539' +CLANG_REVISION = '299960' use_head_revision = 'LLVM_FORCE_HEAD_REVISION' in os.environ if use_head_revision:
diff --git a/tools/perf/benchmark.csv b/tools/perf/benchmark.csv index 0c8de0a..5ef3f0139 100644 --- a/tools/perf/benchmark.csv +++ b/tools/perf/benchmark.csv
@@ -9,6 +9,7 @@ blink_perf.canvas,junov@chromium.org, blink_perf.css,rune@opera.com, blink_perf.dom,"yukishiino@chromium.org, bashi@chromium.org, haraken@chromium.org", +blink_perf.editing,, blink_perf.events,hayato@chromium.org, blink_perf.layout,eae@chromium.org, blink_perf.paint,wangxianzhu@chromium.org,
diff --git a/tools/perf/benchmarks/blink_perf.py b/tools/perf/benchmarks/blink_perf.py index fc2b6f8..f26093c 100644 --- a/tools/perf/benchmarks/blink_perf.py +++ b/tools/perf/benchmarks/blink_perf.py
@@ -244,6 +244,9 @@ tag = 'dom' subdir = 'DOM' +class BlinkPerfEditing(_BlinkPerfBenchmark): + tag = 'editing' + subdir = 'Editing' @benchmark.Owner(emails=['hayato@chromium.org']) class BlinkPerfEvents(_BlinkPerfBenchmark):