sizestats.py 3.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108
  1. from executils import captureStdout
  2. from collections import defaultdict
  3. from os.path import normpath
  4. import re, sys
  5. _reSymbolInfo = re.compile(
  6. r'^([0-9a-f]+\s)?([0-9a-f]+\s)?\s*([A-Za-z])\s([^\t]+)(\t[^\t]+)?$'
  7. )
  8. def parseSymbolSize(objectFile):
  9. text = captureStdout(sys.stderr, 'nm -CSl "%s"' % objectFile)
  10. if text is not None:
  11. for line in text.split('\n'):
  12. if line:
  13. match = _reSymbolInfo.match(line)
  14. assert match is not None, line
  15. addr_, sizeStr, typ, name, originStr = match.groups()
  16. if originStr is None:
  17. origin = (None, None)
  18. else:
  19. source, lineNo = originStr.lstrip().rsplit(':', 1)
  20. origin = (normpath(source), int(lineNo))
  21. if sizeStr is not None:
  22. if typ not in 'Bb':
  23. # Symbols in BSS (uninitialized data section) do not
  24. # contribute to executable size, so ignore them.
  25. yield name, typ, int(sizeStr, 16), origin
  26. if __name__ == '__main__':
  27. if len(sys.argv) == 2:
  28. executable = sys.argv[1]
  29. # Get symbol information.
  30. symbolsBySource = defaultdict(list)
  31. for name, typ, size, (source, lineNo) in parseSymbolSize(executable):
  32. symbolsBySource[source].append((name, typ, size, lineNo))
  33. # Build directory tree.
  34. def newDict():
  35. return defaultdict(newDict)
  36. dirTree = newDict()
  37. for source, symbols in symbolsBySource.iteritems():
  38. if source is None:
  39. parts = [ '(no source)' ]
  40. else:
  41. assert source[0] == '/'
  42. parts = source[1 : ].split('/')
  43. parts[0] = '/' + parts[0]
  44. node = dirTree
  45. for part in parts[ : -1]:
  46. node = node[part + '/']
  47. node[parts[-1]] = symbols
  48. # Combine branches without forks.
  49. def compactTree(node):
  50. names = set(node.iterkeys())
  51. while names:
  52. name = names.pop()
  53. content = node[name]
  54. if isinstance(content, dict) and len(content) == 1:
  55. subName, subContent = content.iteritems().next()
  56. if isinstance(subContent, dict):
  57. # A directory containing a single directory.
  58. del node[name]
  59. node[name + subName] = subContent
  60. names.add(name + subName)
  61. for content in node.itervalues():
  62. if isinstance(content, dict):
  63. compactTree(content)
  64. compactTree(dirTree)
  65. # Compute size of all nodes in the tree.
  66. def buildSizeTree(node):
  67. if isinstance(node, dict):
  68. newNode = {}
  69. for name, content in node.iteritems():
  70. newNode[name] = buildSizeTree(content)
  71. nodeSize = sum(size for size, subNode in newNode.itervalues())
  72. return nodeSize, newNode
  73. else:
  74. nodeSize = sum(size for name, typ, size, lineNo in node)
  75. return nodeSize, node
  76. totalSize, sizeTree = buildSizeTree(dirTree)
  77. # Output.
  78. def printTree(size, node, indent):
  79. if isinstance(node, dict):
  80. for name, (contentSize, content) in sorted(
  81. node.iteritems(),
  82. key = lambda (name, (contentSize, content)):
  83. ( -contentSize, name )
  84. ):
  85. print '%s%8d %s' % (indent, contentSize, name)
  86. printTree(contentSize, content, indent + ' ')
  87. else:
  88. for name, typ, size, lineNo in sorted(
  89. node,
  90. key = lambda (name, typ, size, lineNo): ( -size, name )
  91. ):
  92. print '%s%8d %s %s %s' % (
  93. indent, size, typ, name,
  94. '' if lineNo is None else '(line %d)' % lineNo
  95. )
  96. printTree(totalSize, sizeTree, '')
  97. else:
  98. print >> sys.stderr, 'Usage: python sizestats.py executable'
  99. sys.exit(2)