Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(53)

Side by Side Diff: tools/binary_size/explain_binary_size_delta.py

Issue 375063002: Polishing binary_size tool output. (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@inline_measure_point_20140708
Patch Set: Created 6 years, 5 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
« no previous file with comments | « no previous file | tools/binary_size/explain_binary_size_delta_unittest.py » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
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
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 operator
40 import optparse 41 import optparse
41 import os 42 import os
42 import sys 43 import sys
43 44
44 import binary_size_utils 45 import binary_size_utils
45 46
46 47
47 def Compare(symbols1, symbols2): 48 def Compare(symbols1, symbols2):
48 """Executes a comparison of the symbols in symbols1 and symbols2. 49 """Executes a comparison of the symbols in symbols1 and symbols2.
49 50
(...skipping 49 matching lines...) Expand 10 before | Expand all | Expand 10 after
99 unchanged.append((key[0], key[1], symbol_name, symbol_size, size2)) 100 unchanged.append((key[0], key[1], symbol_name, symbol_size, size2))
100 101
101 # We have now analyzed all symbols that are in cache1 and removed all of 102 # We have now analyzed all symbols that are in cache1 and removed all of
102 # the encountered symbols from cache2. What's left in cache2 is the new 103 # the encountered symbols from cache2. What's left in cache2 is the new
103 # symbols. 104 # symbols.
104 for key, bucket2 in cache2.iteritems(): 105 for key, bucket2 in cache2.iteritems():
105 for symbol_name, symbol_size in bucket2.items(): 106 for symbol_name, symbol_size in bucket2.items():
106 added.append((key[0], key[1], symbol_name, None, symbol_size)) 107 added.append((key[0], key[1], symbol_name, None, symbol_size))
107 return (added, removed, changed, unchanged) 108 return (added, removed, changed, unchanged)
108 109
110 def DeltaStr(number):
111 """Returns the number as a string with a '+' prefix if it's > 0 and
112 a '-' prefix if it's < 0."""
113 result = str(number)
114 if number > 0:
115 result = '+' + result
116 return result
117
118
119 class CrunchStatsData(object):
120 """Stores a summary of data of a certain kind."""
121 def __init__(self, symbols):
122 self.symbols = symbols
123 self.sources = set()
124 self.before_size = 0
125 self.after_size = 0
126 self.symbols_by_path = {}
127
109 128
110 def CrunchStats(added, removed, changed, unchanged, showsources, showsymbols): 129 def CrunchStats(added, removed, changed, unchanged, showsources, showsymbols):
111 """Outputs to stdout a summary of changes based on the symbol lists.""" 130 """Outputs to stdout a summary of changes based on the symbol lists."""
112 print 'Symbol statistics:' 131 # Split changed into grown and shrunk because that is easier to
113 sources_with_new_symbols = set() 132 # discuss.
114 new_symbols_size = 0 133 grown = []
115 new_symbols_by_path = {} 134 shrunk = []
116 for file_path, symbol_type, symbol_name, size1, size2 in added: 135 for item in changed:
117 sources_with_new_symbols.add(file_path) 136 file_path, symbol_type, symbol_name, size1, size2 = item
118 new_symbols_size += size2 137 if size1 < size2:
119 bucket = new_symbols_by_path.setdefault(file_path, []) 138 grown.append(item)
120 bucket.append((symbol_name, symbol_type, None, size2)) 139 else:
121 print(' %d added, totalling %d bytes across %d sources' % 140 shrunk.append(item)
122 (len(added), new_symbols_size, len(sources_with_new_symbols)))
123 141
124 sources_with_removed_symbols = set() 142 new_symbols = CrunchStatsData(added)
125 removed_symbols_size = 0 143 removed_symbols = CrunchStatsData(removed)
126 removed_symbols_by_path = {} 144 grown_symbols = CrunchStatsData(grown)
127 for file_path, symbol_type, symbol_name, size1, size2 in removed: 145 shrunk_symbols = CrunchStatsData(shrunk)
128 sources_with_removed_symbols.add(file_path) 146 sections = [new_symbols, removed_symbols, grown_symbols, shrunk_symbols]
129 removed_symbols_size += size1 147 for section in sections:
130 bucket = removed_symbols_by_path.setdefault(file_path, []) 148 for file_path, symbol_type, symbol_name, size1, size2 in section.symbols:
131 bucket.append((symbol_name, symbol_type, size1, None)) 149 section.sources.add(file_path)
132 print(' %d removed, totalling %d bytes removed across %d sources' % 150 if size1 is not None:
133 (len(removed), removed_symbols_size, len(sources_with_removed_symbols))) 151 section.before_size += size1
152 if size2 is not None:
153 section.after_size += size2
154 bucket = section.symbols_by_path.setdefault(file_path, [])
155 bucket.append((symbol_name, symbol_type, size1, size2))
134 156
135 sources_with_changed_symbols = set() 157 total_change = sum(s.after_size - s.before_size for s in sections)
136 before_size = 0 158 summary = 'Total change: %s bytes' % DeltaStr(total_change)
137 after_size = 0 159 print(summary)
138 changed_symbols_by_path = {} 160 print('=' * len(summary))
139 for file_path, symbol_type, symbol_name, size1, size2 in changed: 161 for section in sections:
140 sources_with_changed_symbols.add(file_path) 162 if not section.symbols:
141 before_size += size1 163 continue
142 after_size += size2 164 if section.before_size == 0:
143 bucket = changed_symbols_by_path.setdefault(file_path, []) 165 description = ('added, totalling %s bytes' % DeltaStr(section.after_size))
144 bucket.append((symbol_name, symbol_type, size1, size2)) 166 elif section.after_size == 0:
145 print(' %d changed, resulting in a net change of %d bytes ' 167 description = ('removed, totalling %s bytes' %
146 '(%d bytes before, %d bytes after) across %d sources' % 168 DeltaStr(-section.before_size))
147 (len(changed), (after_size - before_size), before_size, after_size, 169 else:
148 len(sources_with_changed_symbols))) 170 if section.after_size > section.before_size:
171 type_str = 'grown'
172 else:
173 type_str = 'shrunk'
174 description = ('%s, for a net change of %s bytes '
175 '(%d bytes before, %d bytes after)' %
176 (type_str, DeltaStr(section.after_size - section.before_size),
177 section.before_size, section.after_size))
178 print(' %d %s across %d sources' %
179 (len(section.symbols), description, len(section.sources)))
149 180
150 maybe_unchanged_sources = set() 181 maybe_unchanged_sources = set()
151 unchanged_symbols_size = 0 182 unchanged_symbols_size = 0
152 for file_path, symbol_type, symbol_name, size1, size2 in unchanged: 183 for file_path, symbol_type, symbol_name, size1, size2 in unchanged:
153 maybe_unchanged_sources.add(file_path) 184 maybe_unchanged_sources.add(file_path)
154 unchanged_symbols_size += size1 # == size2 185 unchanged_symbols_size += size1 # == size2
155 print(' %d unchanged, totalling %d bytes' % 186 print(' %d unchanged, totalling %d bytes' %
156 (len(unchanged), unchanged_symbols_size)) 187 (len(unchanged), unchanged_symbols_size))
157 188
158 # High level analysis, always output. 189 # High level analysis, always output.
159 unchanged_sources = (maybe_unchanged_sources - 190 unchanged_sources = maybe_unchanged_sources
160 sources_with_changed_symbols - 191 for section in sections:
161 sources_with_removed_symbols - 192 unchanged_sources = unchanged_sources - section.sources
162 sources_with_new_symbols) 193 new_sources = (new_symbols.sources -
163 new_sources = (sources_with_new_symbols -
164 maybe_unchanged_sources - 194 maybe_unchanged_sources -
165 sources_with_removed_symbols) 195 removed_symbols.sources)
166 removed_sources = (sources_with_removed_symbols - 196 removed_sources = (removed_symbols.sources -
167 maybe_unchanged_sources - 197 maybe_unchanged_sources -
168 sources_with_new_symbols) 198 new_symbols.sources)
169 partially_changed_sources = (sources_with_changed_symbols | 199 partially_changed_sources = (grown_symbols.sources |
170 sources_with_new_symbols | 200 shrunk_symbols.sources | new_symbols.sources |
171 sources_with_removed_symbols) - removed_sources - new_sources 201 removed_symbols.sources) - removed_sources - new_sources
172 allFiles = (sources_with_new_symbols | 202 allFiles = set()
173 sources_with_removed_symbols | 203 for section in sections:
174 sources_with_changed_symbols | 204 allFiles = allFiles | section.sources
175 maybe_unchanged_sources) 205 allFiles = allFiles | maybe_unchanged_sources
176 print 'Source stats:' 206 print 'Source stats:'
177 print(' %d sources encountered.' % len(allFiles)) 207 print(' %d sources encountered.' % len(allFiles))
178 print(' %d completely new.' % len(new_sources)) 208 print(' %d completely new.' % len(new_sources))
179 print(' %d removed completely.' % len(removed_sources)) 209 print(' %d removed completely.' % len(removed_sources))
180 print(' %d partially changed.' % len(partially_changed_sources)) 210 print(' %d partially changed.' % len(partially_changed_sources))
181 print(' %d completely unchanged.' % len(unchanged_sources)) 211 print(' %d completely unchanged.' % len(unchanged_sources))
182 remainder = (allFiles - new_sources - removed_sources - 212 remainder = (allFiles - new_sources - removed_sources -
183 partially_changed_sources - unchanged_sources) 213 partially_changed_sources - unchanged_sources)
184 assert len(remainder) == 0 214 assert len(remainder) == 0
185 215
186 if not showsources: 216 if not showsources:
187 return # Per-source analysis, only if requested 217 return # Per-source analysis, only if requested
188 print 'Per-source Analysis:' 218 print 'Per-source Analysis:'
189 delta_by_path = {} 219 delta_by_path = {}
190 for path in new_symbols_by_path: 220 for section in sections:
191 entry = delta_by_path.get(path) 221 for path in section.symbols_by_path:
192 if not entry: 222 entry = delta_by_path.get(path)
193 entry = {'plus': 0, 'minus': 0} 223 if not entry:
194 delta_by_path[path] = entry 224 entry = {'plus': 0, 'minus': 0}
195 for symbol_name, symbol_type, size1, size2 in new_symbols_by_path[path]: 225 delta_by_path[path] = entry
196 entry['plus'] += size2 226 for symbol_name, symbol_type, size1, size2 in \
197 for path in removed_symbols_by_path: 227 section.symbols_by_path[path]:
198 entry = delta_by_path.get(path) 228 if size1 is None:
199 if not entry: 229 delta = size2
200 entry = {'plus': 0, 'minus': 0} 230 elif size2 is None:
201 delta_by_path[path] = entry 231 delta = -size1
202 for symbol_name, symbol_type, size1, size2 in removed_symbols_by_path[path]: 232 else:
203 entry['minus'] += size1 233 delta = size2 - size1
204 for path in changed_symbols_by_path:
205 entry = delta_by_path.get(path)
206 if not entry:
207 entry = {'plus': 0, 'minus': 0}
208 delta_by_path[path] = entry
209 for symbol_name, symbol_type, size1, size2 in changed_symbols_by_path[path]:
210 delta = size2 - size1
211 if delta > 0:
212 entry['plus'] += delta
213 else:
214 entry['minus'] += (-1 * delta)
215 234
216 for path in sorted(delta_by_path): 235 if delta > 0:
217 print ' Source: ' + path 236 entry['plus'] += delta
218 size_data = delta_by_path[path] 237 else:
238 entry['minus'] += (-1 * delta)
239
240 def delta_sort_key(item):
241 _path, size_data = item
242 growth = size_data['plus'] - size_data['minus']
243 return growth
244
245 for path, size_data in sorted(delta_by_path.iteritems(), key=delta_sort_key,
246 reverse=True):
219 gain = size_data['plus'] 247 gain = size_data['plus']
220 loss = size_data['minus'] 248 loss = size_data['minus']
221 delta = size_data['plus'] - size_data['minus'] 249 delta = size_data['plus'] - size_data['minus']
222 print (' Change: %d bytes (gained %d, lost %d)' % (delta, gain, loss)) 250 header = ' %s - Source: %s - (gained %d, lost %d)' % (DeltaStr(delta),
251 path, gain, loss)
252 divider = '-' * len(header)
253 print ''
254 print divider
255 print header
256 print divider
223 if showsymbols: 257 if showsymbols:
224 if path in new_symbols_by_path: 258 if path in new_symbols.symbols_by_path:
225 print ' New symbols:' 259 print ' New symbols:'
226 for symbol_name, symbol_type, size1, size2 in \ 260 for symbol_name, symbol_type, size1, size2 in \
227 new_symbols_by_path[path]: 261 sorted(new_symbols.symbols_by_path[path],
228 print (' %s type=%s, size=%d bytes' % 262 key=operator.itemgetter(3),
229 (symbol_name, symbol_type, size2)) 263 reverse=True):
230 if path in removed_symbols_by_path: 264 print (' %8s: %s type=%s, size=%d bytes' %
231 print ' Removed symbols:' 265 (DeltaStr(size2), symbol_name, symbol_type, size2))
266 if path in removed_symbols.symbols_by_path:
267 print ' Removed symbols:'
232 for symbol_name, symbol_type, size1, size2 in \ 268 for symbol_name, symbol_type, size1, size2 in \
233 removed_symbols_by_path[path]: 269 sorted(removed_symbols.symbols_by_path[path],
234 print (' %s type=%s, size=%d bytes' % 270 key=operator.itemgetter(2)):
235 (symbol_name, symbol_type, size1)) 271 print (' %8s: %s type=%s, size=%d bytes' %
236 if path in changed_symbols_by_path: 272 (DeltaStr(-size1), symbol_name, symbol_type, size1))
237 print ' Changed symbols:' 273 for (changed_symbols_by_path, type_str) in [
238 def sortkey(item): 274 (grown_symbols.symbols_by_path, "Grown"),
239 symbol_name, _symbol_type, size1, size2 = item 275 (shrunk_symbols.symbols_by_path, "Shrunk")]:
240 return (size1 - size2, symbol_name) 276 if path in changed_symbols_by_path:
241 for symbol_name, symbol_type, size1, size2 in \ 277 print ' %s symbols:' % type_str
242 sorted(changed_symbols_by_path[path], key=sortkey): 278 def changed_symbol_sortkey(item):
243 print (' %s type=%s, delta=%d bytes (was %d bytes, now %d bytes)' 279 symbol_name, _symbol_type, size1, size2 = item
244 % (symbol_name, symbol_type, (size2 - size1), size1, size2)) 280 return (size1 - size2, symbol_name)
281 for symbol_name, symbol_type, size1, size2 in \
282 sorted(changed_symbols_by_path[path], key=changed_symbol_sortkey):
283 print (' %8s: %s type=%s, (was %d bytes, now %d bytes)'
284 % (DeltaStr(size2 - size1), symbol_name,
285 symbol_type, size1, size2))
245 286
246 287
247 def main(): 288 def main():
248 usage = """%prog [options] 289 usage = """%prog [options]
249 290
250 Analyzes the symbolic differences between two binary files 291 Analyzes the symbolic differences between two binary files
251 (typically, not necessarily, two different builds of the same 292 (typically, not necessarily, two different builds of the same
252 library) and produces a detailed description of symbols that have 293 library) and produces a detailed description of symbols that have
253 been added, removed, or whose size has changed. 294 been added, removed, or whose size has changed.
254 295
(...skipping 24 matching lines...) Expand all
279 with file(path, 'r') as nm_input: 320 with file(path, 'r') as nm_input:
280 if opts.verbose: 321 if opts.verbose:
281 print 'parsing ' + path + '...' 322 print 'parsing ' + path + '...'
282 symbols.append(list(binary_size_utils.ParseNm(nm_input))) 323 symbols.append(list(binary_size_utils.ParseNm(nm_input)))
283 (added, removed, changed, unchanged) = Compare(symbols[0], symbols[1]) 324 (added, removed, changed, unchanged) = Compare(symbols[0], symbols[1])
284 CrunchStats(added, removed, changed, unchanged, 325 CrunchStats(added, removed, changed, unchanged,
285 opts.showsources | opts.showsymbols, opts.showsymbols) 326 opts.showsources | opts.showsymbols, opts.showsymbols)
286 327
287 if __name__ == '__main__': 328 if __name__ == '__main__':
288 sys.exit(main()) 329 sys.exit(main())
OLDNEW
« no previous file with comments | « no previous file | tools/binary_size/explain_binary_size_delta_unittest.py » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698