| OLD | NEW |
| 1 #!/usr/bin/python | 1 #!/usr/bin/python |
| 2 # Copyright (c) 2011 The Chromium Authors. All rights reserved. | 2 # Copyright (c) 2011 The Chromium Authors. All rights reserved. |
| 3 # Use of this source code is governed by a BSD-style license that can be | 3 # Use of this source code is governed by a BSD-style license that can be |
| 4 # found in the LICENSE file. | 4 # found in the LICENSE file. |
| 5 | 5 |
| 6 """Prints the size of each given file and optionally computes the size of | 6 """Prints the size of each given file and optionally computes the size of |
| 7 libchrome.so without the dependencies added for building with android NDK. | 7 libchrome.so without the dependencies added for building with android NDK. |
| 8 Also breaks down the contents of the APK to determine the installed size | 8 Also breaks down the contents of the APK to determine the installed size |
| 9 and assign size contributions to different classes of file. | 9 and assign size contributions to different classes of file. |
| 10 """ | 10 """ |
| 11 | 11 |
| 12 import argparse | 12 import argparse |
| 13 import collections | 13 import collections |
| 14 from contextlib import contextmanager | 14 from contextlib import contextmanager |
| 15 import json | 15 import json |
| 16 import logging | 16 import logging |
| 17 import operator | 17 import operator |
| 18 import os | 18 import os |
| 19 import re | 19 import re |
| 20 import struct | 20 import struct |
| 21 import sys | 21 import sys |
| 22 import tempfile | 22 import tempfile |
| 23 import zipfile | 23 import zipfile |
| 24 import zlib | 24 import zlib |
| 25 | 25 |
| 26 from binary_size import apk_downloader |
| 26 import devil_chromium | 27 import devil_chromium |
| 27 from devil.android.sdk import build_tools | 28 from devil.android.sdk import build_tools |
| 28 from devil.utils import cmd_helper | 29 from devil.utils import cmd_helper |
| 29 from devil.utils import lazy | 30 from devil.utils import lazy |
| 30 import method_count | 31 import method_count |
| 31 from pylib import constants | 32 from pylib import constants |
| 32 from pylib.constants import host_paths | 33 from pylib.constants import host_paths |
| 33 | 34 |
| 34 _AAPT_PATH = lazy.WeakConstant(lambda: build_tools.GetPath('aapt')) | 35 _AAPT_PATH = lazy.WeakConstant(lambda: build_tools.GetPath('aapt')) |
| 35 _GRIT_PATH = os.path.join(host_paths.DIR_SOURCE_ROOT, 'tools', 'grit') | 36 _GRIT_PATH = os.path.join(host_paths.DIR_SOURCE_ROOT, 'tools', 'grit') |
| 36 _BUILD_UTILS_PATH = os.path.join( | 37 _BUILD_UTILS_PATH = os.path.join( |
| 37 host_paths.DIR_SOURCE_ROOT, 'build', 'android', 'gyp') | 38 host_paths.DIR_SOURCE_ROOT, 'build', 'android', 'gyp') |
| 39 _APK_PATCH_SIZE_ESTIMATOR_PATH = os.path.join( |
| 40 host_paths.DIR_SOURCE_ROOT, 'third_party', 'apk-patch-size-estimator') |
| 38 | 41 |
| 39 # Prepend the grit module from the source tree so it takes precedence over other | 42 # Prepend the grit module from the source tree so it takes precedence over other |
| 40 # grit versions that might present in the search path. | 43 # grit versions that might present in the search path. |
| 41 with host_paths.SysPath(_GRIT_PATH, 1): | 44 with host_paths.SysPath(_GRIT_PATH, 1): |
| 42 from grit.format import data_pack # pylint: disable=import-error | 45 from grit.format import data_pack # pylint: disable=import-error |
| 43 | 46 |
| 44 with host_paths.SysPath(host_paths.BUILD_COMMON_PATH): | 47 with host_paths.SysPath(host_paths.BUILD_COMMON_PATH): |
| 45 import perf_tests_results_helper # pylint: disable=import-error | 48 import perf_tests_results_helper # pylint: disable=import-error |
| 46 | 49 |
| 47 with host_paths.SysPath(_BUILD_UTILS_PATH, 1): | 50 with host_paths.SysPath(_BUILD_UTILS_PATH, 1): |
| 48 from util import build_utils # pylint: disable=import-error | 51 from util import build_utils # pylint: disable=import-error |
| 49 | 52 |
| 53 with host_paths.SysPath(_APK_PATCH_SIZE_ESTIMATOR_PATH): |
| 54 import apk_patch_size_estimator # pylint: disable=import-error |
| 55 |
| 50 | 56 |
| 51 # Python had a bug in zipinfo parsing that triggers on ChromeModern.apk | 57 # Python had a bug in zipinfo parsing that triggers on ChromeModern.apk |
| 52 # https://bugs.python.org/issue14315 | 58 # https://bugs.python.org/issue14315 |
| 53 def _PatchedDecodeExtra(self): | 59 def _PatchedDecodeExtra(self): |
| 54 # Try to decode the extra field. | 60 # Try to decode the extra field. |
| 55 extra = self.extra | 61 extra = self.extra |
| 56 unpack = struct.unpack | 62 unpack = struct.unpack |
| 57 while len(extra) >= 4: | 63 while len(extra) >= 4: |
| 58 tp, ln = unpack('<HH', extra[:4]) | 64 tp, ln = unpack('<HH', extra[:4]) |
| 59 if tp == 1: | 65 if tp == 1: |
| (...skipping 607 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 667 graph_title = os.path.basename(apk_filename) + '_Dex' | 673 graph_title = os.path.basename(apk_filename) + '_Dex' |
| 668 dex_metrics = method_count.CONTRIBUTORS_TO_DEX_CACHE | 674 dex_metrics = method_count.CONTRIBUTORS_TO_DEX_CACHE |
| 669 for key, label in dex_metrics.iteritems(): | 675 for key, label in dex_metrics.iteritems(): |
| 670 ReportPerfResult(chartjson, graph_title, label, sizes[key], 'entries') | 676 ReportPerfResult(chartjson, graph_title, label, sizes[key], 'entries') |
| 671 | 677 |
| 672 graph_title = '%sCache' % graph_title | 678 graph_title = '%sCache' % graph_title |
| 673 ReportPerfResult(chartjson, graph_title, 'DexCache', sizes['dex_cache_size'], | 679 ReportPerfResult(chartjson, graph_title, 'DexCache', sizes['dex_cache_size'], |
| 674 'bytes') | 680 'bytes') |
| 675 | 681 |
| 676 | 682 |
| 683 def _PrintPatchSizeEstimate(new_apk, builder, bucket, chartjson=None): |
| 684 apk_name = os.path.basename(new_apk) |
| 685 title = apk_name + '_PatchSizeEstimate' |
| 686 # Reference APK paths have spaces replaced by underscores. |
| 687 builder = builder.replace(' ', '_') |
| 688 old_apk = apk_downloader.MaybeDownloadApk( |
| 689 builder, apk_downloader.CURRENT_MILESTONE, apk_name, |
| 690 apk_downloader.DEFAULT_DOWNLOAD_PATH, bucket) |
| 691 if old_apk: |
| 692 # Use a temp dir in case patch size functions fail to clean up temp files. |
| 693 with build_utils.TempDir() as tmp: |
| 694 tmp_name = os.path.join(tmp, 'patch.tmp') |
| 695 bsdiff = apk_patch_size_estimator.calculate_bsdiff( |
| 696 old_apk, new_apk, None, tmp_name) |
| 697 ReportPerfResult(chartjson, title, 'BSDiff (gzipped)', bsdiff, 'bytes') |
| 698 fbf = apk_patch_size_estimator.calculate_filebyfile( |
| 699 old_apk, new_apk, None, tmp_name) |
| 700 ReportPerfResult(chartjson, title, 'FileByFile (gzipped)', fbf, 'bytes') |
| 701 |
| 702 |
| 677 @contextmanager | 703 @contextmanager |
| 678 def Unzip(zip_file, filename=None): | 704 def Unzip(zip_file, filename=None): |
| 679 """Utility for temporary use of a single file in a zip archive.""" | 705 """Utility for temporary use of a single file in a zip archive.""" |
| 680 with build_utils.TempDir() as unzipped_dir: | 706 with build_utils.TempDir() as unzipped_dir: |
| 681 unzipped_files = build_utils.ExtractAll( | 707 unzipped_files = build_utils.ExtractAll( |
| 682 zip_file, unzipped_dir, True, pattern=filename) | 708 zip_file, unzipped_dir, True, pattern=filename) |
| 683 if len(unzipped_files) == 0: | 709 if len(unzipped_files) == 0: |
| 684 raise Exception( | 710 raise Exception( |
| 685 '%s not found in %s' % (filename, zip_file)) | 711 '%s not found in %s' % (filename, zip_file)) |
| 686 yield unzipped_files[0] | 712 yield unzipped_files[0] |
| (...skipping 18 matching lines...) Expand all Loading... |
| 705 help='Location of the build artifacts.') | 731 help='Location of the build artifacts.') |
| 706 argparser.add_argument('--chartjson', action='store_true', | 732 argparser.add_argument('--chartjson', action='store_true', |
| 707 help='Sets output mode to chartjson.') | 733 help='Sets output mode to chartjson.') |
| 708 argparser.add_argument('--output-dir', default='.', | 734 argparser.add_argument('--output-dir', default='.', |
| 709 help='Directory to save chartjson to.') | 735 help='Directory to save chartjson to.') |
| 710 argparser.add_argument('--no-output-dir', action='store_true', | 736 argparser.add_argument('--no-output-dir', action='store_true', |
| 711 help='Skip all measurements that rely on having ' | 737 help='Skip all measurements that rely on having ' |
| 712 'output-dir') | 738 'output-dir') |
| 713 argparser.add_argument('-d', '--device', | 739 argparser.add_argument('-d', '--device', |
| 714 help='Dummy option for perf runner.') | 740 help='Dummy option for perf runner.') |
| 741 argparser.add_argument('--estimate-patch-size', action='store_true', |
| 742 help='Include patch size estimates. Useful for perf ' |
| 743 'builders where a reference APK is available but adds ' |
| 744 '~3 mins to run time.') |
| 745 argparser.add_argument('--reference-apk-builder', |
| 746 default=apk_downloader.DEFAULT_BUILDER, |
| 747 help='Builder name to use for reference APK for patch ' |
| 748 'size estimates.') |
| 749 argparser.add_argument('--reference-apk-bucket', |
| 750 default=apk_downloader.DEFAULT_BUCKET, |
| 751 help='Storage bucket holding reference APKs.') |
| 715 argparser.add_argument('apk', help='APK file path.') | 752 argparser.add_argument('apk', help='APK file path.') |
| 716 args = argparser.parse_args() | 753 args = argparser.parse_args() |
| 717 | 754 |
| 718 chartjson = _BASE_CHART.copy() if args.chartjson else None | 755 chartjson = _BASE_CHART.copy() if args.chartjson else None |
| 719 | 756 |
| 720 if args.chromium_output_directory: | 757 if args.chromium_output_directory: |
| 721 constants.SetOutputDirectory(args.chromium_output_directory) | 758 constants.SetOutputDirectory(args.chromium_output_directory) |
| 722 if not args.no_output_dir: | 759 if not args.no_output_dir: |
| 723 constants.CheckOutputDirectory() | 760 constants.CheckOutputDirectory() |
| 724 devil_chromium.Initialize() | 761 devil_chromium.Initialize() |
| 725 build_vars = _ReadBuildVars(constants.GetOutDirectory()) | 762 build_vars = _ReadBuildVars(constants.GetOutDirectory()) |
| 726 tools_prefix = os.path.join(constants.GetOutDirectory(), | 763 tools_prefix = os.path.join(constants.GetOutDirectory(), |
| 727 build_vars['android_tool_prefix']) | 764 build_vars['android_tool_prefix']) |
| 728 else: | 765 else: |
| 729 tools_prefix = '' | 766 tools_prefix = '' |
| 730 | 767 |
| 731 PrintApkAnalysis(args.apk, tools_prefix, chartjson=chartjson) | 768 PrintApkAnalysis(args.apk, tools_prefix, chartjson=chartjson) |
| 732 _PrintDexAnalysis(args.apk, chartjson=chartjson) | 769 _PrintDexAnalysis(args.apk, chartjson=chartjson) |
| 770 if args.estimate_patch_size: |
| 771 _PrintPatchSizeEstimate(args.apk, args.reference_apk_builder, |
| 772 args.reference_apk_bucket, chartjson=chartjson) |
| 733 if not args.no_output_dir: | 773 if not args.no_output_dir: |
| 734 PrintPakAnalysis(args.apk, args.min_pak_resource_size) | 774 PrintPakAnalysis(args.apk, args.min_pak_resource_size) |
| 735 _PrintStaticInitializersCountFromApk( | 775 _PrintStaticInitializersCountFromApk( |
| 736 args.apk, tools_prefix, chartjson=chartjson) | 776 args.apk, tools_prefix, chartjson=chartjson) |
| 737 if chartjson: | 777 if chartjson: |
| 738 results_path = os.path.join(args.output_dir, 'results-chart.json') | 778 results_path = os.path.join(args.output_dir, 'results-chart.json') |
| 739 logging.critical('Dumping json to %s', results_path) | 779 logging.critical('Dumping json to %s', results_path) |
| 740 with open(results_path, 'w') as json_file: | 780 with open(results_path, 'w') as json_file: |
| 741 json.dump(chartjson, json_file) | 781 json.dump(chartjson, json_file) |
| 742 | 782 |
| 743 | 783 |
| 744 if __name__ == '__main__': | 784 if __name__ == '__main__': |
| 745 sys.exit(main()) | 785 sys.exit(main()) |
| OLD | NEW |