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

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

Issue 2936033002: Supersize diff rewrite + tweaks (Closed)
Patch Set: Improve Disassemble() 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 | « tools/binary_size/libsupersize/console.py ('k') | tools/binary_size/libsupersize/diff.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 """Methods for converting model objects to human-readable formats.""" 4 """Methods for converting model objects to human-readable formats."""
5 5
6 import datetime 6 import datetime
7 import itertools 7 import itertools
8 import time 8 import time
9 9
10 import models 10 import models
11 11
12 12
13 _DIFF_PREFIX_BY_STATUS = ['= ', '~ ', '+ ', '- ']
14
15
16 def _PrettySize(size): 13 def _PrettySize(size):
17 # Arbitrarily chosen cut-off. 14 # Arbitrarily chosen cut-off.
18 if abs(size) < 2000: 15 if abs(size) < 2000:
19 return '%d bytes' % size 16 return '%d bytes' % size
20 # Always show 3 digits. 17 # Always show 3 digits.
21 size /= 1024.0 18 size /= 1024.0
22 if abs(size) < 10: 19 if abs(size) < 10:
23 return '%.2fkb' % size 20 return '%.2fkb' % size
24 elif abs(size) < 100: 21 elif abs(size) < 100:
25 return '%.1fkb' % size 22 return '%.1fkb' % size
26 elif abs(size) < 1024: 23 elif abs(size) < 1024:
27 return '%dkb' % size 24 return '%dkb' % size
28 size /= 1024.0 25 size /= 1024.0
29 if abs(size) < 10: 26 if abs(size) < 10:
30 return '%.2fmb' % size 27 return '%.2fmb' % size
31 # We shouldn't be seeing sizes > 100mb. 28 # We shouldn't be seeing sizes > 100mb.
32 return '%.1fmb' % size 29 return '%.1fmb' % size
33 30
34 31
35 def _FormatPss(pss): 32 def _FormatPss(pss):
36 # Shows a decimal for small numbers to make it clear that a shared symbol has 33 # Shows a decimal for small numbers to make it clear that a shared symbol has
37 # a non-zero pss. 34 # a non-zero pss.
38 if pss > 10: 35 if abs(pss) > 10:
39 return str(int(pss)) 36 return str(int(pss))
40 ret = str(round(pss, 1)) 37 ret = str(round(pss, 1))
41 if ret.endswith('.0'): 38 if ret.endswith('.0'):
42 ret = ret[:-2] 39 ret = ret[:-2]
43 if ret == '0' and pss: 40 if ret == '0' and pss:
44 ret = '~0' 41 ret = '~0'
45 return ret 42 return ret
46 43
47 44
48 def _Divide(a, b): 45 def _Divide(a, b):
(...skipping 49 matching lines...) Expand 10 before | Expand all | Expand 10 after
98 not_included_part = ' (not included in totals)' 95 not_included_part = ' (not included in totals)'
99 yield ' {}: {} ({} bytes){}'.format( 96 yield ' {}: {} ({} bytes){}'.format(
100 name, _PrettySize(section_sizes[name]), section_sizes[name], 97 name, _PrettySize(section_sizes[name]), section_sizes[name],
101 not_included_part) 98 not_included_part)
102 99
103 def _DescribeSymbol(self, sym, single_line=False): 100 def _DescribeSymbol(self, sym, single_line=False):
104 if sym.IsGroup(): 101 if sym.IsGroup():
105 address = 'Group' 102 address = 'Group'
106 else: 103 else:
107 address = hex(sym.address) 104 address = hex(sym.address)
105 last_field = ''
106 if sym.IsGroup():
107 last_field = 'count=%d' % len(sym)
108 elif sym.IsDelta():
109 if sym.before_symbol is None:
110 num_aliases = sym.after_symbol.num_aliases
111 elif sym.after_symbol is None:
112 num_aliases = sym.before_symbol.num_aliases
113 elif sym.before_symbol.num_aliases == sym.after_symbol.num_aliases:
114 num_aliases = sym.before_symbol.num_aliases
115 else:
116 last_field = 'num_aliases=%d->%d' % (
117 sym.before_symbol.num_aliases, sym.after_symbol.num_aliases)
118 if not last_field and (num_aliases > 1 or self.verbose):
119 last_field = 'num_aliases=%d' % num_aliases
120 elif sym.num_aliases > 1 or self.verbose:
121 last_field = 'num_aliases=%d' % sym.num_aliases
122
123 if sym.IsDelta():
124 if sym.IsGroup():
125 b = sum(s.before_symbol.size_without_padding if s.before_symbol else 0
126 for s in sym)
127 a = sum(s.after_symbol.size_without_padding if s.after_symbol else 0
128 for s in sym)
129 else:
130 b = sym.before_symbol.size_without_padding if sym.before_symbol else 0
131 a = sym.after_symbol.size_without_padding if sym.after_symbol else 0
132 pss_with_sign = _FormatPss(sym.pss)
133 if pss_with_sign[0] not in '~-':
estevenson 2017/06/15 16:10:45 nit: does it make sense for _FormatPss() to always
agrieve 2017/06/15 19:57:04 The idea is to only include the explicit + when sh
134 pss_with_sign = '+' + pss_with_sign
135 pss_field = '{} ({}->{})'.format(pss_with_sign, b, a)
136 elif sym.num_aliases > 1:
137 pss_field = '{} (size={})'.format(_FormatPss(sym.pss), sym.size)
138 else:
139 pss_field = '{}'.format(_FormatPss(sym.pss))
140
108 if self.verbose: 141 if self.verbose:
109 count_part = ' count=%d' % len(sym) if sym.IsGroup() else '' 142 if last_field:
110 yield '{}@{:<9s} pss={} padding={} size_without_padding={}{}'.format( 143 last_field = ' ' + last_field
111 sym.section, address, _FormatPss(sym.pss), sym.padding, 144 if sym.IsDelta():
112 sym.size_without_padding, count_part) 145 yield '{}@{:<9s} {}{}'.format(
146 sym.section, address, pss_field, last_field)
147 else:
148 l = '{}@{:<9s} pss={} padding={}{}'.format(
149 sym.section, address, pss_field, sym.padding, last_field)
150 yield l
113 yield ' source_path={} \tobject_path={}'.format( 151 yield ' source_path={} \tobject_path={}'.format(
114 sym.source_path, sym.object_path) 152 sym.source_path, sym.object_path)
115 if sym.name: 153 if sym.name:
116 yield ' flags={} name={}'.format(sym.FlagsString(), sym.name) 154 yield ' flags={} name={}'.format(sym.FlagsString(), sym.name)
117 if sym.full_name is not sym.name: 155 if sym.full_name is not sym.name:
118 yield ' full_name={}'.format(sym.full_name) 156 yield ' full_name={}'.format(sym.full_name)
119 elif sym.full_name: 157 elif sym.full_name:
120 yield ' flags={} full_name={}'.format( 158 yield ' flags={} full_name={}'.format(
121 sym.FlagsString(), sym.full_name) 159 sym.FlagsString(), sym.full_name)
122 elif single_line:
123 count_part = ' (count=%d)' % len(sym) if sym.IsGroup() else ''
124 yield '{}@{:<9s} {:<7} {}{}'.format(
125 sym.section, address, _FormatPss(sym.pss), sym.name, count_part)
126 else: 160 else:
127 yield '{}@{:<9s} {:<7} {}'.format( 161 if last_field:
128 sym.section, address, _FormatPss(sym.pss), 162 last_field = ' ({})'.format(last_field)
129 sym.source_path or sym.object_path or '{no path}') 163 if sym.IsDelta():
130 if sym.name: 164 pss_field = '{:<18}'.format(pss_field)
131 count_part = ' (count=%d)' % len(sym) if sym.IsGroup() else '' 165 else:
132 yield ' {}{}'.format(sym.name, count_part) 166 pss_field = '{:<14}'.format(pss_field)
167 if single_line:
168 yield '{}@{:<9s} {} {}{}'.format(
169 sym.section, address, pss_field, sym.name, last_field)
170 else:
171 yield '{}@{:<9s} {} {}'.format(
172 sym.section, address, pss_field,
173 sym.source_path or sym.object_path or '{no path}')
174 if sym.name:
175 yield ' {}{}'.format(sym.name, last_field)
133 176
134 def _DescribeSymbolGroupChildren(self, group, indent=0): 177 def _DescribeSymbolGroupChildren(self, group, indent=0):
135 running_total = 0 178 running_total = 0
136 running_percent = 0 179 running_percent = 0
137 is_diff = isinstance(group, models.SymbolDiff) 180 is_delta = group.IsDelta()
138 all_groups = all(s.IsGroup() for s in group) 181 all_groups = all(s.IsGroup() for s in group)
139 182
140 indent_prefix = '> ' * indent 183 indent_prefix = '> ' * indent
141 diff_prefix = '' 184 diff_prefix = ''
142 total = group.pss 185 total = group.pss
143 for index, s in enumerate(group): 186 for index, s in enumerate(group):
144 if group.IsBss() or not s.IsBss(): 187 if group.IsBss() or not s.IsBss():
145 running_total += s.pss 188 running_total += s.pss
146 running_percent = _Divide(running_total, total) 189 running_percent = _Divide(running_total, total)
147 for l in self._DescribeSymbol(s, single_line=all_groups): 190 for l in self._DescribeSymbol(s, single_line=all_groups):
148 if l[:4].isspace(): 191 if l[:4].isspace():
149 indent_size = 8 + len(indent_prefix) + len(diff_prefix) 192 indent_size = 8 + len(indent_prefix) + len(diff_prefix)
150 yield '{} {}'.format(' ' * indent_size, l) 193 yield '{} {}'.format(' ' * indent_size, l)
151 else: 194 else:
152 if is_diff: 195 if is_delta:
153 diff_prefix = _DIFF_PREFIX_BY_STATUS[group.DiffStatus(s)] 196 diff_prefix = models.DIFF_PREFIX_BY_STATUS[s.diff_status]
154 yield '{}{}{:<4} {:>8} {:7} {}'.format( 197 yield '{}{}{:<4} {:>8} {:7} {}'.format(
155 indent_prefix, diff_prefix, str(index) + ')', 198 indent_prefix, diff_prefix, str(index) + ')',
156 _FormatPss(running_total), '({:.1%})'.format(running_percent), l) 199 _FormatPss(running_total), '({:.1%})'.format(running_percent), l)
157 200
158 if self.recursive and s.IsGroup(): 201 if self.recursive and s.IsGroup():
159 for l in self._DescribeSymbolGroupChildren(s, indent=indent + 1): 202 for l in self._DescribeSymbolGroupChildren(s, indent=indent + 1):
160 yield l 203 yield l
161 204
162 def _DescribeSymbolGroup(self, group): 205 def _DescribeSymbolGroup(self, group):
163 total_size = group.pss 206 total_size = group.pss
164 code_size = 0 207 code_size = 0
165 ro_size = 0 208 ro_size = 0
166 data_size = 0 209 data_size = 0
167 bss_size = 0 210 bss_size = 0
168 unique_paths = set() 211 unique_paths = set()
169 for s in group.IterLeafSymbols(): 212 for s in group.IterLeafSymbols():
170 if s.section == 't': 213 if s.section == 't':
171 code_size += s.pss 214 code_size += s.pss
172 elif s.section == 'r': 215 elif s.section == 'r':
173 ro_size += s.pss 216 ro_size += s.pss
174 elif s.section == 'd': 217 elif s.section == 'd':
175 data_size += s.pss 218 data_size += s.pss
176 elif s.section == 'b': 219 elif s.section == 'b':
177 bss_size += s.pss 220 bss_size += s.pss
178 # Ignore paths like foo/{shared}/2 221 # Ignore paths like foo/{shared}/2
179 if '{' not in s.object_path: 222 if '{' not in s.object_path:
180 unique_paths.add(s.object_path) 223 unique_paths.add(s.object_path)
224
225 if group.IsDelta():
226 unique_part = 'aliases not grouped for diffs'
227 else:
228 unique_part = '{:,} unique'.format(group.CountUniqueSymbols())
229
230 if self.verbose:
231 titles = 'Index | Running Total | Section@Address | ...'
232 elif group.IsDelta():
233 titles = (u'Index | Running Total | Section@Address | \u0394 PSS '
234 u'(\u0394 size_without_padding) | Path').encode('utf-8')
235 else:
236 titles = ('Index | Running Total | Section@Address | PSS | Path')
237
181 header_desc = [ 238 header_desc = [
182 'Showing {:,} symbols ({:,} unique) with total pss: {} bytes'.format( 239 'Showing {:,} symbols ({}) with total pss: {} bytes'.format(
183 len(group), group.CountUniqueSymbols(), int(total_size)), 240 len(group), unique_part, int(total_size)),
184 '.text={:<10} .rodata={:<10} .data*={:<10} .bss={:<10} total={}'.format( 241 '.text={:<10} .rodata={:<10} .data*={:<10} .bss={:<10} total={}'.format(
185 _PrettySize(int(code_size)), _PrettySize(int(ro_size)), 242 _PrettySize(int(code_size)), _PrettySize(int(ro_size)),
186 _PrettySize(int(data_size)), _PrettySize(int(bss_size)), 243 _PrettySize(int(data_size)), _PrettySize(int(bss_size)),
187 _PrettySize(int(total_size))), 244 _PrettySize(int(total_size))),
188 'Number of unique paths: {}'.format(len(unique_paths)), 245 'Number of unique paths: {}'.format(len(unique_paths)),
189 '', 246 '',
190 'Index, Running Total, Section@Address, PSS', 247 titles,
191 '-' * 60 248 '-' * 60
192 ] 249 ]
250 # Apply this filter after calcualating stats since an alias being removed
251 # causes a some symbols to be UNCHANGED, yet have pss != 0.
estevenson 2017/06/15 16:10:45 nit: remove 'a'
agrieve 2017/06/15 19:57:04 Done.
252 if group.IsDelta() and not self.verbose:
253 group = group.WhereDiffStatusIs(models.DIFF_STATUS_UNCHANGED).Inverted()
193 children_desc = self._DescribeSymbolGroupChildren(group) 254 children_desc = self._DescribeSymbolGroupChildren(group)
194 return itertools.chain(header_desc, children_desc) 255 return itertools.chain(header_desc, children_desc)
195 256
196 def _DescribeDiffObjectPaths(self, diff): 257 def _DescribeDiffObjectPaths(self, delta_group):
197 paths_by_status = [set(), set(), set(), set()] 258 paths_by_status = [set(), set(), set(), set()]
198 def helper(group): 259 for s in delta_group.IterLeafSymbols():
199 for s in group: 260 path = s.source_path or s.object_path
200 if s.IsGroup(): 261 # Ignore paths like foo/{shared}/2
201 helper(s) 262 if '{' not in path:
202 else: 263 paths_by_status[s.diff_status].add(path)
203 path = s.source_path or s.object_path
204 # Ignore paths like foo/{shared}/2
205 if '{' not in path:
206 paths_by_status[group.DiffStatus(s)].add(path)
207 helper(diff)
208 # Initial paths sets are those where *any* symbol is 264 # Initial paths sets are those where *any* symbol is
209 # unchanged/changed/added/removed. 265 # unchanged/changed/added/removed.
210 unchanged, changed, added, removed = paths_by_status 266 unchanged, changed, added, removed = paths_by_status
211 # Consider a path with both adds & removes as "changed". 267 # Consider a path with both adds & removes as "changed".
212 changed.update(added.intersection(removed)) 268 changed.update(added.intersection(removed))
213 # Consider a path added / removed only when all symbols are new/removed. 269 # Consider a path added / removed only when all symbols are new/removed.
214 added.difference_update(unchanged) 270 added.difference_update(unchanged)
215 added.difference_update(changed) 271 added.difference_update(changed)
216 added.difference_update(removed) 272 added.difference_update(removed)
217 removed.difference_update(unchanged) 273 removed.difference_update(unchanged)
218 removed.difference_update(changed) 274 removed.difference_update(changed)
219 removed.difference_update(added) 275 removed.difference_update(added)
220 yield '{} paths added, {} removed, {} changed'.format( 276 yield '{} paths added, {} removed, {} changed'.format(
221 len(added), len(removed), len(changed)) 277 len(added), len(removed), len(changed))
222 278
223 if self.verbose and len(added): 279 if self.verbose and len(added):
224 yield 'Added files:' 280 yield 'Added files:'
225 for p in sorted(added): 281 for p in sorted(added):
226 yield ' ' + p 282 yield ' ' + p
227 if self.verbose and len(removed): 283 if self.verbose and len(removed):
228 yield 'Removed files:' 284 yield 'Removed files:'
229 for p in sorted(removed): 285 for p in sorted(removed):
230 yield ' ' + p 286 yield ' ' + p
231 if self.verbose and len(changed): 287 if self.verbose and len(changed):
232 yield 'Changed files:' 288 yield 'Changed files:'
233 for p in sorted(changed): 289 for p in sorted(changed):
234 yield ' ' + p 290 yield ' ' + p
235 291
236 def _DescribeSymbolDiff(self, diff): 292 def _DescribeDeltaSymbolGroup(self, delta_group):
237 header_template = ('{} symbols added (+), {} changed (~), {} removed (-), ' 293 header_template = ('{} symbols added (+), {} changed (~), {} removed (-), '
238 '{} unchanged ({})') 294 '{} unchanged ({})')
239 unchanged_msg = '=' if self.verbose else 'not shown' 295 unchanged_msg = '=' if self.verbose else 'not shown'
240 symbol_delta_desc = [header_template.format( 296 counts = delta_group.CountsByDiffStatus()
241 diff.added_count, diff.changed_count, diff.removed_count, 297 unqiue_counts = delta_group.CountUniqueSymbols()
estevenson 2017/06/15 16:10:45 nit: spelling. Might be a little more readable to
agrieve 2017/06/15 19:57:04 Done.
242 diff.unchanged_count, unchanged_msg)] 298 diff_summary_desc = [
243 path_delta_desc = self._DescribeDiffObjectPaths(diff) 299 header_template.format(
300 counts[models.DIFF_STATUS_ADDED],
301 counts[models.DIFF_STATUS_CHANGED],
302 counts[models.DIFF_STATUS_REMOVED],
303 counts[models.DIFF_STATUS_UNCHANGED],
304 unchanged_msg),
305 'Number of unique symbols {} -> {} ({:+})'.format(
306 unqiue_counts[0], unqiue_counts[1],
307 unqiue_counts[1] - unqiue_counts[0]),
308 ]
309 path_delta_desc = self._DescribeDiffObjectPaths(delta_group)
244 310
245 diff = diff if self.verbose else diff.WhereNotUnchanged() 311 group_desc = self._DescribeSymbolGroup(delta_group)
246 group_desc = self._DescribeSymbolGroup(diff) 312 return itertools.chain(diff_summary_desc, path_delta_desc, ('',),
247 return itertools.chain(symbol_delta_desc, path_delta_desc, ('',),
248 group_desc) 313 group_desc)
249 314
250 def _DescribeSizeInfoDiff(self, diff): 315 def _DescribeDeltaSizeInfo(self, diff):
251 common_metadata = {k: v for k, v in diff.before_metadata.iteritems() 316 common_metadata = {k: v for k, v in diff.before_metadata.iteritems()
252 if diff.after_metadata[k] == v} 317 if diff.after_metadata[k] == v}
253 before_metadata = {k: v for k, v in diff.before_metadata.iteritems() 318 before_metadata = {k: v for k, v in diff.before_metadata.iteritems()
254 if k not in common_metadata} 319 if k not in common_metadata}
255 after_metadata = {k: v for k, v in diff.after_metadata.iteritems() 320 after_metadata = {k: v for k, v in diff.after_metadata.iteritems()
256 if k not in common_metadata} 321 if k not in common_metadata}
257 metadata_desc = itertools.chain( 322 metadata_desc = itertools.chain(
258 ('Common Metadata:',), 323 ('Common Metadata:',),
259 (' %s' % line for line in DescribeMetadata(common_metadata)), 324 (' %s' % line for line in DescribeMetadata(common_metadata)),
260 ('Old Metadata:',), 325 ('Old Metadata:',),
(...skipping 11 matching lines...) Expand all
272 section_desc = self._DescribeSectionSizes(size_info.section_sizes) 337 section_desc = self._DescribeSectionSizes(size_info.section_sizes)
273 coverage_desc = () 338 coverage_desc = ()
274 if self.verbose: 339 if self.verbose:
275 coverage_desc = itertools.chain( 340 coverage_desc = itertools.chain(
276 ('',), DescribeSizeInfoCoverage(size_info)) 341 ('',), DescribeSizeInfoCoverage(size_info))
277 group_desc = self.GenerateLines(size_info.symbols) 342 group_desc = self.GenerateLines(size_info.symbols)
278 return itertools.chain(metadata_desc, section_desc, coverage_desc, ('',), 343 return itertools.chain(metadata_desc, section_desc, coverage_desc, ('',),
279 group_desc) 344 group_desc)
280 345
281 def GenerateLines(self, obj): 346 def GenerateLines(self, obj):
282 if isinstance(obj, models.SizeInfoDiff): 347 if isinstance(obj, models.DeltaSizeInfo):
283 return self._DescribeSizeInfoDiff(obj) 348 return self._DescribeDeltaSizeInfo(obj)
284 if isinstance(obj, models.SizeInfo): 349 if isinstance(obj, models.SizeInfo):
285 return self._DescribeSizeInfo(obj) 350 return self._DescribeSizeInfo(obj)
286 if isinstance(obj, models.SymbolDiff): 351 if isinstance(obj, models.DeltaSymbolGroup):
287 return self._DescribeSymbolDiff(obj) 352 return self._DescribeDeltaSymbolGroup(obj)
288 if isinstance(obj, models.SymbolGroup): 353 if isinstance(obj, models.SymbolGroup):
289 return self._DescribeSymbolGroup(obj) 354 return self._DescribeSymbolGroup(obj)
290 if isinstance(obj, models.Symbol): 355 if isinstance(obj, models.Symbol):
291 return self._DescribeSymbol(obj) 356 return self._DescribeSymbol(obj)
292 return (repr(obj),) 357 return (repr(obj),)
293 358
294 359
295 def DescribeSizeInfoCoverage(size_info): 360 def DescribeSizeInfoCoverage(size_info):
296 """Yields lines describing how accurate |size_info| is.""" 361 """Yields lines describing how accurate |size_info| is."""
297 for section in models.SECTION_TO_SECTION_NAME: 362 for section in models.SECTION_TO_SECTION_NAME:
(...skipping 67 matching lines...) Expand 10 before | Expand all | Expand 10 after
365 430
366 def GenerateLines(obj, verbose=False, recursive=False): 431 def GenerateLines(obj, verbose=False, recursive=False):
367 """Returns an iterable of lines (without \n) that describes |obj|.""" 432 """Returns an iterable of lines (without \n) that describes |obj|."""
368 return Describer(verbose=verbose, recursive=recursive).GenerateLines(obj) 433 return Describer(verbose=verbose, recursive=recursive).GenerateLines(obj)
369 434
370 435
371 def WriteLines(lines, func): 436 def WriteLines(lines, func):
372 for l in lines: 437 for l in lines:
373 func(l) 438 func(l)
374 func('\n') 439 func('\n')
OLDNEW
« no previous file with comments | « tools/binary_size/libsupersize/console.py ('k') | tools/binary_size/libsupersize/diff.py » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698