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 collections | 13 import collections |
| 14 from contextlib import contextmanager |
13 import json | 15 import json |
14 import logging | 16 import logging |
15 import operator | 17 import operator |
16 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 52 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
107 'unwind': ['.ARM.extab', '.ARM.exidx', '.eh_frame', '.eh_frame_hdr',], | 112 'unwind': ['.ARM.extab', '.ARM.exidx', '.eh_frame', '.eh_frame_hdr',], |
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 _RunReadelf(so_path, options, tools_prefix=''): |
118 tmpdir = tempfile.mkdtemp(suffix='_apk_extract') | 123 return cmd_helper.GetCmdOutput( |
119 grouped_section_sizes = collections.defaultdict(int) | 124 [tools_prefix + 'readelf'] + options + [so_path]) |
120 try: | |
121 with zipfile.ZipFile(apk_path, 'r') as z: | |
122 extracted_lib_path = z.extract(main_lib_path, tmpdir) | |
123 section_sizes = _CreateSectionNameSizeMap(extracted_lib_path) | |
124 | |
125 for group_name, section_names in _READELF_SIZES_METRICS.iteritems(): | |
126 for section_name in section_names: | |
127 if section_name in section_sizes: | |
128 grouped_section_sizes[group_name] += section_sizes.pop(section_name) | |
129 | |
130 # Group any unknown section headers into the "other" group. | |
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 | 125 |
139 | 126 |
140 def _CreateSectionNameSizeMap(so_path): | 127 def _ExtractMainLibSectionSizesFromApk(apk_path, main_lib_path, tools_prefix): |
141 stdout = cmd_helper.GetCmdOutput(['readelf', '-S', '--wide', so_path]) | 128 with Unzip(apk_path, filename=main_lib_path) as extracted_lib_path: |
| 129 grouped_section_sizes = collections.defaultdict(int) |
| 130 section_sizes = _CreateSectionNameSizeMap(extracted_lib_path, tools_prefix) |
| 131 for group_name, section_names in _READELF_SIZES_METRICS.iteritems(): |
| 132 for section_name in section_names: |
| 133 if section_name in section_sizes: |
| 134 grouped_section_sizes[group_name] += section_sizes.pop(section_name) |
| 135 |
| 136 # Group any unknown section headers into the "other" group. |
| 137 for section_header, section_size in section_sizes.iteritems(): |
| 138 print "Unknown elf section header:", section_header |
| 139 grouped_section_sizes['other'] += section_size |
| 140 |
| 141 return grouped_section_sizes |
| 142 |
| 143 |
| 144 def _CreateSectionNameSizeMap(so_path, tools_prefix): |
| 145 stdout = _RunReadelf(so_path, ['-S', '--wide'], tools_prefix) |
142 section_sizes = {} | 146 section_sizes = {} |
143 # Matches [ 2] .hash HASH 00000000006681f0 0001f0 003154 04 A 3 0 8 | 147 # Matches [ 2] .hash HASH 00000000006681f0 0001f0 003154 04 A 3 0 8 |
144 for match in re.finditer(r'\[[\s\d]+\] (\..*)$', stdout, re.MULTILINE): | 148 for match in re.finditer(r'\[[\s\d]+\] (\..*)$', stdout, re.MULTILINE): |
145 items = match.group(1).split() | 149 items = match.group(1).split() |
146 section_sizes[items[0]] = int(items[4], 16) | 150 section_sizes[items[0]] = int(items[4], 16) |
147 | 151 |
148 return section_sizes | 152 return section_sizes |
149 | 153 |
150 | 154 |
151 def CountStaticInitializers(so_path): | 155 def _ParseLibBuildId(so_path, tools_prefix): |
| 156 """Returns the Build ID of the given native library.""" |
| 157 stdout = _RunReadelf(so_path, ['n'], tools_prefix) |
| 158 match = re.search(r'Build ID: (\w+)', stdout) |
| 159 return match.group(1) if match else None |
| 160 |
| 161 |
| 162 def CountStaticInitializers(so_path, tools_prefix): |
152 # Static initializers expected in official builds. Note that this list is | 163 # 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 | 164 # built using 'nm' on libchrome.so which results from a GCC official build |
154 # (i.e. Clang is not supported currently). | 165 # (i.e. Clang is not supported currently). |
155 def get_elf_section_size(readelf_stdout, section_name): | 166 def get_elf_section_size(readelf_stdout, section_name): |
156 # Matches: .ctors PROGBITS 000000000516add0 5169dd0 000010 00 WA 0 0 8 | 167 # Matches: .ctors PROGBITS 000000000516add0 5169dd0 000010 00 WA 0 0 8 |
157 match = re.search(r'\.%s.*$' % re.escape(section_name), | 168 match = re.search(r'\.%s.*$' % re.escape(section_name), |
158 readelf_stdout, re.MULTILINE) | 169 readelf_stdout, re.MULTILINE) |
159 if not match: | 170 if not match: |
160 return (False, -1) | 171 return (False, -1) |
161 size_str = re.split(r'\W+', match.group(0))[5] | 172 size_str = re.split(r'\W+', match.group(0))[5] |
162 return (True, int(size_str, 16)) | 173 return (True, int(size_str, 16)) |
163 | 174 |
164 # Find the number of files with at least one static initializer. | 175 # Find the number of files with at least one static initializer. |
165 # First determine if we're 32 or 64 bit | 176 # First determine if we're 32 or 64 bit |
166 stdout = cmd_helper.GetCmdOutput(['readelf', '-h', so_path]) | 177 stdout = _RunReadelf(so_path, ['-h'], tools_prefix) |
167 elf_class_line = re.search('Class:.*$', stdout, re.MULTILINE).group(0) | 178 elf_class_line = re.search('Class:.*$', stdout, re.MULTILINE).group(0) |
168 elf_class = re.split(r'\W+', elf_class_line)[1] | 179 elf_class = re.split(r'\W+', elf_class_line)[1] |
169 if elf_class == 'ELF32': | 180 if elf_class == 'ELF32': |
170 word_size = 4 | 181 word_size = 4 |
171 else: | 182 else: |
172 word_size = 8 | 183 word_size = 8 |
173 | 184 |
174 # Then find the number of files with global static initializers. | 185 # Then find the number of files with global static initializers. |
175 # NOTE: this is very implementation-specific and makes assumptions | 186 # NOTE: this is very implementation-specific and makes assumptions |
176 # about how compiler and linker implement global static initializers. | 187 # about how compiler and linker implement global static initializers. |
177 si_count = 0 | 188 si_count = 0 |
178 stdout = cmd_helper.GetCmdOutput(['readelf', '-SW', so_path]) | 189 stdout = _RunReadelf(so_path, ['-SW'], tools_prefix) |
179 has_init_array, init_array_size = get_elf_section_size(stdout, 'init_array') | 190 has_init_array, init_array_size = get_elf_section_size(stdout, 'init_array') |
180 if has_init_array: | 191 if has_init_array: |
181 si_count = init_array_size / word_size | 192 si_count = init_array_size / word_size |
182 si_count = max(si_count, 0) | 193 si_count = max(si_count, 0) |
183 return si_count | 194 return si_count |
184 | 195 |
185 | 196 |
186 def GetStaticInitializers(so_path): | 197 def GetStaticInitializers(so_path, tools_prefix): |
187 output = cmd_helper.GetCmdOutput([_DUMP_STATIC_INITIALIZERS_PATH, '-d', | 198 output = cmd_helper.GetCmdOutput([_DUMP_STATIC_INITIALIZERS_PATH, '-d', |
188 so_path]) | 199 so_path, '-t', tools_prefix]) |
189 return output.splitlines() | 200 summary = re.search(r'Found \d+ static initializers in (\d+) files.', output) |
| 201 return output.splitlines()[:-1], int(summary.group(1)) |
190 | 202 |
191 | 203 |
192 def _NormalizeResourcesArsc(apk_path): | 204 def _NormalizeResourcesArsc(apk_path): |
193 """Estimates the expected overhead of untranslated strings in resources.arsc. | 205 """Estimates the expected overhead of untranslated strings in resources.arsc. |
194 | 206 |
195 See http://crbug.com/677966 for why this is necessary. | 207 See http://crbug.com/677966 for why this is necessary. |
196 """ | 208 """ |
197 aapt_output = _RunAaptDumpResources(apk_path) | 209 aapt_output = _RunAaptDumpResources(apk_path) |
198 | 210 |
199 # en-rUS is in the default config and may be cluttered with non-translatable | 211 # en-rUS is in the default config and may be cluttered with non-translatable |
(...skipping 51 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
251 'value': value, | 263 'value': value, |
252 'units': units, | 264 'units': units, |
253 'improvement_direction': improvement_direction, | 265 'improvement_direction': improvement_direction, |
254 'important': important | 266 'important': important |
255 } | 267 } |
256 else: | 268 else: |
257 perf_tests_results_helper.PrintPerfResult( | 269 perf_tests_results_helper.PrintPerfResult( |
258 graph_title, trace_title, [value], units) | 270 graph_title, trace_title, [value], units) |
259 | 271 |
260 | 272 |
261 def PrintResourceSizes(files, chartjson=None): | |
262 """Prints the sizes of each given file. | |
263 | |
264 Args: | |
265 files: List of files to print sizes for. | |
266 """ | |
267 for f in files: | |
268 ReportPerfResult(chartjson, 'ResourceSizes', os.path.basename(f) + ' size', | |
269 os.path.getsize(f), 'bytes') | |
270 | |
271 | |
272 class _FileGroup(object): | 273 class _FileGroup(object): |
273 """Represents a category that apk files can fall into.""" | 274 """Represents a category that apk files can fall into.""" |
274 | 275 |
275 def __init__(self, name): | 276 def __init__(self, name): |
276 self.name = name | 277 self.name = name |
277 self._zip_infos = [] | 278 self._zip_infos = [] |
278 self._extracted = [] | 279 self._extracted = [] |
279 | 280 |
280 def AddZipInfo(self, zip_info, extracted=False): | 281 def AddZipInfo(self, zip_info, extracted=False): |
281 self._zip_infos.append(zip_info) | 282 self._zip_infos.append(zip_info) |
(...skipping 22 matching lines...) Expand all Loading... |
304 ret = 0 | 305 ret = 0 |
305 for zi, extracted in zip(self._zip_infos, self._extracted): | 306 for zi, extracted in zip(self._zip_infos, self._extracted): |
306 if extracted: | 307 if extracted: |
307 ret += zi.file_size | 308 ret += zi.file_size |
308 return ret | 309 return ret |
309 | 310 |
310 def ComputeInstallSize(self): | 311 def ComputeInstallSize(self): |
311 return self.ComputeExtractedSize() + self.ComputeZippedSize() | 312 return self.ComputeExtractedSize() + self.ComputeZippedSize() |
312 | 313 |
313 | 314 |
314 def PrintApkAnalysis(apk_filename, chartjson=None): | 315 def PrintApkAnalysis(apk_filename, tools_prefix, chartjson=None): |
315 """Analyse APK to determine size contributions of different file classes.""" | 316 """Analyse APK to determine size contributions of different file classes.""" |
316 file_groups = [] | 317 file_groups = [] |
317 | 318 |
318 def make_group(name): | 319 def make_group(name): |
319 group = _FileGroup(name) | 320 group = _FileGroup(name) |
320 file_groups.append(group) | 321 file_groups.append(group) |
321 return group | 322 return group |
322 | 323 |
323 native_code = make_group('Native code') | 324 native_code = make_group('Native code') |
324 java_code = make_group('Java code') | 325 java_code = make_group('Java code') |
(...skipping 83 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
408 main_lib_info = native_code.FindLargest() | 409 main_lib_info = native_code.FindLargest() |
409 if main_lib_info: | 410 if main_lib_info: |
410 main_lib_size = main_lib_info.file_size | 411 main_lib_size = main_lib_info.file_size |
411 ReportPerfResult(chartjson, apk_basename + '_Specifics', | 412 ReportPerfResult(chartjson, apk_basename + '_Specifics', |
412 'main lib size', main_lib_size, 'bytes') | 413 'main lib size', main_lib_size, 'bytes') |
413 secondary_size = native_code.ComputeUncompressedSize() - main_lib_size | 414 secondary_size = native_code.ComputeUncompressedSize() - main_lib_size |
414 ReportPerfResult(chartjson, apk_basename + '_Specifics', | 415 ReportPerfResult(chartjson, apk_basename + '_Specifics', |
415 'other lib size', secondary_size, 'bytes') | 416 'other lib size', secondary_size, 'bytes') |
416 | 417 |
417 main_lib_section_sizes = _ExtractMainLibSectionSizesFromApk( | 418 main_lib_section_sizes = _ExtractMainLibSectionSizesFromApk( |
418 apk_filename, main_lib_info.filename) | 419 apk_filename, main_lib_info.filename, tools_prefix) |
419 for metric_name, size in main_lib_section_sizes.iteritems(): | 420 for metric_name, size in main_lib_section_sizes.iteritems(): |
420 ReportPerfResult(chartjson, apk_basename + '_MainLibInfo', | 421 ReportPerfResult(chartjson, apk_basename + '_MainLibInfo', |
421 metric_name, size, 'bytes') | 422 metric_name, size, 'bytes') |
422 | 423 |
423 # Main metric that we want to monitor for jumps. | 424 # Main metric that we want to monitor for jumps. |
424 normalized_apk_size = total_apk_size | 425 normalized_apk_size = total_apk_size |
425 # Always look at uncompressed .dex & .so. | 426 # Always look at uncompressed .dex & .so. |
426 normalized_apk_size -= java_code.ComputeZippedSize() | 427 normalized_apk_size -= java_code.ComputeZippedSize() |
427 normalized_apk_size += java_code.ComputeUncompressedSize() | 428 normalized_apk_size += java_code.ComputeUncompressedSize() |
428 normalized_apk_size -= native_code.ComputeZippedSize() | 429 normalized_apk_size -= native_code.ComputeZippedSize() |
(...skipping 147 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
576 i = int(m.group('id')) | 577 i = int(m.group('id')) |
577 name = m.group('name') | 578 name = m.group('name') |
578 if i in id_name_map and name != id_name_map[i]: | 579 if i in id_name_map and name != id_name_map[i]: |
579 print 'WARNING: Resource ID conflict %s (%s vs %s)' % ( | 580 print 'WARNING: Resource ID conflict %s (%s vs %s)' % ( |
580 i, id_name_map[i], name) | 581 i, id_name_map[i], name) |
581 id_name_map[i] = name | 582 id_name_map[i] = name |
582 id_header_map[i] = os.path.relpath(header, out_dir) | 583 id_header_map[i] = os.path.relpath(header, out_dir) |
583 return id_name_map, id_header_map | 584 return id_name_map, id_header_map |
584 | 585 |
585 | 586 |
586 def _PrintStaticInitializersCountFromApk(apk_filename, chartjson=None): | 587 def _PrintStaticInitializersCountFromApk(apk_filename, tools_prefix, |
| 588 chartjson=None): |
587 print 'Finding static initializers (can take a minute)' | 589 print 'Finding static initializers (can take a minute)' |
588 with zipfile.ZipFile(apk_filename) as z: | 590 with zipfile.ZipFile(apk_filename) as z: |
589 infolist = z.infolist() | 591 infolist = z.infolist() |
590 out_dir = constants.GetOutDirectory() | 592 out_dir = constants.GetOutDirectory() |
591 si_count = 0 | 593 si_count = 0 |
592 for zip_info in infolist: | 594 for zip_info in infolist: |
593 # Check file size to account for placeholder libraries. | 595 # Check file size to account for placeholder libraries. |
594 if zip_info.filename.endswith('.so') and zip_info.file_size > 0: | 596 if zip_info.filename.endswith('.so') and zip_info.file_size > 0: |
595 lib_name = os.path.basename(zip_info.filename).replace('crazy.', '') | 597 lib_name = os.path.basename(zip_info.filename).replace('crazy.', '') |
596 unstripped_path = os.path.join(out_dir, 'lib.unstripped', lib_name) | 598 unstripped_path = os.path.join(out_dir, 'lib.unstripped', lib_name) |
597 if os.path.exists(unstripped_path): | 599 if os.path.exists(unstripped_path): |
598 si_count += _PrintStaticInitializersCount(unstripped_path) | 600 si_count += _PrintStaticInitializersCount( |
| 601 apk_filename, zip_info.filename, unstripped_path, tools_prefix) |
599 else: | 602 else: |
600 raise Exception('Unstripped .so not found. Looked here: %s', | 603 raise Exception('Unstripped .so not found. Looked here: %s', |
601 unstripped_path) | 604 unstripped_path) |
602 ReportPerfResult(chartjson, 'StaticInitializersCount', 'count', si_count, | 605 ReportPerfResult(chartjson, 'StaticInitializersCount', 'count', si_count, |
603 'count') | 606 'count') |
604 | 607 |
605 | 608 |
606 def _PrintStaticInitializersCount(so_with_symbols_path): | 609 def _PrintStaticInitializersCount(apk_path, apk_so_name, so_with_symbols_path, |
| 610 tools_prefix): |
607 """Counts the number of static initializers in the given shared library. | 611 """Counts the number of static initializers in the given shared library. |
608 Additionally, files for which static initializers were found are printed | 612 Additionally, files for which static initializers were found are printed |
609 on the standard output. | 613 on the standard output. |
610 | 614 |
611 Args: | 615 Args: |
612 so_with_symbols_path: Path to the unstripped libchrome.so file. | 616 apk_path: Path to the apk. |
613 | 617 apk_so_name: Name of the so. |
| 618 so_with_symbols_path: Path to the unstripped libchrome.so file. |
| 619 tools_prefix: Prefix for arch-specific version of binary utility tools. |
614 Returns: | 620 Returns: |
615 The number of static initializers found. | 621 The number of static initializers found. |
616 """ | 622 """ |
617 # GetStaticInitializers uses get-static-initializers.py to get a list of all | 623 # GetStaticInitializers uses get-static-initializers.py to get a list of all |
618 # static initializers. This does not work on all archs (particularly arm). | 624 # static initializers. This does not work on all archs (particularly arm). |
619 # TODO(rnephew): Get rid of warning when crbug.com/585588 is fixed. | 625 # TODO(rnephew): Get rid of warning when crbug.com/585588 is fixed. |
620 si_count = CountStaticInitializers(so_with_symbols_path) | 626 with Unzip(apk_path, filename=apk_so_name) as unzipped_so: |
621 static_initializers = GetStaticInitializers(so_with_symbols_path) | 627 _VerifyLibBuildIdsMatch(tools_prefix, unzipped_so, so_with_symbols_path) |
622 static_initializers_count = len(static_initializers) - 1 # Minus summary. | 628 readelf_si_count = CountStaticInitializers(unzipped_so, tools_prefix) |
623 if si_count != static_initializers_count: | 629 sis, dump_si_count = GetStaticInitializers( |
| 630 so_with_symbols_path, tools_prefix) |
| 631 if readelf_si_count != dump_si_count: |
624 print ('There are %d files with static initializers, but ' | 632 print ('There are %d files with static initializers, but ' |
625 'dump-static-initializers found %d:' % | 633 'dump-static-initializers found %d: files' % |
626 (si_count, static_initializers_count)) | 634 (readelf_si_count, dump_si_count)) |
627 else: | 635 else: |
628 print '%s - Found %d files with static initializers:' % ( | 636 print '%s - Found %d files with static initializers:' % ( |
629 os.path.basename(so_with_symbols_path), si_count) | 637 os.path.basename(so_with_symbols_path), dump_si_count) |
630 print '\n'.join(static_initializers) | 638 print '\n'.join(sis) |
631 | 639 |
632 return si_count | 640 return readelf_si_count |
633 | 641 |
634 def _FormatBytes(byts): | 642 def _FormatBytes(byts): |
635 """Pretty-print a number of bytes.""" | 643 """Pretty-print a number of bytes.""" |
636 if byts > 2**20.0: | 644 if byts > 2**20.0: |
637 byts /= 2**20.0 | 645 byts /= 2**20.0 |
638 return '%.2fm' % byts | 646 return '%.2fm' % byts |
639 if byts > 2**10.0: | 647 if byts > 2**10.0: |
640 byts /= 2**10.0 | 648 byts /= 2**10.0 |
641 return '%.2fk' % byts | 649 return '%.2fk' % byts |
642 return str(byts) | 650 return str(byts) |
(...skipping 16 matching lines...) Expand all Loading... |
659 graph_title = os.path.basename(apk_filename) + '_Dex' | 667 graph_title = os.path.basename(apk_filename) + '_Dex' |
660 dex_metrics = method_count.CONTRIBUTORS_TO_DEX_CACHE | 668 dex_metrics = method_count.CONTRIBUTORS_TO_DEX_CACHE |
661 for key, label in dex_metrics.iteritems(): | 669 for key, label in dex_metrics.iteritems(): |
662 ReportPerfResult(chartjson, graph_title, label, sizes[key], 'entries') | 670 ReportPerfResult(chartjson, graph_title, label, sizes[key], 'entries') |
663 | 671 |
664 graph_title = '%sCache' % graph_title | 672 graph_title = '%sCache' % graph_title |
665 ReportPerfResult(chartjson, graph_title, 'DexCache', sizes['dex_cache_size'], | 673 ReportPerfResult(chartjson, graph_title, 'DexCache', sizes['dex_cache_size'], |
666 'bytes') | 674 'bytes') |
667 | 675 |
668 | 676 |
669 def main(argv): | 677 @contextmanager |
670 usage = """Usage: %prog [options] file1 file2 ... | 678 def Unzip(zip_file, filename=None): |
| 679 """Utility for temporary use of a single file in a zip archive.""" |
| 680 with build_utils.TempDir() as unzipped_dir: |
| 681 unzipped_files = build_utils.ExtractAll( |
| 682 zip_file, unzipped_dir, True, pattern=filename) |
| 683 if len(unzipped_files) == 0: |
| 684 raise Exception( |
| 685 '%s not found in %s' % (filename, zip_file)) |
| 686 yield unzipped_files[0] |
671 | 687 |
672 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.""" | |
674 option_parser = optparse.OptionParser(usage=usage) | |
675 option_parser.add_option('--so-path', | |
676 help='Obsolete. Pass .so as positional arg instead.') | |
677 option_parser.add_option('--so-with-symbols-path', | |
678 help='Mostly obsolete. Use .so within .apk instead.') | |
679 option_parser.add_option('--min-pak-resource-size', type='int', | |
680 default=20*1024, | |
681 help='Minimum byte size of displayed pak resources.') | |
682 option_parser.add_option('--build_type', dest='build_type', default='Debug', | |
683 help='Obsoleted by --chromium-output-directory.') | |
684 option_parser.add_option('--chromium-output-directory', | |
685 help='Location of the build artifacts. ' | |
686 'Takes precidence over --build_type.') | |
687 option_parser.add_option('--chartjson', action='store_true', | |
688 help='Sets output mode to chartjson.') | |
689 option_parser.add_option('--output-dir', default='.', | |
690 help='Directory to save chartjson to.') | |
691 option_parser.add_option('--no-output-dir', action='store_true', | |
692 help='Skip all measurements that rely on having ' | |
693 'output-dir') | |
694 option_parser.add_option('-d', '--device', | |
695 help='Dummy option for perf runner.') | |
696 options, args = option_parser.parse_args(argv) | |
697 files = args[1:] | |
698 chartjson = _BASE_CHART.copy() if options.chartjson else None | |
699 | 688 |
700 constants.SetBuildType(options.build_type) | 689 def _VerifyLibBuildIdsMatch(tools_prefix, *so_files): |
701 if options.chromium_output_directory: | 690 if len(set(_ParseLibBuildId(f, tools_prefix) for f in so_files)) > 1: |
702 constants.SetOutputDirectory(options.chromium_output_directory) | 691 raise Exception('Found differing build ids in output directory and apk. ' |
703 if not options.no_output_dir: | 692 'Your output directory is likely stale.') |
| 693 |
| 694 |
| 695 def _ReadBuildVars(output_dir): |
| 696 with open(os.path.join(output_dir, 'build_vars.txt')) as f: |
| 697 return dict(l.replace('//', '').rstrip().split('=', 1) for l in f) |
| 698 |
| 699 |
| 700 def main(): |
| 701 argparser = argparse.ArgumentParser(description='Print APK size metrics.') |
| 702 argparser.add_argument('--min-pak-resource-size', type=int, default=20*1024, |
| 703 help='Minimum byte size of displayed pak resources.') |
| 704 argparser.add_argument('--chromium-output-directory', |
| 705 help='Location of the build artifacts.') |
| 706 argparser.add_argument('--chartjson', action='store_true', |
| 707 help='Sets output mode to chartjson.') |
| 708 argparser.add_argument('--output-dir', default='.', |
| 709 help='Directory to save chartjson to.') |
| 710 argparser.add_argument('--no-output-dir', action='store_true', |
| 711 help='Skip all measurements that rely on having ' |
| 712 'output-dir') |
| 713 argparser.add_argument('-d', '--device', |
| 714 help='Dummy option for perf runner.') |
| 715 argparser.add_argument('apk', help='APK file path.') |
| 716 args = argparser.parse_args() |
| 717 |
| 718 chartjson = _BASE_CHART.copy() if args.chartjson else None |
| 719 |
| 720 if args.chromium_output_directory: |
| 721 constants.SetOutputDirectory(args.chromium_output_directory) |
| 722 if not args.no_output_dir: |
704 constants.CheckOutputDirectory() | 723 constants.CheckOutputDirectory() |
705 devil_chromium.Initialize() | 724 devil_chromium.Initialize() |
| 725 build_vars = _ReadBuildVars(constants.GetOutDirectory()) |
| 726 tools_prefix = build_vars['android_tool_prefix'] |
| 727 else: |
| 728 tools_prefix = '' |
706 | 729 |
707 # For backward compatibilty with buildbot scripts, treat --so-path as just | 730 PrintApkAnalysis(args.apk, tools_prefix, chartjson=chartjson) |
708 # another file to print the size of. We don't need it for anything special any | 731 _PrintDexAnalysis(args.apk, chartjson=chartjson) |
709 # more. | 732 if not args.no_output_dir: |
710 if options.so_path: | 733 PrintPakAnalysis(args.apk, args.min_pak_resource_size) |
711 files.append(options.so_path) | 734 _PrintStaticInitializersCountFromApk( |
712 | 735 args.apk, tools_prefix, chartjson=chartjson) |
713 if not files: | |
714 option_parser.error('Must specify a file') | |
715 | |
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) | |
722 | |
723 for f in files: | |
724 if f.endswith('.apk'): | |
725 PrintApkAnalysis(f, chartjson=chartjson) | |
726 _PrintDexAnalysis(f, chartjson=chartjson) | |
727 if not options.no_output_dir: | |
728 PrintPakAnalysis(f, options.min_pak_resource_size) | |
729 if not options.so_with_symbols_path: | |
730 _PrintStaticInitializersCountFromApk(f, chartjson=chartjson) | |
731 | |
732 if chartjson: | 736 if chartjson: |
733 results_path = os.path.join(options.output_dir, 'results-chart.json') | 737 results_path = os.path.join(args.output_dir, 'results-chart.json') |
734 logging.critical('Dumping json to %s', results_path) | 738 logging.critical('Dumping json to %s', results_path) |
735 with open(results_path, 'w') as json_file: | 739 with open(results_path, 'w') as json_file: |
736 json.dump(chartjson, json_file) | 740 json.dump(chartjson, json_file) |
737 | 741 |
738 | 742 |
739 if __name__ == '__main__': | 743 if __name__ == '__main__': |
740 sys.exit(main(sys.argv)) | 744 sys.exit(main()) |
OLD | NEW |