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