diff options
author | bratell <bratell@opera.com> | 2015-02-18 08:46:18 -0800 |
---|---|---|
committer | Commit bot <commit-bot@chromium.org> | 2015-02-18 16:47:06 +0000 |
commit | 5ec91b1d40fb6174d43d5f5cc84209b9a4505b6b (patch) | |
tree | 2b2c267eb1b11d9cf47895a9ae363d2f2c716107 /tools/binary_size | |
parent | 6d3e1fd0bcb68f3e0f26119b783945e31e104528 (diff) | |
download | chromium_src-5ec91b1d40fb6174d43d5f5cc84209b9a4505b6b.zip chromium_src-5ec91b1d40fb6174d43d5f5cc84209b9a4505b6b.tar.gz chromium_src-5ec91b1d40fb6174d43d5f5cc84209b9a4505b6b.tar.bz2 |
Handle shared memory symbols better in the binarysize tool.
The linker can let two symbols share the same memory space and then
it is wrong to count that memory space twice. Better to let each symbol
contribute with a proportional part of that symbol.
This only affects the explain_binary_size_delta program. The graphical
treeview will still use the full symbol size since it's valuable
information in that context.
BUG=
Review URL: https://codereview.chromium.org/397593007
Cr-Commit-Position: refs/heads/master@{#316844}
Diffstat (limited to 'tools/binary_size')
-rw-r--r-- | tools/binary_size/binary_size_utils.py | 8 | ||||
-rwxr-xr-x | tools/binary_size/explain_binary_size_delta.py | 234 | ||||
-rwxr-xr-x | tools/binary_size/explain_binary_size_delta_unittest.py | 417 | ||||
-rwxr-xr-x | tools/binary_size/run_binary_size_analysis.py | 4 |
4 files changed, 526 insertions, 137 deletions
diff --git a/tools/binary_size/binary_size_utils.py b/tools/binary_size/binary_size_utils.py index f265f61..5521ba7 100644 --- a/tools/binary_size/binary_size_utils.py +++ b/tools/binary_size/binary_size_utils.py @@ -23,7 +23,7 @@ def ParseNm(nm_lines): """ # Match lines with size, symbol, optional location, optional discriminator - sym_re = re.compile(r'^[0-9a-f]{8,} ' # address (8+ hex digits) + sym_re = re.compile(r'^([0-9a-f]{8,}) ' # address (8+ hex digits) '([0-9a-f]{8,}) ' # size (8+ hex digits) '(.) ' # symbol type, one character '([^\t]+)' # symbol name, separated from next by tab @@ -39,12 +39,12 @@ def ParseNm(nm_lines): line = line.rstrip() match = sym_re.match(line) if match: - size, sym_type, sym = match.groups()[0:3] + address, size, sym_type, sym = match.groups()[0:4] size = int(size, 16) if sym_type in ('B', 'b'): continue # skip all BSS for now. - path = match.group(4) - yield sym, sym_type, size, path + path = match.group(5) + yield sym, sym_type, size, path, address continue match = addr_re.match(line) if match: diff --git a/tools/binary_size/explain_binary_size_delta.py b/tools/binary_size/explain_binary_size_delta.py index d6eba26..8bc6c5a 100755 --- a/tools/binary_size/explain_binary_size_delta.py +++ b/tools/binary_size/explain_binary_size_delta.py @@ -38,6 +38,8 @@ dumps. Example: """ import collections +from collections import Counter +from math import ceil import operator import optparse import os @@ -46,11 +48,81 @@ import sys import binary_size_utils +def CalculateSharedAddresses(symbols): + """Checks how many symbols share the same memory space. This returns a +Counter result where result[address] will tell you how many times address was +used by symbols.""" + count = Counter() + for _, _, _, _, address in symbols: + count[address] += 1 + + return count + + +def CalculateEffectiveSize(share_count, address, symbol_size): + """Given a raw symbol_size and an address, this method returns the + size we should blame on this symbol considering it might share the + machine code/data with other symbols. Using the raw symbol_size for + each symbol would in those cases over estimate the true cost of that + block. + + """ + shared_count = share_count[address] + if shared_count == 1: + return symbol_size + + assert shared_count > 1 + return int(ceil(symbol_size / float(shared_count))) + +class SymbolDelta(object): + """Stores old size, new size and some metadata.""" + def __init__(self, shared): + self.old_size = None + self.new_size = None + self.shares_space_with_other_symbols = shared + + def __eq__(self, other): + return (self.old_size == other.old_size and + self.new_size == other.new_size and + self.shares_space_with_other_symbols == + other.shares_space_with_other_symbols) + + def __ne__(self, other): + return not self.__eq__(other) + + def copy_symbol_delta(self): + symbol_delta = SymbolDelta(self.shares_space_with_other_symbols) + symbol_delta.old_size = self.old_size + symbol_delta.new_size = self.new_size + return symbol_delta + +class DeltaInfo(SymbolDelta): + """Summary of a the change for one symbol between two instances.""" + def __init__(self, file_path, symbol_type, symbol_name, shared): + SymbolDelta.__init__(self, shared) + self.file_path = file_path + self.symbol_type = symbol_type + self.symbol_name = symbol_name + + def __eq__(self, other): + return (self.file_path == other.file_path and + self.symbol_type == other.symbol_type and + self.symbol_name == other.symbol_name and + SymbolDelta.__eq__(self, other)) + + def __ne__(self, other): + return not self.__eq__(other) + + def ExtractSymbolDelta(self): + """Returns a copy of the SymbolDelta for this DeltaInfo.""" + return SymbolDelta.copy_symbol_delta(self) + def Compare(symbols1, symbols2): """Executes a comparison of the symbols in symbols1 and symbols2. Returns: tuple of lists: (added_symbols, removed_symbols, changed_symbols, others) + where each list contains DeltaInfo objects. """ added = [] # tuples removed = [] # tuples @@ -59,9 +131,12 @@ def Compare(symbols1, symbols2): cache1 = {} cache2 = {} - # Make a map of (file, symbol_type) : (symbol_name, symbol_size) - for cache, symbols in ((cache1, symbols1), (cache2, symbols2)): - for symbol_name, symbol_type, symbol_size, file_path in symbols: + # Make a map of (file, symbol_type) : (symbol_name, effective_symbol_size) + share_count1 = CalculateSharedAddresses(symbols1) + share_count2 = CalculateSharedAddresses(symbols2) + for cache, symbols, share_count in ((cache1, symbols1, share_count1), + (cache2, symbols2, share_count2)): + for symbol_name, symbol_type, symbol_size, file_path, address in symbols: if 'vtable for ' in symbol_name: symbol_type = '@' # hack to categorize these separately if file_path: @@ -70,10 +145,15 @@ def Compare(symbols1, symbols2): file_path = file_path.replace('\\', '/') else: file_path = '(No Path)' + # Take into consideration that multiple symbols might share the same + # block of code. + effective_symbol_size = CalculateEffectiveSize(share_count, address, + symbol_size) key = (file_path, symbol_type) bucket = cache.setdefault(key, {}) size_list = bucket.setdefault(symbol_name, []) - size_list.append(symbol_size) + size_list.append((effective_symbol_size, + effective_symbol_size != symbol_size)) # Now diff them. We iterate over the elements in cache1. For each symbol # that we find in cache2, we record whether it was deleted, changed, or @@ -81,52 +161,69 @@ def Compare(symbols1, symbols2): # in cache2 at the end of the iteration over cache1 are the 'new' symbols. for key, bucket1 in cache1.items(): bucket2 = cache2.get(key) + file_path, symbol_type = key; if not bucket2: # A file was removed. Everything in bucket1 is dead. for symbol_name, symbol_size_list in bucket1.items(): - for symbol_size in symbol_size_list: - removed.append((key[0], key[1], symbol_name, symbol_size, None)) + for (symbol_size, shared) in symbol_size_list: + delta_info = DeltaInfo(file_path, symbol_type, symbol_name, shared) + delta_info.old_size = symbol_size + removed.append(delta_info) else: # File still exists, look for changes within. for symbol_name, symbol_size_list in bucket1.items(): size_list2 = bucket2.get(symbol_name) if size_list2 is None: # Symbol no longer exists in bucket2. - for symbol_size in symbol_size_list: - removed.append((key[0], key[1], symbol_name, symbol_size, None)) + for (symbol_size, shared) in symbol_size_list: + delta_info = DeltaInfo(file_path, symbol_type, symbol_name, shared) + delta_info.old_size = symbol_size + removed.append(delta_info) else: del bucket2[symbol_name] # Symbol is not new, delete from cache2. if len(symbol_size_list) == 1 and len(size_list2) == 1: - symbol_size = symbol_size_list[0] - size2 = size_list2[0] + symbol_size, shared1 = symbol_size_list[0] + size2, shared2 = size_list2[0] + delta_info = DeltaInfo(file_path, symbol_type, symbol_name, + shared1 or shared2) + delta_info.old_size = symbol_size + delta_info.new_size = size2 if symbol_size != size2: # Symbol has change size in bucket. - changed.append((key[0], key[1], symbol_name, symbol_size, size2)) + changed.append(delta_info) else: # Symbol is unchanged. - unchanged.append((key[0], key[1], symbol_name, symbol_size, - size2)) + unchanged.append(delta_info) else: # Complex comparison for when a symbol exists multiple times # in the same file (where file can be "unknown file"). symbol_size_counter = collections.Counter(symbol_size_list) delta_counter = collections.Counter(symbol_size_list) delta_counter.subtract(size_list2) - for symbol_size in sorted(delta_counter.keys()): - delta = delta_counter[symbol_size] - unchanged_count = symbol_size_counter[symbol_size] + for delta_counter_key in sorted(delta_counter.keys()): + delta = delta_counter[delta_counter_key] + unchanged_count = symbol_size_counter[delta_counter_key] + (symbol_size, shared) = delta_counter_key if delta > 0: unchanged_count -= delta for _ in range(unchanged_count): - unchanged.append((key[0], key[1], symbol_name, symbol_size, - symbol_size)) + delta_info = DeltaInfo(file_path, symbol_type, + symbol_name, shared) + delta_info.old_size = symbol_size + delta_info.new_size = symbol_size + unchanged.append(delta_info) if delta > 0: # Used to be more of these than there is now. for _ in range(delta): - removed.append((key[0], key[1], symbol_name, symbol_size, - None)) + delta_info = DeltaInfo(file_path, symbol_type, + symbol_name, shared) + delta_info.old_size = symbol_size + removed.append(delta_info) elif delta < 0: # More of this (symbol,size) now. for _ in range(-delta): - added.append((key[0], key[1], symbol_name, None, symbol_size)) + delta_info = DeltaInfo(file_path, symbol_type, + symbol_name, shared) + delta_info.new_size = symbol_size + added.append(delta_info) if len(bucket2) == 0: del cache1[key] # Entire bucket is empty, delete from cache2 @@ -135,11 +232,15 @@ def Compare(symbols1, symbols2): # the encountered symbols from cache2. What's left in cache2 is the new # symbols. for key, bucket2 in cache2.iteritems(): + file_path, symbol_type = key; for symbol_name, symbol_size_list in bucket2.items(): - for symbol_size in symbol_size_list: - added.append((key[0], key[1], symbol_name, None, symbol_size)) + for (symbol_size, shared) in symbol_size_list: + delta_info = DeltaInfo(file_path, symbol_type, symbol_name, shared) + delta_info.new_size = symbol_size + added.append(delta_info) 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.""" @@ -149,6 +250,16 @@ def DeltaStr(number): return result +def SharedInfoStr(symbol_info): + """Returns a string (prefixed by space) explaining that numbers are + adjusted because of shared space between symbols, or an empty string + if space had not been shared.""" + + if symbol_info.shares_space_with_other_symbols: + return " (adjusted sizes because of memory sharing)" + + return "" + class CrunchStatsData(object): """Stores a summary of data of a certain kind.""" def __init__(self, symbols): @@ -166,8 +277,7 @@ def CrunchStats(added, removed, changed, unchanged, showsources, showsymbols): grown = [] shrunk = [] for item in changed: - file_path, symbol_type, symbol_name, size1, size2 = item - if size1 < size2: + if item.old_size < item.new_size: grown.append(item) else: shrunk.append(item) @@ -178,14 +288,15 @@ def CrunchStats(added, removed, changed, unchanged, showsources, showsymbols): 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)) + for item in section.symbols: + section.sources.add(item.file_path) + if item.old_size is not None: + section.before_size += item.old_size + if item.new_size is not None: + section.after_size += item.new_size + bucket = section.symbols_by_path.setdefault(item.file_path, []) + bucket.append((item.symbol_name, item.symbol_type, + item.ExtractSymbolDelta())) total_change = sum(s.after_size - s.before_size for s in sections) summary = 'Total change: %s bytes' % DeltaStr(total_change) @@ -213,9 +324,9 @@ def CrunchStats(added, removed, changed, unchanged, showsources, showsymbols): maybe_unchanged_sources = set() unchanged_symbols_size = 0 - for file_path, symbol_type, symbol_name, size1, size2 in unchanged: - maybe_unchanged_sources.add(file_path) - unchanged_symbols_size += size1 # == size2 + for item in unchanged: + maybe_unchanged_sources.add(item.file_path) + unchanged_symbols_size += item.old_size # == item.new_size print(' %d unchanged, totalling %d bytes' % (len(unchanged), unchanged_symbols_size)) @@ -256,14 +367,14 @@ def CrunchStats(added, removed, changed, unchanged, showsources, showsymbols): if not entry: entry = {'plus': 0, 'minus': 0} delta_by_path[path] = entry - for symbol_name, symbol_type, size1, size2 in \ + for symbol_name, symbol_type, symbol_delta in \ section.symbols_by_path[path]: - if size1 is None: - delta = size2 - elif size2 is None: - delta = -size1 + if symbol_delta.old_size is None: + delta = symbol_delta.new_size + elif symbol_delta.new_size is None: + delta = -symbol_delta.old_size else: - delta = size2 - size1 + delta = symbol_delta.new_size - symbol_delta.old_size if delta > 0: entry['plus'] += delta @@ -288,34 +399,45 @@ def CrunchStats(added, removed, changed, unchanged, showsources, showsymbols): print header print divider if showsymbols: + def ExtractNewSize(tup): + symbol_delta = tup[2] + return symbol_delta.new_size + def ExtractOldSize(tup): + symbol_delta = tup[2] + return symbol_delta.old_size if path in new_symbols.symbols_by_path: print ' New symbols:' - for symbol_name, symbol_type, size1, size2 in \ + for symbol_name, symbol_type, symbol_delta in \ sorted(new_symbols.symbols_by_path[path], - key=operator.itemgetter(3), + key=ExtractNewSize, reverse=True): - print (' %8s: %s type=%s, size=%d bytes' % - (DeltaStr(size2), symbol_name, symbol_type, size2)) + print (' %8s: %s type=%s, size=%d bytes%s' % + (DeltaStr(symbol_delta.new_size), symbol_name, symbol_type, + symbol_delta.new_size, SharedInfoStr(symbol_delta))) if path in removed_symbols.symbols_by_path: print ' Removed symbols:' - for symbol_name, symbol_type, size1, size2 in \ + for symbol_name, symbol_type, symbol_delta in \ 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)) + key=ExtractOldSize): + print (' %8s: %s type=%s, size=%d bytes%s' % + (DeltaStr(-symbol_delta.old_size), symbol_name, symbol_type, + symbol_delta.old_size, + SharedInfoStr(symbol_delta))) 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 \ + symbol_name, _symbol_type, symbol_delta = item + return (symbol_delta.old_size - symbol_delta.new_size, symbol_name) + for symbol_name, symbol_type, symbol_delta 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)) + print (' %8s: %s type=%s, (was %d bytes, now %d bytes)%s' + % (DeltaStr(symbol_delta.new_size - symbol_delta.old_size), + symbol_name, symbol_type, + symbol_delta.old_size, symbol_delta.new_size, + SharedInfoStr(symbol_delta))) def main(): diff --git a/tools/binary_size/explain_binary_size_delta_unittest.py b/tools/binary_size/explain_binary_size_delta_unittest.py index 87ecb36..d818d83 100755 --- a/tools/binary_size/explain_binary_size_delta_unittest.py +++ b/tools/binary_size/explain_binary_size_delta_unittest.py @@ -15,114 +15,122 @@ import explain_binary_size_delta class ExplainBinarySizeDeltaTest(unittest.TestCase): def testCompare(self): - # List entries have form: symbol_name, symbol_type, symbol_size, file_path + # List entries have form: + # symbol_name, symbol_type, symbol_size, file_path, memory_address symbol_list1 = ( # File with one symbol, left as-is. - ( 'unchanged', 't', 1000, '/file_unchanged' ), + ( 'unchanged', 't', 1000, '/file_unchanged', 0x1 ), # File with one symbol, changed. - ( 'changed', 't', 1000, '/file_all_changed' ), + ( 'changed', 't', 1000, '/file_all_changed', 0x2 ), # File with one symbol, deleted. - ( 'removed', 't', 1000, '/file_all_deleted' ), + ( 'removed', 't', 1000, '/file_all_deleted', 0x3 ), # File with two symbols, one unchanged, one changed, same bucket - ( 'unchanged', 't', 1000, '/file_pair_unchanged_changed' ), - ( 'changed', 't', 1000, '/file_pair_unchanged_changed' ), + ( 'unchanged', 't', 1000, '/file_pair_unchanged_changed', 0x4 ), + ( 'changed', 't', 1000, '/file_pair_unchanged_changed', 0x5 ), # File with two symbols, one unchanged, one deleted, same bucket - ( 'unchanged', 't', 1000, '/file_pair_unchanged_removed' ), - ( 'removed', 't', 1000, '/file_pair_unchanged_removed' ), + ( 'unchanged', 't', 1000, '/file_pair_unchanged_removed', 0x6 ), + ( 'removed', 't', 1000, '/file_pair_unchanged_removed', 0x7 ), # File with two symbols, one unchanged, one added, same bucket - ( 'unchanged', 't', 1000, '/file_pair_unchanged_added' ), + ( 'unchanged', 't', 1000, '/file_pair_unchanged_added', 0x8 ), # File with two symbols, one unchanged, one changed, different bucket - ( 'unchanged', 't', 1000, '/file_pair_unchanged_diffbuck_changed' ), - ( 'changed', '@', 1000, '/file_pair_unchanged_diffbuck_changed' ), + ( 'unchanged', 't', 1000, '/file_pair_unchanged_diffbuck_changed', 0x9 ), + ( 'changed', '@', 1000, '/file_pair_unchanged_diffbuck_changed', 0xa ), # File with two symbols, one unchanged, one deleted, different bucket - ( 'unchanged', 't', 1000, '/file_pair_unchanged_diffbuck_removed' ), - ( 'removed', '@', 1000, '/file_pair_unchanged_diffbuck_removed' ), + ( 'unchanged', 't', 1000, '/file_pair_unchanged_diffbuck_removed', 0xb ), + ( 'removed', '@', 1000, '/file_pair_unchanged_diffbuck_removed', 0xc ), # File with two symbols, one unchanged, one added, different bucket - ( 'unchanged', 't', 1000, '/file_pair_unchanged_diffbuck_added' ), + ( 'unchanged', 't', 1000, '/file_pair_unchanged_diffbuck_added', 0xd ), # File with four symbols, one added, one removed, # one changed, one unchanged - ( 'size_changed', 't', 1000, '/file_tetra' ), - ( 'removed', 't', 1000, '/file_tetra' ), - ( 'unchanged', 't', 1000, '/file_tetra' ), + ( 'size_changed', 't', 1000, '/file_tetra', 0xe ), + ( 'removed', 't', 1000, '/file_tetra', 0xf ), + ( 'unchanged', 't', 1000, '/file_tetra', 0x10 ), ) symbol_list2 = ( # File with one symbol, left as-is. - ( 'unchanged', 't', 1000, '/file_unchanged' ), + ( 'unchanged', 't', 1000, '/file_unchanged', 0x1 ), # File with one symbol, changed. - ( 'changed', 't', 2000, '/file_all_changed' ), + ( 'changed', 't', 2000, '/file_all_changed', 0x2 ), # File with two symbols, one unchanged, one changed, same bucket - ( 'unchanged', 't', 1000, '/file_pair_unchanged_changed' ), - ( 'changed', 't', 2000, '/file_pair_unchanged_changed' ), + ( 'unchanged', 't', 1000, '/file_pair_unchanged_changed', 0x3 ), + ( 'changed', 't', 2000, '/file_pair_unchanged_changed', 0x4 ), # File with two symbols, one unchanged, one deleted, same bucket - ( 'unchanged', 't', 1000, '/file_pair_unchanged_removed' ), + ( 'unchanged', 't', 1000, '/file_pair_unchanged_removed', 0x5 ), # File with two symbols, one unchanged, one added, same bucket - ( 'unchanged', 't', 1000, '/file_pair_unchanged_added' ), - ( 'added', 't', 1000, '/file_pair_unchanged_added' ), + ( 'unchanged', 't', 1000, '/file_pair_unchanged_added', 0x6 ), + ( 'added', 't', 1000, '/file_pair_unchanged_added', 0x7 ), # File with two symbols, one unchanged, one changed, different bucket - ( 'unchanged', 't', 1000, '/file_pair_unchanged_diffbuck_changed' ), - ( 'changed', '@', 2000, '/file_pair_unchanged_diffbuck_changed' ), + ( 'unchanged', 't', 1000, '/file_pair_unchanged_diffbuck_changed', 0x8 ), + ( 'changed', '@', 2000, '/file_pair_unchanged_diffbuck_changed', 0x9 ), # File with two symbols, one unchanged, one deleted, different bucket - ( 'unchanged', 't', 1000, '/file_pair_unchanged_diffbuck_removed' ), + ( 'unchanged', 't', 1000, '/file_pair_unchanged_diffbuck_removed', 0xa ), # File with two symbols, one unchanged, one added, different bucket - ( 'unchanged', 't', 1000, '/file_pair_unchanged_diffbuck_added' ), - ( 'added', '@', 1000, '/file_pair_unchanged_diffbuck_added' ), + ( 'unchanged', 't', 1000, '/file_pair_unchanged_diffbuck_added', 0xb ), + ( 'added', '@', 1000, '/file_pair_unchanged_diffbuck_added', 0xc ), # File with four symbols, one added, one removed, # one changed, one unchanged - ( 'size_changed', 't', 2000, '/file_tetra' ), - ( 'unchanged', 't', 1000, '/file_tetra' ), - ( 'added', 't', 1000, '/file_tetra' ), + ( 'size_changed', 't', 2000, '/file_tetra', 0xd ), + ( 'unchanged', 't', 1000, '/file_tetra', 0xe ), + ( 'added', 't', 1000, '/file_tetra', 0xf ), # New file with one symbol added - ( 'added', 't', 1000, '/file_new' ), + ( 'added', 't', 1000, '/file_new', 0x10 ), ) # Here we go (added, removed, changed, unchanged) = \ explain_binary_size_delta.Compare(symbol_list1, symbol_list2) + def delta(file_path, symbol_type, symbol_name, old_size, new_size): + delta_info = explain_binary_size_delta.DeltaInfo( + file_path, symbol_type, symbol_name, False) + delta_info.old_size = old_size + delta_info.new_size = new_size + return delta_info + # File with one symbol, left as-is. - assert ('/file_unchanged', 't', 'unchanged', 1000, 1000) in unchanged + assert delta('/file_unchanged', 't', 'unchanged', 1000, 1000) in unchanged # File with one symbol, changed. - assert ('/file_all_changed', 't', 'changed', 1000, 2000) in changed + assert delta('/file_all_changed', 't', 'changed', 1000, 2000) in changed # File with one symbol, deleted. - assert ('/file_all_deleted', 't', 'removed', 1000, None) in removed + assert delta('/file_all_deleted', 't', 'removed', 1000, None) in removed # New file with one symbol added - assert ('/file_new', 't', 'added', None, 1000) in added + assert delta('/file_new', 't', 'added', None, 1000) in added # File with two symbols, one unchanged, one changed, same bucket - assert ('/file_pair_unchanged_changed', + assert delta('/file_pair_unchanged_changed', 't', 'unchanged', 1000, 1000) in unchanged - assert ('/file_pair_unchanged_changed', + assert delta('/file_pair_unchanged_changed', 't', 'changed', 1000, 2000) in changed # File with two symbols, one unchanged, one removed, same bucket - assert ('/file_pair_unchanged_removed', + assert delta('/file_pair_unchanged_removed', 't', 'unchanged', 1000, 1000) in unchanged - assert ('/file_pair_unchanged_removed', + assert delta('/file_pair_unchanged_removed', 't', 'removed', 1000, None) in removed # File with two symbols, one unchanged, one added, same bucket - assert ('/file_pair_unchanged_added', + assert delta('/file_pair_unchanged_added', 't', 'unchanged', 1000, 1000) in unchanged - assert ('/file_pair_unchanged_added', + assert delta('/file_pair_unchanged_added', 't', 'added', None, 1000) in added # File with two symbols, one unchanged, one changed, different bucket - assert ('/file_pair_unchanged_diffbuck_changed', + assert delta('/file_pair_unchanged_diffbuck_changed', 't', 'unchanged', 1000, 1000) in unchanged - assert ('/file_pair_unchanged_diffbuck_changed', + assert delta('/file_pair_unchanged_diffbuck_changed', '@', 'changed', 1000, 2000) in changed # File with two symbols, one unchanged, one removed, different bucket - assert ('/file_pair_unchanged_diffbuck_removed', + assert delta('/file_pair_unchanged_diffbuck_removed', 't', 'unchanged', 1000, 1000) in unchanged - assert ('/file_pair_unchanged_diffbuck_removed', + assert delta('/file_pair_unchanged_diffbuck_removed', '@', 'removed', 1000, None) in removed # File with two symbols, one unchanged, one added, different bucket - assert ('/file_pair_unchanged_diffbuck_added', + assert delta('/file_pair_unchanged_diffbuck_added', 't', 'unchanged', 1000, 1000) in unchanged - assert ('/file_pair_unchanged_diffbuck_added', + assert delta('/file_pair_unchanged_diffbuck_added', '@', 'added', None, 1000) in added # File with four symbols, one added, one removed, one changed, one unchanged - assert ('/file_tetra', 't', 'size_changed', 1000, 2000) in changed - assert ('/file_tetra', 't', 'unchanged', 1000, 1000) in unchanged - assert ('/file_tetra', 't', 'added', None, 1000) in added - assert ('/file_tetra', 't', 'removed', 1000, None) in removed + assert delta('/file_tetra', 't', 'size_changed', 1000, 2000) in changed + assert delta('/file_tetra', 't', 'unchanged', 1000, 1000) in unchanged + assert delta('/file_tetra', 't', 'added', None, 1000) in added + assert delta('/file_tetra', 't', 'removed', 1000, None) in removed # Now check final stats. orig_stdout = sys.stdout @@ -218,20 +226,20 @@ Per-source Analysis: self.maxDiff = None self.assertMultiLineEqual(expected_output, result) - print "explain_binary_size_delta_unittest: All tests passed" def testCompareStringEntries(self): - # List entries have form: symbol_name, symbol_type, symbol_size, file_path + # List entries have form: + # symbol_name, symbol_type, symbol_size, file_path, memory_address symbol_list1 = ( # File with one string. - ( '.L.str107', 'r', 8, '/file_with_strs' ), + ( '.L.str107', 'r', 8, '/file_with_strs', 0x1 ), ) symbol_list2 = ( # Two files with one string each, same name. - ( '.L.str107', 'r', 8, '/file_with_strs' ), - ( '.L.str107', 'r', 7, '/other_file_with_strs' ), + ( '.L.str107', 'r', 8, '/file_with_strs', 0x1 ), + ( '.L.str107', 'r', 7, '/other_file_with_strs', 0x2 ), ) # Here we go @@ -272,29 +280,29 @@ Per-source Analysis: self.maxDiff = None self.assertMultiLineEqual(expected_output, result) - print "explain_binary_size_delta_unittest: All tests passed" def testCompareStringEntriesWithNoFile(self): - # List entries have form: symbol_name, symbol_type, symbol_size, file_path + # List entries have form: + # symbol_name, symbol_type, symbol_size, file_path, memory_address symbol_list1 = ( - ( '.L.str104', 'r', 21, '??' ), # Will change size. - ( '.L.str105', 'r', 17, '??' ), # Same. - ( '.L.str106', 'r', 13, '??' ), # Will be removed. - ( '.L.str106', 'r', 3, '??' ), # Same. - ( '.L.str106', 'r', 3, '??' ), # Will be removed. - ( '.L.str107', 'r', 8, '??' ), # Will be removed (other sizes). + ( '.L.str104', 'r', 21, '??', 0x1 ), # Will change size. + ( '.L.str105', 'r', 17, '??', 0x2 ), # Same. + ( '.L.str106', 'r', 13, '??', 0x3 ), # Will be removed. + ( '.L.str106', 'r', 3, '??', 0x4 ), # Same. + ( '.L.str106', 'r', 3, '??', 0x5 ), # Will be removed. + ( '.L.str107', 'r', 8, '??', 0x6 ), # Will be removed (other sizes). ) symbol_list2 = ( # Two files with one string each, same name. - ( '.L.str104', 'r', 19, '??' ), # Changed. - ( '.L.str105', 'r', 11, '??' ), # New size for multi-symbol. - ( '.L.str105', 'r', 17, '??' ), # New of same size for multi-symbol. - ( '.L.str105', 'r', 17, '??' ), # Same. - ( '.L.str106', 'r', 3, '??' ), # Same. - ( '.L.str107', 'r', 5, '??' ), # New size for symbol. - ( '.L.str107', 'r', 7, '??' ), # New size for symbol. - ( '.L.str108', 'r', 8, '??' ), # New symbol. + ( '.L.str104', 'r', 19, '??', 0x1 ), # Changed. + ( '.L.str105', 'r', 11, '??', 0x2 ), # New size for multi-symbol. + ( '.L.str105', 'r', 17, '??', 0x3 ), # New of same size for multi-symbol. + ( '.L.str105', 'r', 17, '??', 0x4 ), # Same. + ( '.L.str106', 'r', 3, '??', 0x5 ), # Same. + ( '.L.str107', 'r', 5, '??', 0x6 ), # New size for symbol. + ( '.L.str107', 'r', 7, '??', 0x7 ), # New size for symbol. + ( '.L.str108', 'r', 8, '??', 0x8 ), # New symbol. ) # Here we go @@ -348,7 +356,266 @@ Per-source Analysis: self.maxDiff = None self.assertMultiLineEqual(expected_output, result) - print "explain_binary_size_delta_unittest: All tests passed" + + def testCompareSharedSpace(self): + # List entries have form: + # symbol_name, symbol_type, symbol_size, file_path, memory_address + symbol_list1 = ( + # File with two symbols, same address. + ( 'sym1', 'r', 8, '/file', 0x1 ), + ( 'sym2', 'r', 8, '/file', 0x1 ), + ) + + symbol_list2 = ( + # File with two symbols, same address. + ( 'sym1', 'r', 4, '/file', 0x1 ), + ( 'sym2', 'r', 4, '/file', 0x1 ), + ) + + # Here we go + (added, removed, changed, unchanged) = \ + explain_binary_size_delta.Compare(symbol_list1, symbol_list2) + + + # Now check final stats. + orig_stdout = sys.stdout + output_collector = cStringIO.StringIO() + sys.stdout = output_collector + try: + explain_binary_size_delta.CrunchStats(added, removed, changed, + unchanged, True, True) + finally: + sys.stdout = orig_stdout + result = output_collector.getvalue() + + expected_output = """\ +Total change: -4 bytes +====================== + 2 shrunk, for a net change of -4 bytes (8 bytes before, 4 bytes after) \ +across 1 sources + 0 unchanged, totalling 0 bytes +Source stats: + 1 sources encountered. + 0 completely new. + 0 removed completely. + 1 partially changed. + 0 completely unchanged. +Per-source Analysis: + +---------------------------------------- + -4 - Source: /file - (gained 0, lost 4) +---------------------------------------- + Shrunk symbols: + -2: sym1 type=r, (was 4 bytes, now 2 bytes) (adjusted sizes because \ +of memory sharing) + -2: sym2 type=r, (was 4 bytes, now 2 bytes) (adjusted sizes because \ +of memory sharing) +""" + + self.maxDiff = None + self.assertMultiLineEqual(expected_output, result) + + + def testCompareSharedSpaceDuplicateSymbols(self): + # List entries have form: + # symbol_name, symbol_type, symbol_size, file_path, memory_address + symbol_list1 = ( + # File with two symbols, same address. + ( 'sym1', 'r', 7, '/file', 0x2 ), + ( 'sym1', 'r', 8, '/file', 0x1 ), + ( 'sym2', 'r', 8, '/file', 0x1 ), + ) + + symbol_list2 = ( + # File with two symbols, same address. + ( 'sym1', 'r', 7, '/file', 0x2 ), + ( 'sym1', 'r', 4, '/file', 0x1 ), + ( 'sym2', 'r', 4, '/file', 0x1 ), + ) + + # Here we go + (added, removed, changed, unchanged) = \ + explain_binary_size_delta.Compare(symbol_list1, symbol_list2) + + + # Now check final stats. + orig_stdout = sys.stdout + output_collector = cStringIO.StringIO() + sys.stdout = output_collector + try: + explain_binary_size_delta.CrunchStats(added, removed, changed, + unchanged, True, True) + finally: + sys.stdout = orig_stdout + result = output_collector.getvalue() + + expected_output = """\ +Total change: -4 bytes +====================== + 1 added, totalling +2 bytes across 1 sources + 1 removed, totalling -4 bytes across 1 sources + 1 shrunk, for a net change of -2 bytes (4 bytes before, 2 bytes after) \ +across 1 sources + 1 unchanged, totalling 7 bytes +Source stats: + 1 sources encountered. + 0 completely new. + 0 removed completely. + 1 partially changed. + 0 completely unchanged. +Per-source Analysis: + +---------------------------------------- + -4 - Source: /file - (gained 2, lost 6) +---------------------------------------- + New symbols: + +2: sym1 type=r, size=2 bytes (adjusted sizes because of memory \ +sharing) + Removed symbols: + -4: sym1 type=r, size=4 bytes (adjusted sizes because of memory \ +sharing) + Shrunk symbols: + -2: sym2 type=r, (was 4 bytes, now 2 bytes) (adjusted sizes because \ +of memory sharing) +""" + + self.maxDiff = None + self.assertMultiLineEqual(expected_output, result) + + def testCompareSharedSpaceBecomingUnshared(self): + # List entries have form: + # symbol_name, symbol_type, symbol_size, file_path, memory_address + symbol_list1 = ( + # File with two symbols, same address. + ( 'sym1', 'r', 8, '/file', 0x1 ), + ( 'sym2', 'r', 8, '/file', 0x1 ), + ) + + symbol_list2 = ( + # File with two symbols, not the same address. + ( 'sym1', 'r', 8, '/file', 0x1 ), + ( 'sym2', 'r', 6, '/file', 0x2 ), + ) + + # Here we go + (added, removed, changed, unchanged) = \ + explain_binary_size_delta.Compare(symbol_list1, symbol_list2) + + + # Now check final stats. + orig_stdout = sys.stdout + output_collector = cStringIO.StringIO() + sys.stdout = output_collector + try: + explain_binary_size_delta.CrunchStats(added, removed, changed, + unchanged, True, True) + finally: + sys.stdout = orig_stdout + result = output_collector.getvalue() + + expected_output = """\ +Total change: +6 bytes +====================== + 2 grown, for a net change of +6 bytes (8 bytes before, 14 bytes after) \ +across 1 sources + 0 unchanged, totalling 0 bytes +Source stats: + 1 sources encountered. + 0 completely new. + 0 removed completely. + 1 partially changed. + 0 completely unchanged. +Per-source Analysis: + +---------------------------------------- + +6 - Source: /file - (gained 6, lost 0) +---------------------------------------- + Grown symbols: + +4: sym1 type=r, (was 4 bytes, now 8 bytes) (adjusted sizes because \ +of memory sharing) + +2: sym2 type=r, (was 4 bytes, now 6 bytes) (adjusted sizes because \ +of memory sharing) +""" + + self.maxDiff = None + self.assertMultiLineEqual(expected_output, result) + + def testCompareSymbolsBecomingUnshared(self): + # List entries have form: + # symbol_name, symbol_type, symbol_size, file_path, memory_address + symbol_list1 = ( + # File with two symbols, not the same address. + ( 'sym1', 'r', 8, '/file', 0x1 ), + ( 'sym2', 'r', 6, '/file', 0x2 ), + ) + + symbol_list2 = ( + # File with two symbols, same address. + ( 'sym1', 'r', 8, '/file', 0x1 ), + ( 'sym2', 'r', 8, '/file', 0x1 ), + ) + + # Here we go + (added, removed, changed, unchanged) = \ + explain_binary_size_delta.Compare(symbol_list1, symbol_list2) + + + # Now check final stats. + orig_stdout = sys.stdout + output_collector = cStringIO.StringIO() + sys.stdout = output_collector + try: + explain_binary_size_delta.CrunchStats(added, removed, changed, + unchanged, True, True) + finally: + sys.stdout = orig_stdout + result = output_collector.getvalue() + + expected_output = """\ +Total change: -6 bytes +====================== + 2 shrunk, for a net change of -6 bytes (14 bytes before, 8 bytes after) \ +across 1 sources + 0 unchanged, totalling 0 bytes +Source stats: + 1 sources encountered. + 0 completely new. + 0 removed completely. + 1 partially changed. + 0 completely unchanged. +Per-source Analysis: + +---------------------------------------- + -6 - Source: /file - (gained 0, lost 6) +---------------------------------------- + Shrunk symbols: + -2: sym2 type=r, (was 6 bytes, now 4 bytes) (adjusted sizes because \ +of memory sharing) + -4: sym1 type=r, (was 8 bytes, now 4 bytes) (adjusted sizes because \ +of memory sharing) +""" + + self.maxDiff = None + self.assertMultiLineEqual(expected_output, result) + + def testDeltaInfo(self): + x = explain_binary_size_delta.DeltaInfo("path", "t", "sym_name", False) + assert x == x + y = explain_binary_size_delta.DeltaInfo("path", "t", "sym_name", False) + assert x == y + + y.new_size = 12 + assert x != y + + x.new_size = 12 + assert x == y + + z = explain_binary_size_delta.DeltaInfo("path", "t", "sym_name", True) + assert not (x == z) + assert x != z + + w = explain_binary_size_delta.DeltaInfo("other_path", "t", "sym_name", True) + assert w != z if __name__ == '__main__': unittest.main() diff --git a/tools/binary_size/run_binary_size_analysis.py b/tools/binary_size/run_binary_size_analysis.py index 68982528..241fa64 100755 --- a/tools/binary_size/run_binary_size_analysis.py +++ b/tools/binary_size/run_binary_size_analysis.py @@ -169,7 +169,7 @@ def MakeCompactTree(symbols, symbol_path_origin_dir): NODE_MAX_DEPTH_KEY: 0} seen_symbol_with_path = False cwd = os.path.abspath(os.getcwd()) - for symbol_name, symbol_type, symbol_size, file_path in symbols: + for symbol_name, symbol_type, symbol_size, file_path, _address in symbols: if 'vtable for ' in symbol_name: symbol_type = '@' # hack to categorize these separately @@ -231,7 +231,7 @@ def DumpCompactTree(symbols, symbol_path_origin_dir, outfile): def MakeSourceMap(symbols): sources = {} - for _sym, _symbol_type, size, path in symbols: + for _sym, _symbol_type, size, path, _address in symbols: key = None if path: key = os.path.normpath(path) |