| 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 64 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 75 self.symbols = grouped_symbols | 75 self.symbols = grouped_symbols |
| 76 self.metadata = metadata or {} | 76 self.metadata = metadata or {} |
| 77 | 77 |
| 78 | 78 |
| 79 class SizeInfoDiff(object): | 79 class SizeInfoDiff(object): |
| 80 """What you get when you Diff() two SizeInfo objects. | 80 """What you get when you Diff() two SizeInfo objects. |
| 81 | 81 |
| 82 Fields: | 82 Fields: |
| 83 section_sizes: A dict of section_name -> size delta. | 83 section_sizes: A dict of section_name -> size delta. |
| 84 symbols: A SymbolDiff with all symbols in it. | 84 symbols: A SymbolDiff with all symbols in it. |
| 85 old_metadata: metadata of the "old" SizeInfo. | 85 before_metadata: metadata of the "before" SizeInfo. |
| 86 new_metadata: metadata of the "new" SizeInfo. | 86 after_metadata: metadata of the "after" SizeInfo. |
| 87 """ | 87 """ |
| 88 __slots__ = ( | 88 __slots__ = ( |
| 89 'section_sizes', | 89 'section_sizes', |
| 90 'symbols', | 90 'symbols', |
| 91 'old_metadata', | 91 'before_metadata', |
| 92 'new_metadata', | 92 'after_metadata', |
| 93 ) | 93 ) |
| 94 | 94 |
| 95 def __init__(self, section_sizes, symbols, old_metadata, new_metadata): | 95 def __init__(self, section_sizes, symbols, before_metadata, after_metadata): |
| 96 self.section_sizes = section_sizes | 96 self.section_sizes = section_sizes |
| 97 self.symbols = symbols | 97 self.symbols = symbols |
| 98 self.old_metadata = old_metadata | 98 self.before_metadata = before_metadata |
| 99 self.new_metadata = new_metadata | 99 self.after_metadata = after_metadata |
| 100 | 100 |
| 101 | 101 |
| 102 class BaseSymbol(object): | 102 class BaseSymbol(object): |
| 103 """Base class for Symbol and SymbolGroup. | 103 """Base class for Symbol and SymbolGroup. |
| 104 | 104 |
| 105 Refer to module docs for field descriptions. | 105 Refer to module docs for field descriptions. |
| 106 """ | 106 """ |
| 107 __slots__ = () | 107 __slots__ = () |
| 108 | 108 |
| 109 @property | 109 @property |
| (...skipping 136 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 246 return self._symbols.__getitem__(key) | 246 return self._symbols.__getitem__(key) |
| 247 if isinstance(key, basestring) or key > len(self._symbols): | 247 if isinstance(key, basestring) or key > len(self._symbols): |
| 248 found = self.WhereAddressInRange(key) | 248 found = self.WhereAddressInRange(key) |
| 249 if len(found) != 1: | 249 if len(found) != 1: |
| 250 raise KeyError('%d symbols found at address %s.' % (len(found), key)) | 250 raise KeyError('%d symbols found at address %s.' % (len(found), key)) |
| 251 return found[0] | 251 return found[0] |
| 252 return self._symbols[key] | 252 return self._symbols[key] |
| 253 | 253 |
| 254 def __sub__(self, other): | 254 def __sub__(self, other): |
| 255 other_ids = set(id(s) for s in other) | 255 other_ids = set(id(s) for s in other) |
| 256 new_symbols = [s for s in self if id(s) not in other_ids] | 256 after_symbols = [s for s in self if id(s) not in other_ids] |
| 257 return self._CreateTransformed(new_symbols, section_name=self.section_name) | 257 return self._CreateTransformed(after_symbols, |
| 258 section_name=self.section_name) |
| 258 | 259 |
| 259 def __add__(self, other): | 260 def __add__(self, other): |
| 260 self_ids = set(id(s) for s in self) | 261 self_ids = set(id(s) for s in self) |
| 261 new_symbols = self._symbols + [s for s in other if id(s) not in self_ids] | 262 after_symbols = self._symbols + [s for s in other if id(s) not in self_ids] |
| 262 return self._CreateTransformed(new_symbols, section_name=self.section_name, | 263 return self._CreateTransformed( |
| 263 is_sorted=False) | 264 after_symbols, section_name=self.section_name, is_sorted=False) |
| 264 | 265 |
| 265 @property | 266 @property |
| 266 def address(self): | 267 def address(self): |
| 267 first = self._symbols[0].address | 268 first = self._symbols[0].address |
| 268 return first if all(s.address == first for s in self._symbols) else 0 | 269 return first if all(s.address == first for s in self._symbols) else 0 |
| 269 | 270 |
| 270 @property | 271 @property |
| 271 def is_anonymous(self): | 272 def is_anonymous(self): |
| 272 first = self._symbols[0].is_anonymous | 273 first = self._symbols[0].is_anonymous |
| 273 return first if all( | 274 return first if all( |
| (...skipping 32 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 306 is_sorted = self.is_sorted | 307 is_sorted = self.is_sorted |
| 307 return SymbolGroup(symbols, filtered_symbols=filtered_symbols, name=name, | 308 return SymbolGroup(symbols, filtered_symbols=filtered_symbols, name=name, |
| 308 section_name=section_name, is_sorted=is_sorted) | 309 section_name=section_name, is_sorted=is_sorted) |
| 309 | 310 |
| 310 def Sorted(self, cmp_func=None, key=None, reverse=False): | 311 def Sorted(self, cmp_func=None, key=None, reverse=False): |
| 311 # Default to sorting by abs(size) then name. | 312 # Default to sorting by abs(size) then name. |
| 312 if cmp_func is None and key is None: | 313 if cmp_func is None and key is None: |
| 313 cmp_func = lambda a, b: cmp((a.IsBss(), abs(b.size), a.name), | 314 cmp_func = lambda a, b: cmp((a.IsBss(), abs(b.size), a.name), |
| 314 (b.IsBss(), abs(a.size), b.name)) | 315 (b.IsBss(), abs(a.size), b.name)) |
| 315 | 316 |
| 316 new_symbols = sorted(self._symbols, cmp_func, key, reverse) | 317 after_symbols = sorted(self._symbols, cmp_func, key, reverse) |
| 317 return self._CreateTransformed( | 318 return self._CreateTransformed( |
| 318 new_symbols, filtered_symbols=self._filtered_symbols, | 319 after_symbols, filtered_symbols=self._filtered_symbols, |
| 319 section_name=self.section_name, is_sorted=True) | 320 section_name=self.section_name, is_sorted=True) |
| 320 | 321 |
| 321 def SortedByName(self, reverse=False): | 322 def SortedByName(self, reverse=False): |
| 322 return self.Sorted(key=(lambda s:s.name), reverse=reverse) | 323 return self.Sorted(key=(lambda s:s.name), reverse=reverse) |
| 323 | 324 |
| 324 def SortedByAddress(self, reverse=False): | 325 def SortedByAddress(self, reverse=False): |
| 325 return self.Sorted(key=(lambda s:s.address), reverse=reverse) | 326 return self.Sorted(key=(lambda s:s.address), reverse=reverse) |
| 326 | 327 |
| 327 def SortedByCount(self, reverse=False): | 328 def SortedByCount(self, reverse=False): |
| 328 return self.Sorted(key=(lambda s:len(s) if s.IsGroup() else 1), | 329 return self.Sorted(key=(lambda s:len(s) if s.IsGroup() else 1), |
| (...skipping 80 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 409 | 410 |
| 410 Args: | 411 Args: |
| 411 func: Grouping function. Passed a symbol and returns a string for the | 412 func: Grouping function. Passed a symbol and returns a string for the |
| 412 name of the subgroup to put the symbol in. If None is returned, the | 413 name of the subgroup to put the symbol in. If None is returned, the |
| 413 symbol is omitted. | 414 symbol is omitted. |
| 414 min_count: Miniumum number of symbols for a group. If fewer than this many | 415 min_count: Miniumum number of symbols for a group. If fewer than this many |
| 415 symbols end up in a group, they will not be put within a group. | 416 symbols end up in a group, they will not be put within a group. |
| 416 Use a negative value to omit symbols entirely rather than | 417 Use a negative value to omit symbols entirely rather than |
| 417 include them outside of a group. | 418 include them outside of a group. |
| 418 """ | 419 """ |
| 419 new_syms = [] | 420 after_syms = [] |
| 420 filtered_symbols = [] | 421 filtered_symbols = [] |
| 421 symbols_by_token = collections.defaultdict(list) | 422 symbols_by_token = collections.defaultdict(list) |
| 422 # Index symbols by |func|. | 423 # Index symbols by |func|. |
| 423 for symbol in self: | 424 for symbol in self: |
| 424 token = func(symbol) | 425 token = func(symbol) |
| 425 if token is None: | 426 if token is None: |
| 426 filtered_symbols.append(symbol) | 427 filtered_symbols.append(symbol) |
| 427 symbols_by_token[token].append(symbol) | 428 symbols_by_token[token].append(symbol) |
| 428 # Create the subgroups. | 429 # Create the subgroups. |
| 429 include_singles = min_count >= 0 | 430 include_singles = min_count >= 0 |
| 430 min_count = abs(min_count) | 431 min_count = abs(min_count) |
| 431 for token, symbols in symbols_by_token.iteritems(): | 432 for token, symbols in symbols_by_token.iteritems(): |
| 432 if len(symbols) >= min_count: | 433 if len(symbols) >= min_count: |
| 433 new_syms.append(self._CreateTransformed( | 434 after_syms.append(self._CreateTransformed( |
| 434 symbols, name=token, section_name=self.section_name, | 435 symbols, name=token, section_name=self.section_name, |
| 435 is_sorted=False)) | 436 is_sorted=False)) |
| 436 elif include_singles: | 437 elif include_singles: |
| 437 new_syms.extend(symbols) | 438 after_syms.extend(symbols) |
| 438 else: | 439 else: |
| 439 filtered_symbols.extend(symbols) | 440 filtered_symbols.extend(symbols) |
| 440 return self._CreateTransformed( | 441 return self._CreateTransformed( |
| 441 new_syms, filtered_symbols=filtered_symbols, | 442 after_syms, filtered_symbols=filtered_symbols, |
| 442 section_name=self.section_name, is_sorted=False) | 443 section_name=self.section_name, is_sorted=False) |
| 443 | 444 |
| 444 def GroupBySectionName(self): | 445 def GroupBySectionName(self): |
| 445 return self.GroupBy(lambda s: s.section_name) | 446 return self.GroupBy(lambda s: s.section_name) |
| 446 | 447 |
| 447 def GroupByNamespace(self, depth=0, fallback='{global}', min_count=0): | 448 def GroupByNamespace(self, depth=0, fallback='{global}', min_count=0): |
| 448 """Groups by symbol namespace (as denoted by ::s). | 449 """Groups by symbol namespace (as denoted by ::s). |
| 449 | 450 |
| 450 Does not differentiate between C++ namespaces and C++ classes. | 451 Does not differentiate between C++ namespaces and C++ classes. |
| 451 | 452 |
| (...skipping 130 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 582 key = id(sym) | 583 key = id(sym) |
| 583 return key not in self._added_ids and key not in self._removed_ids | 584 return key not in self._added_ids and key not in self._removed_ids |
| 584 | 585 |
| 585 def IsRemoved(self, sym): | 586 def IsRemoved(self, sym): |
| 586 return id(sym) in self._removed_ids | 587 return id(sym) in self._removed_ids |
| 587 | 588 |
| 588 def WhereNotUnchanged(self): | 589 def WhereNotUnchanged(self): |
| 589 return self.Filter(lambda s: not self.IsSimilar(s) or s.size) | 590 return self.Filter(lambda s: not self.IsSimilar(s) or s.size) |
| 590 | 591 |
| 591 | 592 |
| 592 def Diff(new, old): | 593 def Diff(before, after): |
| 593 """Diffs two SizeInfo or SymbolGroup objects. | 594 """Diffs two SizeInfo or SymbolGroup objects. |
| 594 | 595 |
| 595 When diffing SizeInfos, a SizeInfoDiff is returned. | 596 When diffing SizeInfos, a SizeInfoDiff is returned. |
| 596 When diffing SymbolGroups, a SymbolDiff is returned. | 597 When diffing SymbolGroups, a SymbolDiff is returned. |
| 597 | 598 |
| 598 Returns: | 599 Returns: |
| 599 Returns a SizeInfo when args are of type SizeInfo. | 600 Returns a SizeInfo when args are of type SizeInfo. |
| 600 Returns a SymbolDiff when args are of type SymbolGroup. | 601 Returns a SymbolDiff when args are of type SymbolGroup. |
| 601 """ | 602 """ |
| 602 if isinstance(new, SizeInfo): | 603 if isinstance(after, SizeInfo): |
| 603 assert isinstance(old, SizeInfo) | 604 assert isinstance(before, SizeInfo) |
| 604 section_sizes = { | 605 section_sizes = {k: after.section_sizes[k] - v |
| 605 k:new.section_sizes[k] - v for k, v in old.section_sizes.iteritems()} | 606 for k, v in before.section_sizes.iteritems()} |
| 606 symbol_diff = Diff(new.symbols, old.symbols) | 607 symbol_diff = _DiffSymbols(before.symbols, after.symbols) |
| 607 return SizeInfoDiff(section_sizes, symbol_diff, old.metadata, new.metadata) | 608 return SizeInfoDiff(section_sizes, symbol_diff, before.metadata, |
| 609 after.metadata) |
| 608 | 610 |
| 609 assert isinstance(new, SymbolGroup) and isinstance(old, SymbolGroup) | 611 assert isinstance(after, SymbolGroup) and isinstance(before, SymbolGroup) |
| 610 return _DiffSymbols(new, old) | 612 return _DiffSymbols(before, after) |
| 611 | 613 |
| 612 | 614 |
| 613 def _NegateAll(symbols): | 615 def _NegateAll(symbols): |
| 614 ret = [] | 616 ret = [] |
| 615 for symbol in symbols: | 617 for symbol in symbols: |
| 616 if symbol.IsGroup(): | 618 if symbol.IsGroup(): |
| 617 duped = SymbolDiff([], _NegateAll(symbol), [], name=symbol.name, | 619 duped = SymbolDiff([], _NegateAll(symbol), [], name=symbol.name, |
| 618 full_name=symbol.full_name, | 620 full_name=symbol.full_name, |
| 619 section_name=symbol.section_name) | 621 section_name=symbol.section_name) |
| 620 else: | 622 else: |
| 621 duped = copy.copy(symbol) | 623 duped = copy.copy(symbol) |
| 622 duped.size = -duped.size | 624 duped.size = -duped.size |
| 623 duped.padding = -duped.padding | 625 duped.padding = -duped.padding |
| 624 ret.append(duped) | 626 ret.append(duped) |
| 625 return ret | 627 return ret |
| 626 | 628 |
| 627 | 629 |
| 628 def _DiffSymbols(new_group, old_group): | 630 def _DiffSymbols(before, after): |
| 629 symbols_by_key = collections.defaultdict(list) | 631 symbols_by_key = collections.defaultdict(list) |
| 630 for s in old_group: | 632 for s in before: |
| 631 symbols_by_key[s._Key()].append(s) | 633 symbols_by_key[s._Key()].append(s) |
| 632 | 634 |
| 633 added = [] | 635 added = [] |
| 634 similar = [] | 636 similar = [] |
| 635 # For similar symbols, padding is zeroed out. In order to not lose the | 637 # For similar symbols, padding is zeroed out. In order to not lose the |
| 636 # information entirely, store it in aggregate. | 638 # information entirely, store it in aggregate. |
| 637 padding_by_section_name = collections.defaultdict(int) | 639 padding_by_section_name = collections.defaultdict(int) |
| 638 for new_sym in new_group: | 640 for after_sym in after: |
| 639 matching_syms = symbols_by_key.get(new_sym._Key()) | 641 matching_syms = symbols_by_key.get(after_sym._Key()) |
| 640 if matching_syms: | 642 if matching_syms: |
| 641 old_sym = matching_syms.pop(0) | 643 before_sym = matching_syms.pop(0) |
| 642 if old_sym.IsGroup() and new_sym.IsGroup(): | 644 if before_sym.IsGroup() and after_sym.IsGroup(): |
| 643 merged_sym = _DiffSymbols(new_sym, old_sym) | 645 merged_sym = _DiffSymbols(before_sym, after_sym) |
| 644 else: | 646 else: |
| 645 size_diff = new_sym.size_without_padding - old_sym.size_without_padding | 647 size_diff = (after_sym.size_without_padding - |
| 646 merged_sym = Symbol(new_sym.section_name, size_diff, | 648 before_sym.size_without_padding) |
| 647 address=new_sym.address, name=new_sym.name, | 649 merged_sym = Symbol(after_sym.section_name, size_diff, |
| 648 source_path=new_sym.source_path, | 650 address=after_sym.address, name=after_sym.name, |
| 649 object_path=new_sym.object_path, | 651 source_path=after_sym.source_path, |
| 650 full_name=new_sym.full_name, | 652 object_path=after_sym.object_path, |
| 651 is_anonymous=new_sym.is_anonymous) | 653 full_name=after_sym.full_name, |
| 654 is_anonymous=after_sym.is_anonymous) |
| 652 | 655 |
| 653 # Diffs are more stable when comparing size without padding, except when | 656 # Diffs are more stable when comparing size without padding, except when |
| 654 # the symbol is a padding-only symbol. | 657 # the symbol is a padding-only symbol. |
| 655 if new_sym.size_without_padding == 0 and size_diff == 0: | 658 if after_sym.size_without_padding == 0 and size_diff == 0: |
| 656 merged_sym.padding = new_sym.padding - old_sym.padding | 659 merged_sym.padding = after_sym.padding - before_sym.padding |
| 657 else: | 660 else: |
| 658 padding_by_section_name[new_sym.section_name] += ( | 661 padding_by_section_name[after_sym.section_name] += ( |
| 659 new_sym.padding - old_sym.padding) | 662 after_sym.padding - before_sym.padding) |
| 660 | 663 |
| 661 similar.append(merged_sym) | 664 similar.append(merged_sym) |
| 662 else: | 665 else: |
| 663 added.append(new_sym) | 666 added.append(after_sym) |
| 664 | 667 |
| 665 removed = [] | 668 removed = [] |
| 666 for remaining_syms in symbols_by_key.itervalues(): | 669 for remaining_syms in symbols_by_key.itervalues(): |
| 667 if remaining_syms: | 670 if remaining_syms: |
| 668 removed.extend(_NegateAll(remaining_syms)) | 671 removed.extend(_NegateAll(remaining_syms)) |
| 669 | 672 |
| 670 for section_name, padding in padding_by_section_name.iteritems(): | 673 for section_name, padding in padding_by_section_name.iteritems(): |
| 671 if padding != 0: | 674 if padding != 0: |
| 672 similar.append(Symbol(section_name, padding, | 675 similar.append(Symbol(section_name, padding, |
| 673 name="** aggregate padding of diff'ed symbols")) | 676 name="** aggregate padding of diff'ed symbols")) |
| 674 return SymbolDiff(added, removed, similar, name=new_group.name, | 677 return SymbolDiff(added, removed, similar, name=after.name, |
| 675 full_name=new_group.full_name, | 678 full_name=after.full_name, |
| 676 section_name=new_group.section_name) | 679 section_name=after.section_name) |
| 677 | 680 |
| 678 | 681 |
| 679 def _ExtractPrefixBeforeSeparator(string, separator, count=1): | 682 def _ExtractPrefixBeforeSeparator(string, separator, count=1): |
| 680 idx = -len(separator) | 683 idx = -len(separator) |
| 681 prev_idx = None | 684 prev_idx = None |
| 682 for _ in xrange(count): | 685 for _ in xrange(count): |
| 683 idx = string.find(separator, idx + len(separator)) | 686 idx = string.find(separator, idx + len(separator)) |
| 684 if idx < 0: | 687 if idx < 0: |
| 685 break | 688 break |
| 686 prev_idx = idx | 689 prev_idx = idx |
| 687 return string[:prev_idx] | 690 return string[:prev_idx] |
| OLD | NEW |