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

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

Issue 797363002: binary_size: Remove legacy path, keep / as / for unittests on windows (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: . Created 6 years 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
« no previous file with comments | « tools/binary_size/legacy_template/index.html ('k') | no next file » | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
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.
11 """ 11 """
12 12
13 import collections 13 import collections
14 import json 14 import json
15 import logging 15 import logging
16 import multiprocessing 16 import multiprocessing
17 import optparse 17 import optparse
18 import os 18 import os
19 import re 19 import re
20 import shutil 20 import shutil
21 import struct 21 import struct
22 import subprocess 22 import subprocess
23 import sys 23 import sys
24 import tempfile 24 import tempfile
25 import time 25 import time
26 26
27 import binary_size_utils 27 import binary_size_utils
28 28
29 # This path changee is not beautiful. Temporary (I hope) measure until 29 # This path change is not beautiful. Temporary (I hope) measure until
30 # the chromium project has figured out a proper way to organize the 30 # the chromium project has figured out a proper way to organize the
31 # library of python tools. http://crbug.com/375725 31 # library of python tools. http://crbug.com/375725
32 elf_symbolizer_path = os.path.abspath(os.path.join( 32 elf_symbolizer_path = os.path.abspath(os.path.join(
33 os.path.dirname(__file__), 33 os.path.dirname(__file__),
34 '..', 34 '..',
35 '..', 35 '..',
36 'build', 36 'build',
37 'android', 37 'android',
38 'pylib')) 38 'pylib'))
39 sys.path.append(elf_symbolizer_path) 39 sys.path.append(elf_symbolizer_path)
(...skipping 173 matching lines...) Expand 10 before | Expand all | Expand 10 after
213 SplitNoPathBucket(result) 213 SplitNoPathBucket(result)
214 214
215 largest_list_len = MakeChildrenDictsIntoLists(result) 215 largest_list_len = MakeChildrenDictsIntoLists(result)
216 216
217 if largest_list_len > BIG_BUCKET_LIMIT: 217 if largest_list_len > BIG_BUCKET_LIMIT:
218 logging.warning('There are sections with %d nodes. ' 218 logging.warning('There are sections with %d nodes. '
219 'Results might be unusable.' % largest_list_len) 219 'Results might be unusable.' % largest_list_len)
220 return result 220 return result
221 221
222 222
223 # TODO(andrewhayden): Only used for legacy reports. Delete.
224 def TreeifySymbols(symbols):
225 """Convert symbols into a path-based tree, calculating size information
226 along the way.
227
228 The result is a dictionary that contains two kinds of nodes:
229 1. Leaf nodes, representing source code locations (e.g., c++ files)
230 These nodes have the following dictionary entries:
231 sizes: a dictionary whose keys are categories (such as code, data,
232 vtable, etceteras) and whose values are the size, in bytes, of
233 those categories;
234 size: the total size, in bytes, of all the entries in the sizes dict
235 2. Non-leaf nodes, representing directories
236 These nodes have the following dictionary entries:
237 children: a dictionary whose keys are names (path entries; either
238 directory or file names) and whose values are other nodes;
239 size: the total size, in bytes, of all the leaf nodes that are
240 contained within the children dict (recursively expanded)
241
242 The result object is itself a dictionary that represents the common ancestor
243 of all child nodes, e.g. a path to which all other nodes beneath it are
244 relative. The 'size' attribute of this dict yields the sum of the size of all
245 leaf nodes within the data structure.
246 """
247 dirs = {'children': {}, 'size': 0}
248 for sym, symbol_type, size, path in symbols:
249 dirs['size'] += size
250 if path:
251 path = os.path.normpath(path)
252 if path.startswith('/'):
253 path = path[1:]
254
255 parts = None
256 if path:
257 parts = path.split('/')
258
259 if parts:
260 assert path
261 file_key = parts.pop()
262 tree = dirs
263 try:
264 # Traverse the tree to the parent of the file node, creating as needed
265 for part in parts:
266 assert part != ''
267 if part not in tree['children']:
268 tree['children'][part] = {'children': {}, 'size': 0}
269 tree = tree['children'][part]
270 tree['size'] += size
271
272 # Get (creating if necessary) the node for the file
273 # This node doesn't have a 'children' attribute
274 if file_key not in tree['children']:
275 tree['children'][file_key] = {'sizes': collections.defaultdict(int),
276 'size': 0}
277 tree = tree['children'][file_key]
278 tree['size'] += size
279
280 # Accumulate size into a bucket within the file
281 symbol_type = symbol_type.lower()
282 if 'vtable for ' in sym:
283 tree['sizes']['[vtable]'] += size
284 elif 'r' == symbol_type:
285 tree['sizes']['[rodata]'] += size
286 elif 'd' == symbol_type:
287 tree['sizes']['[data]'] += size
288 elif 'b' == symbol_type:
289 tree['sizes']['[bss]'] += size
290 elif 't' == symbol_type:
291 # 'text' in binary parlance means 'code'.
292 tree['sizes']['[code]'] += size
293 elif 'w' == symbol_type:
294 tree['sizes']['[weak]'] += size
295 else:
296 tree['sizes']['[other]'] += size
297 except:
298 print >> sys.stderr, sym, parts, file_key
299 raise
300 else:
301 key = 'symbols without paths'
302 if key not in dirs['children']:
303 dirs['children'][key] = {'sizes': collections.defaultdict(int),
304 'size': 0}
305 tree = dirs['children'][key]
306 subkey = 'misc'
307 if (sym.endswith('::__FUNCTION__') or
308 sym.endswith('::__PRETTY_FUNCTION__')):
309 subkey = '__FUNCTION__'
310 elif sym.startswith('CSWTCH.'):
311 subkey = 'CSWTCH'
312 elif '::' in sym:
313 subkey = sym[0:sym.find('::') + 2]
314 tree['sizes'][subkey] = tree['sizes'].get(subkey, 0) + size
315 tree['size'] += size
316 return dirs
317
318
319 # TODO(andrewhayden): Only used for legacy reports. Delete.
320 def JsonifyTree(tree, name):
321 """Convert TreeifySymbols output to a JSON treemap.
322
323 The format is very similar, with the notable exceptions being
324 lists of children instead of maps and some different attribute names."""
325 children = []
326 css_class_map = {
327 '[vtable]': 'vtable',
328 '[rodata]': 'read-only_data',
329 '[data]': 'data',
330 '[bss]': 'bss',
331 '[code]': 'code',
332 '[weak]': 'weak_symbol'
333 }
334 if 'children' in tree:
335 # Non-leaf node. Recurse.
336 for child_name, child in tree['children'].iteritems():
337 children.append(JsonifyTree(child, child_name))
338 else:
339 # Leaf node; dump per-file stats as entries in the treemap
340 for kind, size in tree['sizes'].iteritems():
341 child_json = {'name': kind + ' (' + FormatBytes(size) + ')',
342 'data': { '$area': size }}
343 css_class = css_class_map.get(kind)
344 if css_class is not None:
345 child_json['data']['$symbol'] = css_class
346 children.append(child_json)
347 # Sort children by size, largest to smallest.
348 children.sort(key=lambda child: -child['data']['$area'])
349
350 # For leaf nodes, the 'size' attribute is the size of the leaf;
351 # Non-leaf nodes don't really have a size, but their 'size' attribute is
352 # the sum of the sizes of all their children.
353 return {'name': name + ' (' + FormatBytes(tree['size']) + ')',
354 'data': { '$area': tree['size'] },
355 'children': children }
356
357 def DumpCompactTree(symbols, symbol_path_origin_dir, outfile): 223 def DumpCompactTree(symbols, symbol_path_origin_dir, outfile):
358 tree_root = MakeCompactTree(symbols, symbol_path_origin_dir) 224 tree_root = MakeCompactTree(symbols, symbol_path_origin_dir)
359 with open(outfile, 'w') as out: 225 with open(outfile, 'w') as out:
360 out.write('var tree_data=') 226 out.write('var tree_data=')
361 # Use separators without whitespace to get a smaller file. 227 # Use separators without whitespace to get a smaller file.
362 json.dump(tree_root, out, separators=(',', ':')) 228 json.dump(tree_root, out, separators=(',', ':'))
363 print('Writing %d bytes json' % os.path.getsize(outfile)) 229 print('Writing %d bytes json' % os.path.getsize(outfile))
364 230
365 231
366 # TODO(andrewhayden): Only used for legacy reports. Delete.
367 def DumpTreemap(symbols, outfile):
368 dirs = TreeifySymbols(symbols)
369 out = open(outfile, 'w')
370 try:
371 out.write('var kTree = ' + json.dumps(JsonifyTree(dirs, '/')))
372 finally:
373 out.flush()
374 out.close()
375
376
377 # TODO(andrewhayden): Only used for legacy reports. Delete.
378 def DumpLargestSymbols(symbols, outfile, n):
379 # a list of (sym, symbol_type, size, path); sort by size.
380 symbols = sorted(symbols, key=lambda x: -x[2])
381 dumped = 0
382 out = open(outfile, 'w')
383 try:
384 out.write('var largestSymbols = [\n')
385 for sym, symbol_type, size, path in symbols:
386 if symbol_type in ('b', 'w'):
387 continue # skip bss and weak symbols
388 if path is None:
389 path = ''
390 entry = {'size': FormatBytes(size),
391 'symbol': sym,
392 'type': SymbolTypeToHuman(symbol_type),
393 'location': path }
394 out.write(json.dumps(entry))
395 out.write(',\n')
396 dumped += 1
397 if dumped >= n:
398 return
399 finally:
400 out.write('];\n')
401 out.flush()
402 out.close()
403
404
405 def MakeSourceMap(symbols): 232 def MakeSourceMap(symbols):
406 sources = {} 233 sources = {}
407 for _sym, _symbol_type, size, path in symbols: 234 for _sym, _symbol_type, size, path in symbols:
408 key = None 235 key = None
409 if path: 236 if path:
410 key = os.path.normpath(path) 237 key = os.path.normpath(path)
411 else: 238 else:
412 key = '[no path]' 239 key = '[no path]'
413 if key not in sources: 240 if key not in sources:
414 sources[key] = {'path': path, 'symbol_count': 0, 'size': 0} 241 sources[key] = {'path': path, 'symbol_count': 0, 'size': 0}
415 record = sources[key] 242 record = sources[key]
416 record['size'] += size 243 record['size'] += size
417 record['symbol_count'] += 1 244 record['symbol_count'] += 1
418 return sources 245 return sources
419 246
420 247
421 # TODO(andrewhayden): Only used for legacy reports. Delete.
422 def DumpLargestSources(symbols, outfile, n):
423 source_map = MakeSourceMap(symbols)
424 sources = sorted(source_map.values(), key=lambda x: -x['size'])
425 dumped = 0
426 out = open(outfile, 'w')
427 try:
428 out.write('var largestSources = [\n')
429 for record in sources:
430 entry = {'size': FormatBytes(record['size']),
431 'symbol_count': str(record['symbol_count']),
432 'location': record['path']}
433 out.write(json.dumps(entry))
434 out.write(',\n')
435 dumped += 1
436 if dumped >= n:
437 return
438 finally:
439 out.write('];\n')
440 out.flush()
441 out.close()
442
443
444 # TODO(andrewhayden): Only used for legacy reports. Delete.
445 def DumpLargestVTables(symbols, outfile, n):
446 vtables = []
447 for symbol, _type, size, path in symbols:
448 if 'vtable for ' in symbol:
449 vtables.append({'symbol': symbol, 'path': path, 'size': size})
450 vtables = sorted(vtables, key=lambda x: -x['size'])
451 dumped = 0
452 out = open(outfile, 'w')
453 try:
454 out.write('var largestVTables = [\n')
455 for record in vtables:
456 entry = {'size': FormatBytes(record['size']),
457 'symbol': record['symbol'],
458 'location': record['path']}
459 out.write(json.dumps(entry))
460 out.write(',\n')
461 dumped += 1
462 if dumped >= n:
463 return
464 finally:
465 out.write('];\n')
466 out.flush()
467 out.close()
468
469
470 # Regex for parsing "nm" output. A sample line looks like this: 248 # Regex for parsing "nm" output. A sample line looks like this:
471 # 0167b39c 00000018 t ACCESS_DESCRIPTION_free /path/file.c:95 249 # 0167b39c 00000018 t ACCESS_DESCRIPTION_free /path/file.c:95
472 # 250 #
473 # The fields are: address, size, type, name, source location 251 # The fields are: address, size, type, name, source location
474 # Regular expression explained ( see also: https://xkcd.com/208 ): 252 # Regular expression explained ( see also: https://xkcd.com/208 ):
475 # ([0-9a-f]{8,}+) The address 253 # ([0-9a-f]{8,}+) The address
476 # [\s]+ Whitespace separator 254 # [\s]+ Whitespace separator
477 # ([0-9a-f]{8,}+) The size. From here on out it's all optional. 255 # ([0-9a-f]{8,}+) The size. From here on out it's all optional.
478 # [\s]+ Whitespace separator 256 # [\s]+ Whitespace separator
479 # (\S?) The symbol type, which is any non-whitespace char 257 # (\S?) The symbol type, which is any non-whitespace char
(...skipping 399 matching lines...) Expand 10 before | Expand all | Expand 10 after
879 opts.disable_disambiguation is None, 657 opts.disable_disambiguation is None,
880 opts.source_path) 658 opts.source_path)
881 659
882 if opts.pak: 660 if opts.pak:
883 AddPakData(symbols, opts.pak) 661 AddPakData(symbols, opts.pak)
884 662
885 if not os.path.exists(opts.destdir): 663 if not os.path.exists(opts.destdir):
886 os.makedirs(opts.destdir, 0755) 664 os.makedirs(opts.destdir, 0755)
887 665
888 666
889 if opts.legacy: # legacy report 667 if opts.library:
890 DumpTreemap(symbols, os.path.join(opts.destdir, 'treemap-dump.js')) 668 symbol_path_origin_dir = os.path.dirname(os.path.abspath(opts.library))
891 DumpLargestSymbols(symbols, 669 else:
892 os.path.join(opts.destdir, 'largest-symbols.js'), 100) 670 # Just a guess. Hopefully all paths in the input file are absolute.
893 DumpLargestSources(symbols, 671 symbol_path_origin_dir = os.path.abspath(os.getcwd())
894 os.path.join(opts.destdir, 'largest-sources.js'), 100) 672 data_js_file_name = os.path.join(opts.destdir, 'data.js')
895 DumpLargestVTables(symbols, 673 DumpCompactTree(symbols, symbol_path_origin_dir, data_js_file_name)
896 os.path.join(opts.destdir, 'largest-vtables.js'), 100) 674 d3_out = os.path.join(opts.destdir, 'd3')
897 treemap_out = os.path.join(opts.destdir, 'webtreemap') 675 if not os.path.exists(d3_out):
898 if not os.path.exists(treemap_out): 676 os.makedirs(d3_out, 0755)
899 os.makedirs(treemap_out, 0755) 677 d3_src = os.path.join(os.path.dirname(__file__),
900 treemap_src = os.path.join('third_party', 'webtreemap', 'src') 678 '..',
901 shutil.copy(os.path.join(treemap_src, 'COPYING'), treemap_out) 679 '..',
902 shutil.copy(os.path.join(treemap_src, 'webtreemap.js'), treemap_out) 680 'third_party', 'd3', 'src')
903 shutil.copy(os.path.join(treemap_src, 'webtreemap.css'), treemap_out) 681 template_src = os.path.join(os.path.dirname(__file__),
904 shutil.copy(os.path.join('tools', 'binary_size', 'legacy_template', 682 'template')
905 'index.html'), opts.destdir) 683 shutil.copy(os.path.join(d3_src, 'LICENSE'), d3_out)
906 else: # modern report 684 shutil.copy(os.path.join(d3_src, 'd3.js'), d3_out)
907 if opts.library: 685 shutil.copy(os.path.join(template_src, 'index.html'), opts.destdir)
908 symbol_path_origin_dir = os.path.dirname(os.path.abspath(opts.library)) 686 shutil.copy(os.path.join(template_src, 'D3SymbolTreeMap.js'), opts.destdir)
909 else:
910 # Just a guess. Hopefully all paths in the input file are absolute.
911 symbol_path_origin_dir = os.path.abspath(os.getcwd())
912 data_js_file_name = os.path.join(opts.destdir, 'data.js')
913 DumpCompactTree(symbols, symbol_path_origin_dir, data_js_file_name)
914 d3_out = os.path.join(opts.destdir, 'd3')
915 if not os.path.exists(d3_out):
916 os.makedirs(d3_out, 0755)
917 d3_src = os.path.join(os.path.dirname(__file__),
918 '..',
919 '..',
920 'third_party', 'd3', 'src')
921 template_src = os.path.join(os.path.dirname(__file__),
922 'template')
923 shutil.copy(os.path.join(d3_src, 'LICENSE'), d3_out)
924 shutil.copy(os.path.join(d3_src, 'd3.js'), d3_out)
925 shutil.copy(os.path.join(template_src, 'index.html'), opts.destdir)
926 shutil.copy(os.path.join(template_src, 'D3SymbolTreeMap.js'), opts.destdir)
927 687
928 print 'Report saved to ' + opts.destdir + '/index.html' 688 print 'Report saved to ' + opts.destdir + '/index.html'
929 689
930 690
931 if __name__ == '__main__': 691 if __name__ == '__main__':
932 sys.exit(main()) 692 sys.exit(main())
OLDNEW
« no previous file with comments | « tools/binary_size/legacy_template/index.html ('k') | no next file » | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698