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 | |
27 import devil_chromium | 26 import devil_chromium |
28 from devil.android.sdk import build_tools | 27 from devil.android.sdk import build_tools |
29 from devil.utils import cmd_helper | 28 from devil.utils import cmd_helper |
30 from devil.utils import lazy | 29 from devil.utils import lazy |
31 import method_count | 30 import method_count |
32 from pylib import constants | 31 from pylib import constants |
33 from pylib.constants import host_paths | 32 from pylib.constants import host_paths |
34 | 33 |
35 _AAPT_PATH = lazy.WeakConstant(lambda: build_tools.GetPath('aapt')) | 34 _AAPT_PATH = lazy.WeakConstant(lambda: build_tools.GetPath('aapt')) |
36 _GRIT_PATH = os.path.join(host_paths.DIR_SOURCE_ROOT, 'tools', 'grit') | 35 _GRIT_PATH = os.path.join(host_paths.DIR_SOURCE_ROOT, 'tools', 'grit') |
37 _BUILD_UTILS_PATH = os.path.join( | 36 _BUILD_UTILS_PATH = os.path.join( |
38 host_paths.DIR_SOURCE_ROOT, 'build', 'android', 'gyp') | 37 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') | |
41 | 38 |
42 # Prepend the grit module from the source tree so it takes precedence over other | 39 # Prepend the grit module from the source tree so it takes precedence over other |
43 # grit versions that might present in the search path. | 40 # grit versions that might present in the search path. |
44 with host_paths.SysPath(_GRIT_PATH, 1): | 41 with host_paths.SysPath(_GRIT_PATH, 1): |
45 from grit.format import data_pack # pylint: disable=import-error | 42 from grit.format import data_pack # pylint: disable=import-error |
46 | 43 |
47 with host_paths.SysPath(host_paths.BUILD_COMMON_PATH): | 44 with host_paths.SysPath(host_paths.BUILD_COMMON_PATH): |
48 import perf_tests_results_helper # pylint: disable=import-error | 45 import perf_tests_results_helper # pylint: disable=import-error |
49 | 46 |
50 with host_paths.SysPath(_BUILD_UTILS_PATH, 1): | 47 with host_paths.SysPath(_BUILD_UTILS_PATH, 1): |
51 from util import build_utils # pylint: disable=import-error | 48 from util import build_utils # pylint: disable=import-error |
52 | 49 |
53 with host_paths.SysPath(_APK_PATCH_SIZE_ESTIMATOR_PATH): | |
54 import apk_patch_size_estimator # pylint: disable=import-error | |
55 | |
56 | 50 |
57 # Python had a bug in zipinfo parsing that triggers on ChromeModern.apk | 51 # Python had a bug in zipinfo parsing that triggers on ChromeModern.apk |
58 # https://bugs.python.org/issue14315 | 52 # https://bugs.python.org/issue14315 |
59 def _PatchedDecodeExtra(self): | 53 def _PatchedDecodeExtra(self): |
60 # Try to decode the extra field. | 54 # Try to decode the extra field. |
61 extra = self.extra | 55 extra = self.extra |
62 unpack = struct.unpack | 56 unpack = struct.unpack |
63 while len(extra) >= 4: | 57 while len(extra) >= 4: |
64 tp, ln = unpack('<HH', extra[:4]) | 58 tp, ln = unpack('<HH', extra[:4]) |
65 if tp == 1: | 59 if tp == 1: |
(...skipping 607 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
673 graph_title = os.path.basename(apk_filename) + '_Dex' | 667 graph_title = os.path.basename(apk_filename) + '_Dex' |
674 dex_metrics = method_count.CONTRIBUTORS_TO_DEX_CACHE | 668 dex_metrics = method_count.CONTRIBUTORS_TO_DEX_CACHE |
675 for key, label in dex_metrics.iteritems(): | 669 for key, label in dex_metrics.iteritems(): |
676 ReportPerfResult(chartjson, graph_title, label, sizes[key], 'entries') | 670 ReportPerfResult(chartjson, graph_title, label, sizes[key], 'entries') |
677 | 671 |
678 graph_title = '%sCache' % graph_title | 672 graph_title = '%sCache' % graph_title |
679 ReportPerfResult(chartjson, graph_title, 'DexCache', sizes['dex_cache_size'], | 673 ReportPerfResult(chartjson, graph_title, 'DexCache', sizes['dex_cache_size'], |
680 'bytes') | 674 'bytes') |
681 | 675 |
682 | 676 |
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 | |
703 @contextmanager | 677 @contextmanager |
704 def Unzip(zip_file, filename=None): | 678 def Unzip(zip_file, filename=None): |
705 """Utility for temporary use of a single file in a zip archive.""" | 679 """Utility for temporary use of a single file in a zip archive.""" |
706 with build_utils.TempDir() as unzipped_dir: | 680 with build_utils.TempDir() as unzipped_dir: |
707 unzipped_files = build_utils.ExtractAll( | 681 unzipped_files = build_utils.ExtractAll( |
708 zip_file, unzipped_dir, True, pattern=filename) | 682 zip_file, unzipped_dir, True, pattern=filename) |
709 if len(unzipped_files) == 0: | 683 if len(unzipped_files) == 0: |
710 raise Exception( | 684 raise Exception( |
711 '%s not found in %s' % (filename, zip_file)) | 685 '%s not found in %s' % (filename, zip_file)) |
712 yield unzipped_files[0] | 686 yield unzipped_files[0] |
(...skipping 18 matching lines...) Expand all Loading... |
731 help='Location of the build artifacts.') | 705 help='Location of the build artifacts.') |
732 argparser.add_argument('--chartjson', action='store_true', | 706 argparser.add_argument('--chartjson', action='store_true', |
733 help='Sets output mode to chartjson.') | 707 help='Sets output mode to chartjson.') |
734 argparser.add_argument('--output-dir', default='.', | 708 argparser.add_argument('--output-dir', default='.', |
735 help='Directory to save chartjson to.') | 709 help='Directory to save chartjson to.') |
736 argparser.add_argument('--no-output-dir', action='store_true', | 710 argparser.add_argument('--no-output-dir', action='store_true', |
737 help='Skip all measurements that rely on having ' | 711 help='Skip all measurements that rely on having ' |
738 'output-dir') | 712 'output-dir') |
739 argparser.add_argument('-d', '--device', | 713 argparser.add_argument('-d', '--device', |
740 help='Dummy option for perf runner.') | 714 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.') | |
752 argparser.add_argument('apk', help='APK file path.') | 715 argparser.add_argument('apk', help='APK file path.') |
753 args = argparser.parse_args() | 716 args = argparser.parse_args() |
754 | 717 |
755 chartjson = _BASE_CHART.copy() if args.chartjson else None | 718 chartjson = _BASE_CHART.copy() if args.chartjson else None |
756 | 719 |
757 if args.chromium_output_directory: | 720 if args.chromium_output_directory: |
758 constants.SetOutputDirectory(args.chromium_output_directory) | 721 constants.SetOutputDirectory(args.chromium_output_directory) |
759 if not args.no_output_dir: | 722 if not args.no_output_dir: |
760 constants.CheckOutputDirectory() | 723 constants.CheckOutputDirectory() |
761 devil_chromium.Initialize() | 724 devil_chromium.Initialize() |
762 build_vars = _ReadBuildVars(constants.GetOutDirectory()) | 725 build_vars = _ReadBuildVars(constants.GetOutDirectory()) |
763 tools_prefix = build_vars['android_tool_prefix'] | 726 tools_prefix = build_vars['android_tool_prefix'] |
764 else: | 727 else: |
765 tools_prefix = '' | 728 tools_prefix = '' |
766 | 729 |
767 PrintApkAnalysis(args.apk, tools_prefix, chartjson=chartjson) | 730 PrintApkAnalysis(args.apk, tools_prefix, chartjson=chartjson) |
768 _PrintDexAnalysis(args.apk, chartjson=chartjson) | 731 _PrintDexAnalysis(args.apk, chartjson=chartjson) |
769 if args.estimate_patch_size: | |
770 _PrintPatchSizeEstimate( | |
771 args.apk, args.builder, args.bucket, chartjson=chartjson) | |
772 if not args.no_output_dir: | 732 if not args.no_output_dir: |
773 PrintPakAnalysis(args.apk, args.min_pak_resource_size) | 733 PrintPakAnalysis(args.apk, args.min_pak_resource_size) |
774 _PrintStaticInitializersCountFromApk( | 734 _PrintStaticInitializersCountFromApk( |
775 args.apk, tools_prefix, chartjson=chartjson) | 735 args.apk, tools_prefix, chartjson=chartjson) |
776 if chartjson: | 736 if chartjson: |
777 results_path = os.path.join(args.output_dir, 'results-chart.json') | 737 results_path = os.path.join(args.output_dir, 'results-chart.json') |
778 logging.critical('Dumping json to %s', results_path) | 738 logging.critical('Dumping json to %s', results_path) |
779 with open(results_path, 'w') as json_file: | 739 with open(results_path, 'w') as json_file: |
780 json.dump(chartjson, json_file) | 740 json.dump(chartjson, json_file) |
781 | 741 |
782 | 742 |
783 if __name__ == '__main__': | 743 if __name__ == '__main__': |
784 sys.exit(main()) | 744 sys.exit(main()) |
OLD | NEW |