| 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 19 matching lines...) Expand all Loading... |
| 30 --destdir /tmp/throwaway | 30 --destdir /tmp/throwaway |
| 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 operator | 41 import operator |
| 41 import optparse | 42 import optparse |
| 42 import os | 43 import os |
| 43 import sys | 44 import sys |
| 44 | 45 |
| 45 import binary_size_utils | 46 import binary_size_utils |
| 46 | 47 |
| 47 | 48 |
| 48 def Compare(symbols1, symbols2): | 49 def Compare(symbols1, symbols2): |
| 49 """Executes a comparison of the symbols in symbols1 and symbols2. | 50 """Executes a comparison of the symbols in symbols1 and symbols2. |
| (...skipping 12 matching lines...) Expand all Loading... |
| 62 for cache, symbols in ((cache1, symbols1), (cache2, symbols2)): | 63 for cache, symbols in ((cache1, symbols1), (cache2, symbols2)): |
| 63 for symbol_name, symbol_type, symbol_size, file_path in symbols: | 64 for symbol_name, symbol_type, symbol_size, file_path in symbols: |
| 64 if 'vtable for ' in symbol_name: | 65 if 'vtable for ' in symbol_name: |
| 65 symbol_type = '@' # hack to categorize these separately | 66 symbol_type = '@' # hack to categorize these separately |
| 66 if file_path: | 67 if file_path: |
| 67 file_path = os.path.normpath(file_path) | 68 file_path = os.path.normpath(file_path) |
| 68 else: | 69 else: |
| 69 file_path = '(No Path)' | 70 file_path = '(No Path)' |
| 70 key = (file_path, symbol_type) | 71 key = (file_path, symbol_type) |
| 71 bucket = cache.setdefault(key, {}) | 72 bucket = cache.setdefault(key, {}) |
| 72 bucket[symbol_name] = symbol_size | 73 size_list = bucket.setdefault(symbol_name, []) |
| 74 size_list.append(symbol_size) |
| 73 | 75 |
| 74 # Now diff them. We iterate over the elements in cache1. For each symbol | 76 # Now diff them. We iterate over the elements in cache1. For each symbol |
| 75 # that we find in cache2, we record whether it was deleted, changed, or | 77 # that we find in cache2, we record whether it was deleted, changed, or |
| 76 # unchanged. We then remove it from cache2; all the symbols that remain | 78 # unchanged. We then remove it from cache2; all the symbols that remain |
| 77 # in cache2 at the end of the iteration over cache1 are the 'new' symbols. | 79 # in cache2 at the end of the iteration over cache1 are the 'new' symbols. |
| 78 for key, bucket1 in cache1.items(): | 80 for key, bucket1 in cache1.items(): |
| 79 bucket2 = cache2.get(key) | 81 bucket2 = cache2.get(key) |
| 80 if not bucket2: | 82 if not bucket2: |
| 81 # A file was removed. Everything in bucket1 is dead. | 83 # A file was removed. Everything in bucket1 is dead. |
| 82 for symbol_name, symbol_size in bucket1.items(): | 84 for symbol_name, symbol_size_list in bucket1.items(): |
| 83 removed.append((key[0], key[1], symbol_name, symbol_size, None)) | 85 for symbol_size in symbol_size_list: |
| 86 removed.append((key[0], key[1], symbol_name, symbol_size, None)) |
| 84 else: | 87 else: |
| 85 # File still exists, look for changes within. | 88 # File still exists, look for changes within. |
| 86 for symbol_name, symbol_size in bucket1.items(): | 89 for symbol_name, symbol_size_list in bucket1.items(): |
| 87 size2 = bucket2.get(symbol_name) | 90 size_list2 = bucket2.get(symbol_name) |
| 88 if size2 is None: | 91 if size_list2 is None: |
| 89 # Symbol no longer exists in bucket2. | 92 # Symbol no longer exists in bucket2. |
| 90 removed.append((key[0], key[1], symbol_name, symbol_size, None)) | 93 for symbol_size in symbol_size_list: |
| 94 removed.append((key[0], key[1], symbol_name, symbol_size, None)) |
| 91 else: | 95 else: |
| 92 del bucket2[symbol_name] # Symbol is not new, delete from cache2. | 96 del bucket2[symbol_name] # Symbol is not new, delete from cache2. |
| 97 if len(symbol_size_list) == 1 and len(size_list2) == 1: |
| 98 symbol_size = symbol_size_list[0] |
| 99 size2 = size_list2[0] |
| 100 if symbol_size != size2: |
| 101 # Symbol has change size in bucket. |
| 102 changed.append((key[0], key[1], symbol_name, symbol_size, size2)) |
| 103 else: |
| 104 # Symbol is unchanged. |
| 105 unchanged.append((key[0], key[1], symbol_name, symbol_size, |
| 106 size2)) |
| 107 else: |
| 108 # Complex comparison for when a symbol exists multiple times |
| 109 # in the same file (where file can be "unknown file"). |
| 110 symbol_size_counter = collections.Counter(symbol_size_list) |
| 111 delta_counter = collections.Counter(symbol_size_list) |
| 112 delta_counter.subtract(size_list2) |
| 113 for symbol_size in sorted(delta_counter.keys()): |
| 114 delta = delta_counter[symbol_size] |
| 115 unchanged_count = symbol_size_counter[symbol_size] |
| 116 if delta > 0: |
| 117 unchanged_count -= delta |
| 118 for _ in range(unchanged_count): |
| 119 unchanged.append((key[0], key[1], symbol_name, symbol_size, |
| 120 symbol_size)) |
| 121 if delta > 0: # Used to be more of these than there is now. |
| 122 for _ in range(delta): |
| 123 removed.append((key[0], key[1], symbol_name, symbol_size, |
| 124 None)) |
| 125 elif delta < 0: # More of this (symbol,size) now. |
| 126 for _ in range(-delta): |
| 127 added.append((key[0], key[1], symbol_name, None, symbol_size)) |
| 128 |
| 93 if len(bucket2) == 0: | 129 if len(bucket2) == 0: |
| 94 del cache1[key] # Entire bucket is empty, delete from cache2 | 130 del cache1[key] # Entire bucket is empty, delete from cache2 |
| 95 if symbol_size != size2: | |
| 96 # Symbol has change size in bucket. | |
| 97 changed.append((key[0], key[1], symbol_name, symbol_size, size2)) | |
| 98 else: | |
| 99 # Symbol is unchanged. | |
| 100 unchanged.append((key[0], key[1], symbol_name, symbol_size, size2)) | |
| 101 | 131 |
| 102 # We have now analyzed all symbols that are in cache1 and removed all of | 132 # We have now analyzed all symbols that are in cache1 and removed all of |
| 103 # the encountered symbols from cache2. What's left in cache2 is the new | 133 # the encountered symbols from cache2. What's left in cache2 is the new |
| 104 # symbols. | 134 # symbols. |
| 105 for key, bucket2 in cache2.iteritems(): | 135 for key, bucket2 in cache2.iteritems(): |
| 106 for symbol_name, symbol_size in bucket2.items(): | 136 for symbol_name, symbol_size_list in bucket2.items(): |
| 107 added.append((key[0], key[1], symbol_name, None, symbol_size)) | 137 for symbol_size in symbol_size_list: |
| 138 added.append((key[0], key[1], symbol_name, None, symbol_size)) |
| 108 return (added, removed, changed, unchanged) | 139 return (added, removed, changed, unchanged) |
| 109 | 140 |
| 110 def DeltaStr(number): | 141 def DeltaStr(number): |
| 111 """Returns the number as a string with a '+' prefix if it's > 0 and | 142 """Returns the number as a string with a '+' prefix if it's > 0 and |
| 112 a '-' prefix if it's < 0.""" | 143 a '-' prefix if it's < 0.""" |
| 113 result = str(number) | 144 result = str(number) |
| 114 if number > 0: | 145 if number > 0: |
| 115 result = '+' + result | 146 result = '+' + result |
| 116 return result | 147 return result |
| 117 | 148 |
| (...skipping 202 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 320 with file(path, 'r') as nm_input: | 351 with file(path, 'r') as nm_input: |
| 321 if opts.verbose: | 352 if opts.verbose: |
| 322 print 'parsing ' + path + '...' | 353 print 'parsing ' + path + '...' |
| 323 symbols.append(list(binary_size_utils.ParseNm(nm_input))) | 354 symbols.append(list(binary_size_utils.ParseNm(nm_input))) |
| 324 (added, removed, changed, unchanged) = Compare(symbols[0], symbols[1]) | 355 (added, removed, changed, unchanged) = Compare(symbols[0], symbols[1]) |
| 325 CrunchStats(added, removed, changed, unchanged, | 356 CrunchStats(added, removed, changed, unchanged, |
| 326 opts.showsources | opts.showsymbols, opts.showsymbols) | 357 opts.showsources | opts.showsymbols, opts.showsymbols) |
| 327 | 358 |
| 328 if __name__ == '__main__': | 359 if __name__ == '__main__': |
| 329 sys.exit(main()) | 360 sys.exit(main()) |
| OLD | NEW |