Index: tools/binary_size/libsupersize/archive.py |
diff --git a/tools/binary_size/libsupersize/archive.py b/tools/binary_size/libsupersize/archive.py |
index 0991f0b8c6be2af46dd509063ffe273bc5130acb..a049b60297ed404c0182612251df556496c36f37 100644 |
--- a/tools/binary_size/libsupersize/archive.py |
+++ b/tools/binary_size/libsupersize/archive.py |
@@ -11,9 +11,12 @@ import datetime |
import gzip |
import logging |
import os |
+import posixpath |
import re |
import subprocess |
import sys |
+import tempfile |
+import zipfile |
import describe |
import file_format |
@@ -205,12 +208,16 @@ def _ClusterSymbols(symbols): |
Groups include: |
* Symbols that have [clone] in their name (created by compiler optimization). |
* Star symbols (such as "** merge strings", and "** symbol gap") |
+ |
+ To view created groups: |
+ Print(size_info.symbols.Filter(lambda s: s.IsGroup()), recursive=True) |
""" |
# http://unix.stackexchange.com/questions/223013/function-symbol-gets-part-suffix-after-compilation |
# Example name suffixes: |
- # [clone .part.322] |
- # [clone .isra.322] |
- # [clone .constprop.1064] |
+ # [clone .part.322] # GCC |
+ # [clone .isra.322] # GCC |
+ # [clone .constprop.1064] # GCC |
+ # [clone .11064] # clang |
# Step 1: Create name map, find clones, collect star syms into replacements. |
logging.debug('Creating name -> symbol map') |
@@ -351,6 +358,9 @@ def CreateSizeInfo(map_path, lazy_paths=None, no_source_paths=False, |
_PostProcessSizeInfo(size_info) |
if logging.getLogger().isEnabledFor(logging.DEBUG): |
+ # Padding is reported in size coverage logs. |
+ if raw_only: |
+ _CalculatePadding(size_info.raw_symbols) |
for line in describe.DescribeSizeInfoCoverage(size_info): |
logging.info(line) |
logging.info('Recorded info for %d symbols', len(size_info.raw_symbols)) |
@@ -399,15 +409,36 @@ def _ParseGnArgs(args_path): |
return ["%s=%s" % x for x in sorted(args.iteritems())] |
+def _SectionSizesFromApk(apk_file, elf_file, build_id, lazy_paths): |
+ with zipfile.ZipFile(apk_file) as apk, \ |
+ tempfile.NamedTemporaryFile() as f: |
+ target = os.path.basename(elf_file) |
+ target_info = next((f for f in apk.infolist() |
+ if posixpath.basename(f.filename) == target), None) |
+ assert target_info, ( |
+ 'Could not find apk entry for %s in %s' % (target, apk_file)) |
+ f.write(apk.read(target_info)) |
+ f.flush() |
+ apk_build_id = BuildIdFromElf(f.name, lazy_paths.tool_prefix) |
+ assert apk_build_id == build_id, ( |
+ 'BuildID for %s within %s did not match the one at %s' % |
+ (target_info.filename, apk_file, elf_file)) |
+ return _SectionSizesFromElf(f.name, lazy_paths.tool_prefix) |
+ |
+ |
def AddArguments(parser): |
parser.add_argument('size_file', help='Path to output .size file.') |
- parser.add_argument('--elf-file', required=True, |
+ parser.add_argument('--apk-file', |
+ help='.apk file to measure. When set, --elf-file will be ' |
+ 'derived (if unset). Providing the .apk allows ' |
+ 'for the size of packed relocations to be recorded') |
+ parser.add_argument('--elf-file', |
help='Path to input ELF file. Currently used for ' |
- 'capturing metadata. Pass "" to skip ' |
- 'metadata collection.') |
+ 'capturing metadata.') |
parser.add_argument('--map-file', |
help='Path to input .map(.gz) file. Defaults to ' |
- '{{elf_file}}.map(.gz)?') |
+ '{{elf_file}}.map(.gz)?. If given without ' |
+ '--elf-file, no size metadata will be recorded.') |
parser.add_argument('--no-source-paths', action='store_true', |
help='Do not use .ninja files to map ' |
'object_path -> source_path') |
@@ -421,26 +452,43 @@ def Run(args, parser): |
if not args.size_file.endswith('.size'): |
parser.error('size_file must end with .size') |
+ any_input = args.apk_file or args.elf_file or args.map_file |
+ if not any_input: |
+ parser.error('Most pass at least one of --apk-file, --elf-file, --map-file') |
+ lazy_paths = paths.LazyPaths(args=args, input_file=any_input) |
+ |
+ elf_file = args.elf_file |
+ if args.apk_file and not elf_file: |
+ with zipfile.ZipFile(args.apk_file) as z: |
+ lib_infos = [f for f in z.infolist() |
+ if f.filename.endswith('.so') and f.file_size > 0] |
+ assert lib_infos, 'APK has not .so files to measure.' |
estevenson
2017/04/12 16:54:42
nit: awkward wording.
agrieve
2017/04/12 19:37:46
Done.
|
+ # TODO(agrieve): Add support for multiple .so files, and take into account |
+ # secondary architectures. |
estevenson
2017/04/12 16:54:42
_SectionSizesFromApk() would fail for 64 bit monoc
agrieve
2017/04/12 19:37:45
Didn't hit any of those other errors, but good cat
|
+ apk_so_path = max(lib_infos, key=lambda x:x.file_size).filename |
+ elf_file = os.path.join(lazy_paths.output_directory, 'lib.unstripped', |
+ os.path.basename(apk_so_path)) |
+ logging.debug('Detected --elf-file=%s', elf_file) |
+ |
if args.map_file: |
if (not args.map_file.endswith('.map') |
and not args.map_file.endswith('.map.gz')): |
parser.error('Expected --map-file to end with .map or .map.gz') |
map_file_path = args.map_file |
else: |
- map_file_path = args.elf_file + '.map' |
+ map_file_path = elf_file + '.map' |
if not os.path.exists(map_file_path): |
map_file_path += '.gz' |
if not os.path.exists(map_file_path): |
parser.error('Could not find .map(.gz)? file. Use --map-file.') |
- lazy_paths = paths.LazyPaths(args=args, input_file=args.elf_file) |
metadata = None |
- if args.elf_file: |
+ if elf_file: |
logging.debug('Constructing metadata') |
- git_rev = _DetectGitRevision(os.path.dirname(args.elf_file)) |
- build_id = BuildIdFromElf(args.elf_file, lazy_paths.tool_prefix) |
+ git_rev = _DetectGitRevision(os.path.dirname(elf_file)) |
+ build_id = BuildIdFromElf(elf_file, lazy_paths.tool_prefix) |
timestamp_obj = datetime.datetime.utcfromtimestamp(os.path.getmtime( |
- args.elf_file)) |
+ elf_file)) |
timestamp = calendar.timegm(timestamp_obj.timetuple()) |
gn_args = _ParseGnArgs(os.path.join(lazy_paths.output_directory, 'args.gn')) |
@@ -450,7 +498,7 @@ def Run(args, parser): |
metadata = { |
models.METADATA_GIT_REVISION: git_rev, |
models.METADATA_MAP_FILENAME: relative_to_out(map_file_path), |
- models.METADATA_ELF_FILENAME: relative_to_out(args.elf_file), |
+ models.METADATA_ELF_FILENAME: relative_to_out(elf_file), |
models.METADATA_ELF_MTIME: timestamp, |
models.METADATA_ELF_BUILD_ID: build_id, |
models.METADATA_GN_ARGS: gn_args, |
@@ -463,12 +511,21 @@ def Run(args, parser): |
if metadata: |
size_info.metadata = metadata |
logging.debug('Validating section sizes') |
- elf_section_sizes = _SectionSizesFromElf(args.elf_file, |
- lazy_paths.tool_prefix) |
+ elf_section_sizes = _SectionSizesFromElf(elf_file, lazy_paths.tool_prefix) |
for k, v in elf_section_sizes.iteritems(): |
assert v == size_info.section_sizes.get(k), ( |
'ELF file and .map file do not match.') |
+ if args.apk_file: |
+ logging.debug('Extracting section sizes from .so within .apk') |
+ unpacked_rel_dyn = size_info.section_sizes.get('.rel.dyn', 0) |
+ if not unpacked_rel_dyn: |
+ logging.warning('Section .rel.dyn did not exist') |
+ # TODO(agrieve): Extracting the .so is slow. Do it on a background thread. |
+ size_info.section_sizes = _SectionSizesFromApk( |
+ args.apk_file, elf_file, build_id, lazy_paths) |
+ size_info.section_sizes['.rel.dyn (unpacked)'] = unpacked_rel_dyn |
+ |
logging.info('Recording metadata: \n %s', |
'\n '.join(describe.DescribeMetadata(size_info.metadata))) |
logging.info('Saving result to %s', args.size_file) |