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

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

Issue 2813963002: //tools/binary_size: Consolidate most tools into "supersize" command (Closed)
Patch Set: Fix readme formatting. Make archive's --outoput-file a positional arg Created 3 years, 8 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
« no previous file with comments | « tools/binary_size/README.md ('k') | tools/binary_size/create_html_breakdown.py » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
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 match_util
32 import models
33 import paths
34
35
36 # Number of lines before using less for Print().
37 _THRESHOLD_FOR_PAGER = 30
38
39
40 @contextlib.contextmanager
41 def _LessPipe():
42 """Output to `less`. Yields a file object to write to."""
43 try:
44 proc = subprocess.Popen(['less'], stdin=subprocess.PIPE, stdout=sys.stdout)
45 yield proc.stdin
46 proc.stdin.close()
47 proc.wait()
48 except IOError:
49 pass # Happens when less is quit before all data is written.
50 except KeyboardInterrupt:
51 pass # Assume used to break out of less.
52
53
54 def _WriteToStream(lines, use_pager=None, to_file=None):
55 if to_file:
56 use_pager = False
57 if use_pager is None and sys.stdout.isatty():
58 # Does not take into account line-wrapping... Oh well.
59 first_lines = list(itertools.islice(lines, _THRESHOLD_FOR_PAGER))
60 if len(first_lines) == _THRESHOLD_FOR_PAGER:
61 use_pager = True
62 lines = itertools.chain(first_lines, lines)
63
64 if use_pager:
65 with _LessPipe() as stdin:
66 describe.WriteLines(lines, stdin.write)
67 elif to_file:
68 with open(to_file, 'w') as file_obj:
69 describe.WriteLines(lines, file_obj.write)
70 else:
71 describe.WriteLines(lines, sys.stdout.write)
72
73
74 class _Session(object):
75 _readline_initialized = False
76
77 def __init__(self, size_infos, lazy_paths):
78 self._variables = {
79 'Print': self._PrintFunc,
80 'Diff': models.Diff,
81 'Disassemble': self._DisassembleFunc,
82 'ExpandRegex': match_util.ExpandRegexIdentifierPlaceholder,
83 'ShowExamples': self._ShowExamplesFunc,
84 }
85 self._lazy_paths = lazy_paths
86 self._size_infos = size_infos
87
88 if len(size_infos) == 1:
89 self._variables['size_info'] = size_infos[0]
90 self._variables['symbols'] = size_infos[0].symbols
91 else:
92 for i, size_info in enumerate(size_infos):
93 self._variables['size_info%d' % (i + 1)] = size_info
94 self._variables['symbols%d' % (i + 1)] = size_info.symbols
95
96 def _PrintFunc(self, obj, verbose=False, recursive=False, use_pager=None,
97 to_file=None):
98 """Prints out the given Symbol / SymbolGroup / SymbolDiff / SizeInfo.
99
100 Args:
101 obj: The object to be printed.
102 verbose: Show more detailed output.
103 recursive: Print children of nested SymbolGroups.
104 use_pager: Pipe output through `less`. Ignored when |obj| is a Symbol.
105 default is to automatically pipe when output is long.
106 to_file: Rather than print to stdio, write to the given file.
107 """
108 lines = describe.GenerateLines(obj, verbose=verbose, recursive=recursive)
109 _WriteToStream(lines, use_pager=use_pager, to_file=to_file)
110
111 def _ElfPathForSymbol(self, symbol):
112 size_info = None
113 for size_info in self._size_infos:
114 if symbol in size_info.symbols:
115 break
116 else:
117 assert False, 'Symbol does not belong to a size_info.'
118
119 filename = size_info.metadata.get(models.METADATA_ELF_FILENAME)
120 output_dir = self._lazy_paths.output_directory or ''
121 path = os.path.normpath(os.path.join(output_dir, filename))
122
123 found_build_id = map2size.BuildIdFromElf(
124 path, self._lazy_paths.tool_prefix)
125 expected_build_id = size_info.metadata.get(models.METADATA_ELF_BUILD_ID)
126 assert found_build_id == expected_build_id, (
127 'Build ID does not match for %s' % path)
128 return path
129
130 def _DisassembleFunc(self, symbol, elf_path=None, use_pager=None,
131 to_file=None):
132 """Shows objdump disassembly for the given symbol.
133
134 Args:
135 symbol: Must be a .text symbol and not a SymbolGroup.
136 elf_path: Path to the executable containing the symbol. Required only
137 when auto-detection fails.
138 """
139 assert symbol.address and symbol.section_name == '.text'
140 if not elf_path:
141 elf_path = self._ElfPathForSymbol(symbol)
142 tool_prefix = self._lazy_paths.tool_prefix
143 args = [tool_prefix + 'objdump', '--disassemble', '--source',
144 '--line-numbers', '--demangle',
145 '--start-address=0x%x' % symbol.address,
146 '--stop-address=0x%x' % symbol.end_address, elf_path]
147 proc = subprocess.Popen(args, stdout=subprocess.PIPE)
148 lines = itertools.chain(('Showing disassembly for %r' % symbol,
149 'Command: %s' % ' '.join(args)),
150 (l.rstrip() for l in proc.stdout))
151 _WriteToStream(lines, use_pager=use_pager, to_file=to_file)
152 proc.kill()
153
154 def _ShowExamplesFunc(self):
155 print '\n'.join([
156 '# Show pydoc for main types:',
157 'import models',
158 'help(models)',
159 '',
160 '# Show all attributes of all symbols & per-section totals:',
161 'Print(size_info, verbose=True)',
162 '',
163 '# Show two levels of .text, grouped by first two subdirectories',
164 'text_syms = symbols.WhereInSection("t")',
165 'by_path = text_syms.GroupBySourcePath(depth=2)',
166 'Print(by_path.WhereBiggerThan(1024))',
167 '',
168 '# Show all non-vtable generated symbols',
169 'generated_syms = symbols.WhereIsGenerated()',
170 'Print(generated_syms.WhereNameMatches(r"vtable").Inverted())',
171 '',
172 '# Show all symbols that have "print" in their name or path, except',
173 '# those within components/.',
174 '# Note: Could have also used Inverted(), as above.',
175 '# Note: Use "help(ExpandRegex)" for more about what {{_print_}} does.',
176 'print_syms = symbols.WhereMatches(r"{{_print_}}")',
177 'Print(print_syms - print_syms.WherePathMatches(r"^components/"))',
178 '',
179 '# Diff two .size files and save result to a file:',
180 'Print(Diff(size_info1, size_info2), to_file="output.txt")',
181 '',
182 ])
183
184 def _CreateBanner(self):
185 symbol_info_keys = sorted(m for m in dir(models.SizeInfo) if m[0] != '_')
186 symbol_keys = sorted(m for m in dir(models.Symbol) if m[0] != '_')
187 symbol_group_keys = [m for m in dir(models.SymbolGroup) if m[0] != '_']
188 symbol_diff_keys = sorted(m for m in dir(models.SymbolDiff)
189 if m[0] != '_' and m not in symbol_group_keys)
190 symbol_group_keys = sorted(m for m in symbol_group_keys
191 if m not in symbol_keys)
192 functions = sorted(k for k in self._variables if k[0].isupper())
193 variables = sorted(k for k in self._variables if k[0].islower())
194 return '\n'.join([
195 '*' * 80,
196 'Entering interactive Python shell. Quick reference:',
197 '',
198 'SizeInfo: %s' % ', '.join(symbol_info_keys),
199 'Symbol: %s' % ', '.join(symbol_keys),
200 'SymbolGroup (extends Symbol): %s' % ', '.join(symbol_group_keys),
201 'SymbolDiff (extends SymbolGroup): %s' % ', '.join(symbol_diff_keys),
202 '',
203 'Functions: %s' % ', '.join('%s()' % f for f in functions),
204 'Variables: %s' % ', '.join(variables),
205 '*' * 80,
206 ])
207
208 @classmethod
209 def _InitReadline(cls):
210 if cls._readline_initialized:
211 return
212 cls._readline_initialized = True
213 # Without initializing readline, arrow keys don't even work!
214 readline.parse_and_bind('tab: complete')
215 history_file = os.path.join(os.path.expanduser('~'),
216 '.binary_size_query_history')
217 if os.path.exists(history_file):
218 readline.read_history_file(history_file)
219 atexit.register(lambda: readline.write_history_file(history_file))
220
221 def Eval(self, query):
222 eval_result = eval(query, self._variables)
223 if eval_result:
224 self._PrintFunc(eval_result)
225
226 def GoInteractive(self):
227 _Session._InitReadline()
228 code.InteractiveConsole(self._variables).interact(self._CreateBanner())
229
230
231 def main(argv):
232 parser = argparse.ArgumentParser()
233 parser.add_argument('inputs', nargs='+',
234 help='Input .size files to load. For a single file, '
235 'it will be mapped to variables as: size_info & '
236 'symbols (where symbols = size_info.symbols). For '
237 'multiple inputs, the names will be size_info1, '
238 'symbols1, etc.')
239 parser.add_argument('--query',
240 help='Print the result of the given snippet. Example: '
241 'symbols.WhereInSection("d").'
242 'WhereBiggerThan(100)')
243 paths.AddOptions(parser)
244 args = helpers.AddCommonOptionsAndParseArgs(parser, argv)
245
246 for path in args.inputs:
247 if not path.endswith('.size'):
248 parser.error('All inputs must end with ".size"')
249
250 size_infos = [map2size.LoadAndPostProcessSizeInfo(p) for p in args.inputs]
251 lazy_paths = paths.LazyPaths(args=args, input_file=args.inputs[0])
252 session = _Session(size_infos, lazy_paths)
253
254 if args.query:
255 logging.info('Running query from command-line.')
256 session.Eval(args.query)
257 else:
258 logging.info('Entering interactive console.')
259 session.GoInteractive()
260
261
262 if __name__ == '__main__':
263 sys.exit(main(sys.argv))
OLDNEW
« no previous file with comments | « tools/binary_size/README.md ('k') | tools/binary_size/create_html_breakdown.py » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698