| #!/usr/bin/env python3 | |
| # | |
| # 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() |