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 |