| OLD | NEW |
| 1 # Copyright 2017 The Chromium Authors. All rights reserved. | 1 # Copyright 2017 The Chromium Authors. All rights reserved. |
| 2 # Use of this source code is governed by a BSD-style license that can be | 2 # Use of this source code is governed by a BSD-style license that can be |
| 3 # found in the LICENSE file. | 3 # found in the LICENSE file. |
| 4 | 4 |
| 5 """Deals with loading & saving .size files.""" | 5 """Deals with loading & saving .size files.""" |
| 6 | 6 |
| 7 import ast | 7 import collections |
| 8 import gzip | 8 import gzip |
| 9 import models | 9 import models |
| 10 import logging |
| 11 import os |
| 10 | 12 |
| 11 | 13 |
| 12 # File format version for .size files. | 14 # File format version for .size files. |
| 13 _SERIALIZATION_VERSION = 1 | 15 _SERIALIZATION_VERSION = 'Size File Format v1' |
| 14 | 16 |
| 15 | 17 |
| 16 def EndsWithMaybeGz(path, suffix): | 18 def _LogSize(file_obj, desc): |
| 17 return path.endswith(suffix) or path.endswith(suffix + '.gz') | 19 if not logging.getLogger().isEnabledFor(logging.DEBUG): |
| 20 return |
| 21 file_obj.flush() |
| 22 size = os.fstat(file_obj.fileno()).st_size |
| 23 logging.debug('File size with %s: %d' % (desc, size)) |
| 18 | 24 |
| 19 | 25 |
| 20 def OpenMaybeGz(path, mode=None): | 26 def _SaveSizeInfoToFile(size_info, file_obj): |
| 21 """Calls `gzip.open()` if |path| ends in ".gz", otherwise calls `open()`.""" | 27 file_obj.write('%s\n' % _SERIALIZATION_VERSION) |
| 22 if path.endswith('.gz'): | 28 file_obj.write('# Created by //tools/binary_size\n') |
| 23 if mode and 'w' in mode: | 29 file_obj.write('%s\n' % '\t'.join(size_info.section_sizes)) |
| 24 return gzip.GzipFile(path, mode, 1) | 30 file_obj.write('%s\n' % '\t'.join( |
| 25 return gzip.open(path, mode) | 31 str(v) for v in size_info.section_sizes.itervalues())) |
| 26 return open(path, mode or 'r') | 32 _LogSize(file_obj, 'header') # For libchrome: 450 bytes. |
| 33 |
| 34 # Store a single copy of all paths and have them referenced by index. |
| 35 # Using an OrderedDict makes the indices more repetitive (better compression). |
| 36 path_tuples = collections.OrderedDict.fromkeys( |
| 37 (s.object_path, s.source_path) for s in size_info.symbols) |
| 38 for i, key in enumerate(path_tuples): |
| 39 path_tuples[key] = i |
| 40 file_obj.write('%d\n' % len(path_tuples)) |
| 41 file_obj.writelines('%s\t%s\n' % pair for pair in path_tuples) |
| 42 _LogSize(file_obj, 'paths') # For libchrome, adds 200kb. |
| 43 |
| 44 # Symbol counts by section. |
| 45 by_section = size_info.symbols.GroupBySectionName().SortedByName() |
| 46 file_obj.write('%s\n' % '\t'.join(g.name for g in by_section)) |
| 47 file_obj.write('%s\n' % '\t'.join(str(len(g)) for g in by_section)) |
| 48 |
| 49 def write_numeric(func, delta=False): |
| 50 for group in by_section: |
| 51 prev_value = 0 |
| 52 last_sym = group[-1] |
| 53 for symbol in group: |
| 54 value = func(symbol) |
| 55 if delta: |
| 56 value, prev_value = value - prev_value, value |
| 57 file_obj.write(str(value)) |
| 58 if symbol is not last_sym: |
| 59 file_obj.write(' ') |
| 60 file_obj.write('\n') |
| 61 |
| 62 write_numeric(lambda s: s.address, delta=True) |
| 63 _LogSize(file_obj, 'addresses') # For libchrome, adds 300kb. |
| 64 # Do not write padding, it will be recalcualted from addresses on load. |
| 65 write_numeric(lambda s: s.size_without_padding) |
| 66 _LogSize(file_obj, 'sizes') # For libchrome, adds 300kb |
| 67 write_numeric(lambda s: path_tuples[(s.object_path, s.source_path)], |
| 68 delta=True) |
| 69 _LogSize(file_obj, 'path indices') # For libchrome: adds 125kb. |
| 70 |
| 71 for group in by_section: |
| 72 for symbol in group: |
| 73 # Do not write name when full_name exists. It will be derived on load. |
| 74 file_obj.write(symbol.full_name or symbol.name) |
| 75 if symbol.is_anonymous: |
| 76 file_obj.write('\t1') |
| 77 file_obj.write('\n') |
| 78 _LogSize(file_obj, 'names (final)') # For libchrome: adds 3.5mb. |
| 27 | 79 |
| 28 | 80 |
| 29 def _SaveSizeInfoToFile(result, file_obj): | 81 def _LoadSizeInfoFromFile(lines): |
| 30 """Saves the result to the given file object.""" | 82 """Loads a size_info from the given file.""" |
| 31 # Store one bucket per line. | 83 actual_version = next(lines)[:-1] |
| 32 file_obj.write('%d\n' % _SERIALIZATION_VERSION) | 84 assert actual_version == _SERIALIZATION_VERSION, ( |
| 33 file_obj.write('%r\n' % result.section_sizes) | 85 'Version mismatch. Need to write some upgrade code.') |
| 34 file_obj.write('%d\n' % len(result.symbols)) | 86 # Comment line. |
| 35 prev_section_name = None | 87 next(lines) |
| 36 # Store symbol fields as tab-separated. | 88 section_names = next(lines)[:-1].split('\t') |
| 37 # Store only non-derived fields. | 89 section_values = next(lines)[:-1].split('\t') |
| 38 for symbol in result.symbols: | 90 section_sizes = {k: int(v) for k, v in zip(section_names, section_values)} |
| 39 if symbol.section_name != prev_section_name: | 91 |
| 40 file_obj.write('%s\n' % symbol.section_name) | 92 num_path_tuples = int(next(lines)) |
| 41 prev_section_name = symbol.section_name | 93 path_tuples = [None] * num_path_tuples |
| 42 # Don't write padding nor name since these are derived values. | 94 for i in xrange(num_path_tuples): |
| 43 file_obj.write('%x\t%x\t%s\t%s\n' % ( | 95 path_tuples[i] = next(lines)[:-1].split('\t') |
| 44 symbol.address, symbol.size_without_padding, | 96 |
| 45 symbol.function_signature or symbol.name, symbol.path)) | 97 section_names = next(lines)[:-1].split('\t') |
| 98 section_counts = [int(c) for c in next(lines)[:-1].split('\t')] |
| 99 |
| 100 def read_numeric(delta=False): |
| 101 ret = [] |
| 102 delta_multiplier = int(delta) |
| 103 for _ in section_counts: |
| 104 value = 0 |
| 105 fields = next(lines).split(' ') |
| 106 for i, f in enumerate(fields): |
| 107 value = value * delta_multiplier + int(f) |
| 108 fields[i] = value |
| 109 ret.append(fields) |
| 110 return ret |
| 111 |
| 112 addresses = read_numeric(delta=True) |
| 113 sizes = read_numeric(delta=False) |
| 114 path_indices = read_numeric(delta=True) |
| 115 |
| 116 symbol_list = [None] * sum(section_counts) |
| 117 symbol_idx = 0 |
| 118 for section_index, cur_section_name in enumerate(section_names): |
| 119 for i in xrange(section_counts[section_index]): |
| 120 line = next(lines)[:-1] |
| 121 is_anonymous = line.endswith('\t1') |
| 122 name = line[:-2] if is_anonymous else line |
| 123 |
| 124 new_sym = models.Symbol.__new__(models.Symbol) |
| 125 new_sym.section_name = cur_section_name |
| 126 new_sym.address = addresses[section_index][i] |
| 127 new_sym.size = sizes[section_index][i] |
| 128 new_sym.name = name |
| 129 paths = path_tuples[path_indices[section_index][i]] |
| 130 new_sym.object_path = paths[0] |
| 131 new_sym.source_path = paths[1] |
| 132 new_sym.is_anonymous = is_anonymous |
| 133 new_sym.padding = 0 # Derived |
| 134 new_sym.full_name = None # Derived |
| 135 symbol_list[symbol_idx] = new_sym |
| 136 symbol_idx += 1 |
| 137 |
| 138 symbols = models.SymbolGroup(symbol_list) |
| 139 return models.SizeInfo(symbols, section_sizes) |
| 46 | 140 |
| 47 | 141 |
| 48 def _LoadSizeInfoFromFile(file_obj): | 142 def SaveSizeInfo(size_info, path): |
| 49 """Loads a result from the given file.""" | 143 """Saves |size_info| to |path}.""" |
| 50 lines = iter(file_obj) | 144 with gzip.open(path, 'wb') as f: |
| 51 actual_version = int(next(lines)) | 145 _SaveSizeInfoToFile(size_info, f) |
| 52 assert actual_version == _SERIALIZATION_VERSION, ( | |
| 53 'Version mismatch. Need to write some upgrade code.') | |
| 54 | |
| 55 section_sizes = ast.literal_eval(next(lines)) | |
| 56 num_syms = int(next(lines)) | |
| 57 symbol_list = [None] * num_syms | |
| 58 section_name = None | |
| 59 for i in xrange(num_syms): | |
| 60 line = next(lines)[:-1] | |
| 61 if '\t' not in line: | |
| 62 section_name = line | |
| 63 line = next(lines)[:-1] | |
| 64 new_sym = models.Symbol.__new__(models.Symbol) | |
| 65 parts = line.split('\t') | |
| 66 new_sym.section_name = section_name | |
| 67 new_sym.address = int(parts[0], 16) | |
| 68 new_sym.size = int(parts[1], 16) | |
| 69 new_sym.name = parts[2] | |
| 70 new_sym.path = parts[3] | |
| 71 new_sym.padding = 0 # Derived | |
| 72 new_sym.function_signature = None # Derived | |
| 73 symbol_list[i] = new_sym | |
| 74 | |
| 75 return models.SizeInfo(models.SymbolGroup(symbol_list), section_sizes) | |
| 76 | |
| 77 | |
| 78 def SaveSizeInfo(result, path): | |
| 79 with OpenMaybeGz(path, 'wb') as f: | |
| 80 _SaveSizeInfoToFile(result, f) | |
| 81 | 146 |
| 82 | 147 |
| 83 def LoadSizeInfo(path): | 148 def LoadSizeInfo(path): |
| 84 with OpenMaybeGz(path) as f: | 149 """Returns a SizeInfo loaded from |path|.""" |
| 85 return _LoadSizeInfoFromFile(f) | 150 with gzip.open(path) as f: |
| 151 return _LoadSizeInfoFromFile(iter(f)) |
| OLD | NEW |