| 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 315 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 326 @property | 326 @property |
| 327 def object_path(self): | 327 def object_path(self): |
| 328 first = self._symbols[0].object_path if self else '' | 328 first = self._symbols[0].object_path if self else '' |
| 329 return first if all(s.object_path == first for s in self._symbols) else '' | 329 return first if all(s.object_path == first for s in self._symbols) else '' |
| 330 | 330 |
| 331 @property | 331 @property |
| 332 def source_path(self): | 332 def source_path(self): |
| 333 first = self._symbols[0].source_path if self else '' | 333 first = self._symbols[0].source_path if self else '' |
| 334 return first if all(s.source_path == first for s in self._symbols) else '' | 334 return first if all(s.source_path == first for s in self._symbols) else '' |
| 335 | 335 |
| 336 def IterUniqueSymbols(self): | |
| 337 seen_aliases_lists = set() | |
| 338 for s in self: | |
| 339 if not s.aliases: | |
| 340 yield s | |
| 341 elif id(s.aliases) not in seen_aliases_lists: | |
| 342 seen_aliases_lists.add(id(s.aliases)) | |
| 343 yield s | |
| 344 | |
| 345 def CountUniqueSymbols(self): | |
| 346 return sum(1 for s in self.IterUniqueSymbols()) | |
| 347 | |
| 348 @property | 336 @property |
| 349 def size(self): | 337 def size(self): |
| 350 if self._size is None: | 338 if self._size is None: |
| 351 if self.IsBss(): | 339 if self.IsBss(): |
| 352 self._size = sum(s.size for s in self) | 340 self._size = sum(s.size for s in self) |
| 353 else: | 341 else: |
| 354 self._size = sum(s.size for s in self.IterUniqueSymbols()) | 342 self._size = sum(s.size for s in self.IterUniqueSymbols()) |
| 355 return self._size | 343 return self._size |
| 356 | 344 |
| 357 @property | 345 @property |
| (...skipping 11 matching lines...) Expand all Loading... |
| 369 self._padding = sum(s.padding for s in self.IterUniqueSymbols()) | 357 self._padding = sum(s.padding for s in self.IterUniqueSymbols()) |
| 370 return self._padding | 358 return self._padding |
| 371 | 359 |
| 372 @property | 360 @property |
| 373 def aliases(self): | 361 def aliases(self): |
| 374 return None | 362 return None |
| 375 | 363 |
| 376 def IsGroup(self): | 364 def IsGroup(self): |
| 377 return True | 365 return True |
| 378 | 366 |
| 367 def IterUniqueSymbols(self): |
| 368 """Yields all symbols, but only one from each alias group.""" |
| 369 seen_aliases_lists = set() |
| 370 for s in self: |
| 371 if not s.aliases: |
| 372 yield s |
| 373 elif id(s.aliases) not in seen_aliases_lists: |
| 374 seen_aliases_lists.add(id(s.aliases)) |
| 375 yield s |
| 376 |
| 377 def IterLeafSymbols(self): |
| 378 """Yields all symbols, recursing into subgroups.""" |
| 379 for s in self: |
| 380 if s.IsGroup(): |
| 381 for x in s.IterLeafSymbols(): |
| 382 yield x |
| 383 else: |
| 384 yield s |
| 385 |
| 386 def CountUniqueSymbols(self): |
| 387 return sum(1 for s in self.IterUniqueSymbols()) |
| 388 |
| 379 def _CreateTransformed(self, symbols, filtered_symbols=None, name=None, | 389 def _CreateTransformed(self, symbols, filtered_symbols=None, name=None, |
| 380 full_name=None, section_name=None, is_sorted=None): | 390 full_name=None, section_name=None, is_sorted=None): |
| 381 if is_sorted is None: | 391 if is_sorted is None: |
| 382 is_sorted = self.is_sorted | 392 is_sorted = self.is_sorted |
| 383 return SymbolGroup(symbols, filtered_symbols=filtered_symbols, name=name, | 393 return SymbolGroup(symbols, filtered_symbols=filtered_symbols, name=name, |
| 384 full_name=full_name, section_name=section_name, | 394 full_name=full_name, section_name=section_name, |
| 385 is_sorted=is_sorted) | 395 is_sorted=is_sorted) |
| 386 | 396 |
| 387 def Cluster(self): | 397 def Cluster(self): |
| 388 """Returns a new SymbolGroup with some symbols moved into subgroups. | 398 """Returns a new SymbolGroup with some symbols moved into subgroups. |
| 389 | 399 |
| 390 Subgroups include: | 400 Subgroups include: |
| 391 * Symbols that have [clone] in their name (created during inlining). | 401 * Symbols that have [clone] in their name (created during inlining). |
| 392 * Star symbols (such as "** merge strings", and "** symbol gap") | 402 * Star symbols (such as "** merge strings", and "** symbol gap") |
| 393 | 403 |
| 394 To view created groups: | 404 To view created groups: |
| 395 Print(clustered.Filter(lambda s: s.IsGroup()), recursive=True) | 405 Print(clustered.Filter(lambda s: s.IsGroup()), recursive=True) |
| 396 """ | 406 """ |
| 397 return self._CreateTransformed(cluster_symbols.ClusterSymbols(self)) | 407 return self._CreateTransformed(cluster_symbols.ClusterSymbols(self)) |
| 398 | 408 |
| 399 def Sorted(self, cmp_func=None, key=None, reverse=False): | 409 def Sorted(self, cmp_func=None, key=None, reverse=False): |
| 400 # Default to sorting by abs(size) then name. | |
| 401 if cmp_func is None and key is None: | 410 if cmp_func is None and key is None: |
| 402 cmp_func = lambda a, b: cmp((a.IsBss(), abs(b.size), a.name), | 411 cmp_func = lambda a, b: cmp((a.IsBss(), abs(b.pss), a.name), |
| 403 (b.IsBss(), abs(a.size), b.name)) | 412 (b.IsBss(), abs(a.pss), b.name)) |
| 404 | 413 |
| 405 after_symbols = sorted(self._symbols, cmp_func, key, reverse) | 414 after_symbols = sorted(self._symbols, cmp_func, key, reverse) |
| 406 return self._CreateTransformed( | 415 return self._CreateTransformed( |
| 407 after_symbols, filtered_symbols=self._filtered_symbols, | 416 after_symbols, filtered_symbols=self._filtered_symbols, |
| 408 section_name=self.section_name, is_sorted=True) | 417 section_name=self.section_name, is_sorted=True) |
| 409 | 418 |
| 410 def SortedByName(self, reverse=False): | 419 def SortedByName(self, reverse=False): |
| 411 return self.Sorted(key=(lambda s:s.name), reverse=reverse) | 420 return self.Sorted(key=(lambda s:s.name), reverse=reverse) |
| 412 | 421 |
| 413 def SortedByAddress(self, reverse=False): | 422 def SortedByAddress(self, reverse=False): |
| 414 return self.Sorted(key=(lambda s:s.address), reverse=reverse) | 423 return self.Sorted(key=(lambda s:(s.address, s.object_path, s.name)), |
| 424 reverse=reverse) |
| 415 | 425 |
| 416 def SortedByCount(self, reverse=False): | 426 def SortedByCount(self, reverse=False): |
| 417 return self.Sorted(key=(lambda s:len(s) if s.IsGroup() else 1), | 427 return self.Sorted(key=(lambda s:len(s) if s.IsGroup() else 1), |
| 418 reverse=not reverse) | 428 reverse=not reverse) |
| 419 | 429 |
| 420 def Filter(self, func): | 430 def Filter(self, func): |
| 421 filtered_and_kept = ([], []) | 431 filtered_and_kept = ([], []) |
| 422 symbol = None | 432 symbol = None |
| 423 try: | 433 try: |
| 424 for symbol in self: | 434 for symbol in self: |
| (...skipping 112 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 537 min_count = abs(min_count) | 547 min_count = abs(min_count) |
| 538 for token, symbols in symbols_by_token.iteritems(): | 548 for token, symbols in symbols_by_token.iteritems(): |
| 539 if len(symbols) >= min_count: | 549 if len(symbols) >= min_count: |
| 540 after_syms.append(self._CreateTransformed( | 550 after_syms.append(self._CreateTransformed( |
| 541 symbols, name=token, section_name=self.section_name, | 551 symbols, name=token, section_name=self.section_name, |
| 542 is_sorted=False)) | 552 is_sorted=False)) |
| 543 elif include_singles: | 553 elif include_singles: |
| 544 after_syms.extend(symbols) | 554 after_syms.extend(symbols) |
| 545 else: | 555 else: |
| 546 filtered_symbols.extend(symbols) | 556 filtered_symbols.extend(symbols) |
| 547 return self._CreateTransformed( | 557 grouped = self._CreateTransformed( |
| 548 after_syms, filtered_symbols=filtered_symbols, | 558 after_syms, filtered_symbols=filtered_symbols, |
| 549 section_name=self.section_name, is_sorted=False) | 559 section_name=self.section_name, is_sorted=False) |
| 560 return grouped |
| 550 | 561 |
| 551 def GroupBySectionName(self): | 562 def GroupBySectionName(self): |
| 552 return self.GroupBy(lambda s: s.section_name) | 563 return self.GroupBy(lambda s: s.section_name) |
| 553 | 564 |
| 554 def GroupByNamespace(self, depth=0, fallback='{global}', min_count=0): | 565 def GroupByNamespace(self, depth=0, fallback='{global}', min_count=0): |
| 555 """Groups by symbol namespace (as denoted by ::s). | 566 """Groups by symbol namespace (as denoted by ::s). |
| 556 | 567 |
| 557 Does not differentiate between C++ namespaces and C++ classes. | 568 Does not differentiate between C++ namespaces and C++ classes. |
| 558 | 569 |
| 559 Args: | 570 Args: |
| (...skipping 14 matching lines...) Expand all Loading... |
| 574 | 585 |
| 575 # Remove after the final :: (not part of the namespace). | 586 # Remove after the final :: (not part of the namespace). |
| 576 colon_idx = name.rfind('::') | 587 colon_idx = name.rfind('::') |
| 577 if colon_idx == -1: | 588 if colon_idx == -1: |
| 578 return fallback | 589 return fallback |
| 579 name = name[:colon_idx] | 590 name = name[:colon_idx] |
| 580 | 591 |
| 581 return _ExtractPrefixBeforeSeparator(name, '::', depth) | 592 return _ExtractPrefixBeforeSeparator(name, '::', depth) |
| 582 return self.GroupBy(extract_namespace, min_count=min_count) | 593 return self.GroupBy(extract_namespace, min_count=min_count) |
| 583 | 594 |
| 584 def GroupBySourcePath(self, depth=0, fallback='{no path}', | 595 def GroupByPath(self, depth=0, fallback='{no path}', |
| 585 fallback_to_object_path=True, min_count=0): | 596 fallback_to_object_path=True, min_count=0): |
| 586 """Groups by source_path. | 597 """Groups by source_path. |
| 587 | 598 |
| 588 Args: | 599 Args: |
| 589 depth: When 0 (default), groups by entire path. When 1, groups by | 600 depth: When 0 (default), groups by entire path. When 1, groups by |
| 590 top-level directory, when 2, groups by top 2 directories, etc. | 601 top-level directory, when 2, groups by top 2 directories, etc. |
| 591 fallback: Use this value when no namespace exists. | 602 fallback: Use this value when no namespace exists. |
| 592 fallback_to_object_path: When True (default), uses object_path when | 603 fallback_to_object_path: When True (default), uses object_path when |
| 593 source_path is missing. | 604 source_path is missing. |
| 594 min_count: Miniumum number of symbols for a group. If fewer than this many | 605 min_count: Miniumum number of symbols for a group. If fewer than this many |
| 595 symbols end up in a group, they will not be put within a group. | 606 symbols end up in a group, they will not be put within a group. |
| 596 Use a negative value to omit symbols entirely rather than | 607 Use a negative value to omit symbols entirely rather than |
| 597 include them outside of a group. | 608 include them outside of a group. |
| 598 """ | 609 """ |
| 599 def extract_path(symbol): | 610 def extract_path(symbol): |
| 600 path = symbol.source_path | 611 path = symbol.source_path |
| 601 if fallback_to_object_path and not path: | 612 if fallback_to_object_path and not path: |
| 602 path = symbol.object_path | 613 path = symbol.object_path |
| 603 path = path or fallback | 614 path = path or fallback |
| 604 return _ExtractPrefixBeforeSeparator(path, os.path.sep, depth) | 615 return _ExtractPrefixBeforeSeparator(path, os.path.sep, depth) |
| 605 return self.GroupBy(extract_path, min_count=min_count) | 616 return self.GroupBy(extract_path, min_count=min_count) |
| 606 | 617 |
| 607 def GroupByObjectPath(self, depth=0, fallback='{no path}', min_count=0): | |
| 608 """Groups by object_path. | |
| 609 | |
| 610 Args: | |
| 611 depth: When 0 (default), groups by entire path. When 1, groups by | |
| 612 top-level directory, when 2, groups by top 2 directories, etc. | |
| 613 fallback: Use this value when no namespace exists. | |
| 614 min_count: Miniumum number of symbols for a group. If fewer than this many | |
| 615 symbols end up in a group, they will not be put within a group. | |
| 616 Use a negative value to omit symbols entirely rather than | |
| 617 include them outside of a group. | |
| 618 """ | |
| 619 def extract_path(symbol): | |
| 620 path = symbol.object_path or fallback | |
| 621 return _ExtractPrefixBeforeSeparator(path, os.path.sep, depth) | |
| 622 return self.GroupBy(extract_path, min_count=min_count) | |
| 623 | |
| 624 | 618 |
| 625 class SymbolDiff(SymbolGroup): | 619 class SymbolDiff(SymbolGroup): |
| 626 """A SymbolGroup subclass representing a diff of two other SymbolGroups. | 620 """A SymbolGroup subclass representing a diff of two other SymbolGroups. |
| 627 | 621 |
| 628 All Symbols contained within have a |size| which is actually the size delta. | 622 All Symbols contained within have a |size| which is actually the size delta. |
| 629 Additionally, metadata is kept about which symbols were added / removed / | 623 Additionally, metadata is kept about which symbols were added / removed / |
| 630 changed. | 624 changed. |
| 631 """ | 625 """ |
| 632 __slots__ = ( | 626 __slots__ = ( |
| 633 '_added_ids', | 627 '_added_ids', |
| (...skipping 88 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 722 | 716 |
| 723 def _ExtractPrefixBeforeSeparator(string, separator, count=1): | 717 def _ExtractPrefixBeforeSeparator(string, separator, count=1): |
| 724 idx = -len(separator) | 718 idx = -len(separator) |
| 725 prev_idx = None | 719 prev_idx = None |
| 726 for _ in xrange(count): | 720 for _ in xrange(count): |
| 727 idx = string.find(separator, idx + len(separator)) | 721 idx = string.find(separator, idx + len(separator)) |
| 728 if idx < 0: | 722 if idx < 0: |
| 729 break | 723 break |
| 730 prev_idx = idx | 724 prev_idx = idx |
| 731 return string[:prev_idx] | 725 return string[:prev_idx] |
| OLD | NEW |