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

Unified 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 side-by-side diff with in-line comments
Download patch
Index: tools/binary_size/libsupersize/models.py
diff --git a/tools/binary_size/libsupersize/models.py b/tools/binary_size/libsupersize/models.py
index 2cfd59bcb4c41baa3a0f4cf4367b38076d1cacab..e4acaedcf8fa4459baa829394e8af7678d8c5a92 100644
--- a/tools/binary_size/libsupersize/models.py
+++ b/tools/binary_size/libsupersize/models.py
@@ -13,6 +13,7 @@ Description of common properties:
* num_aliases: The number of symbols with the same address (including self).
* pss: size / num_aliases.
* padding: The number of bytes of padding before |address| due to this symbol.
+ * padding_pss: padding / num_aliases.
* name: Names with templates and parameter list removed.
Never None, but will be '' for anonymous symbols.
* template_name: Name with parameter list removed (but templates left in).
@@ -65,6 +66,7 @@ DIFF_STATUS_UNCHANGED = 0
DIFF_STATUS_CHANGED = 1
DIFF_STATUS_ADDED = 2
DIFF_STATUS_REMOVED = 3
+DIFF_PREFIX_BY_STATUS = ['= ', '~ ', '+ ', '- ']
class SizeInfo(object):
@@ -77,22 +79,26 @@ class SizeInfo(object):
applicable). May be re-assigned when it is desirable to show custom
groupings while still printing metadata and section_sizes.
metadata: A dict.
+ size_path: Path to .size file this was loaded from (or None).
"""
__slots__ = (
'section_sizes',
'raw_symbols',
'_symbols',
'metadata',
+ 'size_path',
)
"""Root size information."""
- def __init__(self, section_sizes, raw_symbols, metadata=None, symbols=None):
+ def __init__(self, section_sizes, raw_symbols, metadata=None, symbols=None,
+ size_path=None):
if isinstance(raw_symbols, list):
raw_symbols = SymbolGroup(raw_symbols)
self.section_sizes = section_sizes # E.g. {'.text': 0}
self.raw_symbols = raw_symbols
self._symbols = symbols
self.metadata = metadata or {}
+ self.size_path = size_path
@property
def symbols(self):
@@ -105,15 +111,16 @@ class SizeInfo(object):
self._symbols = value
-class SizeInfoDiff(object):
+class DeltaSizeInfo(object):
"""What you get when you Diff() two SizeInfo objects.
Fields:
section_sizes: A dict of section_name -> size delta.
- raw_symbols: A SymbolDiff with all top-level symbols in it (no groups).
- symbols: A SymbolDiff where symbols have been grouped by full_name (where
- applicable). May be re-assigned when it is desirable to show custom
- groupings while still printing metadata and section_sizes.
+ raw_symbols: A DeltaSymbolGroup with all top-level symbols in it
+ (no groups).
+ symbols: A DeltaSymbolGroup where symbols have been grouped by full_name
+ (where applicable). May be re-assigned when it is desirable to show
+ custom groupings while still printing metadata and section_sizes.
before_metadata: metadata of the "before" SizeInfo.
after_metadata: metadata of the "after" SizeInfo.
"""
@@ -189,7 +196,7 @@ class BaseSymbol(object):
def FlagsString(self):
# Most flags are 0.
flags = self.flags
- if not flags and not self.aliases:
+ if not flags:
return '{}'
parts = []
if flags & FLAG_ANONYMOUS:
@@ -206,9 +213,6 @@ class BaseSymbol(object):
parts.append('gen')
if flags & FLAG_CLONE:
parts.append('clone')
- # Not actually a part of flags, but useful to show it here.
- if self.aliases:
- parts.append('{} aliases'.format(self.num_aliases))
return '{%s}' % ','.join(parts)
def IsBss(self):
@@ -217,6 +221,9 @@ class BaseSymbol(object):
def IsGroup(self):
return False
+ def IsDelta(self):
+ return False
+
def IsGeneratedByToolchain(self):
return '.' in self.name or (
self.name.endswith(']') and not self.name.endswith('[]'))
@@ -259,11 +266,11 @@ class Symbol(BaseSymbol):
def __repr__(self):
template = ('{}@{:x}(size_without_padding={},padding={},full_name={},'
- 'object_path={},source_path={},flags={})')
+ 'object_path={},source_path={},flags={},num_aliases={})')
return template.format(
self.section_name, self.address, self.size_without_padding,
self.padding, self.full_name, self.object_path, self.source_path,
- self.FlagsString())
+ self.FlagsString(), self.num_aliases)
@property
def pss(self):
@@ -273,6 +280,138 @@ class Symbol(BaseSymbol):
def pss_without_padding(self):
return float(self.size_without_padding) / self.num_aliases
+ @property
+ def padding_pss(self):
+ return float(self.padding) / self.num_aliases
+
+
+class DeltaSymbol(BaseSymbol):
+ """Represents a changed symbol.
+
+ PSS is not just size / num_aliases, because aliases information is not
+ directly tracked. It is not directly tracked because a symbol may be an alias
+ to one symbol in the |before|, and then be an alias to another in |after|.
+ """
+
+ __slots__ = (
+ 'before_symbol',
+ 'after_symbol',
+ )
+
+ def __init__(self, before_symbol, after_symbol):
+ self.before_symbol = before_symbol
+ self.after_symbol = after_symbol
+
+ def __repr__(self):
+ template = ('{}{}@{:x}(size_without_padding={},padding={},full_name={},'
+ 'object_path={},source_path={},flags={})')
+ return template.format(
+ DIFF_PREFIX_BY_STATUS[self.diff_status], self.section_name,
+ self.address, self.size_without_padding, self.padding,
+ self.full_name, self.object_path, self.source_path,
+ self.FlagsString())
+
+ def IsDelta(self):
+ return True
+
+ @property
+ def diff_status(self):
+ if self.before_symbol is None:
+ return DIFF_STATUS_ADDED
+ if self.after_symbol is None:
+ return DIFF_STATUS_REMOVED
+ if self.size == 0:
+ return DIFF_STATUS_UNCHANGED
+ return DIFF_STATUS_CHANGED
+
+ @property
+ def address(self):
+ return self.after_symbol.address if self.after_symbol else 0
+
+ @property
+ def full_name(self):
+ return (self.after_symbol or self.before_symbol).full_name
+
+ @property
+ def template_name(self):
+ return (self.after_symbol or self.before_symbol).template_name
+
+ @property
+ def name(self):
+ return (self.after_symbol or self.before_symbol).name
+
+ @property
+ def flags(self):
+ before_flags = self.before_symbol.flags if self.before_symbol else 0
+ after_flags = self.after_symbol.flags if self.after_symbol else 0
+ return before_flags ^ after_flags
+
+ @property
+ def object_path(self):
+ return (self.after_symbol or self.before_symbol).object_path
+
+ @property
+ def source_path(self):
+ return (self.after_symbol or self.before_symbol).source_path
+
+ @property
+ def aliases(self):
+ return None
+
+ @property
+ def section_name(self):
+ return (self.after_symbol or self.before_symbol).section_name
+
+ @property
+ def padding_pss(self):
+ if self.after_symbol is None:
+ return -self.before_symbol.padding_pss
+ if self.before_symbol is None:
+ return self.after_symbol.padding_pss
+ # Padding tracked in aggregate, except for padding-only symbols.
+ if self.before_symbol.size_without_padding == 0:
+ return self.after_symbol.padding_pss - self.before_symbol.padding_pss
+ return 0
+
+ @property
+ def padding(self):
+ if self.after_symbol is None:
+ return -self.before_symbol.padding
+ if self.before_symbol is None:
+ return self.after_symbol.padding
+ # Padding tracked in aggregate, except for padding-only symbols.
+ if self.before_symbol.size_without_padding == 0:
+ return self.after_symbol.padding - self.before_symbol.padding
+ return 0
+
+ @property
+ def pss(self):
+ if self.after_symbol is None:
+ return -self.before_symbol.pss
+ if self.before_symbol is None:
+ return self.after_symbol.pss
+ # Padding tracked in aggregate, except for padding-only symbols.
+ if self.before_symbol.size_without_padding == 0:
+ return self.after_symbol.pss - self.before_symbol.pss
+ return (self.after_symbol.pss_without_padding -
+ self.before_symbol.pss_without_padding)
+
+ @property
+ def size(self):
+ if self.after_symbol is None:
+ return -self.before_symbol.size
+ if self.before_symbol is None:
+ return self.after_symbol.size
+ # Padding tracked in aggregate, except for padding-only symbols.
+ if self.before_symbol.size_without_padding == 0:
+ return self.after_symbol.padding - self.before_symbol.padding
+ return (self.after_symbol.size_without_padding -
+ self.before_symbol.size_without_padding)
+
+ @property
+ def pss_without_padding(self):
+ return self.pss - self.padding_pss
+
class SymbolGroup(BaseSymbol):
"""Represents a group of symbols using the same interface as Symbol.
@@ -329,6 +468,9 @@ class SymbolGroup(BaseSymbol):
def __eq__(self, other):
return isinstance(other, SymbolGroup) and self._symbols == other._symbols
+ def __contains__(self, sym):
+ return sym in self._symbols
+
def __getitem__(self, key):
"""|key| can be an index or an address.
@@ -353,6 +495,9 @@ class SymbolGroup(BaseSymbol):
after_symbols = self._symbols + [s for s in other if id(s) not in self_ids]
return self._CreateTransformed(after_symbols, is_sorted=False)
+ def index(self, item):
+ return self._symbols.index(item)
+
@property
def address(self):
first = self._symbols[0].address if self else 0
@@ -377,9 +522,10 @@ class SymbolGroup(BaseSymbol):
def size(self):
if self._size is None:
if self.IsBss():
- self._size = sum(s.size for s in self)
- else:
self._size = sum(s.size for s in self.IterUniqueSymbols())
+ else:
+ self._size = sum(
+ s.size for s in self.IterUniqueSymbols() if not s.IsBss())
return self._size
@property
@@ -438,10 +584,10 @@ class SymbolGroup(BaseSymbol):
is_sorted = self.is_sorted
if section_name is None:
section_name = self.section_name
- return SymbolGroup(symbols, filtered_symbols=filtered_symbols,
- full_name=full_name, template_name=template_name,
- name=name, section_name=section_name,
- is_sorted=is_sorted)
+ return self.__class__(symbols, filtered_symbols=filtered_symbols,
+ full_name=full_name, template_name=template_name,
+ name=name, section_name=section_name,
+ is_sorted=is_sorted)
def Sorted(self, cmp_func=None, key=None, reverse=False):
if cmp_func is None and key is None:
@@ -721,6 +867,9 @@ class SymbolGroup(BaseSymbol):
fallback_to_object_path=True, min_count=0):
"""Groups by source_path.
+ Due to path sharing (symbols where path looks like foo/bar/{shared}/3),
+ grouping by path will not show 100% of they bytes consumed by each path.
+
Args:
depth: When 0 (default), groups by entire path. When 1, groups by
top-level directory, when 2, groups by top 2 directories, etc.
@@ -737,117 +886,58 @@ class SymbolGroup(BaseSymbol):
if fallback_to_object_path and not path:
path = symbol.object_path
path = path or fallback
+ # Group by base of foo/bar/{shared}/2
+ shared_idx = path.find('{shared}')
+ if shared_idx != -1:
+ path = path[:shared_idx + 8]
return _ExtractPrefixBeforeSeparator(path, os.path.sep, depth)
return self.GroupedBy(extract_path, min_count=min_count)
-class SymbolDiff(SymbolGroup):
+class DeltaSymbolGroup(SymbolGroup):
"""A SymbolGroup subclass representing a diff of two other SymbolGroups.
- All Symbols contained within have a |size| which is actually the size delta.
- Additionally, metadata is kept about which symbols were added / removed /
- changed.
+ Contains a list of DeltaSymbols.
"""
- __slots__ = (
- '_added_ids',
- '_removed_ids',
- '_diff_status',
- '_changed_count',
- )
-
- def __init__(self, added, removed, similar):
- self._added_ids = set(id(s) for s in added)
- self._removed_ids = set(id(s) for s in removed)
- self._diff_status = DIFF_STATUS_CHANGED
- self._changed_count = None
- symbols = []
- symbols.extend(added)
- symbols.extend(removed)
- symbols.extend(similar)
- super(SymbolDiff, self).__init__(symbols)
+ __slots__ = ()
def __repr__(self):
+ counts = self.CountsByDiffStatus()
return '%s(%d added, %d removed, %d changed, %d unchanged, size=%d)' % (
- 'SymbolGroup', self.added_count, self.removed_count, self.changed_count,
- self.unchanged_count, self.size)
-
- def _CreateTransformed(self, symbols, filtered_symbols=None, full_name=None,
- template_name=None, name=None, section_name=None,
- is_sorted=None):
- new_added_ids = set()
- new_removed_ids = set()
- group_diff_status = DIFF_STATUS_UNCHANGED
- changed_count = 0
- if symbols:
- group_diff_status = self.DiffStatus(symbols[0])
- for sym in symbols:
- status = self.DiffStatus(sym)
- if status != group_diff_status:
- group_diff_status = DIFF_STATUS_CHANGED
- if status == DIFF_STATUS_ADDED:
- new_added_ids.add(id(sym))
- elif status == DIFF_STATUS_REMOVED:
- new_removed_ids.add(id(sym))
- elif status == DIFF_STATUS_CHANGED:
- changed_count += 1
-
- ret = SymbolDiff.__new__(SymbolDiff)
- ret._added_ids = new_added_ids
- ret._removed_ids = new_removed_ids
- ret._diff_status = group_diff_status
- ret._changed_count = changed_count
- super(SymbolDiff, ret).__init__(
- symbols, filtered_symbols=filtered_symbols, full_name=full_name,
- template_name=template_name, name=name, section_name=section_name,
- is_sorted=is_sorted)
- return ret
+ 'DeltaSymbolGroup', counts[DIFF_STATUS_ADDED],
+ counts[DIFF_STATUS_REMOVED], counts[DIFF_STATUS_CHANGED],
+ counts[DIFF_STATUS_UNCHANGED], self.size)
- @property
- def added_count(self):
- return len(self._added_ids)
+ def IsDelta(self):
+ return True
- @property
- def removed_count(self):
- return len(self._removed_ids)
+ def CountsByDiffStatus(self):
+ """Returns a map of diff_status -> count of children with that status."""
+ ret = [0, 0, 0, 0]
+ for sym in self:
+ ret[sym.diff_status] += 1
+ return ret
- @property
- def changed_count(self):
- if self._changed_count is None:
- self._changed_count = sum(1 for s in self if self.IsChanged(s))
- return self._changed_count
+ def CountUniqueSymbols(self):
+ """Returns (num_unique_before_symbols, num_unique_after_symbols)."""
+ syms = (s.before_symbol for s in self.IterLeafSymbols() if s.before_symbol)
+ before_count = SymbolGroup(syms).CountUniqueSymbols()
+ syms = (s.after_symbol for s in self.IterLeafSymbols() if s.after_symbol)
+ after_count = SymbolGroup(syms).CountUniqueSymbols()
+ return before_count, after_count
@property
- def unchanged_count(self):
- return (len(self) - self.changed_count - self.added_count -
- self.removed_count)
-
- def DiffStatus(self, sym):
- # Groups store their own status, computed during _CreateTransformed().
- if sym.IsGroup():
- return sym._diff_status
- sym_id = id(sym)
- if sym_id in self._added_ids:
- return DIFF_STATUS_ADDED
- if sym_id in self._removed_ids:
- return DIFF_STATUS_REMOVED
- # 0 --> unchanged
- # 1 --> changed
- return int(sym.size != 0)
-
- def IsUnchanged(self, sym):
- return self.DiffStatus(sym) == DIFF_STATUS_UNCHANGED
-
- def IsChanged(self, sym):
- return self.DiffStatus(sym) == DIFF_STATUS_CHANGED
-
- def IsAdded(self, sym):
- return self.DiffStatus(sym) == DIFF_STATUS_ADDED
-
- def IsRemoved(self, sym):
- return self.DiffStatus(sym) == DIFF_STATUS_REMOVED
+ def diff_status(self):
+ if not self:
+ return DIFF_STATUS_UNCHANGED
+ ret = self._symbols[0].diff_status
+ for sym in self._symbols[1:]:
+ if sym.diff_status != ret:
+ return DIFF_STATUS_CHANGED
+ return ret
- def WhereNotUnchanged(self):
- return self.Filter(lambda s: not self.IsUnchanged(s))
+ def WhereDiffStatusIs(self, diff_status):
+ return self.Filter(lambda s: s.diff_status == diff_status)
def _ExtractPrefixBeforeSeparator(string, separator, count):
« 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