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.failed_disambiguations = 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.failed_disambiguation: |
| 506 progress.failed_disambiguations += 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.disambiguations + progress.failed_disambiguations)) |
| 529 sys.stdout.write('\r%.1f%%: Looked up %d symbols (%d collisions, ' |
| 530 '%d disambiguations where %.1f%% succeeded)' |
| 531 '- %.1f lookups/s.' % |
| 532 (progress_percent, progress.count, progress.collisions, |
| 533 progress.disambiguations, disambiguation_percent, speed)) |
519 | 534 |
520 symbolizer = elf_symbolizer.ELFSymbolizer(library, addr2line_binary, | 535 symbolizer = elf_symbolizer.ELFSymbolizer(library, addr2line_binary, |
521 map_address_symbol, | 536 map_address_symbol, |
522 max_concurrent_jobs=jobs) | 537 max_concurrent_jobs=jobs, |
| 538 disambiguate=disambiguate, |
| 539 disambiguation_source_path=src_path) |
523 user_interrupted = False | 540 user_interrupted = False |
524 try: | 541 try: |
525 for line in nm_output_lines: | 542 for line in nm_output_lines: |
526 match = sNmPattern.match(line) | 543 match = sNmPattern.match(line) |
527 if match: | 544 if match: |
528 location = match.group(5) | 545 location = match.group(5) |
529 if not location: | 546 if not location: |
530 addr = int(match.group(1), 16) | 547 addr = int(match.group(1), 16) |
531 size = int(match.group(2), 16) | 548 size = int(match.group(2), 16) |
532 if addr in address_symbol: # Already looked up, shortcut | 549 if addr in address_symbol: # Already looked up, shortcut |
(...skipping 12 matching lines...) Expand all Loading... |
545 user_interrupted = True | 562 user_interrupted = True |
546 print('Interrupting - killing subprocesses. Please wait.') | 563 print('Interrupting - killing subprocesses. Please wait.') |
547 | 564 |
548 try: | 565 try: |
549 symbolizer.Join() | 566 symbolizer.Join() |
550 except KeyboardInterrupt: | 567 except KeyboardInterrupt: |
551 # Don't want to abort here since we will be finished in a few seconds. | 568 # Don't want to abort here since we will be finished in a few seconds. |
552 user_interrupted = True | 569 user_interrupted = True |
553 print('Patience you must have my young padawan.') | 570 print('Patience you must have my young padawan.') |
554 | 571 |
| 572 print '' |
| 573 |
555 if user_interrupted: | 574 if user_interrupted: |
556 print('Skipping the rest of the file mapping. ' | 575 print('Skipping the rest of the file mapping. ' |
557 'Output will not be fully classified.') | 576 'Output will not be fully classified.') |
558 | 577 |
559 with open(outfile, 'w') as out: | 578 with open(outfile, 'w') as out: |
560 for line in nm_output_lines: | 579 for line in nm_output_lines: |
561 match = sNmPattern.match(line) | 580 match = sNmPattern.match(line) |
562 if match: | 581 if match: |
563 location = match.group(5) | 582 location = match.group(5) |
564 if not location: | 583 if not location: |
(...skipping 27 matching lines...) Expand all Loading... |
592 if err_output: | 611 if err_output: |
593 raise Exception, err_output | 612 raise Exception, err_output |
594 else: | 613 else: |
595 raise Exception, process_output | 614 raise Exception, process_output |
596 | 615 |
597 print('Finished nm') | 616 print('Finished nm') |
598 return process_output | 617 return process_output |
599 | 618 |
600 | 619 |
601 def GetNmSymbols(nm_infile, outfile, library, jobs, verbose, | 620 def GetNmSymbols(nm_infile, outfile, library, jobs, verbose, |
602 addr2line_binary, nm_binary): | 621 addr2line_binary, nm_binary, disambiguate, src_path): |
603 if nm_infile is None: | 622 if nm_infile is None: |
604 if outfile is None: | 623 if outfile is None: |
605 outfile = tempfile.NamedTemporaryFile(delete=False).name | 624 outfile = tempfile.NamedTemporaryFile(delete=False).name |
606 | 625 |
607 if verbose: | 626 if verbose: |
608 print 'Running parallel addr2line, dumping symbols to ' + outfile | 627 print 'Running parallel addr2line, dumping symbols to ' + outfile |
609 RunElfSymbolizer(outfile, library, addr2line_binary, nm_binary, jobs) | 628 RunElfSymbolizer(outfile, library, addr2line_binary, nm_binary, jobs, |
| 629 disambiguate, src_path) |
610 | 630 |
611 nm_infile = outfile | 631 nm_infile = outfile |
612 | 632 |
613 elif verbose: | 633 elif verbose: |
614 print 'Using nm input from ' + nm_infile | 634 print 'Using nm input from ' + nm_infile |
615 with file(nm_infile, 'r') as infile: | 635 with file(nm_infile, 'r') as infile: |
616 return list(binary_size_utils.ParseNm(infile)) | 636 return list(binary_size_utils.ParseNm(infile)) |
617 | 637 |
618 | 638 |
619 def _find_in_system_path(binary): | 639 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.') | 729 help='be verbose, printing lots of status information.') |
710 parser.add_option('--nm-out', metavar='PATH', | 730 parser.add_option('--nm-out', metavar='PATH', |
711 help='keep the nm output file, and store it at the ' | 731 help='keep the nm output file, and store it at the ' |
712 'specified path. This is useful if you want to see the ' | 732 'specified path. This is useful if you want to see the ' |
713 'fully processed nm output after the symbols have been ' | 733 'fully processed nm output after the symbols have been ' |
714 'mapped to source locations. By default, a tempfile is ' | 734 'mapped to source locations. By default, a tempfile is ' |
715 'used and is deleted when the program terminates.' | 735 'used and is deleted when the program terminates.' |
716 'This argument is only valid when using --library.') | 736 'This argument is only valid when using --library.') |
717 parser.add_option('--legacy', action='store_true', | 737 parser.add_option('--legacy', action='store_true', |
718 help='emit legacy binary size report instead of modern') | 738 help='emit legacy binary size report instead of modern') |
| 739 parser.add_option('--disable-disambiguation', action='store_true', |
| 740 help='disables the disambiguation process altogether,' |
| 741 ' NOTE: this will produce output with some symbols at the' |
| 742 ' top layer due to the fact that addr2line could not get' |
| 743 ' the entire source path.') |
| 744 parser.add_option('--source-path', default='./', |
| 745 help='the path to the source code of the output binary, ' |
| 746 'default set to current directory. Used in the' |
| 747 ' disambiguation process.') |
719 opts, _args = parser.parse_args() | 748 opts, _args = parser.parse_args() |
720 | 749 |
721 if ((not opts.library) and (not opts.nm_in)) or (opts.library and opts.nm_in): | 750 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') | 751 parser.error('exactly one of --library or --nm-in is required') |
723 if (opts.nm_in): | 752 if (opts.nm_in): |
724 if opts.jobs: | 753 if opts.jobs: |
725 print >> sys.stderr, ('WARNING: --jobs has no effect ' | 754 print >> sys.stderr, ('WARNING: --jobs has no effect ' |
726 'when used with --nm-in') | 755 'when used with --nm-in') |
727 if not opts.destdir: | 756 if not opts.destdir: |
728 parser.error('--destdir is required argument') | 757 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 '\ | 778 assert nm_binary, 'Unable to find nm in the path. Use --nm-binary '\ |
750 'to specify location.' | 779 'to specify location.' |
751 | 780 |
752 print('addr2line: %s' % addr2line_binary) | 781 print('addr2line: %s' % addr2line_binary) |
753 print('nm: %s' % nm_binary) | 782 print('nm: %s' % nm_binary) |
754 | 783 |
755 CheckDebugFormatSupport(opts.library, addr2line_binary) | 784 CheckDebugFormatSupport(opts.library, addr2line_binary) |
756 | 785 |
757 symbols = GetNmSymbols(opts.nm_in, opts.nm_out, opts.library, | 786 symbols = GetNmSymbols(opts.nm_in, opts.nm_out, opts.library, |
758 opts.jobs, opts.verbose is True, | 787 opts.jobs, opts.verbose is True, |
759 addr2line_binary, nm_binary) | 788 addr2line_binary, nm_binary, |
| 789 not opts.disable_disambiguation, |
| 790 opts.source_path) |
760 if not os.path.exists(opts.destdir): | 791 if not os.path.exists(opts.destdir): |
761 os.makedirs(opts.destdir, 0755) | 792 os.makedirs(opts.destdir, 0755) |
762 | 793 |
763 | 794 |
764 if opts.legacy: # legacy report | 795 if opts.legacy: # legacy report |
765 DumpTreemap(symbols, os.path.join(opts.destdir, 'treemap-dump.js')) | 796 DumpTreemap(symbols, os.path.join(opts.destdir, 'treemap-dump.js')) |
766 DumpLargestSymbols(symbols, | 797 DumpLargestSymbols(symbols, |
767 os.path.join(opts.destdir, 'largest-symbols.js'), 100) | 798 os.path.join(opts.destdir, 'largest-symbols.js'), 100) |
768 DumpLargestSources(symbols, | 799 DumpLargestSources(symbols, |
769 os.path.join(opts.destdir, 'largest-sources.js'), 100) | 800 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) | 823 shutil.copy(os.path.join(d3_src, 'LICENSE'), d3_out) |
793 shutil.copy(os.path.join(d3_src, 'd3.js'), d3_out) | 824 shutil.copy(os.path.join(d3_src, 'd3.js'), d3_out) |
794 shutil.copy(os.path.join(template_src, 'index.html'), opts.destdir) | 825 shutil.copy(os.path.join(template_src, 'index.html'), opts.destdir) |
795 shutil.copy(os.path.join(template_src, 'D3SymbolTreeMap.js'), opts.destdir) | 826 shutil.copy(os.path.join(template_src, 'D3SymbolTreeMap.js'), opts.destdir) |
796 | 827 |
797 print 'Report saved to ' + opts.destdir + '/index.html' | 828 print 'Report saved to ' + opts.destdir + '/index.html' |
798 | 829 |
799 | 830 |
800 if __name__ == '__main__': | 831 if __name__ == '__main__': |
801 sys.exit(main()) | 832 sys.exit(main()) |
OLD | NEW |