Chromium Code Reviews| 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 collections | 12 import collections |
| 13 import json | 13 import json |
| 14 import logging | 14 import logging |
| 15 import operator | 15 import operator |
| 16 import optparse | 16 import optparse |
| 17 import os | 17 import os |
| 18 import re | 18 import re |
| 19 import shutil | |
| 19 import struct | 20 import struct |
| 20 import sys | 21 import sys |
| 21 import tempfile | 22 import tempfile |
| 22 import zipfile | 23 import zipfile |
| 23 import zlib | 24 import zlib |
| 24 | 25 |
| 25 import devil_chromium | 26 import devil_chromium |
| 26 from devil.android.sdk import build_tools | 27 from devil.android.sdk import build_tools |
| 27 from devil.utils import cmd_helper | 28 from devil.utils import cmd_helper |
| 28 from devil.utils import lazy | 29 from devil.utils import lazy |
| (...skipping 63 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 92 'benchmark_name': 'resource_sizes', | 93 'benchmark_name': 'resource_sizes', |
| 93 'benchmark_description': 'APK resource size information.', | 94 'benchmark_description': 'APK resource size information.', |
| 94 'trace_rerun_options': [], | 95 'trace_rerun_options': [], |
| 95 'charts': {} | 96 'charts': {} |
| 96 } | 97 } |
| 97 _DUMP_STATIC_INITIALIZERS_PATH = os.path.join( | 98 _DUMP_STATIC_INITIALIZERS_PATH = os.path.join( |
| 98 host_paths.DIR_SOURCE_ROOT, 'tools', 'linux', 'dump-static-initializers.py') | 99 host_paths.DIR_SOURCE_ROOT, 'tools', 'linux', 'dump-static-initializers.py') |
| 99 # Pragma exists when enable_resource_whitelist_generation=true. | 100 # Pragma exists when enable_resource_whitelist_generation=true. |
| 100 _RC_HEADER_RE = re.compile( | 101 _RC_HEADER_RE = re.compile( |
| 101 r'^#define (?P<name>\w+) (?:_Pragma\(.*?\) )?(?P<id>\d+)$') | 102 r'^#define (?P<name>\w+) (?:_Pragma\(.*?\) )?(?P<id>\d+)$') |
| 103 _READELF_SIZES_METRICS = { | |
| 104 'text': ['text'], | |
| 105 'data': ['data', 'rodata'], | |
| 106 'relocations': ['rel.dyn', 'rel.plt', 'data.rel.ro', 'data.rel.ro.loca'], | |
| 107 'unwind': ['ARM.extab', 'ARM.exidx'], | |
| 108 'symbols': ['dynsym', 'dynstr', 'dynamic', 'shstrtab', 'got', 'plt'], | |
| 109 'other': ['hash', 'init_array', 'fini_array', 'comment', | |
|
agrieve
2017/02/02 19:45:41
I don't think the list of sections is guaranteed t
estevenson
2017/02/02 21:34:54
True! I actually meant to do something like that b
| |
| 110 'note.gnu.gold-ve', 'ARM.attributes', 'note.gnu.build-i', | |
| 111 'gnu.version', 'gnu.version_d', 'gnu.version_r'] | |
| 112 } | |
| 113 | |
| 114 | |
| 115 def _ParseReadElfSectionSize(readelf_stdout, section_name): | |
| 116 # Matches: .|section_name| Type Addr Off Size ES Flg Lk Inf Al | |
| 117 match = re.search( | |
| 118 r'\.%s\s+.*$' % re.escape(section_name), readelf_stdout, re.MULTILINE) | |
| 119 return int(match.group(0).split()[4], 16) if match else None | |
| 120 | |
| 121 | |
| 122 def _ExtractMainLibSectionSizesFromApk(apk_path, main_lib_path): | |
| 123 tmpdir = tempfile.mkdtemp(suffix='_apk_extract') | |
| 124 try: | |
| 125 with zipfile.ZipFile(apk_path, 'r') as z: | |
| 126 extracted_lib_path = z.extract(main_lib_path, tmpdir) | |
| 127 return _ComputeMainLibSectionSizes(extracted_lib_path) | |
| 128 finally: | |
| 129 shutil.rmtree(tmpdir) | |
| 130 | |
| 131 | |
| 132 def _ComputeMainLibSectionSizes(so_path): | |
| 133 stdout = cmd_helper.GetCmdOutput(['readelf', '-S', so_path]) | |
| 134 sizes = collections.defaultdict(int) | |
| 135 for metric, section_names in _READELF_SIZES_METRICS.iteritems(): | |
| 136 for section_name in section_names: | |
| 137 section_size = _ParseReadElfSectionSize(stdout, section_name) | |
| 138 if section_size: | |
| 139 sizes[metric] += section_size | |
| 140 | |
| 141 return sizes | |
| 102 | 142 |
| 103 | 143 |
| 104 def CountStaticInitializers(so_path): | 144 def CountStaticInitializers(so_path): |
| 105 # Static initializers expected in official builds. Note that this list is | 145 # Static initializers expected in official builds. Note that this list is |
| 106 # built using 'nm' on libchrome.so which results from a GCC official build | 146 # built using 'nm' on libchrome.so which results from a GCC official build |
| 107 # (i.e. Clang is not supported currently). | 147 # (i.e. Clang is not supported currently). |
| 108 def get_elf_section_size(readelf_stdout, section_name): | |
| 109 # Matches: .ctors PROGBITS 000000000516add0 5169dd0 000010 00 WA 0 0 8 | |
| 110 match = re.search(r'\.%s.*$' % re.escape(section_name), | |
| 111 readelf_stdout, re.MULTILINE) | |
| 112 if not match: | |
| 113 return (False, -1) | |
| 114 size_str = re.split(r'\W+', match.group(0))[5] | |
| 115 return (True, int(size_str, 16)) | |
| 116 | 148 |
| 117 # Find the number of files with at least one static initializer. | 149 # Find the number of files with at least one static initializer. |
| 118 # First determine if we're 32 or 64 bit | 150 # First determine if we're 32 or 64 bit |
| 119 stdout = cmd_helper.GetCmdOutput(['readelf', '-h', so_path]) | 151 stdout = cmd_helper.GetCmdOutput(['readelf', '-h', so_path]) |
| 120 elf_class_line = re.search('Class:.*$', stdout, re.MULTILINE).group(0) | 152 elf_class_line = re.search('Class:.*$', stdout, re.MULTILINE).group(0) |
| 121 elf_class = re.split(r'\W+', elf_class_line)[1] | 153 elf_class = re.split(r'\W+', elf_class_line)[1] |
| 122 if elf_class == 'ELF32': | 154 if elf_class == 'ELF32': |
| 123 word_size = 4 | 155 word_size = 4 |
| 124 else: | 156 else: |
| 125 word_size = 8 | 157 word_size = 8 |
| 126 | 158 |
| 127 # Then find the number of files with global static initializers. | 159 # Then find the number of files with global static initializers. |
| 128 # NOTE: this is very implementation-specific and makes assumptions | 160 # NOTE: this is very implementation-specific and makes assumptions |
| 129 # about how compiler and linker implement global static initializers. | 161 # about how compiler and linker implement global static initializers. |
| 130 si_count = 0 | 162 si_count = 0 |
| 131 stdout = cmd_helper.GetCmdOutput(['readelf', '-SW', so_path]) | 163 stdout = cmd_helper.GetCmdOutput(['readelf', '-SW', so_path]) |
| 132 has_init_array, init_array_size = get_elf_section_size(stdout, 'init_array') | 164 init_array_size = _ParseReadElfSectionSize(stdout, 'init_array') |
| 133 if has_init_array: | 165 if init_array_size: |
| 134 si_count = init_array_size / word_size | 166 si_count = init_array_size / word_size |
| 135 si_count = max(si_count, 0) | 167 si_count = max(si_count, 0) |
| 136 return si_count | 168 return si_count |
| 137 | 169 |
| 138 | 170 |
| 139 def GetStaticInitializers(so_path): | 171 def GetStaticInitializers(so_path): |
| 140 output = cmd_helper.GetCmdOutput([_DUMP_STATIC_INITIALIZERS_PATH, '-d', | 172 output = cmd_helper.GetCmdOutput([_DUMP_STATIC_INITIALIZERS_PATH, '-d', |
| 141 so_path]) | 173 so_path]) |
| 142 return output.splitlines() | 174 return output.splitlines() |
| 143 | 175 |
| (...skipping 214 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 358 # Size of main .so vs remaining. | 390 # Size of main .so vs remaining. |
| 359 main_lib_info = native_code.FindLargest() | 391 main_lib_info = native_code.FindLargest() |
| 360 if main_lib_info: | 392 if main_lib_info: |
| 361 main_lib_size = main_lib_info.file_size | 393 main_lib_size = main_lib_info.file_size |
| 362 ReportPerfResult(chartjson, apk_basename + '_Specifics', | 394 ReportPerfResult(chartjson, apk_basename + '_Specifics', |
| 363 'main lib size', main_lib_size, 'bytes') | 395 'main lib size', main_lib_size, 'bytes') |
| 364 secondary_size = native_code.ComputeUncompressedSize() - main_lib_size | 396 secondary_size = native_code.ComputeUncompressedSize() - main_lib_size |
| 365 ReportPerfResult(chartjson, apk_basename + '_Specifics', | 397 ReportPerfResult(chartjson, apk_basename + '_Specifics', |
| 366 'other lib size', secondary_size, 'bytes') | 398 'other lib size', secondary_size, 'bytes') |
| 367 | 399 |
| 400 main_lib_section_sizes = _ExtractMainLibSectionSizesFromApk( | |
| 401 apk_filename, main_lib_info.filename) | |
| 402 for metric_name, size in main_lib_section_sizes.iteritems(): | |
| 403 ReportPerfResult(chartjson, apk_basename + '_MainLibInfo', | |
| 404 metric_name, size, 'bytes') | |
| 405 | |
| 368 # Main metric that we want to monitor for jumps. | 406 # Main metric that we want to monitor for jumps. |
| 369 normalized_apk_size = total_apk_size | 407 normalized_apk_size = total_apk_size |
| 370 # Always look at uncompressed .dex & .so. | 408 # Always look at uncompressed .dex & .so. |
| 371 normalized_apk_size -= java_code.ComputeZippedSize() | 409 normalized_apk_size -= java_code.ComputeZippedSize() |
| 372 normalized_apk_size += java_code.ComputeUncompressedSize() | 410 normalized_apk_size += java_code.ComputeUncompressedSize() |
| 373 normalized_apk_size -= native_code.ComputeZippedSize() | 411 normalized_apk_size -= native_code.ComputeZippedSize() |
| 374 normalized_apk_size += native_code.ComputeUncompressedSize() | 412 normalized_apk_size += native_code.ComputeUncompressedSize() |
| 375 # Avoid noise caused when strings change and translations haven't yet been | 413 # Avoid noise caused when strings change and translations haven't yet been |
| 376 # updated. | 414 # updated. |
| 377 english_pak = translations.FindByPattern(r'.*/en[-_][Uu][Ss]\.l?pak') | 415 english_pak = translations.FindByPattern(r'.*/en[-_][Uu][Ss]\.l?pak') |
| (...skipping 279 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 657 | 695 |
| 658 if chartjson: | 696 if chartjson: |
| 659 results_path = os.path.join(options.output_dir, 'results-chart.json') | 697 results_path = os.path.join(options.output_dir, 'results-chart.json') |
| 660 logging.critical('Dumping json to %s', results_path) | 698 logging.critical('Dumping json to %s', results_path) |
| 661 with open(results_path, 'w') as json_file: | 699 with open(results_path, 'w') as json_file: |
| 662 json.dump(chartjson, json_file) | 700 json.dump(chartjson, json_file) |
| 663 | 701 |
| 664 | 702 |
| 665 if __name__ == '__main__': | 703 if __name__ == '__main__': |
| 666 sys.exit(main(sys.argv)) | 704 sys.exit(main(sys.argv)) |
| OLD | NEW |