| OLD | NEW |
| 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 |
| 11 import itertools | 11 import itertools |
| 12 import logging | 12 import logging |
| 13 import os | 13 import os |
| 14 import readline | 14 import readline |
| 15 import subprocess | 15 import subprocess |
| 16 import sys | 16 import sys |
| 17 | 17 |
| 18 import archive | 18 import archive |
| 19 import canned_queries |
| 19 import describe | 20 import describe |
| 20 import diff | 21 import diff |
| 21 import file_format | 22 import file_format |
| 22 import match_util | 23 import match_util |
| 23 import models | 24 import models |
| 24 import paths | 25 import paths |
| 25 | 26 |
| 26 | 27 |
| 27 # Number of lines before using less for Print(). | 28 # Number of lines before using less for Print(). |
| 28 _THRESHOLD_FOR_PAGER = 30 | 29 _THRESHOLD_FOR_PAGER = 50 |
| 29 | 30 |
| 30 | 31 |
| 31 @contextlib.contextmanager | 32 @contextlib.contextmanager |
| 32 def _LessPipe(): | 33 def _LessPipe(): |
| 33 """Output to `less`. Yields a file object to write to.""" | 34 """Output to `less`. Yields a file object to write to.""" |
| 34 try: | 35 try: |
| 35 proc = subprocess.Popen(['less'], stdin=subprocess.PIPE, stdout=sys.stdout) | 36 proc = subprocess.Popen(['less'], stdin=subprocess.PIPE, stdout=sys.stdout) |
| 36 yield proc.stdin | 37 yield proc.stdin |
| 37 proc.stdin.close() | 38 proc.stdin.close() |
| 38 proc.wait() | 39 proc.wait() |
| (...skipping 22 matching lines...) Expand all Loading... |
| 61 else: | 62 else: |
| 62 describe.WriteLines(lines, sys.stdout.write) | 63 describe.WriteLines(lines, sys.stdout.write) |
| 63 | 64 |
| 64 | 65 |
| 65 class _Session(object): | 66 class _Session(object): |
| 66 _readline_initialized = False | 67 _readline_initialized = False |
| 67 | 68 |
| 68 def __init__(self, size_infos, lazy_paths): | 69 def __init__(self, size_infos, lazy_paths): |
| 69 self._variables = { | 70 self._variables = { |
| 70 'Print': self._PrintFunc, | 71 'Print': self._PrintFunc, |
| 71 'Diff': diff.Diff, | 72 'Diff': self._DiffFunc, |
| 72 'Disassemble': self._DisassembleFunc, | 73 'Disassemble': self._DisassembleFunc, |
| 73 'ExpandRegex': match_util.ExpandRegexIdentifierPlaceholder, | 74 'ExpandRegex': match_util.ExpandRegexIdentifierPlaceholder, |
| 74 'ShowExamples': self._ShowExamplesFunc, | 75 'ShowExamples': self._ShowExamplesFunc, |
| 76 'canned_queries': canned_queries.CannedQueries(size_infos), |
| 75 } | 77 } |
| 76 self._lazy_paths = lazy_paths | 78 self._lazy_paths = lazy_paths |
| 77 self._size_infos = size_infos | 79 self._size_infos = size_infos |
| 78 | 80 |
| 79 if len(size_infos) == 1: | 81 if len(size_infos) == 1: |
| 80 self._variables['size_info'] = size_infos[0] | 82 self._variables['size_info'] = size_infos[0] |
| 81 else: | 83 else: |
| 82 for i, size_info in enumerate(size_infos): | 84 for i, size_info in enumerate(size_infos): |
| 83 self._variables['size_info%d' % (i + 1)] = size_info | 85 self._variables['size_info%d' % (i + 1)] = size_info |
| 84 | 86 |
| 85 def _PrintFunc(self, obj, verbose=False, recursive=False, use_pager=None, | 87 def _DiffFunc(self, before=None, after=None, cluster=True): |
| 88 """Diffs two SizeInfo objects. Returns a SizeInfoDiff. |
| 89 |
| 90 Args: |
| 91 before: Defaults to first size_infos[0]. |
| 92 after: Defaults to second size_infos[1]. |
| 93 cluster: When True, calls SymbolGroup.Cluster() after diffing. This |
| 94 generally reduces noise. |
| 95 """ |
| 96 before = before if before is not None else self._size_infos[0] |
| 97 after = after if after is not None else self._size_infos[1] |
| 98 return diff.Diff(before, after, cluster=cluster) |
| 99 |
| 100 def _PrintFunc(self, obj=None, verbose=False, recursive=False, use_pager=None, |
| 86 to_file=None): | 101 to_file=None): |
| 87 """Prints out the given Symbol / SymbolGroup / SymbolDiff / SizeInfo. | 102 """Prints out the given Symbol / SymbolGroup / SymbolDiff / SizeInfo. |
| 88 | 103 |
| 89 Args: | 104 Args: |
| 90 obj: The object to be printed. | 105 obj: The object to be printed. Defaults to size_infos[-1]. |
| 91 verbose: Show more detailed output. | 106 verbose: Show more detailed output. |
| 92 recursive: Print children of nested SymbolGroups. | 107 recursive: Print children of nested SymbolGroups. |
| 93 use_pager: Pipe output through `less`. Ignored when |obj| is a Symbol. | 108 use_pager: Pipe output through `less`. Ignored when |obj| is a Symbol. |
| 94 default is to automatically pipe when output is long. | 109 default is to automatically pipe when output is long. |
| 95 to_file: Rather than print to stdio, write to the given file. | 110 to_file: Rather than print to stdio, write to the given file. |
| 96 """ | 111 """ |
| 112 obj = obj if obj is not None else self._size_infos[-1] |
| 97 lines = describe.GenerateLines(obj, verbose=verbose, recursive=recursive) | 113 lines = describe.GenerateLines(obj, verbose=verbose, recursive=recursive) |
| 98 _WriteToStream(lines, use_pager=use_pager, to_file=to_file) | 114 _WriteToStream(lines, use_pager=use_pager, to_file=to_file) |
| 99 | 115 |
| 100 def _ElfPathForSymbol(self, symbol): | 116 def _ElfPathForSymbol(self, symbol): |
| 101 size_info = None | 117 size_info = None |
| 102 for size_info in self._size_infos: | 118 for size_info in self._size_infos: |
| 103 if symbol in size_info.symbols: | 119 if symbol in size_info.symbols: |
| 104 break | 120 break |
| 105 else: | 121 else: |
| 106 assert False, 'Symbol does not belong to a size_info.' | 122 assert False, 'Symbol does not belong to a size_info.' |
| (...skipping 26 matching lines...) Expand all Loading... |
| 133 '--start-address=0x%x' % symbol.address, | 149 '--start-address=0x%x' % symbol.address, |
| 134 '--stop-address=0x%x' % symbol.end_address, elf_path] | 150 '--stop-address=0x%x' % symbol.end_address, elf_path] |
| 135 proc = subprocess.Popen(args, stdout=subprocess.PIPE) | 151 proc = subprocess.Popen(args, stdout=subprocess.PIPE) |
| 136 lines = itertools.chain(('Showing disassembly for %r' % symbol, | 152 lines = itertools.chain(('Showing disassembly for %r' % symbol, |
| 137 'Command: %s' % ' '.join(args)), | 153 'Command: %s' % ' '.join(args)), |
| 138 (l.rstrip() for l in proc.stdout)) | 154 (l.rstrip() for l in proc.stdout)) |
| 139 _WriteToStream(lines, use_pager=use_pager, to_file=to_file) | 155 _WriteToStream(lines, use_pager=use_pager, to_file=to_file) |
| 140 proc.kill() | 156 proc.kill() |
| 141 | 157 |
| 142 def _ShowExamplesFunc(self): | 158 def _ShowExamplesFunc(self): |
| 159 print self._CreateBanner() |
| 143 print '\n'.join([ | 160 print '\n'.join([ |
| 144 '# Show pydoc for main types:', | 161 '# Show pydoc for main types:', |
| 145 'import models', | 162 'import models', |
| 146 'help(models)', | 163 'help(models)', |
| 147 '', | 164 '', |
| 148 '# Show all attributes of all symbols & per-section totals:', | 165 '# Show all attributes of all symbols & per-section totals:', |
| 149 'Print(size_info, verbose=True)', | 166 'Print(size_info, verbose=True)', |
| 150 '', | 167 '', |
| 151 '# Show two levels of .text, grouped by first two subdirectories', | 168 '# Show two levels of .text, grouped by first two subdirectories', |
| 152 'text_syms = size_info.symbols.WhereInSection("t")', | 169 'text_syms = size_info.symbols.WhereInSection("t")', |
| 153 'by_path = text_syms.GroupBySourcePath(depth=2)', | 170 'by_path = text_syms.GroupByPath(depth=2)', |
| 154 'Print(by_path.WhereBiggerThan(1024))', | 171 'Print(by_path.WhereBiggerThan(1024))', |
| 155 '', | 172 '', |
| 156 '# Show all non-vtable generated symbols', | 173 '# Show all non-vtable generated symbols', |
| 157 'generated_syms = size_info.symbols.WhereGeneratedByToolchain()', | 174 'generated_syms = size_info.symbols.WhereGeneratedByToolchain()', |
| 158 'Print(generated_syms.WhereNameMatches(r"vtable").Inverted().Sorted())', | 175 'Print(generated_syms.WhereNameMatches(r"vtable").Inverted().Sorted())', |
| 159 '', | 176 '', |
| 160 '# Show all symbols that have "print" in their name or path, except', | 177 '# Show all symbols that have "print" in their name or path, except', |
| 161 '# those within components/.', | 178 '# those within components/.', |
| 162 '# Note: Could have also used Inverted(), as above.', | 179 '# Note: Could have also used Inverted(), as above.', |
| 163 '# Note: Use "help(ExpandRegex)" for more about what {{_print_}} does.', | 180 '# Note: Use "help(ExpandRegex)" for more about what {{_print_}} does.', |
| 164 'print_syms = size_info.symbols.WhereMatches(r"{{_print_}}")', | 181 'print_syms = size_info.symbols.WhereMatches(r"{{_print_}}")', |
| 165 'Print(print_syms - print_syms.WherePathMatches(r"^components/"))', | 182 'Print(print_syms - print_syms.WherePathMatches(r"^components/"))', |
| 166 '', | 183 '', |
| 167 '# Diff two .size files and save result to a file:', | 184 '# Diff two .size files and save result to a file:', |
| 168 'Print(Diff(size_info1, size_info2), to_file="output.txt")', | 185 'Print(Diff(size_info1, size_info2), to_file="output.txt")', |
| 169 '', | 186 '', |
| 187 '# View per-component breakdowns, then drill into the last entry.', |
| 188 'c = canned_queries.CategorizeByChromeComponent()', |
| 189 'Print(c)', |
| 190 'Print(c[-1].GroupByPath(depth=2).Sorted())', |
| 191 '', |
| 192 '# For even more inspiration, look at canned_queries.py', |
| 193 '# (and feel free to add your own!).', |
| 170 ]) | 194 ]) |
| 171 | 195 |
| 172 def _CreateBanner(self): | 196 def _CreateBanner(self): |
| 173 symbol_info_keys = sorted(m for m in dir(models.SizeInfo) if m[0] != '_') | 197 symbol_info_keys = sorted(m for m in dir(models.SizeInfo) if m[0] != '_') |
| 174 symbol_keys = sorted(m for m in dir(models.Symbol) if m[0] != '_') | 198 symbol_keys = sorted(m for m in dir(models.Symbol) if m[0] != '_') |
| 175 symbol_group_keys = [m for m in dir(models.SymbolGroup) if m[0] != '_'] | 199 symbol_group_keys = [m for m in dir(models.SymbolGroup) if m[0] != '_'] |
| 176 symbol_diff_keys = sorted(m for m in dir(models.SymbolDiff) | 200 symbol_diff_keys = sorted(m for m in dir(models.SymbolDiff) |
| 177 if m[0] != '_' and m not in symbol_group_keys) | 201 if m[0] != '_' and m not in symbol_group_keys) |
| 178 symbol_group_keys = sorted(m for m in symbol_group_keys | 202 symbol_group_keys = sorted(m for m in symbol_group_keys |
| 179 if m not in symbol_keys) | 203 if m not in symbol_keys) |
| 204 canned_queries_keys = sorted(m for m in dir(canned_queries.CannedQueries) |
| 205 if m[0] != '_') |
| 180 functions = sorted(k for k in self._variables if k[0].isupper()) | 206 functions = sorted(k for k in self._variables if k[0].isupper()) |
| 181 variables = sorted(k for k in self._variables if k[0].islower()) | 207 variables = sorted(k for k in self._variables if k[0].islower()) |
| 182 return '\n'.join([ | 208 return '\n'.join([ |
| 183 '*' * 80, | 209 '*' * 80, |
| 184 'Entering interactive Python shell. Quick reference:', | 210 'Entering interactive Python shell. Quick reference:', |
| 185 '', | 211 '', |
| 186 'SizeInfo: %s' % ', '.join(symbol_info_keys), | 212 'SizeInfo: %s' % ', '.join(symbol_info_keys), |
| 187 'Symbol: %s' % ', '.join(symbol_keys), | 213 'Symbol: %s' % ', '.join(symbol_keys), |
| 214 '', |
| 188 'SymbolGroup (extends Symbol): %s' % ', '.join(symbol_group_keys), | 215 'SymbolGroup (extends Symbol): %s' % ', '.join(symbol_group_keys), |
| 216 '', |
| 189 'SymbolDiff (extends SymbolGroup): %s' % ', '.join(symbol_diff_keys), | 217 'SymbolDiff (extends SymbolGroup): %s' % ', '.join(symbol_diff_keys), |
| 190 '', | 218 '', |
| 219 'canned_queries: %s' % ', '.join(canned_queries_keys), |
| 220 '', |
| 191 'Functions: %s' % ', '.join('%s()' % f for f in functions), | 221 'Functions: %s' % ', '.join('%s()' % f for f in functions), |
| 192 'Variables: %s' % ', '.join(variables), | 222 'Variables: %s' % ', '.join(variables), |
| 193 '*' * 80, | 223 '*' * 80, |
| 194 ]) | 224 ]) |
| 195 | 225 |
| 196 @classmethod | 226 @classmethod |
| 197 def _InitReadline(cls): | 227 def _InitReadline(cls): |
| 198 if cls._readline_initialized: | 228 if cls._readline_initialized: |
| 199 return | 229 return |
| 200 cls._readline_initialized = True | 230 cls._readline_initialized = True |
| (...skipping 40 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 241 output_directory=args.output_directory, | 271 output_directory=args.output_directory, |
| 242 any_path_within_output_directory=args.inputs[0]) | 272 any_path_within_output_directory=args.inputs[0]) |
| 243 session = _Session(size_infos, lazy_paths) | 273 session = _Session(size_infos, lazy_paths) |
| 244 | 274 |
| 245 if args.query: | 275 if args.query: |
| 246 logging.info('Running query from command-line.') | 276 logging.info('Running query from command-line.') |
| 247 session.Eval(args.query) | 277 session.Eval(args.query) |
| 248 else: | 278 else: |
| 249 logging.info('Entering interactive console.') | 279 logging.info('Entering interactive console.') |
| 250 session.GoInteractive() | 280 session.GoInteractive() |
| OLD | NEW |