Index: tools/binary_size/explain_binary_size_delta.py |
diff --git a/tools/binary_size/explain_binary_size_delta.py b/tools/binary_size/explain_binary_size_delta.py |
index 80683ffe9f3782030b03ab60100d695b0e653d27..88a517bf2738adddc1c0d098f530da7c62a6a449 100755 |
--- a/tools/binary_size/explain_binary_size_delta.py |
+++ b/tools/binary_size/explain_binary_size_delta.py |
@@ -37,6 +37,7 @@ dumps. Example: |
explain_binary_size_delta.py --nm1 /tmp/nm1.dump --nm2 /tmp/nm2.dump |
""" |
+import operator |
import optparse |
import os |
import sys |
@@ -106,46 +107,76 @@ def Compare(symbols1, symbols2): |
added.append((key[0], key[1], symbol_name, None, symbol_size)) |
return (added, removed, changed, unchanged) |
+def DeltaStr(number): |
+ """Returns the number as a string with a '+' prefix if it's > 0 and |
+ a '-' prefix if it's < 0.""" |
+ result = str(number) |
+ if number > 0: |
+ result = '+' + result |
+ return result |
+ |
+ |
+class CrunchStatsData(object): |
+ """Stores a summary of data of a certain kind.""" |
+ def __init__(self, symbols): |
+ self.symbols = symbols |
+ self.sources = set() |
+ self.before_size = 0 |
+ self.after_size = 0 |
+ self.symbols_by_path = {} |
+ |
def CrunchStats(added, removed, changed, unchanged, showsources, showsymbols): |
"""Outputs to stdout a summary of changes based on the symbol lists.""" |
- print 'Symbol statistics:' |
- sources_with_new_symbols = set() |
- new_symbols_size = 0 |
- new_symbols_by_path = {} |
- for file_path, symbol_type, symbol_name, size1, size2 in added: |
- sources_with_new_symbols.add(file_path) |
- new_symbols_size += size2 |
- bucket = new_symbols_by_path.setdefault(file_path, []) |
- bucket.append((symbol_name, symbol_type, None, size2)) |
- print(' %d added, totalling %d bytes across %d sources' % |
- (len(added), new_symbols_size, len(sources_with_new_symbols))) |
- |
- sources_with_removed_symbols = set() |
- removed_symbols_size = 0 |
- removed_symbols_by_path = {} |
- for file_path, symbol_type, symbol_name, size1, size2 in removed: |
- sources_with_removed_symbols.add(file_path) |
- removed_symbols_size += size1 |
- bucket = removed_symbols_by_path.setdefault(file_path, []) |
- bucket.append((symbol_name, symbol_type, size1, None)) |
- print(' %d removed, totalling %d bytes removed across %d sources' % |
- (len(removed), removed_symbols_size, len(sources_with_removed_symbols))) |
- |
- sources_with_changed_symbols = set() |
- before_size = 0 |
- after_size = 0 |
- changed_symbols_by_path = {} |
- for file_path, symbol_type, symbol_name, size1, size2 in changed: |
- sources_with_changed_symbols.add(file_path) |
- before_size += size1 |
- after_size += size2 |
- bucket = changed_symbols_by_path.setdefault(file_path, []) |
- bucket.append((symbol_name, symbol_type, size1, size2)) |
- print(' %d changed, resulting in a net change of %d bytes ' |
- '(%d bytes before, %d bytes after) across %d sources' % |
- (len(changed), (after_size - before_size), before_size, after_size, |
- len(sources_with_changed_symbols))) |
+ # Split changed into grown and shrunk because that is easier to |
+ # discuss. |
+ grown = [] |
+ shrunk = [] |
+ for item in changed: |
+ file_path, symbol_type, symbol_name, size1, size2 = item |
+ if size1 < size2: |
+ grown.append(item) |
+ else: |
+ shrunk.append(item) |
+ |
+ new_symbols = CrunchStatsData(added) |
+ removed_symbols = CrunchStatsData(removed) |
+ grown_symbols = CrunchStatsData(grown) |
+ shrunk_symbols = CrunchStatsData(shrunk) |
+ sections = [new_symbols, removed_symbols, grown_symbols, shrunk_symbols] |
+ for section in sections: |
+ for file_path, symbol_type, symbol_name, size1, size2 in section.symbols: |
+ section.sources.add(file_path) |
+ if size1 is not None: |
+ section.before_size += size1 |
+ if size2 is not None: |
+ section.after_size += size2 |
+ bucket = section.symbols_by_path.setdefault(file_path, []) |
+ bucket.append((symbol_name, symbol_type, size1, size2)) |
+ |
+ total_change = sum(s.after_size - s.before_size for s in sections) |
+ summary = 'Total change: %s bytes' % DeltaStr(total_change) |
+ print(summary) |
+ print('=' * len(summary)) |
+ for section in sections: |
+ if not section.symbols: |
+ continue |
+ if section.before_size == 0: |
+ description = ('added, totalling %s bytes' % DeltaStr(section.after_size)) |
+ elif section.after_size == 0: |
+ description = ('removed, totalling %s bytes' % |
+ DeltaStr(-section.before_size)) |
+ else: |
+ if section.after_size > section.before_size: |
+ type_str = 'grown' |
+ else: |
+ type_str = 'shrunk' |
+ description = ('%s, for a net change of %s bytes ' |
+ '(%d bytes before, %d bytes after)' % |
+ (type_str, DeltaStr(section.after_size - section.before_size), |
+ section.before_size, section.after_size)) |
+ print(' %d %s across %d sources' % |
+ (len(section.symbols), description, len(section.sources))) |
maybe_unchanged_sources = set() |
unchanged_symbols_size = 0 |
@@ -156,23 +187,22 @@ def CrunchStats(added, removed, changed, unchanged, showsources, showsymbols): |
(len(unchanged), unchanged_symbols_size)) |
# High level analysis, always output. |
- unchanged_sources = (maybe_unchanged_sources - |
- sources_with_changed_symbols - |
- sources_with_removed_symbols - |
- sources_with_new_symbols) |
- new_sources = (sources_with_new_symbols - |
+ unchanged_sources = maybe_unchanged_sources |
+ for section in sections: |
+ unchanged_sources = unchanged_sources - section.sources |
+ new_sources = (new_symbols.sources - |
maybe_unchanged_sources - |
- sources_with_removed_symbols) |
- removed_sources = (sources_with_removed_symbols - |
+ removed_symbols.sources) |
+ removed_sources = (removed_symbols.sources - |
maybe_unchanged_sources - |
- sources_with_new_symbols) |
- partially_changed_sources = (sources_with_changed_symbols | |
- sources_with_new_symbols | |
- sources_with_removed_symbols) - removed_sources - new_sources |
- allFiles = (sources_with_new_symbols | |
- sources_with_removed_symbols | |
- sources_with_changed_symbols | |
- maybe_unchanged_sources) |
+ new_symbols.sources) |
+ partially_changed_sources = (grown_symbols.sources | |
+ shrunk_symbols.sources | new_symbols.sources | |
+ removed_symbols.sources) - removed_sources - new_sources |
+ allFiles = set() |
+ for section in sections: |
+ allFiles = allFiles | section.sources |
+ allFiles = allFiles | maybe_unchanged_sources |
print 'Source stats:' |
print(' %d sources encountered.' % len(allFiles)) |
print(' %d completely new.' % len(new_sources)) |
@@ -187,61 +217,72 @@ def CrunchStats(added, removed, changed, unchanged, showsources, showsymbols): |
return # Per-source analysis, only if requested |
print 'Per-source Analysis:' |
delta_by_path = {} |
- for path in new_symbols_by_path: |
- entry = delta_by_path.get(path) |
- if not entry: |
- entry = {'plus': 0, 'minus': 0} |
- delta_by_path[path] = entry |
- for symbol_name, symbol_type, size1, size2 in new_symbols_by_path[path]: |
- entry['plus'] += size2 |
- for path in removed_symbols_by_path: |
- entry = delta_by_path.get(path) |
- if not entry: |
- entry = {'plus': 0, 'minus': 0} |
- delta_by_path[path] = entry |
- for symbol_name, symbol_type, size1, size2 in removed_symbols_by_path[path]: |
- entry['minus'] += size1 |
- for path in changed_symbols_by_path: |
- entry = delta_by_path.get(path) |
- if not entry: |
- entry = {'plus': 0, 'minus': 0} |
- delta_by_path[path] = entry |
- for symbol_name, symbol_type, size1, size2 in changed_symbols_by_path[path]: |
- delta = size2 - size1 |
- if delta > 0: |
- entry['plus'] += delta |
- else: |
- entry['minus'] += (-1 * delta) |
+ for section in sections: |
+ for path in section.symbols_by_path: |
+ entry = delta_by_path.get(path) |
+ if not entry: |
+ entry = {'plus': 0, 'minus': 0} |
+ delta_by_path[path] = entry |
+ for symbol_name, symbol_type, size1, size2 in \ |
+ section.symbols_by_path[path]: |
+ if size1 is None: |
+ delta = size2 |
+ elif size2 is None: |
+ delta = -size1 |
+ else: |
+ delta = size2 - size1 |
- for path in sorted(delta_by_path): |
- print ' Source: ' + path |
- size_data = delta_by_path[path] |
+ if delta > 0: |
+ entry['plus'] += delta |
+ else: |
+ entry['minus'] += (-1 * delta) |
+ |
+ def delta_sort_key(item): |
+ _path, size_data = item |
+ growth = size_data['plus'] - size_data['minus'] |
+ return growth |
+ |
+ for path, size_data in sorted(delta_by_path.iteritems(), key=delta_sort_key, |
+ reverse=True): |
gain = size_data['plus'] |
loss = size_data['minus'] |
delta = size_data['plus'] - size_data['minus'] |
- print (' Change: %d bytes (gained %d, lost %d)' % (delta, gain, loss)) |
+ header = ' %s - Source: %s - (gained %d, lost %d)' % (DeltaStr(delta), |
+ path, gain, loss) |
+ divider = '-' * len(header) |
+ print '' |
+ print divider |
+ print header |
+ print divider |
if showsymbols: |
- if path in new_symbols_by_path: |
- print ' New symbols:' |
- for symbol_name, symbol_type, size1, size2 in \ |
- new_symbols_by_path[path]: |
- print (' %s type=%s, size=%d bytes' % |
- (symbol_name, symbol_type, size2)) |
- if path in removed_symbols_by_path: |
- print ' Removed symbols:' |
+ if path in new_symbols.symbols_by_path: |
+ print ' New symbols:' |
for symbol_name, symbol_type, size1, size2 in \ |
- removed_symbols_by_path[path]: |
- print (' %s type=%s, size=%d bytes' % |
- (symbol_name, symbol_type, size1)) |
- if path in changed_symbols_by_path: |
- print ' Changed symbols:' |
- def sortkey(item): |
- symbol_name, _symbol_type, size1, size2 = item |
- return (size1 - size2, symbol_name) |
+ sorted(new_symbols.symbols_by_path[path], |
+ key=operator.itemgetter(3), |
+ reverse=True): |
+ print (' %8s: %s type=%s, size=%d bytes' % |
+ (DeltaStr(size2), symbol_name, symbol_type, size2)) |
+ if path in removed_symbols.symbols_by_path: |
+ print ' Removed symbols:' |
for symbol_name, symbol_type, size1, size2 in \ |
- sorted(changed_symbols_by_path[path], key=sortkey): |
- print (' %s type=%s, delta=%d bytes (was %d bytes, now %d bytes)' |
- % (symbol_name, symbol_type, (size2 - size1), size1, size2)) |
+ sorted(removed_symbols.symbols_by_path[path], |
+ key=operator.itemgetter(2)): |
+ print (' %8s: %s type=%s, size=%d bytes' % |
+ (DeltaStr(-size1), symbol_name, symbol_type, size1)) |
+ for (changed_symbols_by_path, type_str) in [ |
+ (grown_symbols.symbols_by_path, "Grown"), |
+ (shrunk_symbols.symbols_by_path, "Shrunk")]: |
+ if path in changed_symbols_by_path: |
+ print ' %s symbols:' % type_str |
+ def changed_symbol_sortkey(item): |
+ symbol_name, _symbol_type, size1, size2 = item |
+ return (size1 - size2, symbol_name) |
+ for symbol_name, symbol_type, size1, size2 in \ |
+ sorted(changed_symbols_by_path[path], key=changed_symbol_sortkey): |
+ print (' %8s: %s type=%s, (was %d bytes, now %d bytes)' |
+ % (DeltaStr(size2 - size1), symbol_name, |
+ symbol_type, size1, size2)) |
def main(): |