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

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

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