sizestats.py 3.3 KB

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