Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(397)

Side by Side Diff: build/android/resource_sizes.py

Issue 2706243013: Android: improve static initializer counting in resource_sizes.py. (Closed)
Patch Set: Created 3 years, 10 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
« no previous file with comments | « build/android/gyp/util/build_utils.py ('k') | no next file » | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
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 from contextlib import contextmanager
13 import json 14 import json
14 import logging 15 import logging
15 import operator 16 import operator
16 import optparse 17 import optparse
17 import os 18 import os
18 import re 19 import re
19 import shutil
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 import devil_chromium 26 import devil_chromium
27 from devil.android.sdk import build_tools 27 from devil.android.sdk import build_tools
28 from devil.utils import cmd_helper 28 from devil.utils import cmd_helper
29 from devil.utils import lazy 29 from devil.utils import lazy
30 import method_count 30 import method_count
31 from pylib import constants 31 from pylib import constants
32 from pylib.constants import host_paths 32 from pylib.constants import host_paths
33 33
34 _AAPT_PATH = lazy.WeakConstant(lambda: build_tools.GetPath('aapt')) 34 _AAPT_PATH = lazy.WeakConstant(lambda: build_tools.GetPath('aapt'))
35 _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')
36 _BUILD_UTILS_PATH = os.path.join(
37 host_paths.DIR_SOURCE_ROOT, 'build', 'android', 'gyp')
36 38
37 # 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
38 # grit versions that might present in the search path. 40 # grit versions that might present in the search path.
39 with host_paths.SysPath(_GRIT_PATH, 1): 41 with host_paths.SysPath(_GRIT_PATH, 1):
40 from grit.format import data_pack # pylint: disable=import-error 42 from grit.format import data_pack # pylint: disable=import-error
41 43
42 with host_paths.SysPath(host_paths.BUILD_COMMON_PATH): 44 with host_paths.SysPath(host_paths.BUILD_COMMON_PATH):
43 import perf_tests_results_helper # pylint: disable=import-error 45 import perf_tests_results_helper # pylint: disable=import-error
44 46
47 with host_paths.SysPath(_BUILD_UTILS_PATH, 1):
48 from util import build_utils # pylint: disable=import-error
49
45 50
46 # 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
47 # https://bugs.python.org/issue14315 52 # https://bugs.python.org/issue14315
48 def _PatchedDecodeExtra(self): 53 def _PatchedDecodeExtra(self):
49 # Try to decode the extra field. 54 # Try to decode the extra field.
50 extra = self.extra 55 extra = self.extra
51 unpack = struct.unpack 56 unpack = struct.unpack
52 while len(extra) >= 4: 57 while len(extra) >= 4:
53 tp, ln = unpack('<HH', extra[:4]) 58 tp, ln = unpack('<HH', extra[:4])
54 if tp == 1: 59 if tp == 1:
(...skipping 53 matching lines...) Expand 10 before | Expand all | Expand 10 after
108 'symbols': ['.dynsym', '.dynstr', '.dynamic', '.shstrtab', '.got', '.plt', 113 'symbols': ['.dynsym', '.dynstr', '.dynamic', '.shstrtab', '.got', '.plt',
109 '.got.plt', '.hash'], 114 '.got.plt', '.hash'],
110 'bss': ['.bss'], 115 'bss': ['.bss'],
111 'other': ['.init_array', '.fini_array', '.comment', '.note.gnu.gold-version', 116 'other': ['.init_array', '.fini_array', '.comment', '.note.gnu.gold-version',
112 '.ARM.attributes', '.note.gnu.build-id', '.gnu.version', 117 '.ARM.attributes', '.note.gnu.build-id', '.gnu.version',
113 '.gnu.version_d', '.gnu.version_r', '.interp', '.gcc_except_table'] 118 '.gnu.version_d', '.gnu.version_r', '.interp', '.gcc_except_table']
114 } 119 }
115 120
116 121
117 def _ExtractMainLibSectionSizesFromApk(apk_path, main_lib_path): 122 def _ExtractMainLibSectionSizesFromApk(apk_path, main_lib_path):
118 tmpdir = tempfile.mkdtemp(suffix='_apk_extract') 123 with Unzip(apk_path, pattern=main_lib_path) as extracted_lib_path:
119 grouped_section_sizes = collections.defaultdict(int) 124 grouped_section_sizes = collections.defaultdict(int)
120 try: 125 section_sizes = _CreateSectionNameSizeMap(extracted_lib_path)
121 with zipfile.ZipFile(apk_path, 'r') as z: 126 for group_name, section_names in _READELF_SIZES_METRICS.iteritems():
122 extracted_lib_path = z.extract(main_lib_path, tmpdir) 127 for section_name in section_names:
123 section_sizes = _CreateSectionNameSizeMap(extracted_lib_path) 128 if section_name in section_sizes:
129 grouped_section_sizes[group_name] += section_sizes.pop(section_name)
124 130
125 for group_name, section_names in _READELF_SIZES_METRICS.iteritems(): 131 # Group any unknown section headers into the "other" group.
126 for section_name in section_names: 132 for section_header, section_size in section_sizes.iteritems():
127 if section_name in section_sizes: 133 print "Unknown elf section header:", section_header
128 grouped_section_sizes[group_name] += section_sizes.pop(section_name) 134 grouped_section_sizes['other'] += section_size
129 135
130 # Group any unknown section headers into the "other" group. 136 return grouped_section_sizes
131 for section_header, section_size in section_sizes.iteritems():
132 print "Unknown elf section header:", section_header
133 grouped_section_sizes['other'] += section_size
134
135 return grouped_section_sizes
136 finally:
137 shutil.rmtree(tmpdir)
138 137
139 138
140 def _CreateSectionNameSizeMap(so_path): 139 def _CreateSectionNameSizeMap(so_path):
141 stdout = cmd_helper.GetCmdOutput(['readelf', '-S', '--wide', so_path]) 140 stdout = cmd_helper.GetCmdOutput(['readelf', '-S', '--wide', so_path])
142 section_sizes = {} 141 section_sizes = {}
143 # Matches [ 2] .hash HASH 00000000006681f0 0001f0 003154 04 A 3 0 8 142 # Matches [ 2] .hash HASH 00000000006681f0 0001f0 003154 04 A 3 0 8
144 for match in re.finditer(r'\[[\s\d]+\] (\..*)$', stdout, re.MULTILINE): 143 for match in re.finditer(r'\[[\s\d]+\] (\..*)$', stdout, re.MULTILINE):
145 items = match.group(1).split() 144 items = match.group(1).split()
146 section_sizes[items[0]] = int(items[4], 16) 145 section_sizes[items[0]] = int(items[4], 16)
147 146
148 return section_sizes 147 return section_sizes
149 148
150 149
150 def _ParseLibBuildId(so_path):
151 """Returns the Build ID of the given native library."""
152 stdout = cmd_helper.GetCmdOutput(['readelf', '-n', so_path])
153 match = re.search(r'Build ID: (\w{40})', stdout)
agrieve 2017/02/24 20:04:21 nit: BuildIDs don't need to be 40 characters (alth
estevenson 2017/02/28 00:58:45 Left it in. Done.
154 return match.group(1) if match else None
155
156
151 def CountStaticInitializers(so_path): 157 def CountStaticInitializers(so_path):
152 # Static initializers expected in official builds. Note that this list is 158 # Static initializers expected in official builds. Note that this list is
153 # built using 'nm' on libchrome.so which results from a GCC official build 159 # built using 'nm' on libchrome.so which results from a GCC official build
154 # (i.e. Clang is not supported currently). 160 # (i.e. Clang is not supported currently).
155 def get_elf_section_size(readelf_stdout, section_name): 161 def get_elf_section_size(readelf_stdout, section_name):
156 # Matches: .ctors PROGBITS 000000000516add0 5169dd0 000010 00 WA 0 0 8 162 # Matches: .ctors PROGBITS 000000000516add0 5169dd0 000010 00 WA 0 0 8
157 match = re.search(r'\.%s.*$' % re.escape(section_name), 163 match = re.search(r'\.%s.*$' % re.escape(section_name),
158 readelf_stdout, re.MULTILINE) 164 readelf_stdout, re.MULTILINE)
159 if not match: 165 if not match:
160 return (False, -1) 166 return (False, -1)
(...skipping 427 matching lines...) Expand 10 before | Expand all | Expand 10 after
588 with zipfile.ZipFile(apk_filename) as z: 594 with zipfile.ZipFile(apk_filename) as z:
589 infolist = z.infolist() 595 infolist = z.infolist()
590 out_dir = constants.GetOutDirectory() 596 out_dir = constants.GetOutDirectory()
591 si_count = 0 597 si_count = 0
592 for zip_info in infolist: 598 for zip_info in infolist:
593 # Check file size to account for placeholder libraries. 599 # Check file size to account for placeholder libraries.
594 if zip_info.filename.endswith('.so') and zip_info.file_size > 0: 600 if zip_info.filename.endswith('.so') and zip_info.file_size > 0:
595 lib_name = os.path.basename(zip_info.filename).replace('crazy.', '') 601 lib_name = os.path.basename(zip_info.filename).replace('crazy.', '')
596 unstripped_path = os.path.join(out_dir, 'lib.unstripped', lib_name) 602 unstripped_path = os.path.join(out_dir, 'lib.unstripped', lib_name)
597 if os.path.exists(unstripped_path): 603 if os.path.exists(unstripped_path):
598 si_count += _PrintStaticInitializersCount(unstripped_path) 604 si_count += _PrintStaticInitializersCount(
605 apk_filename, zip_info.filename, unstripped_path)
599 else: 606 else:
600 raise Exception('Unstripped .so not found. Looked here: %s', 607 raise Exception('Unstripped .so not found. Looked here: %s',
601 unstripped_path) 608 unstripped_path)
602 ReportPerfResult(chartjson, 'StaticInitializersCount', 'count', si_count, 609 ReportPerfResult(chartjson, 'StaticInitializersCount', 'count', si_count,
603 'count') 610 'count')
604 611
605 612
606 def _PrintStaticInitializersCount(so_with_symbols_path): 613 def _PrintStaticInitializersCount(apk_path, apk_so_name, so_with_symbols_path):
607 """Counts the number of static initializers in the given shared library. 614 """Counts the number of static initializers in the given shared library.
608 Additionally, files for which static initializers were found are printed 615 Additionally, files for which static initializers were found are printed
609 on the standard output. 616 on the standard output.
610 617
611 Args: 618 Args:
612 so_with_symbols_path: Path to the unstripped libchrome.so file. 619 apk_path: Path to the apk.
613 620 apk_so_name: Name of the so.
621 so_with_symbols_path: Path to the unstripped libchrome.so file.
614 Returns: 622 Returns:
615 The number of static initializers found. 623 The number of static initializers found.
616 """ 624 """
617 # GetStaticInitializers uses get-static-initializers.py to get a list of all 625 # GetStaticInitializers uses get-static-initializers.py to get a list of all
618 # static initializers. This does not work on all archs (particularly arm). 626 # static initializers. This does not work on all archs (particularly arm).
619 # TODO(rnephew): Get rid of warning when crbug.com/585588 is fixed. 627 # TODO(rnephew): Get rid of warning when crbug.com/585588 is fixed.
620 si_count = CountStaticInitializers(so_with_symbols_path) 628 with Unzip(apk_path, pattern="*%s" % apk_so_name) as unzipped_so:
629 _VerifyLibBuildIdsMatch(unzipped_so, so_with_symbols_path)
630 si_count = CountStaticInitializers(unzipped_so)
621 static_initializers = GetStaticInitializers(so_with_symbols_path) 631 static_initializers = GetStaticInitializers(so_with_symbols_path)
622 static_initializers_count = len(static_initializers) - 1 # Minus summary. 632 static_initializers_count = len(static_initializers) - 1 # Minus summary.
623 if si_count != static_initializers_count: 633 if si_count != static_initializers_count:
624 print ('There are %d files with static initializers, but ' 634 print ('There are %d files with static initializers, but '
625 'dump-static-initializers found %d:' % 635 'dump-static-initializers found %d:' %
626 (si_count, static_initializers_count)) 636 (si_count, static_initializers_count))
627 else: 637 else:
628 print '%s - Found %d files with static initializers:' % ( 638 print '%s - Found %d files with static initializers:' % (
629 os.path.basename(so_with_symbols_path), si_count) 639 os.path.basename(so_with_symbols_path), si_count)
630 print '\n'.join(static_initializers) 640 print '\n'.join(static_initializers)
(...skipping 28 matching lines...) Expand all
659 graph_title = os.path.basename(apk_filename) + '_Dex' 669 graph_title = os.path.basename(apk_filename) + '_Dex'
660 dex_metrics = method_count.CONTRIBUTORS_TO_DEX_CACHE 670 dex_metrics = method_count.CONTRIBUTORS_TO_DEX_CACHE
661 for key, label in dex_metrics.iteritems(): 671 for key, label in dex_metrics.iteritems():
662 ReportPerfResult(chartjson, graph_title, label, sizes[key], 'entries') 672 ReportPerfResult(chartjson, graph_title, label, sizes[key], 'entries')
663 673
664 graph_title = '%sCache' % graph_title 674 graph_title = '%sCache' % graph_title
665 ReportPerfResult(chartjson, graph_title, 'DexCache', sizes['dex_cache_size'], 675 ReportPerfResult(chartjson, graph_title, 'DexCache', sizes['dex_cache_size'],
666 'bytes') 676 'bytes')
667 677
668 678
679 @contextmanager
680 def Unzip(zip_file, pattern=None, predicate=None):
681 """Utility for temporary use of a set of files in a zip archive."""
682 with build_utils.TempDir() as unzipped_dir:
683 unzipped_files = build_utils.ExtractAll(
684 zip_file, unzipped_dir, True, pattern=pattern, predicate=predicate)
685 yield unzipped_files[0] if len(unzipped_files) == 1 else unzipped_files
686
687
688 def _VerifyLibBuildIdsMatch(*so_files):
689 if len(set(_ParseLibBuildId(f) for f in so_files)) > 1:
690 raise Exception('Found differing build ids in output directory and apk. '
691 'Your output directory is likely stale.')
692
693
669 def main(argv): 694 def main(argv):
670 usage = """Usage: %prog [options] file1 file2 ... 695 usage = """Usage: %prog [options] file1 file2 ...
671 696
672 Pass any number of files to graph their sizes. Any files with the extension 697 Pass any number of files to graph their sizes. Any files with the extension
673 '.apk' will be broken down into their components on a separate graph.""" 698 '.apk' will be broken down into their components on a separate graph."""
674 option_parser = optparse.OptionParser(usage=usage) 699 option_parser = optparse.OptionParser(usage=usage)
675 option_parser.add_option('--so-path', 700 option_parser.add_option('--so-path',
676 help='Obsolete. Pass .so as positional arg instead.') 701 help='Obsolete. Pass .so as positional arg instead.')
677 option_parser.add_option('--so-with-symbols-path', 702 option_parser.add_option('--so-with-symbols-path',
678 help='Mostly obsolete. Use .so within .apk instead.') 703 help='Mostly obsolete. Use .so within .apk instead.')
(...skipping 27 matching lines...) Expand all
706 731
707 # For backward compatibilty with buildbot scripts, treat --so-path as just 732 # For backward compatibilty with buildbot scripts, treat --so-path as just
708 # another file to print the size of. We don't need it for anything special any 733 # another file to print the size of. We don't need it for anything special any
709 # more. 734 # more.
710 if options.so_path: 735 if options.so_path:
711 files.append(options.so_path) 736 files.append(options.so_path)
712 737
713 if not files: 738 if not files:
714 option_parser.error('Must specify a file') 739 option_parser.error('Must specify a file')
715 740
716 if options.so_with_symbols_path:
717 si_count = _PrintStaticInitializersCount(options.so_with_symbols_path)
718 ReportPerfResult(chartjson, 'StaticInitializersCount', 'count', si_count,
719 'count')
720
721 PrintResourceSizes(files, chartjson=chartjson) 741 PrintResourceSizes(files, chartjson=chartjson)
722 742
723 for f in files: 743 for f in files:
724 if f.endswith('.apk'): 744 if f.endswith('.apk'):
725 PrintApkAnalysis(f, chartjson=chartjson) 745 PrintApkAnalysis(f, chartjson=chartjson)
726 _PrintDexAnalysis(f, chartjson=chartjson) 746 _PrintDexAnalysis(f, chartjson=chartjson)
727 if not options.no_output_dir: 747 if not options.no_output_dir:
728 PrintPakAnalysis(f, options.min_pak_resource_size) 748 PrintPakAnalysis(f, options.min_pak_resource_size)
729 if not options.so_with_symbols_path: 749 so_path = options.so_with_symbols_path
agrieve 2017/02/24 20:04:21 This actually makes less sense inside the loop. Ho
estevenson 2017/02/28 00:58:45 I removed obsolete args and changed the script to
750 if so_path:
751 si_count = _PrintStaticInitializersCount(
752 f, os.path.basename(so_path), so_path)
753 ReportPerfResult(
754 chartjson, 'StaticInitializersCount', 'count', si_count, 'count')
755 else:
730 _PrintStaticInitializersCountFromApk(f, chartjson=chartjson) 756 _PrintStaticInitializersCountFromApk(f, chartjson=chartjson)
731 757
732 if chartjson: 758 if chartjson:
733 results_path = os.path.join(options.output_dir, 'results-chart.json') 759 results_path = os.path.join(options.output_dir, 'results-chart.json')
734 logging.critical('Dumping json to %s', results_path) 760 logging.critical('Dumping json to %s', results_path)
735 with open(results_path, 'w') as json_file: 761 with open(results_path, 'w') as json_file:
736 json.dump(chartjson, json_file) 762 json.dump(chartjson, json_file)
737 763
738 764
739 if __name__ == '__main__': 765 if __name__ == '__main__':
740 sys.exit(main(sys.argv)) 766 sys.exit(main(sys.argv))
OLDNEW
« no previous file with comments | « build/android/gyp/util/build_utils.py ('k') | no next file » | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698