Caffe2 - Python API
A deep learning, cross platform ML framework
binarysize.py
1 """A tool to inspect the binary size of a built binary file.
2 
3 This script prints out a tree of symbols and their corresponding sizes, using
4 Linux's nm functionality.
5 
6 Usage:
7 
8  python binary_size.py -- \
9  --target=/path/to/your/target/binary \
10  [--nm_command=/path/to/your/custom/nm] \
11  [--max_depth=10] [--min_size=1024] \
12  [--color] \
13 
14 To assist visualization, pass in '--color' to make the symbols color coded to
15 green, assuming that you have a xterm connection that supports color.
16 """
17 
18 from __future__ import absolute_import
19 from __future__ import division
20 from __future__ import print_function
21 from __future__ import unicode_literals
22 import argparse
23 import subprocess
24 import sys
25 
26 
27 class Trie(object):
28  """A simple class that represents a Trie."""
29 
30  def __init__(self, name):
31  """Initializes a Trie object."""
32  self.name = name
33  self.size = 0
34  self.dictionary = {}
35 
36 
37 def GetSymbolTrie(target, nm_command, max_depth):
38  """Gets a symbol trie with the passed in target.
39 
40  Args:
41  target: the target binary to inspect.
42  nm_command: the command to run nm.
43  max_depth: the maximum depth to create the trie.
44  """
45  # Run nm to get a dump on the strings.
46  proc = subprocess.Popen(
47  [nm_command, '--radix=d', '--size-sort', '--print-size', target],
48  stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
49  nm_out, _ = proc.communicate()
50  if proc.returncode != 0:
51  print('NM command failed. Output is as follows:')
52  print(nm_out)
53  sys.exit(1)
54  # Run c++filt to get proper symbols.
55  proc = subprocess.Popen(['c++filt'],
56  stdin=subprocess.PIPE, stdout=subprocess.PIPE,
57  stderr=subprocess.STDOUT)
58  out, _ = proc.communicate(input=nm_out)
59  if proc.returncode != 0:
60  print('c++filt failed. Output is as follows:')
61  print(out)
62  sys.exit(1)
63  # Splits the output to size and function name.
64  data = []
65  for line in out.split('\n'):
66  if line:
67  content = line.split(' ')
68  if len(content) < 4:
69  # This is a line not representing symbol sizes. skip.
70  continue
71  data.append([int(content[1]), ' '.join(content[3:])])
72  symbol_trie = Trie('')
73  for size, name in data:
74  curr = symbol_trie
75  for c in name:
76  if c not in curr.dictionary:
77  curr.dictionary[c] = Trie(curr.name + c)
78  curr = curr.dictionary[c]
79  curr.size += size
80  if len(curr.name) > max_depth:
81  break
82  symbol_trie.size = sum(t.size for t in symbol_trie.dictionary.values())
83  return symbol_trie
84 
85 
86 def MaybeAddColor(s, color):
87  """Wrap the input string to the xterm green color, if color is set.
88  """
89  if color:
90  return '\033[92m{0}\033[0m'.format(s)
91  else:
92  return s
93 
94 
95 def ReadableSize(num):
96  """Get a human-readable size."""
97  for unit in ['B', 'KB', 'MB', 'GB']:
98  if abs(num) <= 1024.0:
99  return '%3.2f%s' % (num, unit)
100  num /= 1024.0
101  return '%.1f TB' % (num,)
102 
103 
104 # Note(jiayq): I know, I know, this is a recursive function, but it is
105 # convenient to write.
106 def PrintTrie(trie, prefix, max_depth, min_size, color):
107  """Prints the symbol trie in a readable manner.
108  """
109  if len(trie.name) == max_depth or not trie.dictionary.keys():
110  # If we are reaching a leaf node or the maximum depth, we will print the
111  # result.
112  if trie.size > min_size:
113  print('{0}{1} {2}'.format(
114  prefix,
115  MaybeAddColor(trie.name, color),
116  ReadableSize(trie.size)))
117  elif len(trie.dictionary.keys()) == 1:
118  # There is only one child in this dictionary, so we will just delegate
119  # to the downstream trie to print stuff.
120  PrintTrie(
121  trie.dictionary.values()[0], prefix, max_depth, min_size, color)
122  elif trie.size > min_size:
123  print('{0}{1} {2}'.format(
124  prefix,
125  MaybeAddColor(trie.name, color),
126  ReadableSize(trie.size)))
127  keys_with_sizes = [
128  (k, trie.dictionary[k].size) for k in trie.dictionary.keys()]
129  keys_with_sizes.sort(key=lambda x: x[1])
130  for k, _ in keys_with_sizes[::-1]:
131  PrintTrie(
132  trie.dictionary[k], prefix + ' |', max_depth, min_size, color)
133 
134 
135 def main(argv):
136  if not sys.platform.startswith('linux'):
137  raise RuntimeError('Currently this tool only supports Linux.')
138  parser = argparse.ArgumentParser(
139  description="Tool to inspect binary size.")
140  parser.add_argument(
141  '--max_depth', type=int, default=10,
142  help='The maximum depth to print the symbol tree.')
143  parser.add_argument(
144  '--min_size', type=int, default=1024,
145  help='The mininum symbol size to print.')
146  parser.add_argument(
147  '--nm_command', type=str, default='nm',
148  help='The path to the nm command that the tool needs.')
149  parser.add_argument(
150  '--color', action='store_true',
151  help='If set, use ascii color for output.')
152  parser.add_argument(
153  '--target', type=str,
154  help='The binary target to inspect.')
155  args = parser.parse_args(argv)
156  if not args.target:
157  raise RuntimeError('You must specify a target to inspect.')
158  symbol_trie = GetSymbolTrie(
159  args.target, args.nm_command, args.max_depth)
160  PrintTrie(symbol_trie, '', args.max_depth, args.min_size, args.color)
161 
162 
163 if __name__ == '__main__':
164  main(sys.argv[1:])
def GetSymbolTrie(target, nm_command, max_depth)
Definition: binarysize.py:37
def __init__(self, name)
Definition: binarysize.py:30
def PrintTrie(trie, prefix, max_depth, min_size, color)
Definition: binarysize.py:106
def MaybeAddColor(s, color)
Definition: binarysize.py:86