Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(1081)

Side by Side Diff: tools/binary_size/run_binary_size_analysis.py

Issue 258633003: Graphical version of the run_binary_size_analysis tool. (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: Using the python addr2line wrapper. Created 6 years, 7 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
1 #!/usr/bin/python 1 #!/usr/bin/python
Primiano Tucci (use gerrit) 2014/05/20 15:22:57 see my previous comment, on env vs /usr/bin/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.
11 """ 11 """
12 12
13 import collections 13 import collections
14 import fileinput 14 import fileinput
15 import json 15 import json
16 import multiprocessing
16 import optparse 17 import optparse
17 import os 18 import os
18 import pprint 19 import pprint
19 import re 20 import re
20 import shutil 21 import shutil
21 import subprocess 22 import subprocess
22 import sys 23 import sys
23 import tempfile 24 import tempfile
25 import time
26
27 import binary_size_utils
28
29 elf_symbolizer_path = os.path.abspath(os.path.join(
30 os.path.dirname(__file__),
31 '..',
32 '..',
33 'build',
34 'android',
35 'pylib'))
36 sys.path.append(elf_symbolizer_path)
37 import symbols.elf_symbolizer as elf_symbolizer
24 38
25 39
26 # TODO(andrewhayden): Only used for legacy reports. Delete. 40 # TODO(andrewhayden): Only used for legacy reports. Delete.
27 def FormatBytes(bytes): 41 def FormatBytes(bytes):
28 """Pretty-print a number of bytes.""" 42 """Pretty-print a number of bytes."""
29 if bytes > 1e6: 43 if bytes > 1e6:
30 bytes = bytes / 1.0e6 44 bytes = bytes / 1.0e6
31 return '%.1fm' % bytes 45 return '%.1fm' % bytes
32 if bytes > 1e3: 46 if bytes > 1e3:
33 bytes = bytes / 1.0e3 47 bytes = bytes / 1.0e3
34 return '%.1fk' % bytes 48 return '%.1fk' % bytes
35 return str(bytes) 49 return str(bytes)
36 50
37 51
38 # TODO(andrewhayden): Only used for legacy reports. Delete. 52 # TODO(andrewhayden): Only used for legacy reports. Delete.
39 def SymbolTypeToHuman(type): 53 def SymbolTypeToHuman(type):
40 """Convert a symbol type as printed by nm into a human-readable name.""" 54 """Convert a symbol type as printed by nm into a human-readable name."""
41 return {'b': 'bss', 55 return {'b': 'bss',
42 'd': 'data', 56 'd': 'data',
43 'r': 'read-only data', 57 'r': 'read-only data',
44 't': 'code', 58 't': 'code',
45 'w': 'weak symbol', 59 'w': 'weak symbol',
46 'v': 'weak symbol'}[type] 60 'v': 'weak symbol'}[type]
47 61
48 62
49 def ParseNm(input):
50 """Parse nm output.
51
52 Argument: an iterable over lines of nm output.
53
54 Yields: (symbol name, symbol type, symbol size, source file path).
55 Path may be None if nm couldn't figure out the source file.
56 """
57
58 # Match lines with size, symbol, optional location, optional discriminator
59 sym_re = re.compile(r'^[0-9a-f]{8} ' # address (8 hex digits)
60 '([0-9a-f]{8}) ' # size (8 hex digits)
61 '(.) ' # symbol type, one character
62 '([^\t]+)' # symbol name, separated from next by tab
63 '(?:\t(.*):[\d\?]+)?.*$') # location
64 # Match lines with addr but no size.
65 addr_re = re.compile(r'^[0-9a-f]{8} (.) ([^\t]+)(?:\t.*)?$')
66 # Match lines that don't have an address at all -- typically external symbols.
67 noaddr_re = re.compile(r'^ {8} (.) (.*)$')
68
69 for line in input:
70 line = line.rstrip()
71 match = sym_re.match(line)
72 if match:
73 size, type, sym = match.groups()[0:3]
74 size = int(size, 16)
75 if type.lower() == 'b':
76 continue # skip all BSS for now
77 path = match.group(4)
78 yield sym, type, size, path
79 continue
80 match = addr_re.match(line)
81 if match:
82 type, sym = match.groups()[0:2]
83 # No size == we don't care.
84 continue
85 match = noaddr_re.match(line)
86 if match:
87 type, sym = match.groups()
88 if type in ('U', 'w'):
89 # external or weak symbol
90 continue
91
92 print >>sys.stderr, 'unparsed:', repr(line)
93
94
95 def _MkChild(node, name): 63 def _MkChild(node, name):
Primiano Tucci (use gerrit) 2014/05/20 15:22:57 _MkChild looks a "long"cut for return node.setdefa
Daniel Bratell 2014/05/21 08:42:13 Hmm, yes. Border case. Using setdefault forces you
96 child = None 64 child = node['children'].get(name)
97 for test in node['children']: 65 if child is None:
98 if test['n'] == name: 66 child = {'n': name, 'children': {}}
99 child = test 67 node['children'][name] = child
100 break
101 if not child:
102 child = {'n': name, 'children': []}
103 node['children'].append(child)
104 return child 68 return child
105 69
106 70
71 def MakeChildrenDictsIntoLists(node):
72 largest_list_len = 0
73 if 'children' in node:
74 largest_list_len = len(node['children'])
75 child_list = []
76 for child in node['children'].itervalues():
77 child_largest_list_len = MakeChildrenDictsIntoLists(child)
78 if child_largest_list_len > largest_list_len:
79 largest_list_len = child_largest_list_len
80 child_list.append(child)
81 node['children'] = child_list
82
83 return largest_list_len
84
Primiano Tucci (use gerrit) 2014/05/20 15:22:57 Nit: add extra newline
107 def MakeCompactTree(symbols): 85 def MakeCompactTree(symbols):
108 result = {'n': '/', 'children': [], 'k': 'p', 'maxDepth': 0} 86 result = {'n': '/', 'children': {}, 'k': 'p', 'maxDepth': 0}
87 seen_symbol_with_path = False
109 for symbol_name, symbol_type, symbol_size, file_path in symbols: 88 for symbol_name, symbol_type, symbol_size, file_path in symbols:
110 89
111 if 'vtable for ' in symbol_name: 90 if 'vtable for ' in symbol_name:
112 symbol_type = '@' # hack to categorize these separately 91 symbol_type = '@' # hack to categorize these separately
113 # Take path like '/foo/bar/baz', convert to ['foo', 'bar', 'baz'] 92 # Take path like '/foo/bar/baz', convert to ['foo', 'bar', 'baz']
114 if file_path: 93 if file_path:
115 file_path = os.path.normpath(file_path) 94 file_path = os.path.normpath(file_path)
95 seen_symbol_with_path = True
116 else: 96 else:
117 file_path = '(No Path)' 97 file_path = '(No Path)'
118 98
119 if file_path.startswith('/'): 99 if file_path.startswith('/'):
120 file_path = file_path[1:] 100 file_path = file_path[1:]
121 path_parts = file_path.split('/') 101 path_parts = file_path.split('/')
122 102
123 # Find pre-existing node in tree, or update if it already exists 103 # Find pre-existing node in tree, or update if it already exists
124 node = result 104 node = result
125 depth = 0 105 depth = 0
126 while len(path_parts) > 0: 106 while len(path_parts) > 0:
127 path_part = path_parts.pop(0) 107 path_part = path_parts.pop(0)
128 if len(path_part) == 0: 108 if len(path_part) == 0:
129 continue 109 continue
130 depth += 1 110 depth += 1
131 node = _MkChild(node, path_part); 111 node = _MkChild(node, path_part);
112 assert not 'k' in node or node['k'] == 'p'
132 node['k'] = 'p' # p for path 113 node['k'] = 'p' # p for path
133 114
134 # 'node' is now the file node. Find the symbol-type bucket. 115 # 'node' is now the file node. Find the symbol-type bucket.
135 node['lastPathElement'] = True 116 node['lastPathElement'] = True
136 node = _MkChild(node, symbol_type) 117 node = _MkChild(node, symbol_type)
118 assert not 'k' in node or node['k'] == 'b'
137 node['t'] = symbol_type 119 node['t'] = symbol_type
138 node['k'] = 'b' # b for bucket 120 node['k'] = 'b' # b for bucket
139 depth += 1 121 depth += 1
140 122
141 # 'node' is now the symbol-type bucket. Make the child entry. 123 # 'node' is now the symbol-type bucket. Make the child entry.
142 node = _MkChild(node, symbol_name) 124 node = _MkChild(node, symbol_name)
143 if 'children' in node: # Only possible if we're adding duplicate entries!!! 125 if 'children' in node:
126 if node['children']:
127 print('A container node used as symbol for %s.' % symbol_name)
128 # This is going to be used as a leaf so no use for child list.
144 del node['children'] 129 del node['children']
145 node['value'] = symbol_size 130 node['value'] = symbol_size
146 node['t'] = symbol_type 131 node['t'] = symbol_type
147 node['k'] = 's' # s for symbol 132 node['k'] = 's' # s for symbol
148 depth += 1 133 depth += 1
149 result['maxDepth'] = max(result['maxDepth'], depth); 134 result['maxDepth'] = max(result['maxDepth'], depth);
150 135
136 if not seen_symbol_with_path:
137 print('Symbols lack paths. Data will not be structured.')
Primiano Tucci (use gerrit) 2014/05/20 15:22:57 shouldn't all these prints be logging.warning/erro
138
139 largest_list_len = MakeChildrenDictsIntoLists(result)
140
141 if largest_list_len > 1000:
142 print('There are sections with %d nodes. Results might be unusable.' %
143 largest_list_len)
151 return result 144 return result
152 145
153 146
154 # TODO(andrewhayden): Only used for legacy reports. Delete. 147 # TODO(andrewhayden): Only used for legacy reports. Delete.
155 def TreeifySymbols(symbols): 148 def TreeifySymbols(symbols):
156 """Convert symbols into a path-based tree, calculating size information 149 """Convert symbols into a path-based tree, calculating size information
157 along the way. 150 along the way.
158 151
159 The result is a dictionary that contains two kinds of nodes: 152 The result is a dictionary that contains two kinds of nodes:
160 1. Leaf nodes, representing source code locations (e.g., c++ files) 153 1. Leaf nodes, representing source code locations (e.g., c++ files)
(...skipping 117 matching lines...) Expand 10 before | Expand all | Expand 10 after
278 children.sort(key=lambda child: -child['data']['$area']) 271 children.sort(key=lambda child: -child['data']['$area'])
279 272
280 # For leaf nodes, the 'size' attribute is the size of the leaf; 273 # For leaf nodes, the 'size' attribute is the size of the leaf;
281 # Non-leaf nodes don't really have a size, but their 'size' attribute is 274 # Non-leaf nodes don't really have a size, but their 'size' attribute is
282 # the sum of the sizes of all their children. 275 # the sum of the sizes of all their children.
283 return {'name': name + ' (' + FormatBytes(tree['size']) + ')', 276 return {'name': name + ' (' + FormatBytes(tree['size']) + ')',
284 'data': { '$area': tree['size'] }, 277 'data': { '$area': tree['size'] },
285 'children': children } 278 'children': children }
286 279
287 def DumpCompactTree(symbols, outfile): 280 def DumpCompactTree(symbols, outfile):
288 out = open(outfile, 'w') 281 tree_root = MakeCompactTree(symbols)
289 try: 282 json_string = json.dumps(tree_root)
290 out.write('var tree_data = ' + json.dumps(MakeCompactTree(symbols))) 283 print('Writing %d bytes json' % len(json_string))
291 finally: 284 with open(outfile, 'w') as out:
292 out.flush() 285 out.write('var tree_data = ' + json_string)
Primiano Tucci (use gerrit) 2014/05/20 15:22:57 I've no idea how big this string is going to be. I
Daniel Bratell 2014/05/21 08:42:13 It can be big so this is a nice suggestion. Won't
293 out.close()
294 286
295 287
296 # TODO(andrewhayden): Only used for legacy reports. Delete. 288 # TODO(andrewhayden): Only used for legacy reports. Delete.
297 def DumpTreemap(symbols, outfile): 289 def DumpTreemap(symbols, outfile):
298 dirs = TreeifySymbols(symbols) 290 dirs = TreeifySymbols(symbols)
299 out = open(outfile, 'w') 291 out = open(outfile, 'w')
300 try: 292 try:
301 out.write('var kTree = ' + json.dumps(JsonifyTree(dirs, '/'))) 293 out.write('var kTree = ' + json.dumps(JsonifyTree(dirs, '/')))
302 finally: 294 finally:
303 out.flush() 295 out.flush()
(...skipping 86 matching lines...) Expand 10 before | Expand all | Expand 10 after
390 out.write(',\n') 382 out.write(',\n')
391 dumped += 1 383 dumped += 1
392 if dumped >= n: 384 if dumped >= n:
393 return 385 return
394 finally: 386 finally:
395 out.write('];\n') 387 out.write('];\n')
396 out.flush() 388 out.flush()
397 out.close() 389 out.close()
398 390
399 391
400 # TODO(andrewhayden): Switch to Primiano's python-based version. 392 # Regex for parsing "nm" output. A sample line looks like this:
401 def RunParallelAddress2Line(outfile, library, arch, jobs, verbose): 393 # 0167b39c 00000018 t ACCESS_DESCRIPTION_free /path/file.c:95
402 """Run a parallel addr2line processing engine to dump and resolve symbols.""" 394 #
403 out_dir = os.getenv('CHROMIUM_OUT_DIR', 'out') 395 # The fields are: address, size, type, name, source location
404 build_type = os.getenv('BUILDTYPE', 'Release') 396 # Regular expression explained ( see also: https://xkcd.com/208 ):
405 classpath = os.path.join(out_dir, build_type, 'lib.java', 397 # ([0-9a-f]{8,}+) The address
406 'binary_size_java.jar') 398 # [\s]+ Whitespace separator
407 cmd = ['java', 399 # ([0-9a-f]{8,}+) The size. From here on out it's all optional.
408 '-classpath', classpath, 400 # [\s]+ Whitespace separator
409 'org.chromium.tools.binary_size.ParallelAddress2Line', 401 # (\S?) The symbol type, which is any non-whitespace char
410 '--disambiguate', 402 # [\s*] Whitespace separator
411 '--outfile', outfile, 403 # ([^\t]*) Symbol name, any non-tab character (spaces ok!)
412 '--library', library, 404 # [\t]? Tab separator
413 '--threads', jobs] 405 # (.*) The location (filename[:linennum|?][ (discriminator n)]
414 if verbose is True: 406 sNmPattern = re.compile(
415 cmd.append('--verbose') 407 r'([0-9a-f]{8,})[\s]+([0-9a-f]{8,})[\s]*(\S?)[\s*]([^\t]*)[\t]?(.*)')
416 prefix = os.path.join('third_party', 'android_tools', 'ndk', 'toolchains')
417 if arch == 'android-arm':
418 prefix = os.path.join(prefix, 'arm-linux-androideabi-4.8', 'prebuilt',
419 'linux-x86_64', 'bin', 'arm-linux-androideabi-')
420 cmd.extend(['--nm', prefix + 'nm', '--addr2line', prefix + 'addr2line'])
421 elif arch == 'android-mips':
422 prefix = os.path.join(prefix, 'mipsel-linux-android-4.8', 'prebuilt',
423 'linux-x86_64', 'bin', 'mipsel-linux-android-')
424 cmd.extend(['--nm', prefix + 'nm', '--addr2line', prefix + 'addr2line'])
425 elif arch == 'android-x86':
426 prefix = os.path.join(prefix, 'x86-4.8', 'prebuilt',
427 'linux-x86_64', 'bin', 'i686-linux-android-')
428 cmd.extend(['--nm', prefix + 'nm', '--addr2line', prefix + 'addr2line'])
429 # else, use whatever is in PATH (don't pass --nm or --addr2line)
430 408
431 if verbose: 409 class Progress():
432 print cmd 410 def __init__(self):
433 411 self.count = 0
434 return_code = subprocess.call(cmd) 412 self.skip_count = 0
435 if return_code: 413 self.collisions = 0
436 raise RuntimeError('Failed to run ParallelAddress2Line: returned ' + 414 self.time_last_output = time.time()
437 str(return_code)) 415 self.count_last_output = 0
438 416
439 417
440 def GetNmSymbols(infile, outfile, library, arch, jobs, verbose): 418 def RunElfSymbolizer(outfile, library, addr2line_binary, nm_binary):
441 if infile is None: 419 nm_output = run_nm(library, nm_binary)
420 nm_output_lines = nm_output.splitlines()
421 nm_output_lines_len = len(nm_output_lines)
422 address_symbol = {}
423 progress = Progress()
424 def map_address_symbol(symbol, addr):
425 progress.count += 1
426 if addr in address_symbol:
427 # 'Collision between %s and %s.' % (str(symbol.name),
428 # str(address_symbol[addr].name))
429 progress.collisions += 1
430 else:
431 address_symbol[addr] = symbol
432
433 progress_chunk = 100
434 if progress.count % progress_chunk == 0:
435 time_now = time.time()
436 time_spent = time_now - progress.time_last_output
437 if time_spent > 1.0:
438 # Only output at most once per second.
439 progress.time_last_output = time_now
440 chunk_size = progress.count - progress.count_last_output
441 progress.count_last_output = progress.count
442 if time_spent > 0:
443 speed = chunk_size / time_spent
444 else:
445 speed = 0
446 progress_percent = (100.0 * (progress.count + progress.skip_count) /
447 nm_output_lines_len)
448 print('%.1f%%: Looked up %d symbols (%d collisions) - %.1f lookups/s.' %
449 (progress_percent, progress.count, progress.collisions, speed))
450
451 symbolizer = elf_symbolizer.ELFSymbolizer(library, addr2line_binary,
452 map_address_symbol,
453 max_concurrent_jobs=2)
454 for line in nm_output_lines:
455 match = sNmPattern.match(line)
456 if match:
457 location = match.group(5)
458 if not location:
459 addr = int(match.group(1), 16)
460 size = int(match.group(2), 16)
461 if addr in address_symbol: # Already looked up, shortcut ELFSymbolizer.
462 map_address_symbol(address_symbol[addr], addr)
463 continue
464 elif size == 0:
465 # Save time by not looking up empty symbols (do they even exist?)
466 print('Empty symbol: ' + line)
467 else:
468 symbolizer.SymbolizeAsync(addr, addr)
469 continue
470
471 progress.skip_count += 1
472
473 symbolizer.Join()
474
475 with open(outfile, 'w') as out:
476 for line in nm_output_lines:
477 match = sNmPattern.match(line)
478 if match:
479 location = match.group(5)
480 if not location:
481 addr = int(match.group(1), 16)
482 symbol = address_symbol[addr]
483 path = '??'
484 if symbol.source_path is not None:
485 path = symbol.source_path
486 line_number = 0
487 if symbol.source_line is not None:
488 line_number = symbol.source_line
489 out.write('%s\t%s:%d\n' % (line, path, line_number))
490 continue
491
492 out.write('%s\n' % line)
493
494 print('%d symbols in the results.' % len(address_symbol))
495
496
497 def run_nm(binary, nm_binary):
Primiano Tucci (use gerrit) 2014/05/20 15:22:57 nit: RunNm
498 print('Starting nm')
499 cmd = [nm_binary, '-C', '--print-size', binary]
500 nm_process = subprocess.Popen(cmd,
501 stdout=subprocess.PIPE,
502 stderr=subprocess.PIPE)
503 (process_output, err_output) = nm_process.communicate()
504
505 if nm_process.returncode != 0:
506 if err_output:
507 raise Exception, err_output
508 else:
509 raise Exception, process_output
510
511 print('Finished nm')
512 return process_output
513
514
515 def GetNmSymbols(nm_infile, outfile, library, jobs, verbose,
516 addr2line_binary, nm_binary):
517 if nm_infile is None:
442 if outfile is None: 518 if outfile is None:
443 infile = tempfile.NamedTemporaryFile(delete=False).name 519 outfile = tempfile.NamedTemporaryFile(delete=False).name
444 else:
445 infile = outfile
446 520
447 if verbose: 521 if verbose:
448 print 'Running parallel addr2line, dumping symbols to ' + infile; 522 print 'Running parallel addr2line, dumping symbols to ' + outfile;
449 RunParallelAddress2Line(outfile=infile, library=library, arch=arch, 523 RunElfSymbolizer(outfile, library, addr2line_binary, nm_binary)
450 jobs=jobs, verbose=verbose) 524
525 nm_infile = outfile
526
451 elif verbose: 527 elif verbose:
452 print 'Using nm input from ' + infile 528 print 'Using nm input from ' + nm_infile
453 with file(infile, 'r') as infile: 529 with file(nm_infile, 'r') as infile:
454 return list(ParseNm(infile)) 530 return list(binary_size_utils.ParseNm(infile))
531
532
533 def _find_in_system_path(binary):
534 """Locate the full path to binary in the system path or return None
535 if not found."""
536 system_path = os.environ["PATH"].split(os.pathsep)
537 for path in system_path:
538 binary_path = os.path.join(path, binary)
539 if os.path.isfile(binary_path):
540 return binary_path
541 return None
455 542
456 543
457 def main(): 544 def main():
458 usage="""%prog [options] 545 usage="""%prog [options]
459 546
460 Runs a spatial analysis on a given library, looking up the source locations 547 Runs a spatial analysis on a given library, looking up the source locations
461 of its symbols and calculating how much space each directory, source file, 548 of its symbols and calculating how much space each directory, source file,
462 and so on is taking. The result is a report that can be used to pinpoint 549 and so on is taking. The result is a report that can be used to pinpoint
463 sources of large portions of the binary, etceteras. 550 sources of large portions of the binary, etceteras.
464 551
(...skipping 14 matching lines...) Expand all
479 'present in the file; i.e., no addr2line symbol lookups ' 566 'present in the file; i.e., no addr2line symbol lookups '
480 'will be performed when this option is specified. ' 567 'will be performed when this option is specified. '
481 'Mutually exclusive with --library.') 568 'Mutually exclusive with --library.')
482 parser.add_option('--destdir', metavar='PATH', 569 parser.add_option('--destdir', metavar='PATH',
483 help='write output to the specified directory. An HTML ' 570 help='write output to the specified directory. An HTML '
484 'report is generated here along with supporting files; ' 571 'report is generated here along with supporting files; '
485 'any existing report will be overwritten.') 572 'any existing report will be overwritten.')
486 parser.add_option('--library', metavar='PATH', 573 parser.add_option('--library', metavar='PATH',
487 help='if specified, process symbols in the library at ' 574 help='if specified, process symbols in the library at '
488 'the specified path. Mutually exclusive with --nm-in.') 575 'the specified path. Mutually exclusive with --nm-in.')
489 parser.add_option('--arch', 576 parser.add_option('--nm-binary',
490 help='the architecture that the library is targeted to. ' 577 help='use the specified nm binary to analyze library. '
491 'Determines which nm/addr2line binaries are used. When ' 578 'This is to be used when the nm in the path is not for '
492 '\'host-native\' is chosen, the program will use whichever ' 579 'the right architecture or of the right version.')
493 'nm/addr2line binaries are on the PATH. This is ' 580 parser.add_option('--addr2line-binary',
494 'appropriate when you are analyzing a binary by and for ' 581 help='use the specified addr2line binary to analyze '
495 'your computer. ' 582 'library. This is to be used when the addr2line in '
496 'This argument is only valid when using --library. ' 583 'the path is not for the right architecture or '
497 'Default is \'host-native\'.', 584 'of the right version.')
498 choices=['host-native', 'android-arm',
499 'android-mips', 'android-x86'],)
500 parser.add_option('--jobs', 585 parser.add_option('--jobs',
501 help='number of jobs to use for the parallel ' 586 help='number of jobs to use for the parallel '
502 'addr2line processing pool; defaults to 1. More ' 587 'addr2line processing pool; defaults to 1. More '
503 'jobs greatly improve throughput but eat RAM like ' 588 'jobs greatly improve throughput but eat RAM like '
504 'popcorn, and take several gigabytes each. Start low ' 589 'popcorn, and take several gigabytes each. Start low '
505 'and ramp this number up until your machine begins to ' 590 'and ramp this number up until your machine begins to '
506 'struggle with RAM. ' 591 'struggle with RAM. '
507 'This argument is only valid when using --library.') 592 'This argument is only valid when using --library.')
508 parser.add_option('-v', dest='verbose', action='store_true', 593 parser.add_option('-v', dest='verbose', action='store_true',
509 help='be verbose, printing lots of status information.') 594 help='be verbose, printing lots of status information.')
510 parser.add_option('--nm-out', metavar='PATH', 595 parser.add_option('--nm-out', metavar='PATH',
511 help='keep the nm output file, and store it at the ' 596 help='keep the nm output file, and store it at the '
512 'specified path. This is useful if you want to see the ' 597 'specified path. This is useful if you want to see the '
513 'fully processed nm output after the symbols have been ' 598 'fully processed nm output after the symbols have been '
514 'mapped to source locations. By default, a tempfile is ' 599 'mapped to source locations. By default, a tempfile is '
515 'used and is deleted when the program terminates.' 600 'used and is deleted when the program terminates.'
516 'This argument is only valid when using --library.') 601 'This argument is only valid when using --library.')
517 parser.add_option('--legacy', action='store_true', 602 parser.add_option('--legacy', action='store_true',
518 help='emit legacy binary size report instead of modern') 603 help='emit legacy binary size report instead of modern')
519 opts, args = parser.parse_args() 604 opts, args = parser.parse_args()
520 605
521 if ((not opts.library) and (not opts.nm_in)) or (opts.library and opts.nm_in): 606 if ((not opts.library) and (not opts.nm_in)) or (opts.library and opts.nm_in):
522 parser.error('exactly one of --library or --nm-in is required') 607 parser.error('exactly one of --library or --nm-in is required')
523 if (opts.nm_in): 608 if (opts.nm_in):
524 if opts.jobs: 609 if opts.jobs:
525 print >> sys.stderr, ('WARNING: --jobs has no effect ' 610 print >> sys.stderr, ('WARNING: --jobs has no effect '
526 'when used with --nm-in') 611 'when used with --nm-in')
527 if opts.arch:
528 print >> sys.stderr, ('WARNING: --arch has no effect '
529 'when used with --nm-in')
530 if not opts.destdir: 612 if not opts.destdir:
531 parser.error('--destdir is required argument') 613 parser.error('--destdir is required argument')
532 if not opts.jobs: 614 if not opts.jobs:
533 opts.jobs = '1' 615 opts.jobs = str(multiprocessing.cpu_count())
534 if not opts.arch:
535 opts.arch = 'host-native'
536 616
537 symbols = GetNmSymbols(opts.nm_in, opts.nm_out, opts.library, opts.arch, 617 if opts.addr2line_binary:
538 opts.jobs, opts.verbose is True) 618 assert os.path.isfile(opts.addr2line_binary)
619 addr2line_binary = opts.addr2line_binary
620 else:
621 addr2line_binary = _find_in_system_path('addr2line')
622 assert addr2line_binary, 'Unable to find addr2line in the path. '\
623 'Use --addr2line-binary to specify location.'
624
625 if opts.nm_binary:
626 assert os.path.isfile(opts.nm_binary)
627 nm_binary = opts.nm_binary
628 else:
629 nm_binary = _find_in_system_path('nm')
630 assert nm_binary, 'Unable to find nm in the path. Use --nm-binary '\
631 'to specify location.'
632
633 print('nm: %s' % nm_binary)
634 print('addr2line: %s' % addr2line_binary)
635
636 symbols = GetNmSymbols(opts.nm_in, opts.nm_out, opts.library,
637 opts.jobs, opts.verbose is True,
638 addr2line_binary, nm_binary)
539 if not os.path.exists(opts.destdir): 639 if not os.path.exists(opts.destdir):
540 os.makedirs(opts.destdir, 0755) 640 os.makedirs(opts.destdir, 0755)
541 641
542 642
543 if opts.legacy: # legacy report 643 if opts.legacy: # legacy report
544 DumpTreemap(symbols, os.path.join(opts.destdir, 'treemap-dump.js')) 644 DumpTreemap(symbols, os.path.join(opts.destdir, 'treemap-dump.js'))
545 DumpLargestSymbols(symbols, 645 DumpLargestSymbols(symbols,
546 os.path.join(opts.destdir, 'largest-symbols.js'), 100) 646 os.path.join(opts.destdir, 'largest-symbols.js'), 100)
547 DumpLargestSources(symbols, 647 DumpLargestSources(symbols,
548 os.path.join(opts.destdir, 'largest-sources.js'), 100) 648 os.path.join(opts.destdir, 'largest-sources.js'), 100)
549 DumpLargestVTables(symbols, 649 DumpLargestVTables(symbols,
550 os.path.join(opts.destdir, 'largest-vtables.js'), 100) 650 os.path.join(opts.destdir, 'largest-vtables.js'), 100)
551 treemap_out = os.path.join(opts.destdir, 'webtreemap') 651 treemap_out = os.path.join(opts.destdir, 'webtreemap')
552 if not os.path.exists(treemap_out): 652 if not os.path.exists(treemap_out):
553 os.makedirs(treemap_out, 0755) 653 os.makedirs(treemap_out, 0755)
554 treemap_src = os.path.join('third_party', 'webtreemap', 'src') 654 treemap_src = os.path.join('third_party', 'webtreemap', 'src')
555 shutil.copy(os.path.join(treemap_src, 'COPYING'), treemap_out) 655 shutil.copy(os.path.join(treemap_src, 'COPYING'), treemap_out)
556 shutil.copy(os.path.join(treemap_src, 'webtreemap.js'), treemap_out) 656 shutil.copy(os.path.join(treemap_src, 'webtreemap.js'), treemap_out)
557 shutil.copy(os.path.join(treemap_src, 'webtreemap.css'), treemap_out) 657 shutil.copy(os.path.join(treemap_src, 'webtreemap.css'), treemap_out)
558 shutil.copy(os.path.join('tools', 'binary_size', 'legacy_template', 658 shutil.copy(os.path.join('tools', 'binary_size', 'legacy_template',
559 'index.html'), opts.destdir) 659 'index.html'), opts.destdir)
560 else: # modern report 660 else: # modern report
561 DumpCompactTree(symbols, os.path.join(opts.destdir, 'data.js')) 661 DumpCompactTree(symbols, os.path.join(opts.destdir, 'data.js'))
562 d3_out = os.path.join(opts.destdir, 'd3') 662 d3_out = os.path.join(opts.destdir, 'd3')
563 if not os.path.exists(d3_out): 663 if not os.path.exists(d3_out):
564 os.makedirs(d3_out, 0755) 664 os.makedirs(d3_out, 0755)
565 d3_src = os.path.join('third_party', 'd3', 'src') 665 d3_src = os.path.join(os.path.dirname(__file__),
566 template_src = os.path.join('tools', 'binary_size', 666 '..',
667 '..',
668 'third_party', 'd3', 'src')
669 template_src = os.path.join(os.path.dirname(__file__),
567 'template') 670 'template')
568 shutil.copy(os.path.join(d3_src, 'LICENSE'), d3_out) 671 shutil.copy(os.path.join(d3_src, 'LICENSE'), d3_out)
569 shutil.copy(os.path.join(d3_src, 'd3.js'), d3_out) 672 shutil.copy(os.path.join(d3_src, 'd3.js'), d3_out)
673 print('Copying index.html')
570 shutil.copy(os.path.join(template_src, 'index.html'), opts.destdir) 674 shutil.copy(os.path.join(template_src, 'index.html'), opts.destdir)
571 shutil.copy(os.path.join(template_src, 'D3SymbolTreeMap.js'), opts.destdir) 675 shutil.copy(os.path.join(template_src, 'D3SymbolTreeMap.js'), opts.destdir)
572 676
573 if opts.verbose: 677 if opts.verbose:
574 print 'Report saved to ' + opts.destdir + '/index.html' 678 print 'Report saved to ' + opts.destdir + '/index.html'
575 679
576 680
577 if __name__ == '__main__': 681 if __name__ == '__main__':
578 sys.exit(main()) 682 sys.exit(main())
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698