Chromium Code Reviews| OLD | NEW |
|---|---|
| 1 #!/usr/bin/env python | 1 #!/usr/bin/env python |
| 2 # Copyright 2014 The Chromium Authors. All rights reserved. | 2 # Copyright 2014 The Chromium Authors. All rights reserved. |
| 3 # Use of this source code is governed by a BSD-style license that can be | 3 # Use of this source code is governed by a BSD-style license that can be |
| 4 # found in the LICENSE file. | 4 # found in the LICENSE file. |
| 5 | 5 |
| 6 """Generate a spatial analysis against an arbitrary library. | 6 """Generate a spatial analysis against an arbitrary library. |
| 7 | 7 |
| 8 To use, build the 'binary_size_tool' target. Then run this tool, passing | 8 To use, build the 'binary_size_tool' target. Then run this tool, passing |
| 9 in the location of the library to be analyzed along with any other options | 9 in the location of the library to be analyzed along with any other options |
| 10 you desire. | 10 you desire. |
| (...skipping 464 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 475 sNmPattern = re.compile( | 475 sNmPattern = re.compile( |
| 476 r'([0-9a-f]{8,})[\s]+([0-9a-f]{8,})[\s]*(\S?)[\s*]([^\t]*)[\t]?(.*)') | 476 r'([0-9a-f]{8,})[\s]+([0-9a-f]{8,})[\s]*(\S?)[\s*]([^\t]*)[\t]?(.*)') |
| 477 | 477 |
| 478 class Progress(): | 478 class Progress(): |
| 479 def __init__(self): | 479 def __init__(self): |
| 480 self.count = 0 | 480 self.count = 0 |
| 481 self.skip_count = 0 | 481 self.skip_count = 0 |
| 482 self.collisions = 0 | 482 self.collisions = 0 |
| 483 self.time_last_output = time.time() | 483 self.time_last_output = time.time() |
| 484 self.count_last_output = 0 | 484 self.count_last_output = 0 |
| 485 self.disambiguations = 0 | |
| 486 self.was_ambiguous = 0 | |
| 485 | 487 |
| 486 | 488 |
| 487 def RunElfSymbolizer(outfile, library, addr2line_binary, nm_binary, jobs): | 489 def RunElfSymbolizer(outfile, library, addr2line_binary, nm_binary, jobs, |
| 490 disambiguate, src_path): | |
| 488 nm_output = RunNm(library, nm_binary) | 491 nm_output = RunNm(library, nm_binary) |
| 489 nm_output_lines = nm_output.splitlines() | 492 nm_output_lines = nm_output.splitlines() |
| 490 nm_output_lines_len = len(nm_output_lines) | 493 nm_output_lines_len = len(nm_output_lines) |
| 491 address_symbol = {} | 494 address_symbol = {} |
| 492 progress = Progress() | 495 progress = Progress() |
| 493 def map_address_symbol(symbol, addr): | 496 def map_address_symbol(symbol, addr): |
| 494 progress.count += 1 | 497 progress.count += 1 |
| 495 if addr in address_symbol: | 498 if addr in address_symbol: |
| 496 # 'Collision between %s and %s.' % (str(symbol.name), | 499 # 'Collision between %s and %s.' % (str(symbol.name), |
| 497 # str(address_symbol[addr].name)) | 500 # str(address_symbol[addr].name)) |
| 498 progress.collisions += 1 | 501 progress.collisions += 1 |
| 499 else: | 502 else: |
| 503 if symbol.disambiguated: | |
| 504 progress.disambiguations += 1 | |
| 505 if symbol.was_ambiguous: | |
| 506 progress.was_ambiguous += 1 | |
| 507 | |
| 500 address_symbol[addr] = symbol | 508 address_symbol[addr] = symbol |
| 501 | 509 |
| 502 progress_chunk = 100 | 510 progress_chunk = 100 |
| 503 if progress.count % progress_chunk == 0: | 511 if progress.count % progress_chunk == 0: |
| 504 time_now = time.time() | 512 time_now = time.time() |
| 505 time_spent = time_now - progress.time_last_output | 513 time_spent = time_now - progress.time_last_output |
| 506 if time_spent > 1.0: | 514 if time_spent > 1.0: |
| 507 # Only output at most once per second. | 515 # Only output at most once per second. |
| 508 progress.time_last_output = time_now | 516 progress.time_last_output = time_now |
| 509 chunk_size = progress.count - progress.count_last_output | 517 chunk_size = progress.count - progress.count_last_output |
| 510 progress.count_last_output = progress.count | 518 progress.count_last_output = progress.count |
| 511 if time_spent > 0: | 519 if time_spent > 0: |
| 512 speed = chunk_size / time_spent | 520 speed = chunk_size / time_spent |
| 513 else: | 521 else: |
| 514 speed = 0 | 522 speed = 0 |
| 515 progress_percent = (100.0 * (progress.count + progress.skip_count) / | 523 progress_percent = (100.0 * (progress.count + progress.skip_count) / |
| 516 nm_output_lines_len) | 524 nm_output_lines_len) |
| 517 print('%.1f%%: Looked up %d symbols (%d collisions) - %.1f lookups/s.' % | 525 disambiguation_percent = 0 |
| 518 (progress_percent, progress.count, progress.collisions, speed)) | 526 if progress.disambiguations != 0: |
| 527 disambiguation_percent = (100.0 * progress.disambiguations / | |
| 528 progress.was_ambiguous) | |
| 519 | 529 |
| 530 sys.stdout.write('\r%.1f%%: Looked up %d symbols (%d collisions, ' | |
| 531 '%d disambiguations where %.1f%% succeeded)' | |
| 532 '- %.1f lookups/s.' % | |
| 533 (progress_percent, progress.count, progress.collisions, | |
| 534 progress.disambiguations, disambiguation_percent, speed)) | |
| 535 | |
| 536 # In case disambiguation was disabled, we remove the source path (which upon | |
| 537 # being set signals the symbolizer to enable disambiguation) | |
| 538 if not disambiguate: | |
| 539 src_path = None | |
| 520 symbolizer = elf_symbolizer.ELFSymbolizer(library, addr2line_binary, | 540 symbolizer = elf_symbolizer.ELFSymbolizer(library, addr2line_binary, |
| 521 map_address_symbol, | 541 map_address_symbol, |
| 522 max_concurrent_jobs=jobs) | 542 max_concurrent_jobs=jobs, |
| 543 source_root_path=src_path) | |
| 523 user_interrupted = False | 544 user_interrupted = False |
| 524 try: | 545 try: |
| 525 for line in nm_output_lines: | 546 for line in nm_output_lines: |
| 526 match = sNmPattern.match(line) | 547 match = sNmPattern.match(line) |
| 527 if match: | 548 if match: |
| 528 location = match.group(5) | 549 location = match.group(5) |
| 529 if not location: | 550 if not location: |
| 530 addr = int(match.group(1), 16) | 551 addr = int(match.group(1), 16) |
| 531 size = int(match.group(2), 16) | 552 size = int(match.group(2), 16) |
| 532 if addr in address_symbol: # Already looked up, shortcut | 553 if addr in address_symbol: # Already looked up, shortcut |
| (...skipping 12 matching lines...) Expand all Loading... | |
| 545 user_interrupted = True | 566 user_interrupted = True |
| 546 print('Interrupting - killing subprocesses. Please wait.') | 567 print('Interrupting - killing subprocesses. Please wait.') |
| 547 | 568 |
| 548 try: | 569 try: |
| 549 symbolizer.Join() | 570 symbolizer.Join() |
| 550 except KeyboardInterrupt: | 571 except KeyboardInterrupt: |
| 551 # Don't want to abort here since we will be finished in a few seconds. | 572 # Don't want to abort here since we will be finished in a few seconds. |
| 552 user_interrupted = True | 573 user_interrupted = True |
| 553 print('Patience you must have my young padawan.') | 574 print('Patience you must have my young padawan.') |
| 554 | 575 |
| 576 print '' | |
| 577 | |
| 555 if user_interrupted: | 578 if user_interrupted: |
| 556 print('Skipping the rest of the file mapping. ' | 579 print('Skipping the rest of the file mapping. ' |
| 557 'Output will not be fully classified.') | 580 'Output will not be fully classified.') |
| 558 | 581 |
| 559 with open(outfile, 'w') as out: | 582 with open(outfile, 'w') as out: |
| 560 for line in nm_output_lines: | 583 for line in nm_output_lines: |
| 561 match = sNmPattern.match(line) | 584 match = sNmPattern.match(line) |
| 562 if match: | 585 if match: |
| 563 location = match.group(5) | 586 location = match.group(5) |
| 564 if not location: | 587 if not location: |
| (...skipping 27 matching lines...) Expand all Loading... | |
| 592 if err_output: | 615 if err_output: |
| 593 raise Exception, err_output | 616 raise Exception, err_output |
| 594 else: | 617 else: |
| 595 raise Exception, process_output | 618 raise Exception, process_output |
| 596 | 619 |
| 597 print('Finished nm') | 620 print('Finished nm') |
| 598 return process_output | 621 return process_output |
| 599 | 622 |
| 600 | 623 |
| 601 def GetNmSymbols(nm_infile, outfile, library, jobs, verbose, | 624 def GetNmSymbols(nm_infile, outfile, library, jobs, verbose, |
| 602 addr2line_binary, nm_binary): | 625 addr2line_binary, nm_binary, disambiguate, src_path): |
| 603 if nm_infile is None: | 626 if nm_infile is None: |
| 604 if outfile is None: | 627 if outfile is None: |
| 605 outfile = tempfile.NamedTemporaryFile(delete=False).name | 628 outfile = tempfile.NamedTemporaryFile(delete=False).name |
| 606 | 629 |
| 607 if verbose: | 630 if verbose: |
| 608 print 'Running parallel addr2line, dumping symbols to ' + outfile | 631 print 'Running parallel addr2line, dumping symbols to ' + outfile |
| 609 RunElfSymbolizer(outfile, library, addr2line_binary, nm_binary, jobs) | 632 RunElfSymbolizer(outfile, library, addr2line_binary, nm_binary, jobs, |
| 633 disambiguate, src_path) | |
| 610 | 634 |
| 611 nm_infile = outfile | 635 nm_infile = outfile |
| 612 | 636 |
| 613 elif verbose: | 637 elif verbose: |
| 614 print 'Using nm input from ' + nm_infile | 638 print 'Using nm input from ' + nm_infile |
| 615 with file(nm_infile, 'r') as infile: | 639 with file(nm_infile, 'r') as infile: |
| 616 return list(binary_size_utils.ParseNm(infile)) | 640 return list(binary_size_utils.ParseNm(infile)) |
| 617 | 641 |
| 618 | 642 |
| 619 def _find_in_system_path(binary): | 643 def _find_in_system_path(binary): |
| (...skipping 89 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 709 help='be verbose, printing lots of status information.') | 733 help='be verbose, printing lots of status information.') |
| 710 parser.add_option('--nm-out', metavar='PATH', | 734 parser.add_option('--nm-out', metavar='PATH', |
| 711 help='keep the nm output file, and store it at the ' | 735 help='keep the nm output file, and store it at the ' |
| 712 'specified path. This is useful if you want to see the ' | 736 'specified path. This is useful if you want to see the ' |
| 713 'fully processed nm output after the symbols have been ' | 737 'fully processed nm output after the symbols have been ' |
| 714 'mapped to source locations. By default, a tempfile is ' | 738 'mapped to source locations. By default, a tempfile is ' |
| 715 'used and is deleted when the program terminates.' | 739 'used and is deleted when the program terminates.' |
| 716 'This argument is only valid when using --library.') | 740 'This argument is only valid when using --library.') |
| 717 parser.add_option('--legacy', action='store_true', | 741 parser.add_option('--legacy', action='store_true', |
| 718 help='emit legacy binary size report instead of modern') | 742 help='emit legacy binary size report instead of modern') |
| 743 parser.add_option('--disable-disambiguation', action='store_true', | |
| 744 help='disables the disambiguation process altogether,' | |
| 745 ' NOTE: this will produce output with some symbols at the' | |
|
Andrew Hayden (chromium.org)
2014/06/26 09:00:11
"will" -> "may, depending upon your toolchain,"
| |
| 746 ' top layer due to the fact that addr2line could not get' | |
|
Andrew Hayden (chromium.org)
2014/06/26 09:00:11
"due to the fact that" -> "if"
| |
| 747 ' the entire source path.') | |
| 748 parser.add_option('--source-path', default='./', | |
| 749 help='the path to the source code of the output binary, ' | |
| 750 'default set to current directory. Used in the' | |
| 751 ' disambiguation process.') | |
| 719 opts, _args = parser.parse_args() | 752 opts, _args = parser.parse_args() |
| 720 | 753 |
| 721 if ((not opts.library) and (not opts.nm_in)) or (opts.library and opts.nm_in): | 754 if ((not opts.library) and (not opts.nm_in)) or (opts.library and opts.nm_in): |
| 722 parser.error('exactly one of --library or --nm-in is required') | 755 parser.error('exactly one of --library or --nm-in is required') |
| 723 if (opts.nm_in): | 756 if (opts.nm_in): |
| 724 if opts.jobs: | 757 if opts.jobs: |
| 725 print >> sys.stderr, ('WARNING: --jobs has no effect ' | 758 print >> sys.stderr, ('WARNING: --jobs has no effect ' |
| 726 'when used with --nm-in') | 759 'when used with --nm-in') |
| 727 if not opts.destdir: | 760 if not opts.destdir: |
| 728 parser.error('--destdir is required argument') | 761 parser.error('--destdir is required argument') |
| (...skipping 20 matching lines...) Expand all Loading... | |
| 749 assert nm_binary, 'Unable to find nm in the path. Use --nm-binary '\ | 782 assert nm_binary, 'Unable to find nm in the path. Use --nm-binary '\ |
| 750 'to specify location.' | 783 'to specify location.' |
| 751 | 784 |
| 752 print('addr2line: %s' % addr2line_binary) | 785 print('addr2line: %s' % addr2line_binary) |
| 753 print('nm: %s' % nm_binary) | 786 print('nm: %s' % nm_binary) |
| 754 | 787 |
| 755 CheckDebugFormatSupport(opts.library, addr2line_binary) | 788 CheckDebugFormatSupport(opts.library, addr2line_binary) |
| 756 | 789 |
| 757 symbols = GetNmSymbols(opts.nm_in, opts.nm_out, opts.library, | 790 symbols = GetNmSymbols(opts.nm_in, opts.nm_out, opts.library, |
| 758 opts.jobs, opts.verbose is True, | 791 opts.jobs, opts.verbose is True, |
| 759 addr2line_binary, nm_binary) | 792 addr2line_binary, nm_binary, |
| 793 opts.disable_disambiguation is None, | |
| 794 opts.source_path) | |
| 760 if not os.path.exists(opts.destdir): | 795 if not os.path.exists(opts.destdir): |
| 761 os.makedirs(opts.destdir, 0755) | 796 os.makedirs(opts.destdir, 0755) |
| 762 | 797 |
| 763 | 798 |
| 764 if opts.legacy: # legacy report | 799 if opts.legacy: # legacy report |
| 765 DumpTreemap(symbols, os.path.join(opts.destdir, 'treemap-dump.js')) | 800 DumpTreemap(symbols, os.path.join(opts.destdir, 'treemap-dump.js')) |
| 766 DumpLargestSymbols(symbols, | 801 DumpLargestSymbols(symbols, |
| 767 os.path.join(opts.destdir, 'largest-symbols.js'), 100) | 802 os.path.join(opts.destdir, 'largest-symbols.js'), 100) |
| 768 DumpLargestSources(symbols, | 803 DumpLargestSources(symbols, |
| 769 os.path.join(opts.destdir, 'largest-sources.js'), 100) | 804 os.path.join(opts.destdir, 'largest-sources.js'), 100) |
| (...skipping 22 matching lines...) Expand all Loading... | |
| 792 shutil.copy(os.path.join(d3_src, 'LICENSE'), d3_out) | 827 shutil.copy(os.path.join(d3_src, 'LICENSE'), d3_out) |
| 793 shutil.copy(os.path.join(d3_src, 'd3.js'), d3_out) | 828 shutil.copy(os.path.join(d3_src, 'd3.js'), d3_out) |
| 794 shutil.copy(os.path.join(template_src, 'index.html'), opts.destdir) | 829 shutil.copy(os.path.join(template_src, 'index.html'), opts.destdir) |
| 795 shutil.copy(os.path.join(template_src, 'D3SymbolTreeMap.js'), opts.destdir) | 830 shutil.copy(os.path.join(template_src, 'D3SymbolTreeMap.js'), opts.destdir) |
| 796 | 831 |
| 797 print 'Report saved to ' + opts.destdir + '/index.html' | 832 print 'Report saved to ' + opts.destdir + '/index.html' |
| 798 | 833 |
| 799 | 834 |
| 800 if __name__ == '__main__': | 835 if __name__ == '__main__': |
| 801 sys.exit(main()) | 836 sys.exit(main()) |
| OLD | NEW |