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 |