1cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)#!/usr/bin/env python 2cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)# Copyright 2014 The Chromium Authors. All rights reserved. 3cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)# Use of this source code is governed by a BSD-style license that can be 4cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)# found in the LICENSE file. 5cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) 6cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)"""Describe the size difference of two binaries. 7cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) 8cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)Generates a description of the size difference of two binaries based 9cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)on the difference of the size of various symbols. 10cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) 11cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)This tool needs "nm" dumps of each binary with full symbol 12cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)information. You can obtain the necessary dumps by running the 13cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)run_binary_size_analysis.py script upon each binary, with the 14cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)"--nm-out" parameter set to the location in which you want to save the 15cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)dumps. Example: 16cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) 17cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) # obtain symbol data from first binary in /tmp/nm1.dump 18cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) cd $CHECKOUT1_SRC 19cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) ninja -C out/Release binary_size_tool 20cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) tools/binary_size/run_binary_size_analysis \ 21cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) --library <path_to_library> 22cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) --destdir /tmp/throwaway 23cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) --nm-out /tmp/nm1.dump 24cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) 25cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) # obtain symbol data from second binary in /tmp/nm2.dump 26cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) cd $CHECKOUT2_SRC 27cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) ninja -C out/Release binary_size_tool 28cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) tools/binary_size/run_binary_size_analysis \ 29cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) --library <path_to_library> 30cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) --destdir /tmp/throwaway 31cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) --nm-out /tmp/nm2.dump 32cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) 33cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) # cleanup useless files 34cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) rm -r /tmp/throwaway 35cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) 36cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) # run this tool 37cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) explain_binary_size_delta.py --nm1 /tmp/nm1.dump --nm2 /tmp/nm2.dump 38cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)""" 39cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) 401320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucciimport collections 41116680a4aac90f2aa7413d9095a592090648e557Ben Murdochimport operator 42cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)import optparse 43cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)import os 44cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)import sys 45cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) 46cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)import binary_size_utils 47cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) 48cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) 49cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)def Compare(symbols1, symbols2): 50cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) """Executes a comparison of the symbols in symbols1 and symbols2. 51cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) 52cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) Returns: 53cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) tuple of lists: (added_symbols, removed_symbols, changed_symbols, others) 54cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) """ 55cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) added = [] # tuples 56cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) removed = [] # tuples 57cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) changed = [] # tuples 58cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) unchanged = [] # tuples 59cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) 60cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) cache1 = {} 61cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) cache2 = {} 62cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) # Make a map of (file, symbol_type) : (symbol_name, symbol_size) 63cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) for cache, symbols in ((cache1, symbols1), (cache2, symbols2)): 64cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) for symbol_name, symbol_type, symbol_size, file_path in symbols: 65cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) if 'vtable for ' in symbol_name: 66cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) symbol_type = '@' # hack to categorize these separately 67cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) if file_path: 68cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) file_path = os.path.normpath(file_path) 69cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) else: 70cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) file_path = '(No Path)' 71cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) key = (file_path, symbol_type) 72cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) bucket = cache.setdefault(key, {}) 731320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci size_list = bucket.setdefault(symbol_name, []) 741320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci size_list.append(symbol_size) 75cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) 76cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) # Now diff them. We iterate over the elements in cache1. For each symbol 77cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) # that we find in cache2, we record whether it was deleted, changed, or 78cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) # unchanged. We then remove it from cache2; all the symbols that remain 79cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) # in cache2 at the end of the iteration over cache1 are the 'new' symbols. 80cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) for key, bucket1 in cache1.items(): 81cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) bucket2 = cache2.get(key) 82cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) if not bucket2: 83cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) # A file was removed. Everything in bucket1 is dead. 841320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci for symbol_name, symbol_size_list in bucket1.items(): 851320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci for symbol_size in symbol_size_list: 861320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci removed.append((key[0], key[1], symbol_name, symbol_size, None)) 87cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) else: 88cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) # File still exists, look for changes within. 891320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci for symbol_name, symbol_size_list in bucket1.items(): 901320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci size_list2 = bucket2.get(symbol_name) 911320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci if size_list2 is None: 92cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) # Symbol no longer exists in bucket2. 931320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci for symbol_size in symbol_size_list: 941320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci removed.append((key[0], key[1], symbol_name, symbol_size, None)) 95cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) else: 96cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) del bucket2[symbol_name] # Symbol is not new, delete from cache2. 971320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci if len(symbol_size_list) == 1 and len(size_list2) == 1: 981320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci symbol_size = symbol_size_list[0] 991320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci size2 = size_list2[0] 1001320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci if symbol_size != size2: 1011320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci # Symbol has change size in bucket. 1021320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci changed.append((key[0], key[1], symbol_name, symbol_size, size2)) 1031320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci else: 1041320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci # Symbol is unchanged. 1051320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci unchanged.append((key[0], key[1], symbol_name, symbol_size, 1061320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci size2)) 1071320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci else: 1081320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci # Complex comparison for when a symbol exists multiple times 1091320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci # in the same file (where file can be "unknown file"). 1101320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci symbol_size_counter = collections.Counter(symbol_size_list) 1111320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci delta_counter = collections.Counter(symbol_size_list) 1121320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci delta_counter.subtract(size_list2) 1131320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci for symbol_size in sorted(delta_counter.keys()): 1141320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci delta = delta_counter[symbol_size] 1151320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci unchanged_count = symbol_size_counter[symbol_size] 1161320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci if delta > 0: 1171320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci unchanged_count -= delta 1181320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci for _ in range(unchanged_count): 1191320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci unchanged.append((key[0], key[1], symbol_name, symbol_size, 1201320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci symbol_size)) 1211320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci if delta > 0: # Used to be more of these than there is now. 1221320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci for _ in range(delta): 1231320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci removed.append((key[0], key[1], symbol_name, symbol_size, 1241320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci None)) 1251320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci elif delta < 0: # More of this (symbol,size) now. 1261320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci for _ in range(-delta): 1271320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci added.append((key[0], key[1], symbol_name, None, symbol_size)) 1281320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci 129cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) if len(bucket2) == 0: 130cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) del cache1[key] # Entire bucket is empty, delete from cache2 131cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) 132cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) # We have now analyzed all symbols that are in cache1 and removed all of 133cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) # the encountered symbols from cache2. What's left in cache2 is the new 134cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) # symbols. 135cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) for key, bucket2 in cache2.iteritems(): 1361320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci for symbol_name, symbol_size_list in bucket2.items(): 1371320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci for symbol_size in symbol_size_list: 1381320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci added.append((key[0], key[1], symbol_name, None, symbol_size)) 139cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) return (added, removed, changed, unchanged) 140cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) 141116680a4aac90f2aa7413d9095a592090648e557Ben Murdochdef DeltaStr(number): 142116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch """Returns the number as a string with a '+' prefix if it's > 0 and 143116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch a '-' prefix if it's < 0.""" 144116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch result = str(number) 145116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch if number > 0: 146116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch result = '+' + result 147116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch return result 148116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch 149116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch 150116680a4aac90f2aa7413d9095a592090648e557Ben Murdochclass CrunchStatsData(object): 151116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch """Stores a summary of data of a certain kind.""" 152116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch def __init__(self, symbols): 153116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch self.symbols = symbols 154116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch self.sources = set() 155116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch self.before_size = 0 156116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch self.after_size = 0 157116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch self.symbols_by_path = {} 158116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch 159cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) 160cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)def CrunchStats(added, removed, changed, unchanged, showsources, showsymbols): 161cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) """Outputs to stdout a summary of changes based on the symbol lists.""" 162116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch # Split changed into grown and shrunk because that is easier to 163116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch # discuss. 164116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch grown = [] 165116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch shrunk = [] 166116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch for item in changed: 167116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch file_path, symbol_type, symbol_name, size1, size2 = item 168116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch if size1 < size2: 169116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch grown.append(item) 170116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch else: 171116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch shrunk.append(item) 172116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch 173116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch new_symbols = CrunchStatsData(added) 174116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch removed_symbols = CrunchStatsData(removed) 175116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch grown_symbols = CrunchStatsData(grown) 176116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch shrunk_symbols = CrunchStatsData(shrunk) 177116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch sections = [new_symbols, removed_symbols, grown_symbols, shrunk_symbols] 178116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch for section in sections: 179116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch for file_path, symbol_type, symbol_name, size1, size2 in section.symbols: 180116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch section.sources.add(file_path) 181116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch if size1 is not None: 182116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch section.before_size += size1 183116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch if size2 is not None: 184116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch section.after_size += size2 185116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch bucket = section.symbols_by_path.setdefault(file_path, []) 186116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch bucket.append((symbol_name, symbol_type, size1, size2)) 187116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch 188116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch total_change = sum(s.after_size - s.before_size for s in sections) 189116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch summary = 'Total change: %s bytes' % DeltaStr(total_change) 190116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch print(summary) 191116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch print('=' * len(summary)) 192116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch for section in sections: 193116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch if not section.symbols: 194116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch continue 195116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch if section.before_size == 0: 196116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch description = ('added, totalling %s bytes' % DeltaStr(section.after_size)) 197116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch elif section.after_size == 0: 198116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch description = ('removed, totalling %s bytes' % 199116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch DeltaStr(-section.before_size)) 200116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch else: 201116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch if section.after_size > section.before_size: 202116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch type_str = 'grown' 203116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch else: 204116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch type_str = 'shrunk' 205116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch description = ('%s, for a net change of %s bytes ' 206116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch '(%d bytes before, %d bytes after)' % 207116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch (type_str, DeltaStr(section.after_size - section.before_size), 208116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch section.before_size, section.after_size)) 209116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch print(' %d %s across %d sources' % 210116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch (len(section.symbols), description, len(section.sources))) 211cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) 212cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) maybe_unchanged_sources = set() 213cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) unchanged_symbols_size = 0 214cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) for file_path, symbol_type, symbol_name, size1, size2 in unchanged: 215cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) maybe_unchanged_sources.add(file_path) 216cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) unchanged_symbols_size += size1 # == size2 217cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) print(' %d unchanged, totalling %d bytes' % 218cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) (len(unchanged), unchanged_symbols_size)) 219cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) 220cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) # High level analysis, always output. 221116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch unchanged_sources = maybe_unchanged_sources 222116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch for section in sections: 223116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch unchanged_sources = unchanged_sources - section.sources 224116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch new_sources = (new_symbols.sources - 225cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) maybe_unchanged_sources - 226116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch removed_symbols.sources) 227116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch removed_sources = (removed_symbols.sources - 228cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) maybe_unchanged_sources - 229116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch new_symbols.sources) 230116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch partially_changed_sources = (grown_symbols.sources | 231116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch shrunk_symbols.sources | new_symbols.sources | 232116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch removed_symbols.sources) - removed_sources - new_sources 233116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch allFiles = set() 234116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch for section in sections: 235116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch allFiles = allFiles | section.sources 236116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch allFiles = allFiles | maybe_unchanged_sources 237cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) print 'Source stats:' 238cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) print(' %d sources encountered.' % len(allFiles)) 239cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) print(' %d completely new.' % len(new_sources)) 240cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) print(' %d removed completely.' % len(removed_sources)) 241cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) print(' %d partially changed.' % len(partially_changed_sources)) 242cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) print(' %d completely unchanged.' % len(unchanged_sources)) 243cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) remainder = (allFiles - new_sources - removed_sources - 244cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) partially_changed_sources - unchanged_sources) 245cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) assert len(remainder) == 0 246cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) 247cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) if not showsources: 248cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) return # Per-source analysis, only if requested 249cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) print 'Per-source Analysis:' 250cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) delta_by_path = {} 251116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch for section in sections: 252116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch for path in section.symbols_by_path: 253116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch entry = delta_by_path.get(path) 254116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch if not entry: 255116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch entry = {'plus': 0, 'minus': 0} 256116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch delta_by_path[path] = entry 257116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch for symbol_name, symbol_type, size1, size2 in \ 258116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch section.symbols_by_path[path]: 259116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch if size1 is None: 260116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch delta = size2 261116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch elif size2 is None: 262116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch delta = -size1 263116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch else: 264116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch delta = size2 - size1 265cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) 266116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch if delta > 0: 267116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch entry['plus'] += delta 268116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch else: 269116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch entry['minus'] += (-1 * delta) 270116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch 271116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch def delta_sort_key(item): 272116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch _path, size_data = item 273116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch growth = size_data['plus'] - size_data['minus'] 274116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch return growth 275116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch 276116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch for path, size_data in sorted(delta_by_path.iteritems(), key=delta_sort_key, 277116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch reverse=True): 278cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) gain = size_data['plus'] 279cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) loss = size_data['minus'] 280cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) delta = size_data['plus'] - size_data['minus'] 281116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch header = ' %s - Source: %s - (gained %d, lost %d)' % (DeltaStr(delta), 282116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch path, gain, loss) 283116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch divider = '-' * len(header) 284116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch print '' 285116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch print divider 286116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch print header 287116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch print divider 288cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) if showsymbols: 289116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch if path in new_symbols.symbols_by_path: 290116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch print ' New symbols:' 291cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) for symbol_name, symbol_type, size1, size2 in \ 292116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch sorted(new_symbols.symbols_by_path[path], 293116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch key=operator.itemgetter(3), 294116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch reverse=True): 295116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch print (' %8s: %s type=%s, size=%d bytes' % 296116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch (DeltaStr(size2), symbol_name, symbol_type, size2)) 297116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch if path in removed_symbols.symbols_by_path: 298116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch print ' Removed symbols:' 299cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) for symbol_name, symbol_type, size1, size2 in \ 300116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch sorted(removed_symbols.symbols_by_path[path], 301116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch key=operator.itemgetter(2)): 302116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch print (' %8s: %s type=%s, size=%d bytes' % 303116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch (DeltaStr(-size1), symbol_name, symbol_type, size1)) 304116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch for (changed_symbols_by_path, type_str) in [ 305116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch (grown_symbols.symbols_by_path, "Grown"), 306116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch (shrunk_symbols.symbols_by_path, "Shrunk")]: 307116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch if path in changed_symbols_by_path: 308116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch print ' %s symbols:' % type_str 309116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch def changed_symbol_sortkey(item): 310116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch symbol_name, _symbol_type, size1, size2 = item 311116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch return (size1 - size2, symbol_name) 312116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch for symbol_name, symbol_type, size1, size2 in \ 313116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch sorted(changed_symbols_by_path[path], key=changed_symbol_sortkey): 314116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch print (' %8s: %s type=%s, (was %d bytes, now %d bytes)' 315116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch % (DeltaStr(size2 - size1), symbol_name, 316116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch symbol_type, size1, size2)) 317cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) 318cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) 319cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)def main(): 320cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) usage = """%prog [options] 321cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) 322cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) Analyzes the symbolic differences between two binary files 323cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) (typically, not necessarily, two different builds of the same 324cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) library) and produces a detailed description of symbols that have 325cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) been added, removed, or whose size has changed. 326cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) 327cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) Example: 328cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) explain_binary_size_delta.py --nm1 /tmp/nm1.dump --nm2 /tmp/nm2.dump 329cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) 330cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) Options are available via '--help'. 331cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) """ 332cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) parser = optparse.OptionParser(usage=usage) 333cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) parser.add_option('--nm1', metavar='PATH', 334cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) help='the nm dump of the first library') 335cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) parser.add_option('--nm2', metavar='PATH', 336cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) help='the nm dump of the second library') 337cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) parser.add_option('--showsources', action='store_true', default=False, 338cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) help='show per-source statistics') 339cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) parser.add_option('--showsymbols', action='store_true', default=False, 340cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) help='show all symbol information; implies --showfiles') 341cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) parser.add_option('--verbose', action='store_true', default=False, 342cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) help='output internal debugging stuff') 343cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) opts, _args = parser.parse_args() 344cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) 345cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) if not opts.nm1: 346cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) parser.error('--nm1 is required') 347cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) if not opts.nm2: 348cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) parser.error('--nm2 is required') 349cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) symbols = [] 350cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) for path in [opts.nm1, opts.nm2]: 351cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) with file(path, 'r') as nm_input: 352cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) if opts.verbose: 353cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) print 'parsing ' + path + '...' 354cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) symbols.append(list(binary_size_utils.ParseNm(nm_input))) 355cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) (added, removed, changed, unchanged) = Compare(symbols[0], symbols[1]) 356cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) CrunchStats(added, removed, changed, unchanged, 357cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) opts.showsources | opts.showsymbols, opts.showsymbols) 358cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) 359cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)if __name__ == '__main__': 360cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles) sys.exit(main()) 361