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

Side by Side Diff: tools/binary_size/libsupersize/models.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
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 """Classes that comprise the data model for binary size analysis. 4 """Classes that comprise the data model for binary size analysis.
5 5
6 The primary classes are Symbol, and SymbolGroup. 6 The primary classes are Symbol, and SymbolGroup.
7 7
8 Description of common properties: 8 Description of common properties:
9 * address: The start address of the symbol. 9 * address: The start address of the symbol.
10 May be 0 (e.g. for .bss or for SymbolGroups). 10 May be 0 (e.g. for .bss or for SymbolGroups).
11 * size: The number of bytes this symbol takes up, including padding that comes 11 * size: The number of bytes this symbol takes up, including padding that comes
12 before |address|. 12 before |address|.
13 * num_aliases: The number of symbols with the same address (including self). 13 * num_aliases: The number of symbols with the same address (including self).
14 * pss: size / num_aliases. 14 * pss: size / num_aliases.
15 * padding: The number of bytes of padding before |address| due to this symbol. 15 * padding: The number of bytes of padding before |address| due to this symbol.
16 * padding_pss: padding / num_aliases.
16 * name: Names with templates and parameter list removed. 17 * name: Names with templates and parameter list removed.
17 Never None, but will be '' for anonymous symbols. 18 Never None, but will be '' for anonymous symbols.
18 * template_name: Name with parameter list removed (but templates left in). 19 * template_name: Name with parameter list removed (but templates left in).
19 Never None, but will be '' for anonymous symbols. 20 Never None, but will be '' for anonymous symbols.
20 * full_name: Name with template and parameter list left in. 21 * full_name: Name with template and parameter list left in.
21 Never None, but will be '' for anonymous symbols. 22 Never None, but will be '' for anonymous symbols.
22 * is_anonymous: True when the symbol exists in an anonymous namespace (which 23 * is_anonymous: True when the symbol exists in an anonymous namespace (which
23 are removed from both full_name and name during normalization). 24 are removed from both full_name and name during normalization).
24 * section_name: E.g. ".text", ".rodata", ".data.rel.local" 25 * section_name: E.g. ".text", ".rodata", ".data.rel.local"
25 * section: The second character of |section_name|. E.g. "t", "r", "d". 26 * section: The second character of |section_name|. E.g. "t", "r", "d".
(...skipping 32 matching lines...) Expand 10 before | Expand all | Expand 10 after
58 FLAG_UNLIKELY = 4 59 FLAG_UNLIKELY = 4
59 FLAG_REL = 8 60 FLAG_REL = 8
60 FLAG_REL_LOCAL = 16 61 FLAG_REL_LOCAL = 16
61 FLAG_GENERATED_SOURCE = 32 62 FLAG_GENERATED_SOURCE = 32
62 FLAG_CLONE = 64 63 FLAG_CLONE = 64
63 64
64 DIFF_STATUS_UNCHANGED = 0 65 DIFF_STATUS_UNCHANGED = 0
65 DIFF_STATUS_CHANGED = 1 66 DIFF_STATUS_CHANGED = 1
66 DIFF_STATUS_ADDED = 2 67 DIFF_STATUS_ADDED = 2
67 DIFF_STATUS_REMOVED = 3 68 DIFF_STATUS_REMOVED = 3
69 DIFF_PREFIX_BY_STATUS = ['= ', '~ ', '+ ', '- ']
68 70
69 71
70 class SizeInfo(object): 72 class SizeInfo(object):
71 """Represents all size information for a single binary. 73 """Represents all size information for a single binary.
72 74
73 Fields: 75 Fields:
74 section_sizes: A dict of section_name -> size. 76 section_sizes: A dict of section_name -> size.
75 raw_symbols: A SymbolGroup containing all top-level symbols (no groups). 77 raw_symbols: A SymbolGroup containing all top-level symbols (no groups).
76 symbols: A SymbolGroup where symbols have been grouped by full_name (where 78 symbols: A SymbolGroup where symbols have been grouped by full_name (where
77 applicable). May be re-assigned when it is desirable to show custom 79 applicable). May be re-assigned when it is desirable to show custom
78 groupings while still printing metadata and section_sizes. 80 groupings while still printing metadata and section_sizes.
79 metadata: A dict. 81 metadata: A dict.
82 size_path: Path to .size file this was loaded from (or None).
80 """ 83 """
81 __slots__ = ( 84 __slots__ = (
82 'section_sizes', 85 'section_sizes',
83 'raw_symbols', 86 'raw_symbols',
84 '_symbols', 87 '_symbols',
85 'metadata', 88 'metadata',
89 'size_path',
86 ) 90 )
87 91
88 """Root size information.""" 92 """Root size information."""
89 def __init__(self, section_sizes, raw_symbols, metadata=None, symbols=None): 93 def __init__(self, section_sizes, raw_symbols, metadata=None, symbols=None,
94 size_path=None):
90 if isinstance(raw_symbols, list): 95 if isinstance(raw_symbols, list):
91 raw_symbols = SymbolGroup(raw_symbols) 96 raw_symbols = SymbolGroup(raw_symbols)
92 self.section_sizes = section_sizes # E.g. {'.text': 0} 97 self.section_sizes = section_sizes # E.g. {'.text': 0}
93 self.raw_symbols = raw_symbols 98 self.raw_symbols = raw_symbols
94 self._symbols = symbols 99 self._symbols = symbols
95 self.metadata = metadata or {} 100 self.metadata = metadata or {}
101 self.size_path = size_path
96 102
97 @property 103 @property
98 def symbols(self): 104 def symbols(self):
99 if self._symbols is None: 105 if self._symbols is None:
100 self._symbols = self.raw_symbols._Clustered() 106 self._symbols = self.raw_symbols._Clustered()
101 return self._symbols 107 return self._symbols
102 108
103 @symbols.setter 109 @symbols.setter
104 def symbols(self, value): 110 def symbols(self, value):
105 self._symbols = value 111 self._symbols = value
106 112
107 113
108 class SizeInfoDiff(object): 114 class DeltaSizeInfo(object):
109 """What you get when you Diff() two SizeInfo objects. 115 """What you get when you Diff() two SizeInfo objects.
110 116
111 Fields: 117 Fields:
112 section_sizes: A dict of section_name -> size delta. 118 section_sizes: A dict of section_name -> size delta.
113 raw_symbols: A SymbolDiff with all top-level symbols in it (no groups). 119 raw_symbols: A DeltaSymbolGroup with all top-level symbols in it
114 symbols: A SymbolDiff where symbols have been grouped by full_name (where 120 (no groups).
115 applicable). May be re-assigned when it is desirable to show custom 121 symbols: A DeltaSymbolGroup where symbols have been grouped by full_name
116 groupings while still printing metadata and section_sizes. 122 (where applicable). May be re-assigned when it is desirable to show
123 custom groupings while still printing metadata and section_sizes.
117 before_metadata: metadata of the "before" SizeInfo. 124 before_metadata: metadata of the "before" SizeInfo.
118 after_metadata: metadata of the "after" SizeInfo. 125 after_metadata: metadata of the "after" SizeInfo.
119 """ 126 """
120 __slots__ = ( 127 __slots__ = (
121 'section_sizes', 128 'section_sizes',
122 'raw_symbols', 129 'raw_symbols',
123 '_symbols', 130 '_symbols',
124 'before_metadata', 131 'before_metadata',
125 'after_metadata', 132 'after_metadata',
126 ) 133 )
(...skipping 55 matching lines...) Expand 10 before | Expand all | Expand 10 after
182 else: 189 else:
183 self.flags &= ~FLAG_GENERATED_SOURCE 190 self.flags &= ~FLAG_GENERATED_SOURCE
184 191
185 @property 192 @property
186 def num_aliases(self): 193 def num_aliases(self):
187 return len(self.aliases) if self.aliases else 1 194 return len(self.aliases) if self.aliases else 1
188 195
189 def FlagsString(self): 196 def FlagsString(self):
190 # Most flags are 0. 197 # Most flags are 0.
191 flags = self.flags 198 flags = self.flags
192 if not flags and not self.aliases: 199 if not flags:
193 return '{}' 200 return '{}'
194 parts = [] 201 parts = []
195 if flags & FLAG_ANONYMOUS: 202 if flags & FLAG_ANONYMOUS:
196 parts.append('anon') 203 parts.append('anon')
197 if flags & FLAG_STARTUP: 204 if flags & FLAG_STARTUP:
198 parts.append('startup') 205 parts.append('startup')
199 if flags & FLAG_UNLIKELY: 206 if flags & FLAG_UNLIKELY:
200 parts.append('unlikely') 207 parts.append('unlikely')
201 if flags & FLAG_REL: 208 if flags & FLAG_REL:
202 parts.append('rel') 209 parts.append('rel')
203 if flags & FLAG_REL_LOCAL: 210 if flags & FLAG_REL_LOCAL:
204 parts.append('rel.loc') 211 parts.append('rel.loc')
205 if flags & FLAG_GENERATED_SOURCE: 212 if flags & FLAG_GENERATED_SOURCE:
206 parts.append('gen') 213 parts.append('gen')
207 if flags & FLAG_CLONE: 214 if flags & FLAG_CLONE:
208 parts.append('clone') 215 parts.append('clone')
209 # Not actually a part of flags, but useful to show it here.
210 if self.aliases:
211 parts.append('{} aliases'.format(self.num_aliases))
212 return '{%s}' % ','.join(parts) 216 return '{%s}' % ','.join(parts)
213 217
214 def IsBss(self): 218 def IsBss(self):
215 return self.section_name == '.bss' 219 return self.section_name == '.bss'
216 220
217 def IsGroup(self): 221 def IsGroup(self):
218 return False 222 return False
219 223
224 def IsDelta(self):
225 return False
226
220 def IsGeneratedByToolchain(self): 227 def IsGeneratedByToolchain(self):
221 return '.' in self.name or ( 228 return '.' in self.name or (
222 self.name.endswith(']') and not self.name.endswith('[]')) 229 self.name.endswith(']') and not self.name.endswith('[]'))
223 230
224 231
225 class Symbol(BaseSymbol): 232 class Symbol(BaseSymbol):
226 """Represents a single symbol within a binary. 233 """Represents a single symbol within a binary.
227 234
228 Refer to module docs for field descriptions. 235 Refer to module docs for field descriptions.
229 """ 236 """
(...skipping 22 matching lines...) Expand all
252 self.name = name or '' 259 self.name = name or ''
253 self.source_path = source_path or '' 260 self.source_path = source_path or ''
254 self.object_path = object_path or '' 261 self.object_path = object_path or ''
255 self.size = size_without_padding 262 self.size = size_without_padding
256 self.flags = flags 263 self.flags = flags
257 self.aliases = aliases 264 self.aliases = aliases
258 self.padding = 0 265 self.padding = 0
259 266
260 def __repr__(self): 267 def __repr__(self):
261 template = ('{}@{:x}(size_without_padding={},padding={},full_name={},' 268 template = ('{}@{:x}(size_without_padding={},padding={},full_name={},'
262 'object_path={},source_path={},flags={})') 269 'object_path={},source_path={},flags={},num_aliases={})')
263 return template.format( 270 return template.format(
264 self.section_name, self.address, self.size_without_padding, 271 self.section_name, self.address, self.size_without_padding,
265 self.padding, self.full_name, self.object_path, self.source_path, 272 self.padding, self.full_name, self.object_path, self.source_path,
266 self.FlagsString()) 273 self.FlagsString(), self.num_aliases)
267 274
268 @property 275 @property
269 def pss(self): 276 def pss(self):
270 return float(self.size) / self.num_aliases 277 return float(self.size) / self.num_aliases
271 278
272 @property 279 @property
273 def pss_without_padding(self): 280 def pss_without_padding(self):
274 return float(self.size_without_padding) / self.num_aliases 281 return float(self.size_without_padding) / self.num_aliases
275 282
283 @property
284 def padding_pss(self):
285 return float(self.padding) / self.num_aliases
286
287
288 class DeltaSymbol(BaseSymbol):
289 """Represents a changed symbol.
290
291 PSS is not just size / num_aliases, because aliases information is not
292 directly tracked. It is not directly tracked because a symbol may be an alias
293 to one symbol in the |before|, and then be an alias to another in |after|.
294 """
295
296 __slots__ = (
297 'before_symbol',
298 'after_symbol',
299 )
300
301 def __init__(self, before_symbol, after_symbol):
302 self.before_symbol = before_symbol
303 self.after_symbol = after_symbol
304
305 def __repr__(self):
306 template = ('{}{}@{:x}(size_without_padding={},padding={},full_name={},'
307 'object_path={},source_path={},flags={})')
308 return template.format(
309 DIFF_PREFIX_BY_STATUS[self.diff_status], self.section_name,
310 self.address, self.size_without_padding, self.padding,
311 self.full_name, self.object_path, self.source_path,
312 self.FlagsString())
313
314 def IsDelta(self):
315 return True
316
317 @property
318 def diff_status(self):
319 if self.before_symbol is None:
320 return DIFF_STATUS_ADDED
321 if self.after_symbol is None:
322 return DIFF_STATUS_REMOVED
323 if self.size == 0:
324 return DIFF_STATUS_UNCHANGED
325 return DIFF_STATUS_CHANGED
326
327 @property
328 def address(self):
329 return self.after_symbol.address if self.after_symbol else 0
330
331 @property
332 def full_name(self):
333 return (self.after_symbol or self.before_symbol).full_name
334
335 @property
336 def template_name(self):
337 return (self.after_symbol or self.before_symbol).template_name
338
339 @property
340 def name(self):
341 return (self.after_symbol or self.before_symbol).name
342
343 @property
344 def flags(self):
345 before_flags = self.before_symbol.flags if self.before_symbol else 0
346 after_flags = self.after_symbol.flags if self.after_symbol else 0
347 return before_flags ^ after_flags
348
349 @property
350 def object_path(self):
351 return (self.after_symbol or self.before_symbol).object_path
352
353 @property
354 def source_path(self):
355 return (self.after_symbol or self.before_symbol).source_path
356
357 @property
358 def aliases(self):
359 return None
360
361 @property
362 def section_name(self):
363 return (self.after_symbol or self.before_symbol).section_name
364
365 @property
366 def padding_pss(self):
367 if self.after_symbol is None:
368 return -self.before_symbol.padding_pss
369 if self.before_symbol is None:
370 return self.after_symbol.padding_pss
371 # Padding tracked in aggregate, except for padding-only symbols.
372 if self.before_symbol.size_without_padding == 0:
373 return self.after_symbol.padding_pss - self.before_symbol.padding_pss
374 return 0
375
376 @property
377 def padding(self):
378 if self.after_symbol is None:
379 return -self.before_symbol.padding
380 if self.before_symbol is None:
381 return self.after_symbol.padding
382 # Padding tracked in aggregate, except for padding-only symbols.
383 if self.before_symbol.size_without_padding == 0:
384 return self.after_symbol.padding - self.before_symbol.padding
385 return 0
386
387 @property
388 def pss(self):
389 if self.after_symbol is None:
390 return -self.before_symbol.pss
391 if self.before_symbol is None:
392 return self.after_symbol.pss
393 # Padding tracked in aggregate, except for padding-only symbols.
394 if self.before_symbol.size_without_padding == 0:
395 return self.after_symbol.pss - self.before_symbol.pss
396 return (self.after_symbol.pss_without_padding -
397 self.before_symbol.pss_without_padding)
398
399 @property
400 def size(self):
401 if self.after_symbol is None:
402 return -self.before_symbol.size
403 if self.before_symbol is None:
404 return self.after_symbol.size
405 # Padding tracked in aggregate, except for padding-only symbols.
406 if self.before_symbol.size_without_padding == 0:
407 return self.after_symbol.padding - self.before_symbol.padding
408 return (self.after_symbol.size_without_padding -
409 self.before_symbol.size_without_padding)
410
411 @property
412 def pss_without_padding(self):
413 return self.pss - self.padding_pss
414
276 415
277 class SymbolGroup(BaseSymbol): 416 class SymbolGroup(BaseSymbol):
278 """Represents a group of symbols using the same interface as Symbol. 417 """Represents a group of symbols using the same interface as Symbol.
279 418
280 SymbolGroups are immutable. All filtering / sorting will return new 419 SymbolGroups are immutable. All filtering / sorting will return new
281 SymbolGroups objects. 420 SymbolGroups objects.
282 421
283 Overrides many __functions__. E.g. the following are all valid: 422 Overrides many __functions__. E.g. the following are all valid:
284 * len(group) 423 * len(group)
285 * iter(group) 424 * iter(group)
(...skipping 36 matching lines...) Expand 10 before | Expand all | Expand 10 after
322 461
323 def __iter__(self): 462 def __iter__(self):
324 return iter(self._symbols) 463 return iter(self._symbols)
325 464
326 def __len__(self): 465 def __len__(self):
327 return len(self._symbols) 466 return len(self._symbols)
328 467
329 def __eq__(self, other): 468 def __eq__(self, other):
330 return isinstance(other, SymbolGroup) and self._symbols == other._symbols 469 return isinstance(other, SymbolGroup) and self._symbols == other._symbols
331 470
471 def __contains__(self, sym):
472 return sym in self._symbols
473
332 def __getitem__(self, key): 474 def __getitem__(self, key):
333 """|key| can be an index or an address. 475 """|key| can be an index or an address.
334 476
335 Raises if multiple symbols map to the address. 477 Raises if multiple symbols map to the address.
336 """ 478 """
337 if isinstance(key, slice): 479 if isinstance(key, slice):
338 return self._CreateTransformed(self._symbols.__getitem__(key)) 480 return self._CreateTransformed(self._symbols.__getitem__(key))
339 if isinstance(key, basestring) or key > len(self._symbols): 481 if isinstance(key, basestring) or key > len(self._symbols):
340 found = self.WhereAddressInRange(key) 482 found = self.WhereAddressInRange(key)
341 if len(found) != 1: 483 if len(found) != 1:
342 raise KeyError('%d symbols found at address %s.' % (len(found), key)) 484 raise KeyError('%d symbols found at address %s.' % (len(found), key))
343 return found[0] 485 return found[0]
344 return self._symbols[key] 486 return self._symbols[key]
345 487
346 def __sub__(self, other): 488 def __sub__(self, other):
347 other_ids = set(id(s) for s in other) 489 other_ids = set(id(s) for s in other)
348 after_symbols = [s for s in self if id(s) not in other_ids] 490 after_symbols = [s for s in self if id(s) not in other_ids]
349 return self._CreateTransformed(after_symbols) 491 return self._CreateTransformed(after_symbols)
350 492
351 def __add__(self, other): 493 def __add__(self, other):
352 self_ids = set(id(s) for s in self) 494 self_ids = set(id(s) for s in self)
353 after_symbols = self._symbols + [s for s in other if id(s) not in self_ids] 495 after_symbols = self._symbols + [s for s in other if id(s) not in self_ids]
354 return self._CreateTransformed(after_symbols, is_sorted=False) 496 return self._CreateTransformed(after_symbols, is_sorted=False)
355 497
498 def index(self, item):
499 return self._symbols.index(item)
500
356 @property 501 @property
357 def address(self): 502 def address(self):
358 first = self._symbols[0].address if self else 0 503 first = self._symbols[0].address if self else 0
359 return first if all(s.address == first for s in self._symbols) else 0 504 return first if all(s.address == first for s in self._symbols) else 0
360 505
361 @property 506 @property
362 def flags(self): 507 def flags(self):
363 first = self._symbols[0].flags if self else 0 508 first = self._symbols[0].flags if self else 0
364 return first if all(s.flags == first for s in self._symbols) else 0 509 return first if all(s.flags == first for s in self._symbols) else 0
365 510
366 @property 511 @property
367 def object_path(self): 512 def object_path(self):
368 first = self._symbols[0].object_path if self else '' 513 first = self._symbols[0].object_path if self else ''
369 return first if all(s.object_path == first for s in self._symbols) else '' 514 return first if all(s.object_path == first for s in self._symbols) else ''
370 515
371 @property 516 @property
372 def source_path(self): 517 def source_path(self):
373 first = self._symbols[0].source_path if self else '' 518 first = self._symbols[0].source_path if self else ''
374 return first if all(s.source_path == first for s in self._symbols) else '' 519 return first if all(s.source_path == first for s in self._symbols) else ''
375 520
376 @property 521 @property
377 def size(self): 522 def size(self):
378 if self._size is None: 523 if self._size is None:
379 if self.IsBss(): 524 if self.IsBss():
380 self._size = sum(s.size for s in self) 525 self._size = sum(s.size for s in self.IterUniqueSymbols())
381 else: 526 else:
382 self._size = sum(s.size for s in self.IterUniqueSymbols()) 527 self._size = sum(
528 s.size for s in self.IterUniqueSymbols() if not s.IsBss())
383 return self._size 529 return self._size
384 530
385 @property 531 @property
386 def pss(self): 532 def pss(self):
387 if self._pss is None: 533 if self._pss is None:
388 if self.IsBss(): 534 if self.IsBss():
389 self._pss = sum(s.pss for s in self) 535 self._pss = sum(s.pss for s in self)
390 else: 536 else:
391 self._pss = sum(s.pss for s in self if not s.IsBss()) 537 self._pss = sum(s.pss for s in self if not s.IsBss())
392 return self._pss 538 return self._pss
(...skipping 38 matching lines...) Expand 10 before | Expand all | Expand 10 after
431 def CountUniqueSymbols(self): 577 def CountUniqueSymbols(self):
432 return sum(1 for s in self.IterUniqueSymbols()) 578 return sum(1 for s in self.IterUniqueSymbols())
433 579
434 def _CreateTransformed(self, symbols, filtered_symbols=None, full_name=None, 580 def _CreateTransformed(self, symbols, filtered_symbols=None, full_name=None,
435 template_name=None, name=None, section_name=None, 581 template_name=None, name=None, section_name=None,
436 is_sorted=None): 582 is_sorted=None):
437 if is_sorted is None: 583 if is_sorted is None:
438 is_sorted = self.is_sorted 584 is_sorted = self.is_sorted
439 if section_name is None: 585 if section_name is None:
440 section_name = self.section_name 586 section_name = self.section_name
441 return SymbolGroup(symbols, filtered_symbols=filtered_symbols, 587 return self.__class__(symbols, filtered_symbols=filtered_symbols,
442 full_name=full_name, template_name=template_name, 588 full_name=full_name, template_name=template_name,
443 name=name, section_name=section_name, 589 name=name, section_name=section_name,
444 is_sorted=is_sorted) 590 is_sorted=is_sorted)
445 591
446 def Sorted(self, cmp_func=None, key=None, reverse=False): 592 def Sorted(self, cmp_func=None, key=None, reverse=False):
447 if cmp_func is None and key is None: 593 if cmp_func is None and key is None:
448 cmp_func = lambda a, b: cmp((a.IsBss(), abs(b.pss), a.name), 594 cmp_func = lambda a, b: cmp((a.IsBss(), abs(b.pss), a.name),
449 (b.IsBss(), abs(a.pss), b.name)) 595 (b.IsBss(), abs(a.pss), b.name))
450 596
451 after_symbols = sorted(self._symbols, cmp_func, key, reverse) 597 after_symbols = sorted(self._symbols, cmp_func, key, reverse)
452 return self._CreateTransformed( 598 return self._CreateTransformed(
453 after_symbols, filtered_symbols=self._filtered_symbols, 599 after_symbols, filtered_symbols=self._filtered_symbols,
454 is_sorted=True) 600 is_sorted=True)
(...skipping 259 matching lines...) Expand 10 before | Expand all | Expand 10 after
714 else: 860 else:
715 depth = -depth 861 depth = -depth
716 extract_namespace = ( 862 extract_namespace = (
717 lambda s: _ExtractSuffixAfterSeparator(s.name, '::', depth)) 863 lambda s: _ExtractSuffixAfterSeparator(s.name, '::', depth))
718 return self.GroupedBy(extract_namespace, min_count=min_count) 864 return self.GroupedBy(extract_namespace, min_count=min_count)
719 865
720 def GroupedByPath(self, depth=0, fallback='{no path}', 866 def GroupedByPath(self, depth=0, fallback='{no path}',
721 fallback_to_object_path=True, min_count=0): 867 fallback_to_object_path=True, min_count=0):
722 """Groups by source_path. 868 """Groups by source_path.
723 869
870 Due to path sharing (symbols where path looks like foo/bar/{shared}/3),
871 grouping by path will not show 100% of they bytes consumed by each path.
872
724 Args: 873 Args:
725 depth: When 0 (default), groups by entire path. When 1, groups by 874 depth: When 0 (default), groups by entire path. When 1, groups by
726 top-level directory, when 2, groups by top 2 directories, etc. 875 top-level directory, when 2, groups by top 2 directories, etc.
727 fallback: Use this value when no namespace exists. 876 fallback: Use this value when no namespace exists.
728 fallback_to_object_path: When True (default), uses object_path when 877 fallback_to_object_path: When True (default), uses object_path when
729 source_path is missing. 878 source_path is missing.
730 min_count: Miniumum number of symbols for a group. If fewer than this many 879 min_count: Miniumum number of symbols for a group. If fewer than this many
731 symbols end up in a group, they will not be put within a group. 880 symbols end up in a group, they will not be put within a group.
732 Use a negative value to omit symbols entirely rather than 881 Use a negative value to omit symbols entirely rather than
733 include them outside of a group. 882 include them outside of a group.
734 """ 883 """
735 def extract_path(symbol): 884 def extract_path(symbol):
736 path = symbol.source_path 885 path = symbol.source_path
737 if fallback_to_object_path and not path: 886 if fallback_to_object_path and not path:
738 path = symbol.object_path 887 path = symbol.object_path
739 path = path or fallback 888 path = path or fallback
889 # Group by base of foo/bar/{shared}/2
890 shared_idx = path.find('{shared}')
891 if shared_idx != -1:
892 path = path[:shared_idx + 8]
740 return _ExtractPrefixBeforeSeparator(path, os.path.sep, depth) 893 return _ExtractPrefixBeforeSeparator(path, os.path.sep, depth)
741 return self.GroupedBy(extract_path, min_count=min_count) 894 return self.GroupedBy(extract_path, min_count=min_count)
742 895
743 896
744 class SymbolDiff(SymbolGroup): 897 class DeltaSymbolGroup(SymbolGroup):
745 """A SymbolGroup subclass representing a diff of two other SymbolGroups. 898 """A SymbolGroup subclass representing a diff of two other SymbolGroups.
746 899
747 All Symbols contained within have a |size| which is actually the size delta. 900 Contains a list of DeltaSymbols.
748 Additionally, metadata is kept about which symbols were added / removed /
749 changed.
750 """ 901 """
751 __slots__ = ( 902 __slots__ = ()
752 '_added_ids',
753 '_removed_ids',
754 '_diff_status',
755 '_changed_count',
756 )
757
758 def __init__(self, added, removed, similar):
759 self._added_ids = set(id(s) for s in added)
760 self._removed_ids = set(id(s) for s in removed)
761 self._diff_status = DIFF_STATUS_CHANGED
762 self._changed_count = None
763 symbols = []
764 symbols.extend(added)
765 symbols.extend(removed)
766 symbols.extend(similar)
767 super(SymbolDiff, self).__init__(symbols)
768 903
769 def __repr__(self): 904 def __repr__(self):
905 counts = self.CountsByDiffStatus()
770 return '%s(%d added, %d removed, %d changed, %d unchanged, size=%d)' % ( 906 return '%s(%d added, %d removed, %d changed, %d unchanged, size=%d)' % (
771 'SymbolGroup', self.added_count, self.removed_count, self.changed_count, 907 'DeltaSymbolGroup', counts[DIFF_STATUS_ADDED],
772 self.unchanged_count, self.size) 908 counts[DIFF_STATUS_REMOVED], counts[DIFF_STATUS_CHANGED],
909 counts[DIFF_STATUS_UNCHANGED], self.size)
773 910
774 def _CreateTransformed(self, symbols, filtered_symbols=None, full_name=None, 911 def IsDelta(self):
775 template_name=None, name=None, section_name=None, 912 return True
776 is_sorted=None):
777 new_added_ids = set()
778 new_removed_ids = set()
779 group_diff_status = DIFF_STATUS_UNCHANGED
780 changed_count = 0
781 if symbols:
782 group_diff_status = self.DiffStatus(symbols[0])
783 for sym in symbols:
784 status = self.DiffStatus(sym)
785 if status != group_diff_status:
786 group_diff_status = DIFF_STATUS_CHANGED
787 if status == DIFF_STATUS_ADDED:
788 new_added_ids.add(id(sym))
789 elif status == DIFF_STATUS_REMOVED:
790 new_removed_ids.add(id(sym))
791 elif status == DIFF_STATUS_CHANGED:
792 changed_count += 1
793 913
794 ret = SymbolDiff.__new__(SymbolDiff) 914 def CountsByDiffStatus(self):
795 ret._added_ids = new_added_ids 915 """Returns a map of diff_status -> count of children with that status."""
796 ret._removed_ids = new_removed_ids 916 ret = [0, 0, 0, 0]
797 ret._diff_status = group_diff_status 917 for sym in self:
798 ret._changed_count = changed_count 918 ret[sym.diff_status] += 1
799 super(SymbolDiff, ret).__init__(
800 symbols, filtered_symbols=filtered_symbols, full_name=full_name,
801 template_name=template_name, name=name, section_name=section_name,
802 is_sorted=is_sorted)
803 return ret 919 return ret
804 920
805 @property 921 def CountUniqueSymbols(self):
806 def added_count(self): 922 """Returns (num_unique_before_symbols, num_unique_after_symbols)."""
807 return len(self._added_ids) 923 syms = (s.before_symbol for s in self.IterLeafSymbols() if s.before_symbol)
924 before_count = SymbolGroup(syms).CountUniqueSymbols()
925 syms = (s.after_symbol for s in self.IterLeafSymbols() if s.after_symbol)
926 after_count = SymbolGroup(syms).CountUniqueSymbols()
927 return before_count, after_count
808 928
809 @property 929 @property
810 def removed_count(self): 930 def diff_status(self):
811 return len(self._removed_ids) 931 if not self:
932 return DIFF_STATUS_UNCHANGED
933 ret = self._symbols[0].diff_status
934 for sym in self._symbols[1:]:
935 if sym.diff_status != ret:
936 return DIFF_STATUS_CHANGED
937 return ret
812 938
813 @property 939 def WhereDiffStatusIs(self, diff_status):
814 def changed_count(self): 940 return self.Filter(lambda s: s.diff_status == diff_status)
815 if self._changed_count is None:
816 self._changed_count = sum(1 for s in self if self.IsChanged(s))
817 return self._changed_count
818
819 @property
820 def unchanged_count(self):
821 return (len(self) - self.changed_count - self.added_count -
822 self.removed_count)
823
824 def DiffStatus(self, sym):
825 # Groups store their own status, computed during _CreateTransformed().
826 if sym.IsGroup():
827 return sym._diff_status
828 sym_id = id(sym)
829 if sym_id in self._added_ids:
830 return DIFF_STATUS_ADDED
831 if sym_id in self._removed_ids:
832 return DIFF_STATUS_REMOVED
833 # 0 --> unchanged
834 # 1 --> changed
835 return int(sym.size != 0)
836
837 def IsUnchanged(self, sym):
838 return self.DiffStatus(sym) == DIFF_STATUS_UNCHANGED
839
840 def IsChanged(self, sym):
841 return self.DiffStatus(sym) == DIFF_STATUS_CHANGED
842
843 def IsAdded(self, sym):
844 return self.DiffStatus(sym) == DIFF_STATUS_ADDED
845
846 def IsRemoved(self, sym):
847 return self.DiffStatus(sym) == DIFF_STATUS_REMOVED
848
849 def WhereNotUnchanged(self):
850 return self.Filter(lambda s: not self.IsUnchanged(s))
851 941
852 942
853 def _ExtractPrefixBeforeSeparator(string, separator, count): 943 def _ExtractPrefixBeforeSeparator(string, separator, count):
854 idx = -len(separator) 944 idx = -len(separator)
855 prev_idx = None 945 prev_idx = None
856 for _ in xrange(count): 946 for _ in xrange(count):
857 idx = string.find(separator, idx + len(separator)) 947 idx = string.find(separator, idx + len(separator))
858 if idx < 0: 948 if idx < 0:
859 break 949 break
860 prev_idx = idx 950 prev_idx = idx
861 return string[:prev_idx] 951 return string[:prev_idx]
862 952
863 953
864 def _ExtractSuffixAfterSeparator(string, separator, count): 954 def _ExtractSuffixAfterSeparator(string, separator, count):
865 prev_idx = len(string) + 1 955 prev_idx = len(string) + 1
866 for _ in xrange(count): 956 for _ in xrange(count):
867 idx = string.rfind(separator, 0, prev_idx - 1) 957 idx = string.rfind(separator, 0, prev_idx - 1)
868 if idx < 0: 958 if idx < 0:
869 break 959 break
870 prev_idx = idx 960 prev_idx = idx
871 return string[:prev_idx] 961 return string[:prev_idx]
OLDNEW
« no previous file with comments | « tools/binary_size/libsupersize/integration_test.py ('k') | tools/binary_size/libsupersize/testdata/Archive.golden » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698