Chromium Code Reviews| 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 """Classes that comprise the data model for binary size analysis. | 4 """Classes that comprise the data model for binary size analysis. |
| 5 | 5 |
| 6 The primary classes are Symbol, and SymbolGroup. | 6 The primary classes are Symbol, and SymbolGroup. |
| 7 | 7 |
| 8 Description of common properties: | 8 Description of common properties: |
| 9 * address: The start address of the symbol. | 9 * address: The start address of the symbol. |
| 10 May be 0 (e.g. for .bss or for SymbolGroups). | 10 May be 0 (e.g. for .bss or for SymbolGroups). |
| (...skipping 38 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 49 class SizeInfo(object): | 49 class SizeInfo(object): |
| 50 """Represents all size information for a single binary. | 50 """Represents all size information for a single binary. |
| 51 | 51 |
| 52 Fields: | 52 Fields: |
| 53 section_sizes: A dict of section_name -> size. | 53 section_sizes: A dict of section_name -> size. |
| 54 symbols: A SymbolGroup with all symbols in it. | 54 symbols: A SymbolGroup with all symbols in it. |
| 55 metadata: A dict. | 55 metadata: A dict. |
| 56 """ | 56 """ |
| 57 __slots__ = ( | 57 __slots__ = ( |
| 58 'section_sizes', | 58 'section_sizes', |
| 59 'raw_symbols', | |
|
estevenson
2017/04/11 17:11:42
nit: add doc for this field?
agrieve
2017/04/11 17:32:50
Done.
| |
| 59 'symbols', | 60 'symbols', |
| 60 'metadata', | 61 'metadata', |
| 61 ) | 62 ) |
| 62 | 63 |
| 63 """Root size information.""" | 64 """Root size information.""" |
| 64 def __init__(self, section_sizes, symbols, metadata=None): | 65 def __init__(self, section_sizes, raw_symbols, grouped_symbols=None, |
| 66 metadata=None): | |
| 65 self.section_sizes = section_sizes # E.g. {'.text': 0} | 67 self.section_sizes = section_sizes # E.g. {'.text': 0} |
| 66 self.symbols = symbols # List of symbols sorted by address per-section. | 68 # List of symbols sorted by address per-section. |
| 69 self.raw_symbols = raw_symbols | |
| 70 # Root SymbolGroup. Cloned symbols grouped together within sub-SymbolGroups. | |
| 71 self.symbols = grouped_symbols | |
| 67 self.metadata = metadata or {} | 72 self.metadata = metadata or {} |
| 68 | 73 |
| 69 | 74 |
| 70 class SizeInfoDiff(object): | 75 class SizeInfoDiff(object): |
| 71 """What you get when you Diff() two SizeInfo objects. | 76 """What you get when you Diff() two SizeInfo objects. |
| 72 | 77 |
| 73 Fields: | 78 Fields: |
| 74 section_sizes: A dict of section_name -> size delta. | 79 section_sizes: A dict of section_name -> size delta. |
| 75 symbols: A SymbolDiff with all symbols in it. | 80 symbols: A SymbolDiff with all symbols in it. |
| 76 old_metadata: metadata of the "old" SizeInfo. | 81 old_metadata: metadata of the "old" SizeInfo. |
| (...skipping 46 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 123 # TODO(agrieve): Also match generated functions such as: | 128 # TODO(agrieve): Also match generated functions such as: |
| 124 # startup._GLOBAL__sub_I_page_allocator.cc | 129 # startup._GLOBAL__sub_I_page_allocator.cc |
| 125 return self.name.endswith(']') and not self.name.endswith('[]') | 130 return self.name.endswith(']') and not self.name.endswith('[]') |
| 126 | 131 |
| 127 def _Key(self): | 132 def _Key(self): |
| 128 """Returns a tuple that can be used to see if two Symbol are the same. | 133 """Returns a tuple that can be used to see if two Symbol are the same. |
| 129 | 134 |
| 130 Keys are not guaranteed to be unique within a SymbolGroup. For example, it | 135 Keys are not guaranteed to be unique within a SymbolGroup. For example, it |
| 131 is common to have multiple "** merge strings" symbols, which will have a | 136 is common to have multiple "** merge strings" symbols, which will have a |
| 132 common key.""" | 137 common key.""" |
| 133 return (self.section_name, self.full_name or self.name) | 138 stripped_full_name = self.full_name |
| 139 if stripped_full_name: | |
| 140 clone_idx = stripped_full_name.find(' [clone ') | |
| 141 if clone_idx != -1: | |
| 142 stripped_full_name = stripped_full_name[:clone_idx] | |
| 143 return (self.section_name, stripped_full_name or self.name) | |
| 134 | 144 |
| 135 | 145 |
| 136 class Symbol(BaseSymbol): | 146 class Symbol(BaseSymbol): |
| 137 """Represents a single symbol within a binary. | 147 """Represents a single symbol within a binary. |
| 138 | 148 |
| 139 Refer to module docs for field descriptions. | 149 Refer to module docs for field descriptions. |
| 140 """ | 150 """ |
| 141 | 151 |
| 142 __slots__ = ( | 152 __slots__ = ( |
| 143 'address', | 153 'address', |
| (...skipping 42 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 186 * group['0x1234'] # By symbol address | 196 * group['0x1234'] # By symbol address |
| 187 * without_group2 = group1 - group2 | 197 * without_group2 = group1 - group2 |
| 188 * unioned = group1 + group2 | 198 * unioned = group1 + group2 |
| 189 """ | 199 """ |
| 190 | 200 |
| 191 __slots__ = ( | 201 __slots__ = ( |
| 192 '_padding', | 202 '_padding', |
| 193 '_size', | 203 '_size', |
| 194 '_symbols', | 204 '_symbols', |
| 195 '_filtered_symbols', | 205 '_filtered_symbols', |
| 206 'full_name', | |
| 196 'name', | 207 'name', |
| 197 'section_name', | 208 'section_name', |
| 198 'is_sorted', | 209 'is_sorted', |
| 199 ) | 210 ) |
| 200 | 211 |
| 201 def __init__(self, symbols, filtered_symbols=None, name=None, | 212 def __init__(self, symbols, filtered_symbols=None, name=None, |
| 202 section_name=None, is_sorted=False): | 213 full_name=None, section_name=None, is_sorted=False): |
| 203 self._padding = None | 214 self._padding = None |
| 204 self._size = None | 215 self._size = None |
| 205 self._symbols = symbols | 216 self._symbols = symbols |
| 206 self._filtered_symbols = filtered_symbols or [] | 217 self._filtered_symbols = filtered_symbols or [] |
| 207 self.name = name or '' | 218 self.name = name or '' |
| 219 self.full_name = full_name | |
| 208 self.section_name = section_name or '.*' | 220 self.section_name = section_name or '.*' |
| 209 self.is_sorted = is_sorted | 221 self.is_sorted = is_sorted |
| 210 | 222 |
| 211 def __repr__(self): | 223 def __repr__(self): |
| 212 return 'Group(name=%s,count=%d,size=%d)' % ( | 224 return 'Group(name=%s,count=%d,size=%d)' % ( |
| 213 self.name, len(self), self.size) | 225 self.name, len(self), self.size) |
| 214 | 226 |
| 215 def __iter__(self): | 227 def __iter__(self): |
| 216 return iter(self._symbols) | 228 return iter(self._symbols) |
| 217 | 229 |
| (...skipping 23 matching lines...) Expand all Loading... | |
| 241 return self._CreateTransformed(new_symbols, section_name=self.section_name) | 253 return self._CreateTransformed(new_symbols, section_name=self.section_name) |
| 242 | 254 |
| 243 def __add__(self, other): | 255 def __add__(self, other): |
| 244 self_ids = set(id(s) for s in self) | 256 self_ids = set(id(s) for s in self) |
| 245 new_symbols = self._symbols + [s for s in other if id(s) not in self_ids] | 257 new_symbols = self._symbols + [s for s in other if id(s) not in self_ids] |
| 246 return self._CreateTransformed(new_symbols, section_name=self.section_name, | 258 return self._CreateTransformed(new_symbols, section_name=self.section_name, |
| 247 is_sorted=False) | 259 is_sorted=False) |
| 248 | 260 |
| 249 @property | 261 @property |
| 250 def address(self): | 262 def address(self): |
| 251 return 0 | 263 first = self._symbols[0].address |
| 252 | 264 return first if all(s.address == first for s in self._symbols) else 0 |
| 253 @property | |
| 254 def full_name(self): | |
| 255 return None | |
| 256 | 265 |
| 257 @property | 266 @property |
| 258 def is_anonymous(self): | 267 def is_anonymous(self): |
| 259 return False | 268 first = self._symbols[0].is_anonymous |
| 269 return first if all( | |
| 270 s.is_anonymous == first for s in self._symbols) else False | |
| 260 | 271 |
| 261 @property | 272 @property |
| 262 def object_path(self): | 273 def object_path(self): |
| 263 return None | 274 first = self._symbols[0].object_path |
| 275 return first if all(s.object_path == first for s in self._symbols) else None | |
| 264 | 276 |
| 265 @property | 277 @property |
| 266 def source_path(self): | 278 def source_path(self): |
| 267 return None | 279 first = self._symbols[0].source_path |
| 280 return first if all(s.source_path == first for s in self._symbols) else None | |
| 268 | 281 |
| 269 @property | 282 @property |
| 270 def size(self): | 283 def size(self): |
| 271 if self._size is None: | 284 if self._size is None: |
| 272 if self.IsBss(): | 285 if self.IsBss(): |
| 273 self._size = sum(s.size for s in self) | 286 self._size = sum(s.size for s in self) |
| 274 self._size = sum(s.size for s in self if not s.IsBss()) | 287 self._size = sum(s.size for s in self if not s.IsBss()) |
| 275 return self._size | 288 return self._size |
| 276 | 289 |
| 277 @property | 290 @property |
| (...skipping 225 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 503 | 516 |
| 504 All Symbols contained within have a |size| which is actually the size delta. | 517 All Symbols contained within have a |size| which is actually the size delta. |
| 505 Additionally, metadata is kept about which symbols were added / removed / | 518 Additionally, metadata is kept about which symbols were added / removed / |
| 506 changed. | 519 changed. |
| 507 """ | 520 """ |
| 508 __slots__ = ( | 521 __slots__ = ( |
| 509 '_added_ids', | 522 '_added_ids', |
| 510 '_removed_ids', | 523 '_removed_ids', |
| 511 ) | 524 ) |
| 512 | 525 |
| 513 def __init__(self, added, removed, similar): | 526 def __init__(self, added, removed, similar, name=None, full_name=None, |
| 527 section_name=None): | |
| 514 self._added_ids = set(id(s) for s in added) | 528 self._added_ids = set(id(s) for s in added) |
| 515 self._removed_ids = set(id(s) for s in removed) | 529 self._removed_ids = set(id(s) for s in removed) |
| 516 symbols = [] | 530 symbols = [] |
| 517 symbols.extend(added) | 531 symbols.extend(added) |
| 518 symbols.extend(removed) | 532 symbols.extend(removed) |
| 519 symbols.extend(similar) | 533 symbols.extend(similar) |
| 520 super(SymbolDiff, self).__init__(symbols) | 534 super(SymbolDiff, self).__init__(symbols, name=name, full_name=full_name, |
| 535 section_name=section_name) | |
| 521 | 536 |
| 522 def __repr__(self): | 537 def __repr__(self): |
| 523 return '%s(%d added, %d removed, %d changed, %d unchanged, size=%d)' % ( | 538 return '%s(%d added, %d removed, %d changed, %d unchanged, size=%d)' % ( |
| 524 'SymbolGroup', self.added_count, self.removed_count, self.changed_count, | 539 'SymbolGroup', self.added_count, self.removed_count, self.changed_count, |
| 525 self.unchanged_count, self.size) | 540 self.unchanged_count, self.size) |
| 526 | 541 |
| 527 def _CreateTransformed(self, symbols, filtered_symbols=None, name=None, | 542 def _CreateTransformed(self, symbols, filtered_symbols=None, name=None, |
| 528 section_name=None, is_sorted=None): | 543 section_name=None, is_sorted=None): |
| 529 ret = SymbolDiff.__new__(SymbolDiff) | 544 ret = SymbolDiff.__new__(SymbolDiff) |
| 530 # Printing sorts, so fast-path the same symbols case. | 545 # Printing sorts, so fast-path the same symbols case. |
| (...skipping 50 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 581 Returns a SymbolDiff when args are of type SymbolGroup. | 596 Returns a SymbolDiff when args are of type SymbolGroup. |
| 582 """ | 597 """ |
| 583 if isinstance(new, SizeInfo): | 598 if isinstance(new, SizeInfo): |
| 584 assert isinstance(old, SizeInfo) | 599 assert isinstance(old, SizeInfo) |
| 585 section_sizes = { | 600 section_sizes = { |
| 586 k:new.section_sizes[k] - v for k, v in old.section_sizes.iteritems()} | 601 k:new.section_sizes[k] - v for k, v in old.section_sizes.iteritems()} |
| 587 symbol_diff = Diff(new.symbols, old.symbols) | 602 symbol_diff = Diff(new.symbols, old.symbols) |
| 588 return SizeInfoDiff(section_sizes, symbol_diff, old.metadata, new.metadata) | 603 return SizeInfoDiff(section_sizes, symbol_diff, old.metadata, new.metadata) |
| 589 | 604 |
| 590 assert isinstance(new, SymbolGroup) and isinstance(old, SymbolGroup) | 605 assert isinstance(new, SymbolGroup) and isinstance(old, SymbolGroup) |
| 606 return _DiffSymbols(new, old) | |
| 607 | |
| 608 | |
| 609 def _NegateAll(symbols): | |
| 610 ret = [] | |
| 611 for symbol in symbols: | |
| 612 if symbol.IsGroup(): | |
| 613 duped = SymbolDiff([], _NegateAll(symbol), [], name=symbol.name, | |
| 614 full_name=symbol.full_name, | |
| 615 section_name=symbol.section_name) | |
| 616 else: | |
| 617 duped = copy.copy(symbol) | |
| 618 duped.size = -duped.size | |
| 619 duped.padding = -duped.padding | |
| 620 ret.append(duped) | |
| 621 return ret | |
| 622 | |
| 623 | |
| 624 def _DiffSymbols(new_group, old_group): | |
| 591 symbols_by_key = collections.defaultdict(list) | 625 symbols_by_key = collections.defaultdict(list) |
| 592 for s in old: | 626 for s in old_group: |
| 593 symbols_by_key[s._Key()].append(s) | 627 symbols_by_key[s._Key()].append(s) |
| 594 | 628 |
| 595 added = [] | 629 added = [] |
| 596 removed = [] | |
| 597 similar = [] | 630 similar = [] |
| 598 # For similar symbols, padding is zeroed out. In order to not lose the | 631 # For similar symbols, padding is zeroed out. In order to not lose the |
| 599 # information entirely, store it in aggregate. | 632 # information entirely, store it in aggregate. |
| 600 padding_by_section_name = collections.defaultdict(int) | 633 padding_by_section_name = collections.defaultdict(int) |
| 601 for new_sym in new: | 634 for new_sym in new_group: |
| 602 matching_syms = symbols_by_key.get(new_sym._Key()) | 635 matching_syms = symbols_by_key.get(new_sym._Key()) |
| 603 if matching_syms: | 636 if matching_syms: |
| 604 old_sym = matching_syms.pop(0) | 637 old_sym = matching_syms.pop(0) |
| 605 # More stable/useful to compare size without padding. | 638 if old_sym.IsGroup() and new_sym.IsGroup(): |
| 606 size_diff = (new_sym.size_without_padding - | 639 merged_sym = _DiffSymbols(new_sym, old_sym) |
| 607 old_sym.size_without_padding) | 640 else: |
| 608 merged_sym = Symbol(new_sym.section_name, size_diff, | 641 size_diff = new_sym.size_without_padding - old_sym.size_without_padding |
| 609 address=new_sym.address, name=new_sym.name, | 642 merged_sym = Symbol(new_sym.section_name, size_diff, |
| 610 source_path=new_sym.source_path, | 643 address=new_sym.address, name=new_sym.name, |
| 611 object_path=new_sym.object_path, | 644 source_path=new_sym.source_path, |
| 612 full_name=new_sym.full_name, | 645 object_path=new_sym.object_path, |
| 613 is_anonymous=new_sym.is_anonymous) | 646 full_name=new_sym.full_name, |
| 647 is_anonymous=new_sym.is_anonymous) | |
| 648 | |
| 649 # Diffs are more stable when comparing size without padding, except when | |
| 650 # the symbol is a padding-only symbol. | |
| 651 if new_sym.size_without_padding == 0 and size_diff == 0: | |
| 652 merged_sym.padding = new_sym.padding - old_sym.padding | |
| 653 else: | |
| 654 padding_by_section_name[new_sym.section_name] += ( | |
| 655 new_sym.padding - old_sym.padding) | |
| 656 | |
| 614 similar.append(merged_sym) | 657 similar.append(merged_sym) |
| 615 padding_by_section_name[new_sym.section_name] += ( | |
| 616 new_sym.padding - old_sym.padding) | |
| 617 else: | 658 else: |
| 618 added.append(new_sym) | 659 added.append(new_sym) |
| 619 | 660 |
| 661 removed = [] | |
| 620 for remaining_syms in symbols_by_key.itervalues(): | 662 for remaining_syms in symbols_by_key.itervalues(): |
| 621 for old_sym in remaining_syms: | 663 if remaining_syms: |
| 622 duped = copy.copy(old_sym) | 664 removed.extend(_NegateAll(remaining_syms)) |
| 623 duped.size = -duped.size | |
| 624 duped.padding = -duped.padding | |
| 625 removed.append(duped) | |
| 626 | 665 |
| 627 for section_name, padding in padding_by_section_name.iteritems(): | 666 for section_name, padding in padding_by_section_name.iteritems(): |
| 628 similar.append(Symbol(section_name, padding, | 667 if padding != 0: |
| 629 name="** aggregate padding of diff'ed symbols")) | 668 similar.append(Symbol(section_name, padding, |
| 630 return SymbolDiff(added, removed, similar) | 669 name="** aggregate padding of diff'ed symbols")) |
| 670 return SymbolDiff(added, removed, similar, name=new_group.name, | |
| 671 full_name=new_group.full_name, | |
| 672 section_name=new_group.section_name) | |
| 631 | 673 |
| 632 | 674 |
| 633 def _ExtractPrefixBeforeSeparator(string, separator, count=1): | 675 def _ExtractPrefixBeforeSeparator(string, separator, count=1): |
| 634 idx = -len(separator) | 676 idx = -len(separator) |
| 635 prev_idx = None | 677 prev_idx = None |
| 636 for _ in xrange(count): | 678 for _ in xrange(count): |
| 637 idx = string.find(separator, idx + len(separator)) | 679 idx = string.find(separator, idx + len(separator)) |
| 638 if idx < 0: | 680 if idx < 0: |
| 639 break | 681 break |
| 640 prev_idx = idx | 682 prev_idx = idx |
| 641 return string[:prev_idx] | 683 return string[:prev_idx] |
| OLD | NEW |