| 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 """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 Loading... |
| 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 Loading... |
| 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 Loading... |
| 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 Loading... |
| 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 Loading... |
| 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 Loading... |
| 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] |
| OLD | NEW |