|
#!/usr/bin/env python |
|
# |
|
# Copyright (C) 2011 Apple Inc. All rights reserved. |
|
# |
|
# Redistribution and use in source and binary forms, with or without |
|
# modification, are permitted provided that the following conditions |
|
# are met: |
|
# |
|
# 1. Redistributions of source code must retain the above copyright |
|
# notice, this list of conditions and the following disclaimer. |
|
# 2. Redistributions in binary form must reproduce the above copyright |
|
# notice, this list of conditions and the following disclaimer in the |
|
# documentation and/or other materials provided with the distribution. |
|
# 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of |
|
# its contributors may be used to endorse or promote products derived |
|
# from this software without specific prior written permission. |
|
# |
|
# THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY |
|
# EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED |
|
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE |
|
# DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY |
|
# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES |
|
# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; |
|
# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND |
|
# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
|
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF |
|
# THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
|
|
|
import sys |
|
import getopt |
|
from optparse import OptionParser |
|
|
|
oneK = 1024 |
|
oneM = 1024 * 1024 |
|
oneG = 1024 * 1024 * 1024 |
|
|
|
hotspot = False |
|
scaleSize = True |
|
showBars = True |
|
|
|
def byteString(bytes): |
|
if scaleSize: |
|
format = ' %4d ' |
|
val = bytes |
|
|
|
if bytes >= oneG: |
|
format = '%8.1fG' |
|
val = float(bytes) / oneG |
|
elif bytes >= oneM: |
|
format = '%8.1fM' |
|
val = float(bytes) / oneM |
|
elif bytes >= oneK: |
|
format = '%8.1fK' |
|
val = float(bytes) / oneK |
|
|
|
return format % val |
|
if hotspot: |
|
return '%d' % bytes |
|
return '%12d' % bytes |
|
|
|
class Node: |
|
def __init__(self, name, level = 0, bytes = 0): |
|
self.name = name |
|
self.level = level |
|
self.children = {} |
|
self.totalBytes = bytes |
|
|
|
def hasChildren(self): |
|
return len(self.children) > 0 |
|
|
|
def getChild(self, name): |
|
if not name in self.children: |
|
newChild = Node(name, self.level + 1) |
|
self.children[name] = newChild |
|
|
|
return self.children[name] |
|
|
|
def getBytes(self): |
|
return self.totalBytes |
|
|
|
def addBytes(self, bytes): |
|
self.totalBytes = self.totalBytes + bytes |
|
|
|
def processLine(self, bytes, line): |
|
sep = line.find('|') |
|
if sep < 0: |
|
childName = line.strip() |
|
line = '' |
|
else: |
|
childName = line[:sep].strip() |
|
line = line[sep+1:] |
|
|
|
child = self.getChild(childName) |
|
child.addBytes(bytes) |
|
|
|
if len(line) > 0: |
|
child.processLine(bytes, line) |
|
|
|
def printNode(self, prefix = ' '): |
|
global hotspot |
|
global scaleSize |
|
global showBars |
|
|
|
if self.hasChildren(): |
|
byteStr = byteString(self.totalBytes) |
|
|
|
if hotspot: |
|
print(' %s%s %s' % (self.level * ' ', byteString(self.totalBytes), self.name)) |
|
else: |
|
print('%s %s%s' % (byteString(self.totalBytes), prefix[:-1], self.name)) |
|
|
|
sortedChildren = sorted(self.children.values(), key=sortKeyByBytes, reverse=True) |
|
|
|
if showBars and len(self.children) > 1: |
|
newPrefix = prefix + '|' |
|
else: |
|
newPrefix = prefix + ' ' |
|
|
|
childrenLeft = len(sortedChildren) |
|
for child in sortedChildren: |
|
if childrenLeft <= 1: |
|
newPrefix = prefix + ' ' |
|
else: |
|
childrenLeft = childrenLeft - 1 |
|
child.printNode(newPrefix) |
|
else: |
|
byteStr = byteString(self.totalBytes) |
|
|
|
if hotspot: |
|
print(' %s%s %s' % (self.level * ' ', byteString(self.totalBytes), self.name)) |
|
else: |
|
print('%s %s%s' % (byteString(self.totalBytes), prefix[:-1], self.name)) |
|
|
|
def sortKeyByBytes(node): |
|
return node.getBytes(); |
|
|
|
def main(): |
|
global hotspot |
|
global scaleSize |
|
global showBars |
|
|
|
# parse command line options |
|
parser = OptionParser(usage='malloc-tree [options] [malloc_history-file]', |
|
description='Format malloc_history output as a nested tree', |
|
epilog='stdin used if malloc_history-file is missing') |
|
|
|
parser.add_option('-n', '--nobars', action='store_false', dest='showBars', |
|
default=True, help='don\'t show bars lining up siblings in tree'); |
|
parser.add_option('-b', '--size-in-bytes', action='store_false', dest='scaleSize', |
|
default=None, help='show sizes in bytes'); |
|
parser.add_option('-s', '--size-scale', action='store_true', dest='scaleSize', |
|
default=None, help='show sizes with appropriate scale suffix [K,M,G]'); |
|
parser.add_option('-t', '--hotspot', action='store_true', dest='hotspot', |
|
default=False, help='output in HotSpotFinder format, implies -b'); |
|
|
|
(options, args) = parser.parse_args() |
|
|
|
hotspot = options.hotspot |
|
if options.scaleSize is None: |
|
if hotspot: |
|
scaleSize = False |
|
else: |
|
scaleSize = True |
|
else: |
|
scaleSize = options.scaleSize |
|
showBars = options.showBars |
|
|
|
if len(args) < 1: |
|
inputFile = sys.stdin |
|
else: |
|
inputFile = open(args[0], "r") |
|
|
|
line = inputFile.readline() |
|
|
|
rootNodes = {} |
|
|
|
while line: |
|
firstSep = line.find('|') |
|
if firstSep > 0: |
|
firstPart = line[:firstSep].strip() |
|
lineRemain = line[firstSep+1:] |
|
bytesSep = firstPart.find('bytes:') |
|
if bytesSep >= 0: |
|
name = firstPart[bytesSep+7:] |
|
stats = firstPart.split(' ') |
|
bytes = int(stats[3].replace(',', '')) |
|
|
|
if not name in rootNodes: |
|
node = Node(name, 0, bytes); |
|
rootNodes[name] = node |
|
else: |
|
node = rootNodes[name] |
|
node.addBytes(bytes) |
|
|
|
node.processLine(bytes, lineRemain) |
|
|
|
line = inputFile.readline() |
|
|
|
sortedRootNodes = sorted(rootNodes.values(), key=sortKeyByBytes, reverse=True) |
|
|
|
print 'Call graph:' |
|
try: |
|
for node in sortedRootNodes: |
|
node.printNode() |
|
print |
|
except: |
|
pass |
|
|
|
if __name__ == "__main__": |
|
main() |