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

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

Issue 2706243013: Android: improve static initializer counting in resource_sizes.py. (Closed)
Patch Set: Addressed agrieve comments Created 3 years, 9 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 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
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
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
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
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
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
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())
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