| OLD | NEW |
| (Empty) |
| 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 | |
| 3 # found in the LICENSE file. | |
| 4 """Methods for converting model objects to human-readable formats.""" | |
| 5 | |
| 6 import datetime | |
| 7 import itertools | |
| 8 import time | |
| 9 | |
| 10 import models | |
| 11 | |
| 12 | |
| 13 def _PrettySize(size): | |
| 14 # Arbitrarily chosen cut-off. | |
| 15 if abs(size) < 2000: | |
| 16 return '%d bytes' % size | |
| 17 # Always show 3 digits. | |
| 18 size /= 1024.0 | |
| 19 if abs(size) < 10: | |
| 20 return '%.2fkb' % size | |
| 21 elif abs(size) < 100: | |
| 22 return '%.1fkb' % size | |
| 23 elif abs(size) < 1024: | |
| 24 return '%dkb' % size | |
| 25 size /= 1024.0 | |
| 26 if abs(size) < 10: | |
| 27 return '%.2fmb' % size | |
| 28 # We shouldn't be seeing sizes > 100mb. | |
| 29 return '%.1fmb' % size | |
| 30 | |
| 31 | |
| 32 def _DiffPrefix(diff, sym): | |
| 33 if diff.IsAdded(sym): | |
| 34 return '+ ' | |
| 35 if diff.IsRemoved(sym): | |
| 36 return '- ' | |
| 37 if sym.size: | |
| 38 return '~ ' | |
| 39 return '= ' | |
| 40 | |
| 41 | |
| 42 class Describer(object): | |
| 43 def __init__(self, verbose=False, recursive=False): | |
| 44 self.verbose = verbose | |
| 45 self.recursive = recursive | |
| 46 | |
| 47 def _DescribeSectionSizes(self, section_sizes): | |
| 48 relevant_names = models.SECTION_TO_SECTION_NAME.values() | |
| 49 section_names = sorted(k for k in section_sizes.iterkeys() | |
| 50 if k in relevant_names or k.startswith('.data')) | |
| 51 total_bytes = sum(v for k, v in section_sizes.iteritems() | |
| 52 if k in section_names and k != '.bss') | |
| 53 yield '' | |
| 54 yield 'Section Sizes (Total={:,} bytes):'.format(total_bytes) | |
| 55 for name in section_names: | |
| 56 size = section_sizes[name] | |
| 57 if name == '.bss': | |
| 58 yield ' {}: {:,} bytes (not included in totals)'.format(name, size) | |
| 59 else: | |
| 60 percent = float(size) / total_bytes if total_bytes else 0 | |
| 61 yield ' {}: {:,} bytes ({:.1%})'.format(name, size, percent) | |
| 62 | |
| 63 if self.verbose: | |
| 64 yield '' | |
| 65 yield 'Other section sizes:' | |
| 66 section_names = sorted(k for k in section_sizes.iterkeys() | |
| 67 if k not in section_names) | |
| 68 for name in section_names: | |
| 69 yield ' {}: {:,} bytes'.format(name, section_sizes[name]) | |
| 70 | |
| 71 def _DescribeSymbol(self, sym): | |
| 72 if sym.IsGroup(): | |
| 73 address = 'Group' | |
| 74 else: | |
| 75 address = hex(sym.address) | |
| 76 if self.verbose: | |
| 77 count_part = ' count=%d' % len(sym) if sym.IsGroup() else '' | |
| 78 yield '{}@{:<9s} size={} padding={} size_without_padding={}{}'.format( | |
| 79 sym.section, address, sym.size, sym.padding, sym.size_without_padding, | |
| 80 count_part) | |
| 81 yield ' source_path={} \tobject_path={}'.format( | |
| 82 sym.source_path, sym.object_path) | |
| 83 if sym.name: | |
| 84 yield ' is_anonymous={} name={}'.format( | |
| 85 int(sym.is_anonymous), sym.name) | |
| 86 if sym.full_name: | |
| 87 yield ' full_name={}'.format(sym.full_name) | |
| 88 elif sym.full_name: | |
| 89 yield ' is_anonymous={} full_name={}'.format( | |
| 90 int(sym.is_anonymous), sym.full_name) | |
| 91 else: | |
| 92 yield '{}@{:<9s} {:<7} {}'.format( | |
| 93 sym.section, address, sym.size, | |
| 94 sym.source_path or sym.object_path or '{no path}') | |
| 95 if sym.name: | |
| 96 count_part = ' (count=%d)' % len(sym) if sym.IsGroup() else '' | |
| 97 yield ' {}{}'.format(sym.name, count_part) | |
| 98 | |
| 99 def _DescribeSymbolGroupChildren(self, group, indent=0): | |
| 100 running_total = 0 | |
| 101 sorted_syms = group if group.is_sorted else group.Sorted() | |
| 102 is_diff = isinstance(group, models.SymbolDiff) | |
| 103 | |
| 104 indent_prefix = '> ' * indent | |
| 105 diff_prefix = '' | |
| 106 for s in sorted_syms: | |
| 107 if group.IsBss() or not s.IsBss(): | |
| 108 running_total += s.size | |
| 109 for l in self._DescribeSymbol(s): | |
| 110 if l[:4].isspace(): | |
| 111 indent_size = 8 + len(indent_prefix) + len(diff_prefix) | |
| 112 yield '{} {}'.format(' ' * indent_size, l) | |
| 113 else: | |
| 114 if is_diff: | |
| 115 diff_prefix = _DiffPrefix(group, s) | |
| 116 yield '{}{}{:8} {}'.format(indent_prefix, diff_prefix, | |
| 117 running_total, l) | |
| 118 | |
| 119 if self.recursive and s.IsGroup(): | |
| 120 for l in self._DescribeSymbolGroupChildren(s, indent=indent + 1): | |
| 121 yield l | |
| 122 | |
| 123 def _DescribeSymbolGroup(self, group): | |
| 124 total_size = group.size | |
| 125 code_syms = group.WhereInSection('t') | |
| 126 code_size = code_syms.size | |
| 127 ro_size = code_syms.Inverted().WhereInSection('r').size | |
| 128 unique_paths = set(s.object_path for s in group) | |
| 129 header_desc = [ | |
| 130 'Showing {:,} symbols with total size: {} bytes'.format( | |
| 131 len(group), total_size), | |
| 132 '.text={:<10} .rodata={:<10} other={:<10} total={}'.format( | |
| 133 _PrettySize(code_size), _PrettySize(ro_size), | |
| 134 _PrettySize(total_size - code_size - ro_size), | |
| 135 _PrettySize(total_size)), | |
| 136 'Number of object files: {}'.format(len(unique_paths)), | |
| 137 '', | |
| 138 'First columns are: running total, type, size', | |
| 139 ] | |
| 140 children_desc = self._DescribeSymbolGroupChildren(group) | |
| 141 return itertools.chain(header_desc, children_desc) | |
| 142 | |
| 143 def _DescribeSymbolDiff(self, diff): | |
| 144 header_template = ('{} symbols added (+), {} changed (~), {} removed (-), ' | |
| 145 '{} unchanged ({})') | |
| 146 unchanged_msg = '=' if self.verbose else 'not shown' | |
| 147 symbol_delta_desc = [header_template.format( | |
| 148 diff.added_count, diff.changed_count, diff.removed_count, | |
| 149 diff.unchanged_count, unchanged_msg)] | |
| 150 | |
| 151 similar_paths = set() | |
| 152 added_paths = set() | |
| 153 removed_paths = set() | |
| 154 for s in diff: | |
| 155 if diff.IsAdded(s): | |
| 156 added_paths.add(s.object_path) | |
| 157 elif diff.IsRemoved(s): | |
| 158 removed_paths.add(s.object_path) | |
| 159 else: | |
| 160 similar_paths.add(s.object_path) | |
| 161 added_paths.difference_update(similar_paths) | |
| 162 removed_paths.difference_update(similar_paths) | |
| 163 path_delta_desc = ['{} object files added, {} removed'.format( | |
| 164 len(added_paths), len(removed_paths))] | |
| 165 if self.verbose and len(added_paths): | |
| 166 path_delta_desc.append('Added files:') | |
| 167 path_delta_desc.extend(' ' + p for p in sorted(added_paths)) | |
| 168 if self.verbose and len(removed_paths): | |
| 169 path_delta_desc.append('Removed files:') | |
| 170 path_delta_desc.extend(' ' + p for p in sorted(removed_paths)) | |
| 171 | |
| 172 diff = diff if self.verbose else diff.WhereNotUnchanged() | |
| 173 group_desc = self._DescribeSymbolGroup(diff) | |
| 174 return itertools.chain(symbol_delta_desc, path_delta_desc, ('',), | |
| 175 group_desc) | |
| 176 | |
| 177 def _DescribeSizeInfoDiff(self, diff): | |
| 178 common_metadata = {k: v for k, v in diff.old_metadata.iteritems() | |
| 179 if diff.new_metadata[k] == v} | |
| 180 old_metadata = {k: v for k, v in diff.old_metadata.iteritems() | |
| 181 if k not in common_metadata} | |
| 182 new_metadata = {k: v for k, v in diff.new_metadata.iteritems() | |
| 183 if k not in common_metadata} | |
| 184 metadata_desc = itertools.chain( | |
| 185 ('Common Metadata:',), | |
| 186 (' %s' % line for line in DescribeMetadata(common_metadata)), | |
| 187 ('Old Metadata:',), | |
| 188 (' %s' % line for line in DescribeMetadata(old_metadata)), | |
| 189 ('New Metadata:',), | |
| 190 (' %s' % line for line in DescribeMetadata(new_metadata))) | |
| 191 section_desc = self._DescribeSectionSizes(diff.section_sizes) | |
| 192 group_desc = self.GenerateLines(diff.symbols) | |
| 193 return itertools.chain(metadata_desc, section_desc, ('',), group_desc) | |
| 194 | |
| 195 def _DescribeSizeInfo(self, size_info): | |
| 196 metadata_desc = itertools.chain( | |
| 197 ('Metadata:',), | |
| 198 (' %s' % line for line in DescribeMetadata(size_info.metadata))) | |
| 199 section_desc = self._DescribeSectionSizes(size_info.section_sizes) | |
| 200 coverage_desc = () | |
| 201 if self.verbose: | |
| 202 coverage_desc = itertools.chain( | |
| 203 ('',), DescribeSizeInfoCoverage(size_info)) | |
| 204 group_desc = self.GenerateLines(size_info.symbols) | |
| 205 return itertools.chain(metadata_desc, section_desc, coverage_desc, ('',), | |
| 206 group_desc) | |
| 207 | |
| 208 def GenerateLines(self, obj): | |
| 209 if isinstance(obj, models.SizeInfoDiff): | |
| 210 return self._DescribeSizeInfoDiff(obj) | |
| 211 if isinstance(obj, models.SizeInfo): | |
| 212 return self._DescribeSizeInfo(obj) | |
| 213 if isinstance(obj, models.SymbolDiff): | |
| 214 return self._DescribeSymbolDiff(obj) | |
| 215 if isinstance(obj, models.SymbolGroup): | |
| 216 return self._DescribeSymbolGroup(obj) | |
| 217 if isinstance(obj, models.Symbol): | |
| 218 return self._DescribeSymbol(obj) | |
| 219 return (repr(obj),) | |
| 220 | |
| 221 | |
| 222 def DescribeSizeInfoCoverage(size_info): | |
| 223 """Yields lines describing how accurate |size_info| is.""" | |
| 224 symbols = models.SymbolGroup(size_info.raw_symbols) | |
| 225 for section in models.SECTION_TO_SECTION_NAME: | |
| 226 if section == 'd': | |
| 227 expected_size = sum(v for k, v in size_info.section_sizes.iteritems() | |
| 228 if k.startswith('.data')) | |
| 229 else: | |
| 230 expected_size = size_info.section_sizes[ | |
| 231 models.SECTION_TO_SECTION_NAME[section]] | |
| 232 | |
| 233 def one_stat(group): | |
| 234 template = ('Section %s has %.1f%% of %d bytes accounted for from ' | |
| 235 '%d symbols. %d bytes are unaccounted for. Padding ' | |
| 236 'accounts for %d bytes') | |
| 237 actual_size = group.size | |
| 238 count = len(group) | |
| 239 padding = group.padding | |
| 240 size_percent = 100.0 * actual_size / expected_size | |
| 241 return (template % (section, size_percent, actual_size, count, | |
| 242 expected_size - actual_size, padding)) | |
| 243 | |
| 244 in_section = symbols.WhereInSection(section) | |
| 245 yield one_stat(in_section) | |
| 246 | |
| 247 star_syms = in_section.WhereNameMatches(r'^\*') | |
| 248 attributed_syms = star_syms.Inverted().WhereHasAnyAttribution() | |
| 249 anonymous_syms = attributed_syms.Inverted() | |
| 250 if star_syms or anonymous_syms: | |
| 251 missing_size = star_syms.size + anonymous_syms.size | |
| 252 yield ('+ Without %d merge sections and %d anonymous entries (' | |
| 253 'accounting for %d bytes):') % ( | |
| 254 len(star_syms), len(anonymous_syms), missing_size) | |
| 255 yield '+ ' + one_stat(attributed_syms) | |
| 256 | |
| 257 | |
| 258 def _UtcToLocal(utc): | |
| 259 epoch = time.mktime(utc.timetuple()) | |
| 260 offset = (datetime.datetime.fromtimestamp(epoch) - | |
| 261 datetime.datetime.utcfromtimestamp(epoch)) | |
| 262 return utc + offset | |
| 263 | |
| 264 | |
| 265 def DescribeMetadata(metadata): | |
| 266 display_dict = metadata.copy() | |
| 267 timestamp = display_dict.get(models.METADATA_ELF_MTIME) | |
| 268 if timestamp: | |
| 269 timestamp_obj = datetime.datetime.utcfromtimestamp(timestamp) | |
| 270 display_dict[models.METADATA_ELF_MTIME] = ( | |
| 271 _UtcToLocal(timestamp_obj).strftime('%Y-%m-%d %H:%M:%S')) | |
| 272 gn_args = display_dict.get(models.METADATA_GN_ARGS) | |
| 273 if gn_args: | |
| 274 display_dict[models.METADATA_GN_ARGS] = '; '.join(gn_args) | |
| 275 return sorted('%s=%s' % t for t in display_dict.iteritems()) | |
| 276 | |
| 277 | |
| 278 def GenerateLines(obj, verbose=False, recursive=False): | |
| 279 """Returns an iterable of lines (without \n) that describes |obj|.""" | |
| 280 return Describer(verbose=verbose, recursive=recursive).GenerateLines(obj) | |
| 281 | |
| 282 | |
| 283 def WriteLines(lines, func): | |
| 284 for l in lines: | |
| 285 func(l) | |
| 286 func('\n') | |
| OLD | NEW |