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
29import sys
30import getopt
31from optparse import OptionParser
32
33oneK = 1024
34oneM = 1024 * 1024
35oneG = 1024 * 1024 * 1024
36
37hotspot = False
38scaleSize = True
39showBars = True
40
41def 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
61class 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
134def sortKeyByBytes(node):
135    return node.getBytes();
136
137def 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
209if __name__ == "__main__":
210    main()
211