| 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 143 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 154 logging.warning('A container node used as symbol for %s.' % symbol_name) | 154 logging.warning('A container node used as symbol for %s.' % symbol_name) |
| 155 # This is going to be used as a leaf so no use for child list. | 155 # This is going to be used as a leaf so no use for child list. |
| 156 del node[NODE_CHILDREN_KEY] | 156 del node[NODE_CHILDREN_KEY] |
| 157 node[NODE_SYMBOL_SIZE_KEY] = symbol_size | 157 node[NODE_SYMBOL_SIZE_KEY] = symbol_size |
| 158 node[NODE_SYMBOL_TYPE_KEY] = symbol_type | 158 node[NODE_SYMBOL_TYPE_KEY] = symbol_type |
| 159 node[NODE_TYPE_KEY] = 's' # s for symbol | 159 node[NODE_TYPE_KEY] = 's' # s for symbol |
| 160 | 160 |
| 161 return 2 # Depth of the added subtree. | 161 return 2 # Depth of the added subtree. |
| 162 | 162 |
| 163 | 163 |
| 164 def MakeCompactTree(symbols): | 164 def MakeCompactTree(symbols, symbol_path_origin_dir): |
| 165 result = {NODE_NAME_KEY: '/', | 165 result = {NODE_NAME_KEY: '/', |
| 166 NODE_CHILDREN_KEY: {}, | 166 NODE_CHILDREN_KEY: {}, |
| 167 NODE_TYPE_KEY: 'p', | 167 NODE_TYPE_KEY: 'p', |
| 168 NODE_MAX_DEPTH_KEY: 0} | 168 NODE_MAX_DEPTH_KEY: 0} |
| 169 seen_symbol_with_path = False | 169 seen_symbol_with_path = False |
| 170 cwd = os.path.abspath(os.getcwd()) |
| 170 for symbol_name, symbol_type, symbol_size, file_path in symbols: | 171 for symbol_name, symbol_type, symbol_size, file_path in symbols: |
| 171 | 172 |
| 172 if 'vtable for ' in symbol_name: | 173 if 'vtable for ' in symbol_name: |
| 173 symbol_type = '@' # hack to categorize these separately | 174 symbol_type = '@' # hack to categorize these separately |
| 174 # Take path like '/foo/bar/baz', convert to ['foo', 'bar', 'baz'] | 175 # Take path like '/foo/bar/baz', convert to ['foo', 'bar', 'baz'] |
| 175 if file_path: | 176 if file_path and file_path != "??": |
| 176 file_path = os.path.normpath(file_path) | 177 file_path = os.path.abspath(os.path.join(symbol_path_origin_dir, |
| 178 file_path)) |
| 179 # Let the output structure be relative to $CWD if inside $CWD, |
| 180 # otherwise relative to the disk root. This is to avoid |
| 181 # unnecessary click-through levels in the output. |
| 182 if file_path.startswith(cwd + os.sep): |
| 183 file_path = file_path[len(cwd):] |
| 184 if file_path.startswith('/'): |
| 185 file_path = file_path[1:] |
| 177 seen_symbol_with_path = True | 186 seen_symbol_with_path = True |
| 178 else: | 187 else: |
| 179 file_path = NAME_NO_PATH_BUCKET | 188 file_path = NAME_NO_PATH_BUCKET |
| 180 | 189 |
| 181 if file_path.startswith('/'): | |
| 182 file_path = file_path[1:] | |
| 183 path_parts = file_path.split('/') | 190 path_parts = file_path.split('/') |
| 184 | 191 |
| 185 # Find pre-existing node in tree, or update if it already exists | 192 # Find pre-existing node in tree, or update if it already exists |
| 186 node = result | 193 node = result |
| 187 depth = 0 | 194 depth = 0 |
| 188 while len(path_parts) > 0: | 195 while len(path_parts) > 0: |
| 189 path_part = path_parts.pop(0) | 196 path_part = path_parts.pop(0) |
| 190 if len(path_part) == 0: | 197 if len(path_part) == 0: |
| 191 continue | 198 continue |
| 192 depth += 1 | 199 depth += 1 |
| (...skipping 146 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 339 # Sort children by size, largest to smallest. | 346 # Sort children by size, largest to smallest. |
| 340 children.sort(key=lambda child: -child['data']['$area']) | 347 children.sort(key=lambda child: -child['data']['$area']) |
| 341 | 348 |
| 342 # For leaf nodes, the 'size' attribute is the size of the leaf; | 349 # For leaf nodes, the 'size' attribute is the size of the leaf; |
| 343 # Non-leaf nodes don't really have a size, but their 'size' attribute is | 350 # Non-leaf nodes don't really have a size, but their 'size' attribute is |
| 344 # the sum of the sizes of all their children. | 351 # the sum of the sizes of all their children. |
| 345 return {'name': name + ' (' + FormatBytes(tree['size']) + ')', | 352 return {'name': name + ' (' + FormatBytes(tree['size']) + ')', |
| 346 'data': { '$area': tree['size'] }, | 353 'data': { '$area': tree['size'] }, |
| 347 'children': children } | 354 'children': children } |
| 348 | 355 |
| 349 def DumpCompactTree(symbols, outfile): | 356 def DumpCompactTree(symbols, symbol_path_origin_dir, outfile): |
| 350 tree_root = MakeCompactTree(symbols) | 357 tree_root = MakeCompactTree(symbols, symbol_path_origin_dir) |
| 351 with open(outfile, 'w') as out: | 358 with open(outfile, 'w') as out: |
| 352 out.write('var tree_data = ') | 359 out.write('var tree_data=') |
| 353 json.dump(tree_root, out) | 360 # Use separators without whitespace to get a smaller file. |
| 361 json.dump(tree_root, out, separators=(',', ':')) |
| 354 print('Writing %d bytes json' % os.path.getsize(outfile)) | 362 print('Writing %d bytes json' % os.path.getsize(outfile)) |
| 355 | 363 |
| 356 | 364 |
| 357 # TODO(andrewhayden): Only used for legacy reports. Delete. | 365 # TODO(andrewhayden): Only used for legacy reports. Delete. |
| 358 def DumpTreemap(symbols, outfile): | 366 def DumpTreemap(symbols, outfile): |
| 359 dirs = TreeifySymbols(symbols) | 367 dirs = TreeifySymbols(symbols) |
| 360 out = open(outfile, 'w') | 368 out = open(outfile, 'w') |
| 361 try: | 369 try: |
| 362 out.write('var kTree = ' + json.dumps(JsonifyTree(dirs, '/'))) | 370 out.write('var kTree = ' + json.dumps(JsonifyTree(dirs, '/'))) |
| 363 finally: | 371 finally: |
| (...skipping 128 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 492 progress = Progress() | 500 progress = Progress() |
| 493 def map_address_symbol(symbol, addr): | 501 def map_address_symbol(symbol, addr): |
| 494 progress.count += 1 | 502 progress.count += 1 |
| 495 if addr in address_symbol: | 503 if addr in address_symbol: |
| 496 # 'Collision between %s and %s.' % (str(symbol.name), | 504 # 'Collision between %s and %s.' % (str(symbol.name), |
| 497 # str(address_symbol[addr].name)) | 505 # str(address_symbol[addr].name)) |
| 498 progress.collisions += 1 | 506 progress.collisions += 1 |
| 499 else: | 507 else: |
| 500 address_symbol[addr] = symbol | 508 address_symbol[addr] = symbol |
| 501 | 509 |
| 510 progress_output() |
| 511 |
| 512 def progress_output(): |
| 502 progress_chunk = 100 | 513 progress_chunk = 100 |
| 503 if progress.count % progress_chunk == 0: | 514 if progress.count % progress_chunk == 0: |
| 504 time_now = time.time() | 515 time_now = time.time() |
| 505 time_spent = time_now - progress.time_last_output | 516 time_spent = time_now - progress.time_last_output |
| 506 if time_spent > 1.0: | 517 if time_spent > 1.0: |
| 507 # Only output at most once per second. | 518 # Only output at most once per second. |
| 508 progress.time_last_output = time_now | 519 progress.time_last_output = time_now |
| 509 chunk_size = progress.count - progress.count_last_output | 520 chunk_size = progress.count - progress.count_last_output |
| 510 progress.count_last_output = progress.count | 521 progress.count_last_output = progress.count |
| 511 if time_spent > 0: | 522 if time_spent > 0: |
| (...skipping 37 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 549 symbolizer.Join() | 560 symbolizer.Join() |
| 550 except KeyboardInterrupt: | 561 except KeyboardInterrupt: |
| 551 # Don't want to abort here since we will be finished in a few seconds. | 562 # Don't want to abort here since we will be finished in a few seconds. |
| 552 user_interrupted = True | 563 user_interrupted = True |
| 553 print('Patience you must have my young padawan.') | 564 print('Patience you must have my young padawan.') |
| 554 | 565 |
| 555 if user_interrupted: | 566 if user_interrupted: |
| 556 print('Skipping the rest of the file mapping. ' | 567 print('Skipping the rest of the file mapping. ' |
| 557 'Output will not be fully classified.') | 568 'Output will not be fully classified.') |
| 558 | 569 |
| 570 symbol_path_origin_dir = os.path.dirname(os.path.abspath(library)) |
| 571 |
| 559 with open(outfile, 'w') as out: | 572 with open(outfile, 'w') as out: |
| 560 for line in nm_output_lines: | 573 for line in nm_output_lines: |
| 561 match = sNmPattern.match(line) | 574 match = sNmPattern.match(line) |
| 562 if match: | 575 if match: |
| 563 location = match.group(5) | 576 location = match.group(5) |
| 564 if not location: | 577 if not location: |
| 565 addr = int(match.group(1), 16) | 578 addr = int(match.group(1), 16) |
| 566 symbol = address_symbol.get(addr) | 579 symbol = address_symbol.get(addr) |
| 567 if symbol is not None: | 580 if symbol is not None: |
| 568 path = '??' | 581 path = '??' |
| 569 if symbol.source_path is not None: | 582 if symbol.source_path is not None: |
| 570 path = symbol.source_path | 583 path = os.path.abspath(os.path.join(symbol_path_origin_dir, |
| 584 symbol.source_path)) |
| 571 line_number = 0 | 585 line_number = 0 |
| 572 if symbol.source_line is not None: | 586 if symbol.source_line is not None: |
| 573 line_number = symbol.source_line | 587 line_number = symbol.source_line |
| 574 out.write('%s\t%s:%d\n' % (line, path, line_number)) | 588 out.write('%s\t%s:%d\n' % (line, path, line_number)) |
| 575 continue | 589 continue |
| 576 | 590 |
| 577 out.write('%s\n' % line) | 591 out.write('%s\n' % line) |
| 578 | 592 |
| 579 print('%d symbols in the results.' % len(address_symbol)) | 593 print('%d symbols in the results.' % len(address_symbol)) |
| 580 | 594 |
| (...skipping 191 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 772 treemap_out = os.path.join(opts.destdir, 'webtreemap') | 786 treemap_out = os.path.join(opts.destdir, 'webtreemap') |
| 773 if not os.path.exists(treemap_out): | 787 if not os.path.exists(treemap_out): |
| 774 os.makedirs(treemap_out, 0755) | 788 os.makedirs(treemap_out, 0755) |
| 775 treemap_src = os.path.join('third_party', 'webtreemap', 'src') | 789 treemap_src = os.path.join('third_party', 'webtreemap', 'src') |
| 776 shutil.copy(os.path.join(treemap_src, 'COPYING'), treemap_out) | 790 shutil.copy(os.path.join(treemap_src, 'COPYING'), treemap_out) |
| 777 shutil.copy(os.path.join(treemap_src, 'webtreemap.js'), treemap_out) | 791 shutil.copy(os.path.join(treemap_src, 'webtreemap.js'), treemap_out) |
| 778 shutil.copy(os.path.join(treemap_src, 'webtreemap.css'), treemap_out) | 792 shutil.copy(os.path.join(treemap_src, 'webtreemap.css'), treemap_out) |
| 779 shutil.copy(os.path.join('tools', 'binary_size', 'legacy_template', | 793 shutil.copy(os.path.join('tools', 'binary_size', 'legacy_template', |
| 780 'index.html'), opts.destdir) | 794 'index.html'), opts.destdir) |
| 781 else: # modern report | 795 else: # modern report |
| 782 DumpCompactTree(symbols, os.path.join(opts.destdir, 'data.js')) | 796 if opts.library: |
| 797 symbol_path_origin_dir = os.path.dirname(os.path.abspath(opts.library)) |
| 798 else: |
| 799 # Just a guess. Hopefully all paths in the input file are absolute. |
| 800 symbol_path_origin_dir = os.path.abspath(os.getcwd()) |
| 801 data_js_file_name = os.path.join(opts.destdir, 'data.js') |
| 802 DumpCompactTree(symbols, symbol_path_origin_dir, data_js_file_name) |
| 783 d3_out = os.path.join(opts.destdir, 'd3') | 803 d3_out = os.path.join(opts.destdir, 'd3') |
| 784 if not os.path.exists(d3_out): | 804 if not os.path.exists(d3_out): |
| 785 os.makedirs(d3_out, 0755) | 805 os.makedirs(d3_out, 0755) |
| 786 d3_src = os.path.join(os.path.dirname(__file__), | 806 d3_src = os.path.join(os.path.dirname(__file__), |
| 787 '..', | 807 '..', |
| 788 '..', | 808 '..', |
| 789 'third_party', 'd3', 'src') | 809 'third_party', 'd3', 'src') |
| 790 template_src = os.path.join(os.path.dirname(__file__), | 810 template_src = os.path.join(os.path.dirname(__file__), |
| 791 'template') | 811 'template') |
| 792 shutil.copy(os.path.join(d3_src, 'LICENSE'), d3_out) | 812 shutil.copy(os.path.join(d3_src, 'LICENSE'), d3_out) |
| 793 shutil.copy(os.path.join(d3_src, 'd3.js'), d3_out) | 813 shutil.copy(os.path.join(d3_src, 'd3.js'), d3_out) |
| 794 shutil.copy(os.path.join(template_src, 'index.html'), opts.destdir) | 814 shutil.copy(os.path.join(template_src, 'index.html'), opts.destdir) |
| 795 shutil.copy(os.path.join(template_src, 'D3SymbolTreeMap.js'), opts.destdir) | 815 shutil.copy(os.path.join(template_src, 'D3SymbolTreeMap.js'), opts.destdir) |
| 796 | 816 |
| 797 print 'Report saved to ' + opts.destdir + '/index.html' | 817 print 'Report saved to ' + opts.destdir + '/index.html' |
| 798 | 818 |
| 799 | 819 |
| 800 if __name__ == '__main__': | 820 if __name__ == '__main__': |
| 801 sys.exit(main()) | 821 sys.exit(main()) |
| OLD | NEW |