| #!/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 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() |