| OLD | NEW |
| 1 #!/usr/bin/env python | 1 #!/usr/bin/env python |
| 2 # Copyright 2014 The Chromium Authors. All rights reserved. | 2 # Copyright 2014 The Chromium Authors. All rights reserved. |
| 3 # Use of this source code is governed by a BSD-style license that can be | 3 # Use of this source code is governed by a BSD-style license that can be |
| 4 # found in the LICENSE file. | 4 # found in the LICENSE file. |
| 5 | 5 |
| 6 """Describe the size difference of two binaries. | 6 """Describe the size difference of two binaries. |
| 7 | 7 |
| 8 Generates a description of the size difference of two binaries based | 8 Generates a description of the size difference of two binaries based |
| 9 on the difference of the size of various symbols. | 9 on the difference of the size of various symbols. |
| 10 | 10 |
| (...skipping 20 matching lines...) Expand all Loading... |
| 31 --nm-out /tmp/nm2.dump | 31 --nm-out /tmp/nm2.dump |
| 32 | 32 |
| 33 # cleanup useless files | 33 # cleanup useless files |
| 34 rm -r /tmp/throwaway | 34 rm -r /tmp/throwaway |
| 35 | 35 |
| 36 # run this tool | 36 # run this tool |
| 37 explain_binary_size_delta.py --nm1 /tmp/nm1.dump --nm2 /tmp/nm2.dump | 37 explain_binary_size_delta.py --nm1 /tmp/nm1.dump --nm2 /tmp/nm2.dump |
| 38 """ | 38 """ |
| 39 | 39 |
| 40 import collections | 40 import collections |
| 41 from collections import Counter |
| 42 from math import ceil |
| 41 import operator | 43 import operator |
| 42 import optparse | 44 import optparse |
| 43 import os | 45 import os |
| 44 import sys | 46 import sys |
| 45 | 47 |
| 46 import binary_size_utils | 48 import binary_size_utils |
| 47 | 49 |
| 48 | 50 |
| 51 def CalculateSharedAddresses(symbols): |
| 52 """Checks how many symbols share the same memory space. This returns a |
| 53 Counter result where result[address] will tell you how many times address was |
| 54 used by symbols.""" |
| 55 count = Counter() |
| 56 for _, _, _, _, address in symbols: |
| 57 count[address] += 1 |
| 58 |
| 59 return count |
| 60 |
| 61 |
| 62 def CalculateEffectiveSize(share_count, address, symbol_size): |
| 63 """Given a raw symbol_size and an address, this method returns the |
| 64 size we should blame on this symbol considering it might share the |
| 65 machine code/data with other symbols. Using the raw symbol_size for |
| 66 each symbol would in those cases over estimate the true cost of that |
| 67 block. |
| 68 |
| 69 """ |
| 70 shared_count = share_count[address] |
| 71 if shared_count == 1: |
| 72 return symbol_size |
| 73 |
| 74 assert shared_count > 1 |
| 75 return int(ceil(symbol_size / float(shared_count))) |
| 76 |
| 77 class SymbolDelta(object): |
| 78 """Stores old size, new size and some metadata.""" |
| 79 def __init__(self, shared): |
| 80 self.old_size = None |
| 81 self.new_size = None |
| 82 self.shares_space_with_other_symbols = shared |
| 83 |
| 84 def __eq__(self, other): |
| 85 return (self.old_size == other.old_size and |
| 86 self.new_size == other.new_size and |
| 87 self.shares_space_with_other_symbols == |
| 88 other.shares_space_with_other_symbols) |
| 89 |
| 90 def __ne__(self, other): |
| 91 return not self.__eq__(other) |
| 92 |
| 93 def copy_symbol_delta(self): |
| 94 symbol_delta = SymbolDelta(self.shares_space_with_other_symbols) |
| 95 symbol_delta.old_size = self.old_size |
| 96 symbol_delta.new_size = self.new_size |
| 97 return symbol_delta |
| 98 |
| 99 class DeltaInfo(SymbolDelta): |
| 100 """Summary of a the change for one symbol between two instances.""" |
| 101 def __init__(self, file_path, symbol_type, symbol_name, shared): |
| 102 SymbolDelta.__init__(self, shared) |
| 103 self.file_path = file_path |
| 104 self.symbol_type = symbol_type |
| 105 self.symbol_name = symbol_name |
| 106 |
| 107 def __eq__(self, other): |
| 108 return (self.file_path == other.file_path and |
| 109 self.symbol_type == other.symbol_type and |
| 110 self.symbol_name == other.symbol_name and |
| 111 SymbolDelta.__eq__(self, other)) |
| 112 |
| 113 def __ne__(self, other): |
| 114 return not self.__eq__(other) |
| 115 |
| 116 def ExtractSymbolDelta(self): |
| 117 """Returns a copy of the SymbolDelta for this DeltaInfo.""" |
| 118 return SymbolDelta.copy_symbol_delta(self) |
| 119 |
| 49 def Compare(symbols1, symbols2): | 120 def Compare(symbols1, symbols2): |
| 50 """Executes a comparison of the symbols in symbols1 and symbols2. | 121 """Executes a comparison of the symbols in symbols1 and symbols2. |
| 51 | 122 |
| 52 Returns: | 123 Returns: |
| 53 tuple of lists: (added_symbols, removed_symbols, changed_symbols, others) | 124 tuple of lists: (added_symbols, removed_symbols, changed_symbols, others) |
| 125 where each list contains DeltaInfo objects. |
| 54 """ | 126 """ |
| 55 added = [] # tuples | 127 added = [] # tuples |
| 56 removed = [] # tuples | 128 removed = [] # tuples |
| 57 changed = [] # tuples | 129 changed = [] # tuples |
| 58 unchanged = [] # tuples | 130 unchanged = [] # tuples |
| 59 | 131 |
| 60 cache1 = {} | 132 cache1 = {} |
| 61 cache2 = {} | 133 cache2 = {} |
| 62 # Make a map of (file, symbol_type) : (symbol_name, symbol_size) | 134 # Make a map of (file, symbol_type) : (symbol_name, effective_symbol_size) |
| 63 for cache, symbols in ((cache1, symbols1), (cache2, symbols2)): | 135 share_count1 = CalculateSharedAddresses(symbols1) |
| 64 for symbol_name, symbol_type, symbol_size, file_path in symbols: | 136 share_count2 = CalculateSharedAddresses(symbols2) |
| 137 for cache, symbols, share_count in ((cache1, symbols1, share_count1), |
| 138 (cache2, symbols2, share_count2)): |
| 139 for symbol_name, symbol_type, symbol_size, file_path, address in symbols: |
| 65 if 'vtable for ' in symbol_name: | 140 if 'vtable for ' in symbol_name: |
| 66 symbol_type = '@' # hack to categorize these separately | 141 symbol_type = '@' # hack to categorize these separately |
| 67 if file_path: | 142 if file_path: |
| 68 file_path = os.path.normpath(file_path) | 143 file_path = os.path.normpath(file_path) |
| 69 if sys.platform.startswith('win'): | 144 if sys.platform.startswith('win'): |
| 70 file_path = file_path.replace('\\', '/') | 145 file_path = file_path.replace('\\', '/') |
| 71 else: | 146 else: |
| 72 file_path = '(No Path)' | 147 file_path = '(No Path)' |
| 148 # Take into consideration that multiple symbols might share the same |
| 149 # block of code. |
| 150 effective_symbol_size = CalculateEffectiveSize(share_count, address, |
| 151 symbol_size) |
| 73 key = (file_path, symbol_type) | 152 key = (file_path, symbol_type) |
| 74 bucket = cache.setdefault(key, {}) | 153 bucket = cache.setdefault(key, {}) |
| 75 size_list = bucket.setdefault(symbol_name, []) | 154 size_list = bucket.setdefault(symbol_name, []) |
| 76 size_list.append(symbol_size) | 155 size_list.append((effective_symbol_size, |
| 156 effective_symbol_size != symbol_size)) |
| 77 | 157 |
| 78 # Now diff them. We iterate over the elements in cache1. For each symbol | 158 # Now diff them. We iterate over the elements in cache1. For each symbol |
| 79 # that we find in cache2, we record whether it was deleted, changed, or | 159 # that we find in cache2, we record whether it was deleted, changed, or |
| 80 # unchanged. We then remove it from cache2; all the symbols that remain | 160 # unchanged. We then remove it from cache2; all the symbols that remain |
| 81 # in cache2 at the end of the iteration over cache1 are the 'new' symbols. | 161 # in cache2 at the end of the iteration over cache1 are the 'new' symbols. |
| 82 for key, bucket1 in cache1.items(): | 162 for key, bucket1 in cache1.items(): |
| 83 bucket2 = cache2.get(key) | 163 bucket2 = cache2.get(key) |
| 164 file_path, symbol_type = key; |
| 84 if not bucket2: | 165 if not bucket2: |
| 85 # A file was removed. Everything in bucket1 is dead. | 166 # A file was removed. Everything in bucket1 is dead. |
| 86 for symbol_name, symbol_size_list in bucket1.items(): | 167 for symbol_name, symbol_size_list in bucket1.items(): |
| 87 for symbol_size in symbol_size_list: | 168 for (symbol_size, shared) in symbol_size_list: |
| 88 removed.append((key[0], key[1], symbol_name, symbol_size, None)) | 169 delta_info = DeltaInfo(file_path, symbol_type, symbol_name, shared) |
| 170 delta_info.old_size = symbol_size |
| 171 removed.append(delta_info) |
| 89 else: | 172 else: |
| 90 # File still exists, look for changes within. | 173 # File still exists, look for changes within. |
| 91 for symbol_name, symbol_size_list in bucket1.items(): | 174 for symbol_name, symbol_size_list in bucket1.items(): |
| 92 size_list2 = bucket2.get(symbol_name) | 175 size_list2 = bucket2.get(symbol_name) |
| 93 if size_list2 is None: | 176 if size_list2 is None: |
| 94 # Symbol no longer exists in bucket2. | 177 # Symbol no longer exists in bucket2. |
| 95 for symbol_size in symbol_size_list: | 178 for (symbol_size, shared) in symbol_size_list: |
| 96 removed.append((key[0], key[1], symbol_name, symbol_size, None)) | 179 delta_info = DeltaInfo(file_path, symbol_type, symbol_name, shared) |
| 180 delta_info.old_size = symbol_size |
| 181 removed.append(delta_info) |
| 97 else: | 182 else: |
| 98 del bucket2[symbol_name] # Symbol is not new, delete from cache2. | 183 del bucket2[symbol_name] # Symbol is not new, delete from cache2. |
| 99 if len(symbol_size_list) == 1 and len(size_list2) == 1: | 184 if len(symbol_size_list) == 1 and len(size_list2) == 1: |
| 100 symbol_size = symbol_size_list[0] | 185 symbol_size, shared1 = symbol_size_list[0] |
| 101 size2 = size_list2[0] | 186 size2, shared2 = size_list2[0] |
| 187 delta_info = DeltaInfo(file_path, symbol_type, symbol_name, |
| 188 shared1 or shared2) |
| 189 delta_info.old_size = symbol_size |
| 190 delta_info.new_size = size2 |
| 102 if symbol_size != size2: | 191 if symbol_size != size2: |
| 103 # Symbol has change size in bucket. | 192 # Symbol has change size in bucket. |
| 104 changed.append((key[0], key[1], symbol_name, symbol_size, size2)) | 193 changed.append(delta_info) |
| 105 else: | 194 else: |
| 106 # Symbol is unchanged. | 195 # Symbol is unchanged. |
| 107 unchanged.append((key[0], key[1], symbol_name, symbol_size, | 196 unchanged.append(delta_info) |
| 108 size2)) | |
| 109 else: | 197 else: |
| 110 # Complex comparison for when a symbol exists multiple times | 198 # Complex comparison for when a symbol exists multiple times |
| 111 # in the same file (where file can be "unknown file"). | 199 # in the same file (where file can be "unknown file"). |
| 112 symbol_size_counter = collections.Counter(symbol_size_list) | 200 symbol_size_counter = collections.Counter(symbol_size_list) |
| 113 delta_counter = collections.Counter(symbol_size_list) | 201 delta_counter = collections.Counter(symbol_size_list) |
| 114 delta_counter.subtract(size_list2) | 202 delta_counter.subtract(size_list2) |
| 115 for symbol_size in sorted(delta_counter.keys()): | 203 for delta_counter_key in sorted(delta_counter.keys()): |
| 116 delta = delta_counter[symbol_size] | 204 delta = delta_counter[delta_counter_key] |
| 117 unchanged_count = symbol_size_counter[symbol_size] | 205 unchanged_count = symbol_size_counter[delta_counter_key] |
| 206 (symbol_size, shared) = delta_counter_key |
| 118 if delta > 0: | 207 if delta > 0: |
| 119 unchanged_count -= delta | 208 unchanged_count -= delta |
| 120 for _ in range(unchanged_count): | 209 for _ in range(unchanged_count): |
| 121 unchanged.append((key[0], key[1], symbol_name, symbol_size, | 210 delta_info = DeltaInfo(file_path, symbol_type, |
| 122 symbol_size)) | 211 symbol_name, shared) |
| 212 delta_info.old_size = symbol_size |
| 213 delta_info.new_size = symbol_size |
| 214 unchanged.append(delta_info) |
| 123 if delta > 0: # Used to be more of these than there is now. | 215 if delta > 0: # Used to be more of these than there is now. |
| 124 for _ in range(delta): | 216 for _ in range(delta): |
| 125 removed.append((key[0], key[1], symbol_name, symbol_size, | 217 delta_info = DeltaInfo(file_path, symbol_type, |
| 126 None)) | 218 symbol_name, shared) |
| 219 delta_info.old_size = symbol_size |
| 220 removed.append(delta_info) |
| 127 elif delta < 0: # More of this (symbol,size) now. | 221 elif delta < 0: # More of this (symbol,size) now. |
| 128 for _ in range(-delta): | 222 for _ in range(-delta): |
| 129 added.append((key[0], key[1], symbol_name, None, symbol_size)) | 223 delta_info = DeltaInfo(file_path, symbol_type, |
| 224 symbol_name, shared) |
| 225 delta_info.new_size = symbol_size |
| 226 added.append(delta_info) |
| 130 | 227 |
| 131 if len(bucket2) == 0: | 228 if len(bucket2) == 0: |
| 132 del cache1[key] # Entire bucket is empty, delete from cache2 | 229 del cache1[key] # Entire bucket is empty, delete from cache2 |
| 133 | 230 |
| 134 # We have now analyzed all symbols that are in cache1 and removed all of | 231 # We have now analyzed all symbols that are in cache1 and removed all of |
| 135 # the encountered symbols from cache2. What's left in cache2 is the new | 232 # the encountered symbols from cache2. What's left in cache2 is the new |
| 136 # symbols. | 233 # symbols. |
| 137 for key, bucket2 in cache2.iteritems(): | 234 for key, bucket2 in cache2.iteritems(): |
| 235 file_path, symbol_type = key; |
| 138 for symbol_name, symbol_size_list in bucket2.items(): | 236 for symbol_name, symbol_size_list in bucket2.items(): |
| 139 for symbol_size in symbol_size_list: | 237 for (symbol_size, shared) in symbol_size_list: |
| 140 added.append((key[0], key[1], symbol_name, None, symbol_size)) | 238 delta_info = DeltaInfo(file_path, symbol_type, symbol_name, shared) |
| 239 delta_info.new_size = symbol_size |
| 240 added.append(delta_info) |
| 141 return (added, removed, changed, unchanged) | 241 return (added, removed, changed, unchanged) |
| 142 | 242 |
| 243 |
| 143 def DeltaStr(number): | 244 def DeltaStr(number): |
| 144 """Returns the number as a string with a '+' prefix if it's > 0 and | 245 """Returns the number as a string with a '+' prefix if it's > 0 and |
| 145 a '-' prefix if it's < 0.""" | 246 a '-' prefix if it's < 0.""" |
| 146 result = str(number) | 247 result = str(number) |
| 147 if number > 0: | 248 if number > 0: |
| 148 result = '+' + result | 249 result = '+' + result |
| 149 return result | 250 return result |
| 150 | 251 |
| 151 | 252 |
| 253 def SharedInfoStr(symbol_info): |
| 254 """Returns a string (prefixed by space) explaining that numbers are |
| 255 adjusted because of shared space between symbols, or an empty string |
| 256 if space had not been shared.""" |
| 257 |
| 258 if symbol_info.shares_space_with_other_symbols: |
| 259 return " (adjusted sizes because of memory sharing)" |
| 260 |
| 261 return "" |
| 262 |
| 152 class CrunchStatsData(object): | 263 class CrunchStatsData(object): |
| 153 """Stores a summary of data of a certain kind.""" | 264 """Stores a summary of data of a certain kind.""" |
| 154 def __init__(self, symbols): | 265 def __init__(self, symbols): |
| 155 self.symbols = symbols | 266 self.symbols = symbols |
| 156 self.sources = set() | 267 self.sources = set() |
| 157 self.before_size = 0 | 268 self.before_size = 0 |
| 158 self.after_size = 0 | 269 self.after_size = 0 |
| 159 self.symbols_by_path = {} | 270 self.symbols_by_path = {} |
| 160 | 271 |
| 161 | 272 |
| 162 def CrunchStats(added, removed, changed, unchanged, showsources, showsymbols): | 273 def CrunchStats(added, removed, changed, unchanged, showsources, showsymbols): |
| 163 """Outputs to stdout a summary of changes based on the symbol lists.""" | 274 """Outputs to stdout a summary of changes based on the symbol lists.""" |
| 164 # Split changed into grown and shrunk because that is easier to | 275 # Split changed into grown and shrunk because that is easier to |
| 165 # discuss. | 276 # discuss. |
| 166 grown = [] | 277 grown = [] |
| 167 shrunk = [] | 278 shrunk = [] |
| 168 for item in changed: | 279 for item in changed: |
| 169 file_path, symbol_type, symbol_name, size1, size2 = item | 280 if item.old_size < item.new_size: |
| 170 if size1 < size2: | |
| 171 grown.append(item) | 281 grown.append(item) |
| 172 else: | 282 else: |
| 173 shrunk.append(item) | 283 shrunk.append(item) |
| 174 | 284 |
| 175 new_symbols = CrunchStatsData(added) | 285 new_symbols = CrunchStatsData(added) |
| 176 removed_symbols = CrunchStatsData(removed) | 286 removed_symbols = CrunchStatsData(removed) |
| 177 grown_symbols = CrunchStatsData(grown) | 287 grown_symbols = CrunchStatsData(grown) |
| 178 shrunk_symbols = CrunchStatsData(shrunk) | 288 shrunk_symbols = CrunchStatsData(shrunk) |
| 179 sections = [new_symbols, removed_symbols, grown_symbols, shrunk_symbols] | 289 sections = [new_symbols, removed_symbols, grown_symbols, shrunk_symbols] |
| 180 for section in sections: | 290 for section in sections: |
| 181 for file_path, symbol_type, symbol_name, size1, size2 in section.symbols: | 291 for item in section.symbols: |
| 182 section.sources.add(file_path) | 292 section.sources.add(item.file_path) |
| 183 if size1 is not None: | 293 if item.old_size is not None: |
| 184 section.before_size += size1 | 294 section.before_size += item.old_size |
| 185 if size2 is not None: | 295 if item.new_size is not None: |
| 186 section.after_size += size2 | 296 section.after_size += item.new_size |
| 187 bucket = section.symbols_by_path.setdefault(file_path, []) | 297 bucket = section.symbols_by_path.setdefault(item.file_path, []) |
| 188 bucket.append((symbol_name, symbol_type, size1, size2)) | 298 bucket.append((item.symbol_name, item.symbol_type, |
| 299 item.ExtractSymbolDelta())) |
| 189 | 300 |
| 190 total_change = sum(s.after_size - s.before_size for s in sections) | 301 total_change = sum(s.after_size - s.before_size for s in sections) |
| 191 summary = 'Total change: %s bytes' % DeltaStr(total_change) | 302 summary = 'Total change: %s bytes' % DeltaStr(total_change) |
| 192 print(summary) | 303 print(summary) |
| 193 print('=' * len(summary)) | 304 print('=' * len(summary)) |
| 194 for section in sections: | 305 for section in sections: |
| 195 if not section.symbols: | 306 if not section.symbols: |
| 196 continue | 307 continue |
| 197 if section.before_size == 0: | 308 if section.before_size == 0: |
| 198 description = ('added, totalling %s bytes' % DeltaStr(section.after_size)) | 309 description = ('added, totalling %s bytes' % DeltaStr(section.after_size)) |
| 199 elif section.after_size == 0: | 310 elif section.after_size == 0: |
| 200 description = ('removed, totalling %s bytes' % | 311 description = ('removed, totalling %s bytes' % |
| 201 DeltaStr(-section.before_size)) | 312 DeltaStr(-section.before_size)) |
| 202 else: | 313 else: |
| 203 if section.after_size > section.before_size: | 314 if section.after_size > section.before_size: |
| 204 type_str = 'grown' | 315 type_str = 'grown' |
| 205 else: | 316 else: |
| 206 type_str = 'shrunk' | 317 type_str = 'shrunk' |
| 207 description = ('%s, for a net change of %s bytes ' | 318 description = ('%s, for a net change of %s bytes ' |
| 208 '(%d bytes before, %d bytes after)' % | 319 '(%d bytes before, %d bytes after)' % |
| 209 (type_str, DeltaStr(section.after_size - section.before_size), | 320 (type_str, DeltaStr(section.after_size - section.before_size), |
| 210 section.before_size, section.after_size)) | 321 section.before_size, section.after_size)) |
| 211 print(' %d %s across %d sources' % | 322 print(' %d %s across %d sources' % |
| 212 (len(section.symbols), description, len(section.sources))) | 323 (len(section.symbols), description, len(section.sources))) |
| 213 | 324 |
| 214 maybe_unchanged_sources = set() | 325 maybe_unchanged_sources = set() |
| 215 unchanged_symbols_size = 0 | 326 unchanged_symbols_size = 0 |
| 216 for file_path, symbol_type, symbol_name, size1, size2 in unchanged: | 327 for item in unchanged: |
| 217 maybe_unchanged_sources.add(file_path) | 328 maybe_unchanged_sources.add(item.file_path) |
| 218 unchanged_symbols_size += size1 # == size2 | 329 unchanged_symbols_size += item.old_size # == item.new_size |
| 219 print(' %d unchanged, totalling %d bytes' % | 330 print(' %d unchanged, totalling %d bytes' % |
| 220 (len(unchanged), unchanged_symbols_size)) | 331 (len(unchanged), unchanged_symbols_size)) |
| 221 | 332 |
| 222 # High level analysis, always output. | 333 # High level analysis, always output. |
| 223 unchanged_sources = maybe_unchanged_sources | 334 unchanged_sources = maybe_unchanged_sources |
| 224 for section in sections: | 335 for section in sections: |
| 225 unchanged_sources = unchanged_sources - section.sources | 336 unchanged_sources = unchanged_sources - section.sources |
| 226 new_sources = (new_symbols.sources - | 337 new_sources = (new_symbols.sources - |
| 227 maybe_unchanged_sources - | 338 maybe_unchanged_sources - |
| 228 removed_symbols.sources) | 339 removed_symbols.sources) |
| (...skipping 20 matching lines...) Expand all Loading... |
| 249 if not showsources: | 360 if not showsources: |
| 250 return # Per-source analysis, only if requested | 361 return # Per-source analysis, only if requested |
| 251 print 'Per-source Analysis:' | 362 print 'Per-source Analysis:' |
| 252 delta_by_path = {} | 363 delta_by_path = {} |
| 253 for section in sections: | 364 for section in sections: |
| 254 for path in section.symbols_by_path: | 365 for path in section.symbols_by_path: |
| 255 entry = delta_by_path.get(path) | 366 entry = delta_by_path.get(path) |
| 256 if not entry: | 367 if not entry: |
| 257 entry = {'plus': 0, 'minus': 0} | 368 entry = {'plus': 0, 'minus': 0} |
| 258 delta_by_path[path] = entry | 369 delta_by_path[path] = entry |
| 259 for symbol_name, symbol_type, size1, size2 in \ | 370 for symbol_name, symbol_type, symbol_delta in \ |
| 260 section.symbols_by_path[path]: | 371 section.symbols_by_path[path]: |
| 261 if size1 is None: | 372 if symbol_delta.old_size is None: |
| 262 delta = size2 | 373 delta = symbol_delta.new_size |
| 263 elif size2 is None: | 374 elif symbol_delta.new_size is None: |
| 264 delta = -size1 | 375 delta = -symbol_delta.old_size |
| 265 else: | 376 else: |
| 266 delta = size2 - size1 | 377 delta = symbol_delta.new_size - symbol_delta.old_size |
| 267 | 378 |
| 268 if delta > 0: | 379 if delta > 0: |
| 269 entry['plus'] += delta | 380 entry['plus'] += delta |
| 270 else: | 381 else: |
| 271 entry['minus'] += (-1 * delta) | 382 entry['minus'] += (-1 * delta) |
| 272 | 383 |
| 273 def delta_sort_key(item): | 384 def delta_sort_key(item): |
| 274 _path, size_data = item | 385 _path, size_data = item |
| 275 growth = size_data['plus'] - size_data['minus'] | 386 growth = size_data['plus'] - size_data['minus'] |
| 276 return growth | 387 return growth |
| 277 | 388 |
| 278 for path, size_data in sorted(delta_by_path.iteritems(), key=delta_sort_key, | 389 for path, size_data in sorted(delta_by_path.iteritems(), key=delta_sort_key, |
| 279 reverse=True): | 390 reverse=True): |
| 280 gain = size_data['plus'] | 391 gain = size_data['plus'] |
| 281 loss = size_data['minus'] | 392 loss = size_data['minus'] |
| 282 delta = size_data['plus'] - size_data['minus'] | 393 delta = size_data['plus'] - size_data['minus'] |
| 283 header = ' %s - Source: %s - (gained %d, lost %d)' % (DeltaStr(delta), | 394 header = ' %s - Source: %s - (gained %d, lost %d)' % (DeltaStr(delta), |
| 284 path, gain, loss) | 395 path, gain, loss) |
| 285 divider = '-' * len(header) | 396 divider = '-' * len(header) |
| 286 print '' | 397 print '' |
| 287 print divider | 398 print divider |
| 288 print header | 399 print header |
| 289 print divider | 400 print divider |
| 290 if showsymbols: | 401 if showsymbols: |
| 402 def ExtractNewSize(tup): |
| 403 symbol_delta = tup[2] |
| 404 return symbol_delta.new_size |
| 405 def ExtractOldSize(tup): |
| 406 symbol_delta = tup[2] |
| 407 return symbol_delta.old_size |
| 291 if path in new_symbols.symbols_by_path: | 408 if path in new_symbols.symbols_by_path: |
| 292 print ' New symbols:' | 409 print ' New symbols:' |
| 293 for symbol_name, symbol_type, size1, size2 in \ | 410 for symbol_name, symbol_type, symbol_delta in \ |
| 294 sorted(new_symbols.symbols_by_path[path], | 411 sorted(new_symbols.symbols_by_path[path], |
| 295 key=operator.itemgetter(3), | 412 key=ExtractNewSize, |
| 296 reverse=True): | 413 reverse=True): |
| 297 print (' %8s: %s type=%s, size=%d bytes' % | 414 print (' %8s: %s type=%s, size=%d bytes%s' % |
| 298 (DeltaStr(size2), symbol_name, symbol_type, size2)) | 415 (DeltaStr(symbol_delta.new_size), symbol_name, symbol_type, |
| 416 symbol_delta.new_size, SharedInfoStr(symbol_delta))) |
| 299 if path in removed_symbols.symbols_by_path: | 417 if path in removed_symbols.symbols_by_path: |
| 300 print ' Removed symbols:' | 418 print ' Removed symbols:' |
| 301 for symbol_name, symbol_type, size1, size2 in \ | 419 for symbol_name, symbol_type, symbol_delta in \ |
| 302 sorted(removed_symbols.symbols_by_path[path], | 420 sorted(removed_symbols.symbols_by_path[path], |
| 303 key=operator.itemgetter(2)): | 421 key=ExtractOldSize): |
| 304 print (' %8s: %s type=%s, size=%d bytes' % | 422 print (' %8s: %s type=%s, size=%d bytes%s' % |
| 305 (DeltaStr(-size1), symbol_name, symbol_type, size1)) | 423 (DeltaStr(-symbol_delta.old_size), symbol_name, symbol_type, |
| 424 symbol_delta.old_size, |
| 425 SharedInfoStr(symbol_delta))) |
| 306 for (changed_symbols_by_path, type_str) in [ | 426 for (changed_symbols_by_path, type_str) in [ |
| 307 (grown_symbols.symbols_by_path, "Grown"), | 427 (grown_symbols.symbols_by_path, "Grown"), |
| 308 (shrunk_symbols.symbols_by_path, "Shrunk")]: | 428 (shrunk_symbols.symbols_by_path, "Shrunk")]: |
| 309 if path in changed_symbols_by_path: | 429 if path in changed_symbols_by_path: |
| 310 print ' %s symbols:' % type_str | 430 print ' %s symbols:' % type_str |
| 311 def changed_symbol_sortkey(item): | 431 def changed_symbol_sortkey(item): |
| 312 symbol_name, _symbol_type, size1, size2 = item | 432 symbol_name, _symbol_type, symbol_delta = item |
| 313 return (size1 - size2, symbol_name) | 433 return (symbol_delta.old_size - symbol_delta.new_size, symbol_name) |
| 314 for symbol_name, symbol_type, size1, size2 in \ | 434 for symbol_name, symbol_type, symbol_delta in \ |
| 315 sorted(changed_symbols_by_path[path], key=changed_symbol_sortkey): | 435 sorted(changed_symbols_by_path[path], key=changed_symbol_sortkey): |
| 316 print (' %8s: %s type=%s, (was %d bytes, now %d bytes)' | 436 print (' %8s: %s type=%s, (was %d bytes, now %d bytes)%s' |
| 317 % (DeltaStr(size2 - size1), symbol_name, | 437 % (DeltaStr(symbol_delta.new_size - symbol_delta.old_size), |
| 318 symbol_type, size1, size2)) | 438 symbol_name, symbol_type, |
| 439 symbol_delta.old_size, symbol_delta.new_size, |
| 440 SharedInfoStr(symbol_delta))) |
| 319 | 441 |
| 320 | 442 |
| 321 def main(): | 443 def main(): |
| 322 usage = """%prog [options] | 444 usage = """%prog [options] |
| 323 | 445 |
| 324 Analyzes the symbolic differences between two binary files | 446 Analyzes the symbolic differences between two binary files |
| 325 (typically, not necessarily, two different builds of the same | 447 (typically, not necessarily, two different builds of the same |
| 326 library) and produces a detailed description of symbols that have | 448 library) and produces a detailed description of symbols that have |
| 327 been added, removed, or whose size has changed. | 449 been added, removed, or whose size has changed. |
| 328 | 450 |
| (...skipping 24 matching lines...) Expand all Loading... |
| 353 with file(path, 'r') as nm_input: | 475 with file(path, 'r') as nm_input: |
| 354 if opts.verbose: | 476 if opts.verbose: |
| 355 print 'parsing ' + path + '...' | 477 print 'parsing ' + path + '...' |
| 356 symbols.append(list(binary_size_utils.ParseNm(nm_input))) | 478 symbols.append(list(binary_size_utils.ParseNm(nm_input))) |
| 357 (added, removed, changed, unchanged) = Compare(symbols[0], symbols[1]) | 479 (added, removed, changed, unchanged) = Compare(symbols[0], symbols[1]) |
| 358 CrunchStats(added, removed, changed, unchanged, | 480 CrunchStats(added, removed, changed, unchanged, |
| 359 opts.showsources | opts.showsymbols, opts.showsymbols) | 481 opts.showsources | opts.showsymbols, opts.showsymbols) |
| 360 | 482 |
| 361 if __name__ == '__main__': | 483 if __name__ == '__main__': |
| 362 sys.exit(main()) | 484 sys.exit(main()) |
| OLD | NEW |