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

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

Issue 2936033002: Supersize diff rewrite + tweaks (Closed)
Patch Set: review comnts Created 3 years, 6 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 | « no previous file | tools/binary_size/libsupersize/describe.py » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
1 # Copyright 2017 The Chromium Authors. All rights reserved. 1 # Copyright 2017 The Chromium Authors. All rights reserved.
2 # Use of this source code is governed by a BSD-style license that can be 2 # Use of this source code is governed by a BSD-style license that can be
3 # found in the LICENSE file. 3 # found in the LICENSE file.
4 4
5 """An interactive console for looking analyzing .size files.""" 5 """An interactive console for looking analyzing .size files."""
6 6
7 import argparse 7 import argparse
8 import atexit 8 import atexit
9 import code 9 import code
10 import contextlib 10 import contextlib
(...skipping 48 matching lines...) Expand 10 before | Expand all | Expand 10 after
59 elif to_file: 59 elif to_file:
60 with open(to_file, 'w') as file_obj: 60 with open(to_file, 'w') as file_obj:
61 describe.WriteLines(lines, file_obj.write) 61 describe.WriteLines(lines, file_obj.write)
62 else: 62 else:
63 describe.WriteLines(lines, sys.stdout.write) 63 describe.WriteLines(lines, sys.stdout.write)
64 64
65 65
66 class _Session(object): 66 class _Session(object):
67 _readline_initialized = False 67 _readline_initialized = False
68 68
69 def __init__(self, size_infos, size_paths, lazy_paths): 69 def __init__(self, size_infos, lazy_paths):
70 self._printed_variables = [] 70 self._printed_variables = []
71 self._variables = { 71 self._variables = {
72 'Print': self._PrintFunc, 72 'Print': self._PrintFunc,
73 'Diff': self._DiffFunc, 73 'Diff': self._DiffFunc,
74 'Disassemble': self._DisassembleFunc, 74 'Disassemble': self._DisassembleFunc,
75 'ExpandRegex': match_util.ExpandRegexIdentifierPlaceholder, 75 'ExpandRegex': match_util.ExpandRegexIdentifierPlaceholder,
76 'ShowExamples': self._ShowExamplesFunc, 76 'ShowExamples': self._ShowExamplesFunc,
77 'canned_queries': canned_queries.CannedQueries(size_infos), 77 'canned_queries': canned_queries.CannedQueries(size_infos),
78 'printed': self._printed_variables, 78 'printed': self._printed_variables,
79 } 79 }
80 self._lazy_paths = lazy_paths 80 self._lazy_paths = lazy_paths
81 self._size_infos = size_infos 81 self._size_infos = size_infos
82 self._size_paths = size_paths
83 self._disassemble_prefix_len = None 82 self._disassemble_prefix_len = None
84 83
85 if len(size_infos) == 1: 84 if len(size_infos) == 1:
86 self._variables['size_info'] = size_infos[0] 85 self._variables['size_info'] = size_infos[0]
87 else: 86 else:
88 for i, size_info in enumerate(size_infos): 87 for i, size_info in enumerate(size_infos):
89 self._variables['size_info%d' % (i + 1)] = size_info 88 self._variables['size_info%d' % (i + 1)] = size_info
90 89
91 def _DiffFunc(self, before=None, after=None, sort=True): 90 def _DiffFunc(self, before=None, after=None, sort=True):
92 """Diffs two SizeInfo objects. Returns a SizeInfoDiff. 91 """Diffs two SizeInfo objects. Returns a DeltaSizeInfo.
93 92
94 Args: 93 Args:
95 before: Defaults to first size_infos[0]. 94 before: Defaults to first size_infos[0].
96 after: Defaults to second size_infos[1]. 95 after: Defaults to second size_infos[1].
97 sort: When True (default), calls SymbolGroup.Sorted() after diffing. 96 sort: When True (default), calls SymbolGroup.Sorted() after diffing.
98 """ 97 """
99 before = before if before is not None else self._size_infos[0] 98 before = before if before is not None else self._size_infos[0]
100 after = after if after is not None else self._size_infos[1] 99 after = after if after is not None else self._size_infos[1]
101 ret = diff.Diff(before, after) 100 ret = diff.Diff(before, after)
102 if sort: 101 if sort:
103 ret.symbols = ret.symbols.Sorted() 102 ret.symbols = ret.symbols.Sorted()
104 return ret 103 return ret
105 104
106 def _PrintFunc(self, obj=None, verbose=False, recursive=False, use_pager=None, 105 def _PrintFunc(self, obj=None, verbose=False, recursive=False, use_pager=None,
107 to_file=None): 106 to_file=None):
108 """Prints out the given Symbol / SymbolGroup / SymbolDiff / SizeInfo. 107 """Prints out the given Symbol / SymbolGroup / SizeInfo.
109 108
110 For convenience, |obj| will be appended to the global "printed" list. 109 For convenience, |obj| will be appended to the global "printed" list.
111 110
112 Args: 111 Args:
113 obj: The object to be printed. Defaults to size_infos[-1]. Also accepts an 112 obj: The object to be printed. Defaults to size_infos[-1]. Also accepts an
114 index into the |printed| array for showing previous results. 113 index into the |printed| array for showing previous results.
115 verbose: Show more detailed output. 114 verbose: Show more detailed output.
116 recursive: Print children of nested SymbolGroups. 115 recursive: Print children of nested SymbolGroups.
117 use_pager: Pipe output through `less`. Ignored when |obj| is a Symbol. 116 use_pager: Pipe output through `less`. Ignored when |obj| is a Symbol.
118 default is to automatically pipe when output is long. 117 default is to automatically pipe when output is long.
119 to_file: Rather than print to stdio, write to the given file. 118 to_file: Rather than print to stdio, write to the given file.
120 """ 119 """
121 if isinstance(obj, int): 120 if isinstance(obj, int):
122 obj = self._printed_variables[obj] 121 obj = self._printed_variables[obj]
123 elif not self._printed_variables or self._printed_variables[-1] != obj: 122 elif not self._printed_variables or self._printed_variables[-1] != obj:
124 if not isinstance(obj, models.SymbolGroup) or len(obj) > 0: 123 if not isinstance(obj, models.SymbolGroup) or len(obj) > 0:
125 self._printed_variables.append(obj) 124 self._printed_variables.append(obj)
126 obj = obj if obj is not None else self._size_infos[-1] 125 obj = obj if obj is not None else self._size_infos[-1]
127 lines = describe.GenerateLines(obj, verbose=verbose, recursive=recursive) 126 lines = describe.GenerateLines(obj, verbose=verbose, recursive=recursive)
128 _WriteToStream(lines, use_pager=use_pager, to_file=to_file) 127 _WriteToStream(lines, use_pager=use_pager, to_file=to_file)
129 128
130 def _ElfPathAndToolPrefixForSymbol(self, symbol, elf_path, tool_prefix): 129 def _ElfPathAndToolPrefixForSymbol(self, size_info, elf_path):
131 size_info = None 130 tool_prefix = self._lazy_paths.tool_prefix
132 size_path = None
133 for size_info, size_path in zip(self._size_infos, self._size_paths):
134 if symbol in size_info.raw_symbols:
135 break
136 else:
137 # If symbols is from a diff(), use its address+name to find it.
138 for size_info, size_path in zip(self._size_infos, self._size_paths):
139 matched = size_info.raw_symbols.WhereAddressInRange(symbol.address)
140 # Use last matched symbol to skip over padding-only symbols.
141 if len(matched) > 0 and matched[-1].full_name == symbol.full_name:
142 symbol = matched[-1]
143 break
144 else:
145 assert False, 'Symbol does not belong to a size_info.'
146
147 orig_tool_prefix = size_info.metadata.get(models.METADATA_TOOL_PREFIX) 131 orig_tool_prefix = size_info.metadata.get(models.METADATA_TOOL_PREFIX)
148 if orig_tool_prefix: 132 if orig_tool_prefix:
149 orig_tool_prefix = paths.FromSrcRootRelative(orig_tool_prefix) 133 orig_tool_prefix = paths.FromSrcRootRelative(orig_tool_prefix)
150 if os.path.exists(orig_tool_prefix + 'objdump'): 134 if os.path.exists(orig_tool_prefix + 'objdump'):
151 tool_prefix = orig_tool_prefix 135 tool_prefix = orig_tool_prefix
152 136
153 # TODO(agrieve): Would be even better to use objdump --info to check that 137 # TODO(agrieve): Would be even better to use objdump --info to check that
154 # the toolchain is for the correct architecture. 138 # the toolchain is for the correct architecture.
155 assert tool_prefix is not None, ( 139 assert tool_prefix is not None, (
156 'Could not determine --tool-prefix. Possible fixes include setting ' 140 'Could not determine --tool-prefix. Possible fixes include setting '
157 '--tool-prefix, or setting --output-directory') 141 '--tool-prefix, or setting --output-directory')
158 142
159 if elf_path is None: 143 def build_id_matches(elf_path):
160 filename = size_info.metadata.get(models.METADATA_ELF_FILENAME) 144 found_build_id = archive.BuildIdFromElf(elf_path, tool_prefix)
161 output_dir = self._lazy_paths.output_directory 145 expected_build_id = size_info.metadata.get(models.METADATA_ELF_BUILD_ID)
162 size_path = self._size_paths[self._size_infos.index(size_info)] 146 return found_build_id == expected_build_id
163 if output_dir: 147
164 # Local build: File is located in output directory. 148 filename = size_info.metadata.get(models.METADATA_ELF_FILENAME)
165 path = os.path.normpath(os.path.join(output_dir, filename)) 149 paths_to_try = []
166 if not output_dir or not os.path.exists(path): 150 if elf_path:
151 paths_to_try.append(elf_path)
152 else:
153 auto_lazy_paths = [
154 paths.LazyPaths(any_path_within_output_directory=s.size_path)
155 for s in self._size_infos]
156 for lazy_paths in auto_lazy_paths + [self._lazy_paths]:
157 output_dir = lazy_paths.output_directory
158 if output_dir:
159 # Local build: File is located in output directory.
160 paths_to_try.append(
161 os.path.normpath(os.path.join(output_dir, filename)))
167 # Downloaded build: File is located beside .size file. 162 # Downloaded build: File is located beside .size file.
168 path = os.path.normpath(os.path.join( 163 paths_to_try.append(os.path.normpath(os.path.join(
169 os.path.dirname(size_path), os.path.basename(filename))) 164 os.path.dirname(size_info.size_path), os.path.basename(filename))))
170 165
171 assert os.path.exists(path), ( 166 paths_to_try = [p for p in paths_to_try if os.path.exists(p)]
172 'Could locate ELF file. If binary was built locally, ensure '
173 '--output-directory is set. If output directory is unavailable, '
174 'ensure {} is located beside {}, or pass its path explicitly using '
175 'elf_path=').format(os.path.basename(filename), size_path)
176 167
177 found_build_id = archive.BuildIdFromElf(path, tool_prefix) 168 for i, elf_path in enumerate(paths_to_try):
178 expected_build_id = size_info.metadata.get(models.METADATA_ELF_BUILD_ID) 169 if build_id_matches(elf_path):
179 assert found_build_id == expected_build_id, ( 170 return elf_path, tool_prefix
180 'Build ID does not match for %s' % path) 171
181 return path, tool_prefix 172 # Show an error only once all paths are tried.
173 if i + 1 == len(paths_to_try):
174 assert False, 'Build ID does not match for %s' % elf_path
175
176 assert False, (
177 'Could not locate ELF file. If binary was built locally, ensure '
178 '--output-directory is set. If output directory is unavailable, '
179 'ensure {} is located beside {}, or pass its path explicitly using '
180 'elf_path=').format(os.path.basename(filename), size_info.size_path)
182 181
183 def _DetectDisassemblePrefixLen(self, args): 182 def _DetectDisassemblePrefixLen(self, args):
184 # Look for a line that looks like: 183 # Look for a line that looks like:
185 # /usr/{snip}/src/out/Release/../../net/quic/core/quic_time.h:100 184 # /usr/{snip}/src/out/Release/../../net/quic/core/quic_time.h:100
186 output = subprocess.check_output(args) 185 output = subprocess.check_output(args)
187 for line in output.splitlines(): 186 for line in output.splitlines():
188 if line and line[0] == os.path.sep and line[-1].isdigit(): 187 if line and line[0] == os.path.sep and line[-1].isdigit():
189 release_idx = line.find('Release') 188 release_idx = line.find('Release')
190 if release_idx == -1: 189 if release_idx != -1:
191 break 190 return line.count(os.path.sep, 0, release_idx)
192 return line.count(os.path.sep, 0, release_idx) 191 dot_dot_idx = line.find('..')
192 if dot_dot_idx != -1:
193 return line.count(os.path.sep, 0, dot_dot_idx) - 1
194 out_idx = line.find(os.path.sep + 'out')
195 if out_idx != -1:
196 return line.count(os.path.sep, 0, out_idx) + 2
197 logging.warning('Could not guess source path from found path.')
198 return None
193 logging.warning('Found no source paths in objdump output.') 199 logging.warning('Found no source paths in objdump output.')
194 return None 200 return None
195 201
196 def _DisassembleFunc(self, symbol, elf_path=None, use_pager=None, 202 def _DisassembleFunc(self, symbol, elf_path=None, use_pager=None,
197 to_file=None): 203 to_file=None):
198 """Shows objdump disassembly for the given symbol. 204 """Shows objdump disassembly for the given symbol.
199 205
200 Args: 206 Args:
201 symbol: Must be a .text symbol and not a SymbolGroup. 207 symbol: Must be a .text symbol and not a SymbolGroup.
202 elf_path: Path to the executable containing the symbol. Required only 208 elf_path: Path to the executable containing the symbol. Required only
203 when auto-detection fails. 209 when auto-detection fails.
204 """ 210 """
205 assert not symbol.IsGroup() 211 assert not symbol.IsGroup()
206 assert symbol.address and symbol.section_name == '.text' 212 assert symbol.address and symbol.section_name == '.text'
213 assert not symbol.IsDelta(), ('Cannot disasseble a Diff\'ed symbol. Try '
214 'passing .before_symbol or .after_symbol.')
215 size_info = None
216 for size_info in self._size_infos:
217 if symbol in size_info.raw_symbols:
218 break
219 else:
220 assert False, 'Symbol does not belong to a size_info.'
207 221
208 tool_prefix = self._lazy_paths.tool_prefix 222 elf_path, tool_prefix = self._ElfPathAndToolPrefixForSymbol(
209 if not elf_path: 223 size_info, elf_path)
210 elf_path, tool_prefix = self._ElfPathAndToolPrefixForSymbol(
211 symbol, elf_path, tool_prefix)
212 224
213 args = [tool_prefix + 'objdump', '--disassemble', '--source', 225 args = [tool_prefix + 'objdump', '--disassemble', '--source',
214 '--line-numbers', '--demangle', 226 '--line-numbers', '--demangle',
215 '--start-address=0x%x' % symbol.address, 227 '--start-address=0x%x' % symbol.address,
216 '--stop-address=0x%x' % symbol.end_address, elf_path] 228 '--stop-address=0x%x' % symbol.end_address, elf_path]
217 if self._disassemble_prefix_len is None: 229 if self._disassemble_prefix_len is None:
218 prefix_len = self._DetectDisassemblePrefixLen(args) 230 prefix_len = self._DetectDisassemblePrefixLen(args)
219 if prefix_len is not None: 231 if prefix_len is not None:
220 self._disassemble_prefix_len = prefix_len 232 self._disassemble_prefix_len = prefix_len
221 233
(...skipping 49 matching lines...) Expand 10 before | Expand all | Expand 10 after
271 '# View per-component breakdowns, then drill into the last entry.', 283 '# View per-component breakdowns, then drill into the last entry.',
272 'c = canned_queries.CategorizeByChromeComponent()', 284 'c = canned_queries.CategorizeByChromeComponent()',
273 'Print(c)', 285 'Print(c)',
274 'Print(c[-1].GroupedByPath(depth=2).Sorted())', 286 'Print(c[-1].GroupedByPath(depth=2).Sorted())',
275 '', 287 '',
276 '# For even more inspiration, look at canned_queries.py', 288 '# For even more inspiration, look at canned_queries.py',
277 '# (and feel free to add your own!).', 289 '# (and feel free to add your own!).',
278 ]) 290 ])
279 291
280 def _CreateBanner(self): 292 def _CreateBanner(self):
281 symbol_info_keys = sorted(m for m in dir(models.SizeInfo) if m[0] != '_') 293 def keys(cls, super_keys=None):
282 symbol_keys = sorted(m for m in dir(models.Symbol) if m[0] != '_') 294 ret = sorted(m for m in dir(cls) if m[0] != '_')
283 symbol_group_keys = [m for m in dir(models.SymbolGroup) if m[0] != '_'] 295 if super_keys:
284 symbol_diff_keys = sorted(m for m in dir(models.SymbolDiff) 296 ret = sorted(m for m in ret if m not in super_keys)
285 if m[0] != '_' and m not in symbol_group_keys) 297 return ret
286 symbol_group_keys = sorted(m for m in symbol_group_keys 298
287 if m not in symbol_keys) 299 symbol_info_keys = keys(models.SizeInfo)
288 canned_queries_keys = sorted(m for m in dir(canned_queries.CannedQueries) 300 symbol_keys = keys(models.Symbol)
289 if m[0] != '_') 301 symbol_group_keys = keys(models.SymbolGroup, symbol_keys)
302 delta_size_info_keys = keys(models.DeltaSizeInfo)
303 delta_symbol_keys = keys(models.DeltaSymbol, symbol_keys)
304 delta_symbol_group_keys = keys(models.DeltaSymbolGroup,
305 symbol_keys + symbol_group_keys)
306 canned_queries_keys = keys(canned_queries.CannedQueries)
307
290 functions = sorted(k for k in self._variables if k[0].isupper()) 308 functions = sorted(k for k in self._variables if k[0].isupper())
291 variables = sorted(k for k in self._variables if k[0].islower()) 309 lines = [
292 return '\n'.join([
293 '*' * 80, 310 '*' * 80,
294 'Entering interactive Python shell. Quick reference:', 311 'Entering interactive Python shell. Quick reference:',
295 '', 312 '',
296 'SizeInfo: %s' % ', '.join(symbol_info_keys), 313 'SizeInfo: %s' % ', '.join(symbol_info_keys),
297 'Symbol: %s' % ', '.join(symbol_keys), 314 'Symbol: %s' % ', '.join(symbol_keys),
298 '', 315 '',
299 'SymbolGroup (extends Symbol): %s' % ', '.join(symbol_group_keys), 316 'SymbolGroup (extends Symbol): %s' % ', '.join(symbol_group_keys),
300 '', 317 '',
301 'SymbolDiff (extends SymbolGroup): %s' % ', '.join(symbol_diff_keys), 318 'DeltaSizeInfo: %s' % ', '.join(delta_size_info_keys),
319 'DeltaSymbol (extends Symbol): %s' % ', '.join(delta_symbol_keys),
320 'DeltaSymbolGroup (extends SymbolGroup): %s' % ', '.join(
321 delta_symbol_group_keys),
302 '', 322 '',
303 'canned_queries: %s' % ', '.join(canned_queries_keys), 323 'canned_queries: %s' % ', '.join(canned_queries_keys),
304 '', 324 '',
305 'Functions: %s' % ', '.join('%s()' % f for f in functions), 325 'Functions: %s' % ', '.join('%s()' % f for f in functions),
306 'Variables: %s' % ', '.join(variables), 326 'Variables: ',
307 '*' * 80, 327 ' printed: List of objects passed to Print().',
308 ]) 328 ]
329 for key, value in self._variables.iteritems():
330 if key.startswith('size_info'):
331 lines.append(' {}: Loaded from {}'.format(key, value.size_path))
332 lines.append('*' * 80)
333 return '\n'.join(lines)
334
309 335
310 @classmethod 336 @classmethod
311 def _InitReadline(cls): 337 def _InitReadline(cls):
312 if cls._readline_initialized: 338 if cls._readline_initialized:
313 return 339 return
314 cls._readline_initialized = True 340 cls._readline_initialized = True
315 # Without initializing readline, arrow keys don't even work! 341 # Without initializing readline, arrow keys don't even work!
316 readline.parse_and_bind('tab: complete') 342 readline.parse_and_bind('tab: complete')
317 history_file = os.path.join(os.path.expanduser('~'), 343 history_file = os.path.join(os.path.expanduser('~'),
318 '.binary_size_query_history') 344 '.binary_size_query_history')
(...skipping 28 matching lines...) Expand all
347 373
348 def Run(args, parser): 374 def Run(args, parser):
349 for path in args.inputs: 375 for path in args.inputs:
350 if not path.endswith('.size'): 376 if not path.endswith('.size'):
351 parser.error('All inputs must end with ".size"') 377 parser.error('All inputs must end with ".size"')
352 378
353 size_infos = [archive.LoadAndPostProcessSizeInfo(p) for p in args.inputs] 379 size_infos = [archive.LoadAndPostProcessSizeInfo(p) for p in args.inputs]
354 lazy_paths = paths.LazyPaths(tool_prefix=args.tool_prefix, 380 lazy_paths = paths.LazyPaths(tool_prefix=args.tool_prefix,
355 output_directory=args.output_directory, 381 output_directory=args.output_directory,
356 any_path_within_output_directory=args.inputs[0]) 382 any_path_within_output_directory=args.inputs[0])
357 session = _Session(size_infos, args.inputs, lazy_paths) 383 session = _Session(size_infos, lazy_paths)
358 384
359 if args.query: 385 if args.query:
360 logging.info('Running query from command-line.') 386 logging.info('Running query from command-line.')
361 session.Eval(args.query) 387 session.Eval(args.query)
362 else: 388 else:
363 logging.info('Entering interactive console.') 389 logging.info('Entering interactive console.')
364 session.GoInteractive() 390 session.GoInteractive()
OLDNEW
« no previous file with comments | « no previous file | tools/binary_size/libsupersize/describe.py » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698