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