Chromium Code Reviews| OLD | NEW |
|---|---|
| 1 #!/usr/bin/python | 1 #!/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 optparse | 16 import optparse |
| 17 import os | 17 import os |
| 18 import pprint | 18 import pprint |
| 19 import re | 19 import re |
| 20 import shutil | 20 import shutil |
| 21 import subprocess | 21 import subprocess |
| 22 import sys | 22 import sys |
| 23 import tempfile | 23 import tempfile |
| 24 | 24 |
| 25 | 25 |
| 26 # TODO(andrewhayden): Only used for legacy reports. Delete. | |
| 26 def FormatBytes(bytes): | 27 def FormatBytes(bytes): |
| 27 """Pretty-print a number of bytes.""" | 28 """Pretty-print a number of bytes.""" |
| 28 if bytes > 1e6: | 29 if bytes > 1e6: |
| 29 bytes = bytes / 1.0e6 | 30 bytes = bytes / 1.0e6 |
| 30 return '%.1fm' % bytes | 31 return '%.1fm' % bytes |
| 31 if bytes > 1e3: | 32 if bytes > 1e3: |
| 32 bytes = bytes / 1.0e3 | 33 bytes = bytes / 1.0e3 |
| 33 return '%.1fk' % bytes | 34 return '%.1fk' % bytes |
| 34 return str(bytes) | 35 return str(bytes) |
| 35 | 36 |
| 36 | 37 |
| 38 # TODO(andrewhayden): Only used for legacy reports. Delete. | |
| 37 def SymbolTypeToHuman(type): | 39 def SymbolTypeToHuman(type): |
| 38 """Convert a symbol type as printed by nm into a human-readable name.""" | 40 """Convert a symbol type as printed by nm into a human-readable name.""" |
| 39 return {'b': 'bss', | 41 return {'b': 'bss', |
| 40 'd': 'data', | 42 'd': 'data', |
| 41 'r': 'read-only data', | 43 'r': 'read-only data', |
| 42 't': 'code', | 44 't': 'code', |
| 43 'w': 'weak symbol', | 45 'w': 'weak symbol', |
| 44 'v': 'weak symbol'}[type] | 46 'v': 'weak symbol'}[type] |
| 45 | 47 |
| 46 | 48 |
| (...skipping 16 matching lines...) Expand all Loading... | |
| 63 addr_re = re.compile(r'^[0-9a-f]{8} (.) ([^\t]+)(?:\t.*)?$') | 65 addr_re = re.compile(r'^[0-9a-f]{8} (.) ([^\t]+)(?:\t.*)?$') |
| 64 # Match lines that don't have an address at all -- typically external symbols. | 66 # Match lines that don't have an address at all -- typically external symbols. |
| 65 noaddr_re = re.compile(r'^ {8} (.) (.*)$') | 67 noaddr_re = re.compile(r'^ {8} (.) (.*)$') |
| 66 | 68 |
| 67 for line in input: | 69 for line in input: |
| 68 line = line.rstrip() | 70 line = line.rstrip() |
| 69 match = sym_re.match(line) | 71 match = sym_re.match(line) |
| 70 if match: | 72 if match: |
| 71 size, type, sym = match.groups()[0:3] | 73 size, type, sym = match.groups()[0:3] |
| 72 size = int(size, 16) | 74 size = int(size, 16) |
| 73 type = type.lower() | 75 if type.lower() == 'b': |
| 74 if type == 'v': | |
| 75 type = 'w' # just call them all weak | |
| 76 if type == 'b': | |
| 77 continue # skip all BSS for now | 76 continue # skip all BSS for now |
| 78 path = match.group(4) | 77 path = match.group(4) |
| 79 yield sym, type, size, path | 78 yield sym, type, size, path |
| 80 continue | 79 continue |
| 81 match = addr_re.match(line) | 80 match = addr_re.match(line) |
| 82 if match: | 81 if match: |
| 83 type, sym = match.groups()[0:2] | 82 type, sym = match.groups()[0:2] |
| 84 # No size == we don't care. | 83 # No size == we don't care. |
| 85 continue | 84 continue |
| 86 match = noaddr_re.match(line) | 85 match = noaddr_re.match(line) |
| 87 if match: | 86 if match: |
| 88 type, sym = match.groups() | 87 type, sym = match.groups() |
| 89 if type in ('U', 'w'): | 88 if type in ('U', 'w'): |
| 90 # external or weak symbol | 89 # external or weak symbol |
| 91 continue | 90 continue |
| 92 | 91 |
| 93 print >>sys.stderr, 'unparsed:', repr(line) | 92 print >>sys.stderr, 'unparsed:', repr(line) |
| 94 | 93 |
|
bulach
2014/04/16 16:32:16
nit: another \n
also, _MkChuld
Andrew Hayden (chromium.org)
2014/04/16 17:53:21
Done.
| |
| 94 def _mk_child(node, name): | |
| 95 child = None | |
| 96 for test in node['children']: | |
| 97 if test['n'] == name: | |
| 98 child = test | |
| 99 break | |
| 100 if child == None: | |
|
bulach
2014/04/16 16:32:16
nit:
if not child:
Andrew Hayden (chromium.org)
2014/04/16 17:53:21
Done.
| |
| 101 child = {'n': name, 'children': []} | |
| 102 node['children'].append(child) | |
| 103 return child | |
| 95 | 104 |
|
bulach
2014/04/16 16:32:16
nit: another \n
Andrew Hayden (chromium.org)
2014/04/16 17:53:21
Done throughout
| |
| 105 def MakeCompactTree(symbols): | |
| 106 result = {'n': '/', 'children': [], 'k': 'p', 'maxDepth': 0} | |
| 107 for symbol_name, symbol_type, symbol_size, file_path in symbols: | |
| 108 | |
| 109 if 'vtable for ' in symbol_name: | |
| 110 symbol_type = '@' # hack to categorize these separately | |
| 111 # Take path like '/foo/bar/baz', convert to ['foo', 'bar', 'baz'] | |
| 112 if file_path: | |
| 113 file_path = os.path.normpath(file_path) | |
| 114 else: | |
| 115 file_path = '(No Path)' | |
| 116 | |
| 117 if file_path.startswith('/'): | |
| 118 file_path = file_path[1:] | |
| 119 path_parts = file_path.split('/') | |
| 120 | |
| 121 # Find pre-existing node in tree, or update if it already exists | |
| 122 node = result | |
| 123 depth = 0 | |
| 124 while len(path_parts) > 0: | |
| 125 path_part = path_parts.pop(0) | |
| 126 if len(path_part) == 0: | |
| 127 continue | |
| 128 depth += 1 | |
| 129 node = _mk_child(node, path_part); | |
| 130 node['k'] = 'p' # p for path | |
| 131 | |
| 132 # 'node' is now the file node. Find the symbol-type bucket. | |
| 133 node['lastPathElement'] = True | |
| 134 node = _mk_child(node, symbol_type) | |
| 135 node['t'] = symbol_type | |
| 136 node['k'] = 'b' # b for bucket | |
| 137 depth += 1 | |
| 138 | |
| 139 # 'node' is now the symbol-type bucket. Make the child entry. | |
| 140 node = _mk_child(node, symbol_name) | |
| 141 if 'children' in node: # Only possible if we're adding duplicate entries!!! | |
| 142 del node['children'] | |
| 143 node['value'] = symbol_size | |
| 144 node['t'] = symbol_type | |
| 145 node['k'] = 's' # s for symbol | |
| 146 depth += 1 | |
| 147 result['maxDepth'] = max(result['maxDepth'], depth); | |
| 148 | |
| 149 return result | |
| 150 | |
| 151 | |
| 152 # TODO(andrewhayden): Only used for legacy reports. Delete. | |
| 96 def TreeifySymbols(symbols): | 153 def TreeifySymbols(symbols): |
| 97 """Convert symbols into a path-based tree, calculating size information | 154 """Convert symbols into a path-based tree, calculating size information |
| 98 along the way. | 155 along the way. |
| 99 | 156 |
| 100 The result is a dictionary that contains two kinds of nodes: | 157 The result is a dictionary that contains two kinds of nodes: |
| 101 1. Leaf nodes, representing source code locations (e.g., c++ files) | 158 1. Leaf nodes, representing source code locations (e.g., c++ files) |
| 102 These nodes have the following dictionary entries: | 159 These nodes have the following dictionary entries: |
| 103 sizes: a dictionary whose keys are categories (such as code, data, | 160 sizes: a dictionary whose keys are categories (such as code, data, |
| 104 vtable, etceteras) and whose values are the size, in bytes, of | 161 vtable, etceteras) and whose values are the size, in bytes, of |
| 105 those categories; | 162 those categories; |
| (...skipping 75 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 181 subkey = '__FUNCTION__' | 238 subkey = '__FUNCTION__' |
| 182 elif sym.startswith('CSWTCH.'): | 239 elif sym.startswith('CSWTCH.'): |
| 183 subkey = 'CSWTCH' | 240 subkey = 'CSWTCH' |
| 184 elif '::' in sym: | 241 elif '::' in sym: |
| 185 subkey = sym[0:sym.find('::') + 2] | 242 subkey = sym[0:sym.find('::') + 2] |
| 186 tree['sizes'][subkey] = tree['sizes'].get(subkey, 0) + size | 243 tree['sizes'][subkey] = tree['sizes'].get(subkey, 0) + size |
| 187 tree['size'] += size | 244 tree['size'] += size |
| 188 return dirs | 245 return dirs |
| 189 | 246 |
| 190 | 247 |
| 248 # TODO(andrewhayden): Only used for legacy reports. Delete. | |
| 191 def JsonifyTree(tree, name): | 249 def JsonifyTree(tree, name): |
| 192 """Convert TreeifySymbols output to a JSON treemap. | 250 """Convert TreeifySymbols output to a JSON treemap. |
| 193 | 251 |
| 194 The format is very similar, with the notable exceptions being | 252 The format is very similar, with the notable exceptions being |
| 195 lists of children instead of maps and some different attribute names.""" | 253 lists of children instead of maps and some different attribute names.""" |
| 196 children = [] | 254 children = [] |
| 197 css_class_map = { | 255 css_class_map = { |
| 198 '[vtable]': 'vtable', | 256 '[vtable]': 'vtable', |
| 199 '[rodata]': 'read-only_data', | 257 '[rodata]': 'read-only_data', |
| 200 '[data]': 'data', | 258 '[data]': 'data', |
| (...skipping 16 matching lines...) Expand all Loading... | |
| 217 # Sort children by size, largest to smallest. | 275 # Sort children by size, largest to smallest. |
| 218 children.sort(key=lambda child: -child['data']['$area']) | 276 children.sort(key=lambda child: -child['data']['$area']) |
| 219 | 277 |
| 220 # For leaf nodes, the 'size' attribute is the size of the leaf; | 278 # For leaf nodes, the 'size' attribute is the size of the leaf; |
| 221 # Non-leaf nodes don't really have a size, but their 'size' attribute is | 279 # Non-leaf nodes don't really have a size, but their 'size' attribute is |
| 222 # the sum of the sizes of all their children. | 280 # the sum of the sizes of all their children. |
| 223 return {'name': name + ' (' + FormatBytes(tree['size']) + ')', | 281 return {'name': name + ' (' + FormatBytes(tree['size']) + ')', |
| 224 'data': { '$area': tree['size'] }, | 282 'data': { '$area': tree['size'] }, |
| 225 'children': children } | 283 'children': children } |
| 226 | 284 |
| 285 def DumpCompactTree(symbols, outfile): | |
| 286 out = open(outfile, 'w') | |
| 287 try: | |
| 288 out.write('var tree_data = ' + json.dumps(MakeCompactTree(symbols))) | |
| 289 finally: | |
| 290 out.flush() | |
| 291 out.close() | |
| 227 | 292 |
| 293 # TODO(andrewhayden): Only used for legacy reports. Delete. | |
| 228 def DumpTreemap(symbols, outfile): | 294 def DumpTreemap(symbols, outfile): |
| 229 dirs = TreeifySymbols(symbols) | 295 dirs = TreeifySymbols(symbols) |
| 230 out = open(outfile, 'w') | 296 out = open(outfile, 'w') |
| 231 try: | 297 try: |
| 232 out.write('var kTree = ' + json.dumps(JsonifyTree(dirs, '/'))) | 298 out.write('var kTree = ' + json.dumps(JsonifyTree(dirs, '/'))) |
| 233 finally: | 299 finally: |
| 234 out.flush() | 300 out.flush() |
| 235 out.close() | 301 out.close() |
| 236 | 302 |
| 237 | 303 |
| 304 # TODO(andrewhayden): Only used for legacy reports. Delete. | |
| 238 def DumpLargestSymbols(symbols, outfile, n): | 305 def DumpLargestSymbols(symbols, outfile, n): |
| 239 # a list of (sym, type, size, path); sort by size. | 306 # a list of (sym, type, size, path); sort by size. |
| 240 symbols = sorted(symbols, key=lambda x: -x[2]) | 307 symbols = sorted(symbols, key=lambda x: -x[2]) |
| 241 dumped = 0 | 308 dumped = 0 |
| 242 out = open(outfile, 'w') | 309 out = open(outfile, 'w') |
| 243 try: | 310 try: |
| 244 out.write('var largestSymbols = [\n') | 311 out.write('var largestSymbols = [\n') |
| 245 for sym, type, size, path in symbols: | 312 for sym, type, size, path in symbols: |
| 246 if type in ('b', 'w'): | 313 if type in ('b', 'w'): |
| 247 continue # skip bss and weak symbols | 314 continue # skip bss and weak symbols |
| (...skipping 23 matching lines...) Expand all Loading... | |
| 271 else: | 338 else: |
| 272 key = '[no path]' | 339 key = '[no path]' |
| 273 if key not in sources: | 340 if key not in sources: |
| 274 sources[key] = {'path': path, 'symbol_count': 0, 'size': 0} | 341 sources[key] = {'path': path, 'symbol_count': 0, 'size': 0} |
| 275 record = sources[key] | 342 record = sources[key] |
| 276 record['size'] += size | 343 record['size'] += size |
| 277 record['symbol_count'] += 1 | 344 record['symbol_count'] += 1 |
| 278 return sources | 345 return sources |
| 279 | 346 |
| 280 | 347 |
| 348 # TODO(andrewhayden): Only used for legacy reports. Delete. | |
| 281 def DumpLargestSources(symbols, outfile, n): | 349 def DumpLargestSources(symbols, outfile, n): |
| 282 map = MakeSourceMap(symbols) | 350 map = MakeSourceMap(symbols) |
| 283 sources = sorted(map.values(), key=lambda x: -x['size']) | 351 sources = sorted(map.values(), key=lambda x: -x['size']) |
| 284 dumped = 0 | 352 dumped = 0 |
| 285 out = open(outfile, 'w') | 353 out = open(outfile, 'w') |
| 286 try: | 354 try: |
| 287 out.write('var largestSources = [\n') | 355 out.write('var largestSources = [\n') |
| 288 for record in sources: | 356 for record in sources: |
| 289 entry = {'size': FormatBytes(record['size']), | 357 entry = {'size': FormatBytes(record['size']), |
| 290 'symbol_count': str(record['symbol_count']), | 358 'symbol_count': str(record['symbol_count']), |
| 291 'location': record['path']} | 359 'location': record['path']} |
| 292 out.write(json.dumps(entry)) | 360 out.write(json.dumps(entry)) |
| 293 out.write(',\n') | 361 out.write(',\n') |
| 294 dumped += 1 | 362 dumped += 1 |
| 295 if dumped >= n: | 363 if dumped >= n: |
| 296 return | 364 return |
| 297 finally: | 365 finally: |
| 298 out.write('];\n') | 366 out.write('];\n') |
| 299 out.flush() | 367 out.flush() |
| 300 out.close() | 368 out.close() |
| 301 | 369 |
| 302 | 370 |
| 371 # TODO(andrewhayden): Only used for legacy reports. Delete. | |
| 303 def DumpLargestVTables(symbols, outfile, n): | 372 def DumpLargestVTables(symbols, outfile, n): |
| 304 vtables = [] | 373 vtables = [] |
| 305 for symbol, type, size, path in symbols: | 374 for symbol, type, size, path in symbols: |
| 306 if 'vtable for ' in symbol: | 375 if 'vtable for ' in symbol: |
| 307 vtables.append({'symbol': symbol, 'path': path, 'size': size}) | 376 vtables.append({'symbol': symbol, 'path': path, 'size': size}) |
| 308 vtables = sorted(vtables, key=lambda x: -x['size']) | 377 vtables = sorted(vtables, key=lambda x: -x['size']) |
| 309 dumped = 0 | 378 dumped = 0 |
| 310 out = open(outfile, 'w') | 379 out = open(outfile, 'w') |
| 311 try: | 380 try: |
| 312 out.write('var largestVTables = [\n') | 381 out.write('var largestVTables = [\n') |
| 313 for record in vtables: | 382 for record in vtables: |
| 314 entry = {'size': FormatBytes(record['size']), | 383 entry = {'size': FormatBytes(record['size']), |
| 315 'symbol': record['symbol'], | 384 'symbol': record['symbol'], |
| 316 'location': record['path']} | 385 'location': record['path']} |
| 317 out.write(json.dumps(entry)) | 386 out.write(json.dumps(entry)) |
| 318 out.write(',\n') | 387 out.write(',\n') |
| 319 dumped += 1 | 388 dumped += 1 |
| 320 if dumped >= n: | 389 if dumped >= n: |
| 321 return | 390 return |
| 322 finally: | 391 finally: |
| 323 out.write('];\n') | 392 out.write('];\n') |
| 324 out.flush() | 393 out.flush() |
| 325 out.close() | 394 out.close() |
| 326 | 395 |
| 327 | 396 # TODO(andrewhayden): Switch to Primiano's python-based version. |
| 328 def RunParallelAddress2Line(outfile, library, arch, jobs, verbose): | 397 def RunParallelAddress2Line(outfile, library, arch, jobs, verbose): |
| 329 """Run a parallel addr2line processing engine to dump and resolve symbols.""" | 398 """Run a parallel addr2line processing engine to dump and resolve symbols.""" |
| 330 out_dir = os.getenv('CHROMIUM_OUT_DIR', 'out') | 399 out_dir = os.getenv('CHROMIUM_OUT_DIR', 'out') |
| 331 build_type = os.getenv('BUILDTYPE', 'Release') | 400 build_type = os.getenv('BUILDTYPE', 'Release') |
| 332 classpath = os.path.join(out_dir, build_type, 'lib.java', | 401 classpath = os.path.join(out_dir, build_type, 'lib.java', |
| 333 'binary_size_java.jar') | 402 'binary_size_java.jar') |
| 334 cmd = ['java', | 403 cmd = ['java', |
| 335 '-classpath', classpath, | 404 '-classpath', classpath, |
| 336 'org.chromium.tools.binary_size.ParallelAddress2Line', | 405 'org.chromium.tools.binary_size.ParallelAddress2Line', |
| 337 '--disambiguate', | 406 '--disambiguate', |
| 338 '--outfile', outfile, | 407 '--outfile', outfile, |
| 339 '--library', library, | 408 '--library', library, |
| 340 '--threads', jobs] | 409 '--threads', jobs] |
| 341 if verbose is True: | 410 if verbose is True: |
| 342 cmd.append('--verbose') | 411 cmd.append('--verbose') |
| 343 prefix = os.path.join('third_party', 'android_tools', 'ndk', 'toolchains') | 412 prefix = os.path.join('third_party', 'android_tools', 'ndk', 'toolchains') |
| 344 if arch == 'android-arm': | 413 if arch == 'android-arm': |
| 345 prefix = os.path.join(prefix, 'arm-linux-androideabi-4.7', 'prebuilt', | 414 prefix = os.path.join(prefix, 'arm-linux-androideabi-4.8', 'prebuilt', |
| 346 'linux-x86_64', 'bin', 'arm-linux-androideabi-') | 415 'linux-x86_64', 'bin', 'arm-linux-androideabi-') |
| 347 cmd.extend(['--nm', prefix + 'nm', '--addr2line', prefix + 'addr2line']) | 416 cmd.extend(['--nm', prefix + 'nm', '--addr2line', prefix + 'addr2line']) |
| 348 elif arch == 'android-mips': | 417 elif arch == 'android-mips': |
| 349 prefix = os.path.join(prefix, 'mipsel-linux-android-4.7', 'prebuilt', | 418 prefix = os.path.join(prefix, 'mipsel-linux-android-4.8', 'prebuilt', |
| 350 'linux-x86_64', 'bin', 'mipsel-linux-android-') | 419 'linux-x86_64', 'bin', 'mipsel-linux-android-') |
| 351 cmd.extend(['--nm', prefix + 'nm', '--addr2line', prefix + 'addr2line']) | 420 cmd.extend(['--nm', prefix + 'nm', '--addr2line', prefix + 'addr2line']) |
| 352 elif arch == 'android-x86': | 421 elif arch == 'android-x86': |
| 353 prefix = os.path.join(prefix, 'x86-4.7', 'prebuilt', | 422 prefix = os.path.join(prefix, 'x86-4.8', 'prebuilt', |
| 354 'linux-x86_64', 'bin', 'i686-linux-android-') | 423 'linux-x86_64', 'bin', 'i686-linux-android-') |
| 355 cmd.extend(['--nm', prefix + 'nm', '--addr2line', prefix + 'addr2line']) | 424 cmd.extend(['--nm', prefix + 'nm', '--addr2line', prefix + 'addr2line']) |
| 356 # else, use whatever is in PATH (don't pass --nm or --addr2line) | 425 # else, use whatever is in PATH (don't pass --nm or --addr2line) |
| 357 | 426 |
| 358 if verbose: | 427 if verbose: |
| 359 print cmd | 428 print cmd |
| 360 | 429 |
| 361 return_code = subprocess.call(cmd) | 430 return_code = subprocess.call(cmd) |
| 362 if return_code: | 431 if return_code: |
| 363 raise RuntimeError('Failed to run ParallelAddress2Line: returned ' + | 432 raise RuntimeError('Failed to run ParallelAddress2Line: returned ' + |
| (...skipping 70 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 434 'This argument is only valid when using --library.') | 503 'This argument is only valid when using --library.') |
| 435 parser.add_option('-v', dest='verbose', action='store_true', | 504 parser.add_option('-v', dest='verbose', action='store_true', |
| 436 help='be verbose, printing lots of status information.') | 505 help='be verbose, printing lots of status information.') |
| 437 parser.add_option('--nm-out', metavar='PATH', | 506 parser.add_option('--nm-out', metavar='PATH', |
| 438 help='keep the nm output file, and store it at the ' | 507 help='keep the nm output file, and store it at the ' |
| 439 'specified path. This is useful if you want to see the ' | 508 'specified path. This is useful if you want to see the ' |
| 440 'fully processed nm output after the symbols have been ' | 509 'fully processed nm output after the symbols have been ' |
| 441 'mapped to source locations. By default, a tempfile is ' | 510 'mapped to source locations. By default, a tempfile is ' |
| 442 'used and is deleted when the program terminates.' | 511 'used and is deleted when the program terminates.' |
| 443 'This argument is only valid when using --library.') | 512 'This argument is only valid when using --library.') |
| 513 parser.add_option('--legacy', action='store_true', | |
| 514 help='emit legacy binary size report instead of modern') | |
| 444 opts, args = parser.parse_args() | 515 opts, args = parser.parse_args() |
| 445 | 516 |
| 446 if ((not opts.library) and (not opts.nm_in)) or (opts.library and opts.nm_in): | 517 if ((not opts.library) and (not opts.nm_in)) or (opts.library and opts.nm_in): |
| 447 parser.error('exactly one of --library or --nm-in is required') | 518 parser.error('exactly one of --library or --nm-in is required') |
| 448 if (opts.nm_in): | 519 if (opts.nm_in): |
| 449 if opts.jobs: | 520 if opts.jobs: |
| 450 print >> sys.stderr, ('WARNING: --jobs has no effect ' | 521 print >> sys.stderr, ('WARNING: --jobs has no effect ' |
| 451 'when used with --nm-in') | 522 'when used with --nm-in') |
| 452 if opts.arch: | 523 if opts.arch: |
| 453 print >> sys.stderr, ('WARNING: --arch has no effect ' | 524 print >> sys.stderr, ('WARNING: --arch has no effect ' |
| 454 'when used with --nm-in') | 525 'when used with --nm-in') |
| 455 if not opts.destdir: | 526 if not opts.destdir: |
| 456 parser.error('--destdir is required argument') | 527 parser.error('--destdir is required argument') |
| 457 if not opts.jobs: | 528 if not opts.jobs: |
| 458 opts.jobs = '1' | 529 opts.jobs = '1' |
| 459 if not opts.arch: | 530 if not opts.arch: |
| 460 opts.arch = 'host-native' | 531 opts.arch = 'host-native' |
| 461 | 532 |
| 462 symbols = GetNmSymbols(opts.nm_in, opts.nm_out, opts.library, opts.arch, | 533 symbols = GetNmSymbols(opts.nm_in, opts.nm_out, opts.library, opts.arch, |
| 463 opts.jobs, opts.verbose is True) | 534 opts.jobs, opts.verbose is True) |
| 464 if not os.path.exists(opts.destdir): | 535 if not os.path.exists(opts.destdir): |
| 465 os.makedirs(opts.destdir, 0755) | 536 os.makedirs(opts.destdir, 0755) |
| 466 | 537 |
| 467 DumpTreemap(symbols, os.path.join(opts.destdir, 'treemap-dump.js')) | |
| 468 DumpLargestSymbols(symbols, | |
| 469 os.path.join(opts.destdir, 'largest-symbols.js'), 100) | |
| 470 DumpLargestSources(symbols, | |
| 471 os.path.join(opts.destdir, 'largest-sources.js'), 100) | |
| 472 DumpLargestVTables(symbols, | |
| 473 os.path.join(opts.destdir, 'largest-vtables.js'), 100) | |
| 474 | 538 |
| 475 # TODO(andrewhayden): Switch to D3 for greater flexibility | 539 if opts.legacy: # legacy report |
| 476 treemap_out = os.path.join(opts.destdir, 'webtreemap') | 540 DumpTreemap(symbols, os.path.join(opts.destdir, 'treemap-dump.js')) |
| 477 if not os.path.exists(treemap_out): | 541 DumpLargestSymbols(symbols, |
| 478 os.makedirs(treemap_out, 0755) | 542 os.path.join(opts.destdir, 'largest-symbols.js'), 100) |
| 479 treemap_src = os.path.join('third_party', 'webtreemap', 'src') | 543 DumpLargestSources(symbols, |
| 480 shutil.copy(os.path.join(treemap_src, 'COPYING'), treemap_out) | 544 os.path.join(opts.destdir, 'largest-sources.js'), 100) |
| 481 shutil.copy(os.path.join(treemap_src, 'webtreemap.js'), treemap_out) | 545 DumpLargestVTables(symbols, |
| 482 shutil.copy(os.path.join(treemap_src, 'webtreemap.css'), treemap_out) | 546 os.path.join(opts.destdir, 'largest-vtables.js'), 100) |
| 483 shutil.copy(os.path.join('tools', 'binary_size', 'template', 'index.html'), | 547 treemap_out = os.path.join(opts.destdir, 'webtreemap') |
| 484 opts.destdir) | 548 if not os.path.exists(treemap_out): |
| 549 os.makedirs(treemap_out, 0755) | |
| 550 treemap_src = os.path.join('third_party', 'webtreemap', 'src') | |
| 551 shutil.copy(os.path.join(treemap_src, 'COPYING'), treemap_out) | |
| 552 shutil.copy(os.path.join(treemap_src, 'webtreemap.js'), treemap_out) | |
| 553 shutil.copy(os.path.join(treemap_src, 'webtreemap.css'), treemap_out) | |
| 554 shutil.copy(os.path.join('tools', 'binary_size', 'legacy_template', | |
| 555 'index.html'), opts.destdir) | |
| 556 else: # modern report | |
| 557 DumpCompactTree(symbols, os.path.join(opts.destdir, 'data.js')) | |
| 558 d3_out = os.path.join(opts.destdir, 'd3') | |
| 559 if not os.path.exists(d3_out): | |
| 560 os.makedirs(d3_out, 0755) | |
| 561 d3_src = os.path.join('third_party', 'd3', 'src') | |
| 562 template_src = os.path.join('tools', 'binary_size', | |
| 563 'template') | |
| 564 shutil.copy(os.path.join(d3_src, 'LICENSE'), d3_out) | |
| 565 shutil.copy(os.path.join(d3_src, 'd3.js'), d3_out) | |
| 566 shutil.copy(os.path.join(template_src, 'index.html'), opts.destdir) | |
| 567 shutil.copy(os.path.join(template_src, 'D3SymbolTreeMap.js'), opts.destdir) | |
| 568 | |
| 485 if opts.verbose: | 569 if opts.verbose: |
| 486 print 'Report saved to ' + opts.destdir + '/index.html' | 570 print 'Report saved to ' + opts.destdir + '/index.html' |
| 487 | 571 |
| 488 | 572 |
| 489 if __name__ == '__main__': | 573 if __name__ == '__main__': |
| 490 sys.exit(main()) | 574 sys.exit(main()) |
| OLD | NEW |