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 |