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

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

Issue 2769933002: V2 of //tools/binary_size rewrite (diffs). (Closed)
Patch Set: self-review Created 3 years, 9 months 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
OLDNEW
(Empty)
1 #!/usr/bin/env python
2 # Copyright 2017 The Chromium Authors. All rights reserved.
3 # Use of this source code is governed by a BSD-style license that can be
4 # found in the LICENSE file.
5
6 """Tool for analyzing binary size of executables using nm or linker map files.
7
8 Map files can be created by passing "-Map Foo.map" to the linker. If a map file
9 is unavailable, this tool can also be pointed at an unstripped executable, but
10 the information does not seem to be as accurate in this case.
11
12 Inspired by SymbolSort for Windows:
13 https://github.com/adrianstone55/SymbolSort
14 """
15
16 import argparse
17 import atexit
18 import code
19 import contextlib
20 import itertools
21 import logging
22 import os
23 import readline
24 import subprocess
25 import sys
26
27 import describe
28 import file_format
29 import helpers
30 import map2size
31 import models
32
33
34 # Number of lines before using less for Print().
35 _THRESHOLD_FOR_PAGER = 30
36
37
38 @contextlib.contextmanager
39 def _LessPipe():
40 """Output to `less`. Yields a file object to write to."""
41 try:
42 proc = subprocess.Popen(['less'], stdin=subprocess.PIPE, stdout=sys.stdout)
43 yield proc.stdin
44 proc.stdin.close()
45
46 proc.wait()
47 except IOError:
48 pass # Happens when less is quit before all data is written.
49 except KeyboardInterrupt:
50 pass # Assume used to break out of less.
51
52
53 class _Session(object):
54 _readline_initialized = False
55
56 def __init__(self, extra_vars):
57 self._variables = {
58 'Print': self._PrintFunc,
59 'Write': self._WriteFunc,
60 'Diff': models.Diff,
61 }
62 self._variables.update(extra_vars)
63
64 def _PrintFunc(self, obj, verbose=False, use_pager=None):
65 """Prints out the given Symbol / SymbolGroup / SymbolDiff / SizeInfo.
66
67 Args:
68 obj: The object to be printed.
69 use_pager: Whether to pipe output through `less`. Ignored when |obj| is a
70 Symbol.
71 """
72 lines = describe.GenerateLines(obj, verbose=verbose)
73 if use_pager is None and sys.stdout.isatty():
74 # Does not take into account line-wrapping... Oh well.
75 first_lines = list(itertools.islice(lines, _THRESHOLD_FOR_PAGER))
76 if len(first_lines) == _THRESHOLD_FOR_PAGER:
77 use_pager = True
78 lines = itertools.chain(first_lines, lines)
79
80 if use_pager:
81 with _LessPipe() as stdin:
82 describe.WriteLines(lines, stdin.write)
83 else:
84 describe.WriteLines(lines, sys.stdout.write)
85
86
87 def _WriteFunc(self, obj, path, verbose=False):
88 """Same as Print(), but writes to a file.
89
90 Example: Write(Diff(size_info2, size_info1), 'output.txt')
91 """
92 parent_dir = os.path.dirname(path)
93 if parent_dir and not os.path.exists(parent_dir):
94 os.makedirs(parent_dir)
95 with file_format.OpenMaybeGz(path, 'w') as file_obj:
96 lines = describe.GenerateLines(obj, verbose=verbose)
97 describe.WriteLines(lines, file_obj.write)
98
99
100 def _CreateBanner(self):
101 symbol_info_keys = sorted(m for m in dir(models.SizeInfo) if m[0] != '_')
102 symbol_group_keys = sorted(m for m in dir(models.SymbolGroup)
103 if m[0] != '_')
104 symbol_diff_keys = sorted(m for m in dir(models.SymbolDiff)
105 if m[0] != '_' and m not in symbol_group_keys)
106 functions = sorted(k for k in self._variables if k[0].isupper())
107 variables = sorted(k for k in self._variables if k[0].islower())
108 return '\n'.join([
109 '*' * 80,
110 'Entering interactive Python shell. Here is some inspiration:',
111 '',
112 '# Show two levels of .text, grouped by first two subdirectories',
113 'text_syms = size_info1.symbols.WhereInSection("t")',
114 'by_path = text_syms.GroupByPath(depth=2)',
115 'Print(by_path.WhereBiggerThan(1024))',
116 '',
117 '# Show all non-vtable generated symbols',
118 'generated_syms = size_info1.symbols.WhereIsGenerated()',
119 'Print(generated_syms.WhereNameMatches("vtable").Inverted())',
120 '',
121 '*' * 80,
122 '',
123 'SizeInfo: %s' % ', '.join(symbol_info_keys),
124 'SymbolGroup: %s' % ', '.join(symbol_group_keys),
125 'SymbolDiff (extends SymbolGroup): %s' % ', '.join(symbol_diff_keys),
126 '',
127 'Functions: %s' % ', '.join(functions),
128 'Variables: %s' % ', '.join(variables),
estevenson 2017/03/27 17:50:41 Most using this tool will know this already, but y
agrieve 2017/03/27 19:47:21 Done (and added at least class-level pydoc for mod
129 '',
130 ])
131
132 @classmethod
133 def _InitReadline(cls):
134 if cls._readline_initialized:
135 return
136 cls._readline_initialized = True
137 # Without initializing readline, arrow keys don't even work!
138 readline.parse_and_bind('tab: complete')
139 history_file = os.path.join(os.path.expanduser('~'),
140 '.binary_size_query_history')
141 if os.path.exists(history_file):
142 readline.read_history_file(history_file)
143 atexit.register(lambda: readline.write_history_file(history_file))
144
145 def Eval(self, query):
146 eval_result = eval(query, self._variables)
147 if eval_result:
148 self._PrintFunc(eval_result)
149
150 def GoInteractive(self):
151 _Session._InitReadline()
152 code.InteractiveConsole(self._variables).interact(self._CreateBanner())
153
154
155 def main(argv):
156 parser = argparse.ArgumentParser()
157 parser.add_argument('inputs', nargs='*',
158 help='Input .size/.map files to load. They will be '
159 'mapped to variables as: size_info1, size_info2,'
160 ' etc.')
161 parser.add_argument('--query',
162 help='Print the result of the given snippet. Example: '
163 'size_info1.symbols.WhereInSection("d").'
164 'WhereBiggerThan(100)')
165 map2size.AddOptions(parser)
166 args = helpers.AddCommonOptionsAndParseArgs(parser, argv)
167
168 info_variables = {}
169 for i, path in enumerate(args.inputs):
170 size_info = map2size.AnalyzeWithArgs(args, path)
171 info_variables['size_info%d' % (i + 1)] = size_info
172
173 session = _Session(info_variables)
174
175 if args.query:
176 logging.info('Running query from command-line.')
177 session.Eval(args.query)
178 else:
179 logging.info('Entering interactive console.')
180 session.GoInteractive()
181
182
183 if __name__ == '__main__':
184 sys.exit(main(sys.argv))
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698