Home | History | Annotate | Download | only in Scripts
      1 #!/usr/bin/env python
      2 #
      3 # Copyright (C) 2011 Apple Inc. All rights reserved.
      4 #
      5 # Redistribution and use in source and binary forms, with or without
      6 # modification, are permitted provided that the following conditions
      7 # are met:
      8 #
      9 # 1.  Redistributions of source code must retain the above copyright
     10 #     notice, this list of conditions and the following disclaimer.
     11 # 2.  Redistributions in binary form must reproduce the above copyright
     12 #     notice, this list of conditions and the following disclaimer in the
     13 #     documentation and/or other materials provided with the distribution.
     14 # 3.  Neither the name of Apple Computer, Inc. ("Apple") nor the names of
     15 #     its contributors may be used to endorse or promote products derived
     16 #     from this software without specific prior written permission.
     17 #
     18 # THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
     19 # EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
     20 # WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
     21 # DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
     22 # DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
     23 # (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
     24 # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
     25 # ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
     26 # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
     27 # THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
     28 
     29 import sys
     30 import getopt
     31 from optparse import OptionParser
     32 
     33 oneK = 1024
     34 oneM = 1024 * 1024
     35 oneG = 1024 * 1024 * 1024
     36 
     37 hotspot = False
     38 scaleSize = True
     39 showBars = True
     40 
     41 def byteString(bytes):
     42     if scaleSize:
     43         format = '  %4d   '
     44         val = bytes
     45 
     46         if bytes >= oneG:
     47             format = '%8.1fG'
     48             val = float(bytes) / oneG
     49         elif bytes >= oneM:
     50             format = '%8.1fM'
     51             val = float(bytes) / oneM
     52         elif bytes >= oneK:
     53             format = '%8.1fK'
     54             val = float(bytes) / oneK
     55 
     56         return format % val
     57     if hotspot:
     58         return '%d' % bytes
     59     return '%12d' % bytes
     60 
     61 class Node:
     62     def __init__(self, name, level = 0, bytes = 0):
     63         self.name = name
     64         self.level = level
     65         self.children = {}
     66         self.totalBytes = bytes
     67 
     68     def hasChildren(self):
     69         return len(self.children) > 0
     70 
     71     def getChild(self, name):
     72         if not name in self.children:
     73             newChild = Node(name, self.level + 1)
     74             self.children[name] = newChild
     75 
     76         return self.children[name]
     77 
     78     def getBytes(self):
     79         return self.totalBytes
     80 
     81     def addBytes(self, bytes):
     82         self.totalBytes = self.totalBytes + bytes
     83 
     84     def processLine(self, bytes, line):
     85         sep = line.find('|')
     86         if sep < 0:
     87             childName = line.strip()
     88             line = ''
     89         else:
     90             childName = line[:sep].strip()
     91             line = line[sep+1:]
     92 
     93         child = self.getChild(childName)
     94         child.addBytes(bytes)
     95 
     96         if len(line) > 0:
     97             child.processLine(bytes, line)
     98 
     99     def printNode(self, prefix = ' '):
    100         global hotspot
    101         global scaleSize
    102         global showBars
    103 
    104         if self.hasChildren():
    105             byteStr = byteString(self.totalBytes)
    106 
    107             if hotspot:
    108                 print('    %s%s %s' % (self.level * ' ', byteString(self.totalBytes), self.name))
    109             else:
    110                 print('%s %s%s' % (byteString(self.totalBytes), prefix[:-1], self.name))
    111 
    112             sortedChildren = sorted(self.children.values(), key=sortKeyByBytes, reverse=True)
    113 
    114             if showBars and len(self.children) > 1:
    115                 newPrefix = prefix + '|'
    116             else:
    117                 newPrefix = prefix + ' '
    118 
    119             childrenLeft = len(sortedChildren)
    120             for child in sortedChildren:
    121                 if childrenLeft <= 1:
    122                     newPrefix = prefix + ' '
    123                 else:
    124                     childrenLeft = childrenLeft - 1
    125                 child.printNode(newPrefix)
    126         else:
    127             byteStr = byteString(self.totalBytes)
    128 
    129             if hotspot:
    130                 print('    %s%s %s' % (self.level * ' ', byteString(self.totalBytes), self.name))
    131             else:
    132                 print('%s %s%s' % (byteString(self.totalBytes), prefix[:-1], self.name))
    133 
    134 def sortKeyByBytes(node):
    135     return node.getBytes();
    136 
    137 def main():
    138     global hotspot
    139     global scaleSize
    140     global showBars
    141 
    142     # parse command line options
    143     parser = OptionParser(usage='malloc-tree [options] [malloc_history-file]',
    144                           description='Format malloc_history output as a nested tree',
    145                           epilog='stdin used if malloc_history-file is missing')
    146 
    147     parser.add_option('-n', '--nobars', action='store_false', dest='showBars',
    148                       default=True, help='don\'t show bars lining up siblings in tree');
    149     parser.add_option('-b', '--size-in-bytes', action='store_false', dest='scaleSize',
    150                       default=None, help='show sizes in bytes');
    151     parser.add_option('-s', '--size-scale', action='store_true', dest='scaleSize',
    152                       default=None, help='show sizes with appropriate scale suffix [K,M,G]');
    153     parser.add_option('-t', '--hotspot', action='store_true', dest='hotspot',
    154                       default=False, help='output in HotSpotFinder format, implies -b');
    155 
    156     (options, args) = parser.parse_args()
    157 
    158     hotspot = options.hotspot
    159     if options.scaleSize is None:
    160         if hotspot:
    161             scaleSize = False
    162         else:
    163             scaleSize = True
    164     else:
    165         scaleSize = options.scaleSize
    166     showBars = options.showBars
    167 
    168     if len(args) < 1:
    169         inputFile = sys.stdin
    170     else:
    171         inputFile = open(args[0], "r")
    172 
    173     line = inputFile.readline()
    174 
    175     rootNodes = {}
    176 
    177     while line:
    178         firstSep = line.find('|')
    179         if firstSep > 0:
    180             firstPart = line[:firstSep].strip()
    181             lineRemain = line[firstSep+1:]
    182             bytesSep = firstPart.find('bytes:')
    183             if bytesSep >= 0:
    184                 name = firstPart[bytesSep+7:]
    185                 stats = firstPart.split(' ')
    186                 bytes = int(stats[3].replace(',', ''))
    187 
    188                 if not name in rootNodes:
    189                     node = Node(name, 0, bytes);
    190                     rootNodes[name] = node
    191                 else:
    192                     node = rootNodes[name]
    193                     node.addBytes(bytes)
    194 
    195                 node.processLine(bytes, lineRemain)
    196 
    197         line = inputFile.readline()
    198 
    199     sortedRootNodes = sorted(rootNodes.values(), key=sortKeyByBytes, reverse=True)
    200 
    201     print 'Call graph:'
    202     try:
    203         for node in sortedRootNodes:
    204             node.printNode()
    205             print
    206     except:
    207         pass
    208 
    209 if __name__ == "__main__":
    210     main()
    211