1563af33bc48281d19dce701398dbb88cb54fd7ecCary Clark#!/usr/bin/perl
2563af33bc48281d19dce701398dbb88cb54fd7ecCary Clark
3563af33bc48281d19dce701398dbb88cb54fd7ecCary Clark# Copyright (C) 2007 Apple Inc. All rights reserved.
4563af33bc48281d19dce701398dbb88cb54fd7ecCary Clark#
5563af33bc48281d19dce701398dbb88cb54fd7ecCary Clark# Redistribution and use in source and binary forms, with or without
6563af33bc48281d19dce701398dbb88cb54fd7ecCary Clark# modification, are permitted provided that the following conditions
7563af33bc48281d19dce701398dbb88cb54fd7ecCary Clark# are met:
8563af33bc48281d19dce701398dbb88cb54fd7ecCary Clark#
9563af33bc48281d19dce701398dbb88cb54fd7ecCary Clark# 1.  Redistributions of source code must retain the above copyright
10563af33bc48281d19dce701398dbb88cb54fd7ecCary Clark#     notice, this list of conditions and the following disclaimer. 
11563af33bc48281d19dce701398dbb88cb54fd7ecCary Clark# 2.  Redistributions in binary form must reproduce the above copyright
12563af33bc48281d19dce701398dbb88cb54fd7ecCary Clark#     notice, this list of conditions and the following disclaimer in the
13563af33bc48281d19dce701398dbb88cb54fd7ecCary Clark#     documentation and/or other materials provided with the distribution. 
14563af33bc48281d19dce701398dbb88cb54fd7ecCary Clark# 3.  Neither the name of Apple Computer, Inc. ("Apple") nor the names of
15563af33bc48281d19dce701398dbb88cb54fd7ecCary Clark#     its contributors may be used to endorse or promote products derived
16563af33bc48281d19dce701398dbb88cb54fd7ecCary Clark#     from this software without specific prior written permission. 
17563af33bc48281d19dce701398dbb88cb54fd7ecCary Clark#
18563af33bc48281d19dce701398dbb88cb54fd7ecCary Clark# THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
19563af33bc48281d19dce701398dbb88cb54fd7ecCary Clark# EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
20563af33bc48281d19dce701398dbb88cb54fd7ecCary Clark# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
21563af33bc48281d19dce701398dbb88cb54fd7ecCary Clark# DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
22563af33bc48281d19dce701398dbb88cb54fd7ecCary Clark# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
23563af33bc48281d19dce701398dbb88cb54fd7ecCary Clark# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
24563af33bc48281d19dce701398dbb88cb54fd7ecCary Clark# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
25563af33bc48281d19dce701398dbb88cb54fd7ecCary Clark# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
26563af33bc48281d19dce701398dbb88cb54fd7ecCary Clark# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
27563af33bc48281d19dce701398dbb88cb54fd7ecCary Clark# THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28563af33bc48281d19dce701398dbb88cb54fd7ecCary Clark
29563af33bc48281d19dce701398dbb88cb54fd7ecCary Clark# Parses the callstacks in a file with malloc_history formatted content, sorting
30563af33bc48281d19dce701398dbb88cb54fd7ecCary Clark# based on total number of bytes allocated, and filtering based on command-line
31563af33bc48281d19dce701398dbb88cb54fd7ecCary Clark# parameters.
32563af33bc48281d19dce701398dbb88cb54fd7ecCary Clark
33563af33bc48281d19dce701398dbb88cb54fd7ecCary Clarkuse Getopt::Long;
34563af33bc48281d19dce701398dbb88cb54fd7ecCary Clarkuse File::Basename;
35563af33bc48281d19dce701398dbb88cb54fd7ecCary Clark
36563af33bc48281d19dce701398dbb88cb54fd7ecCary Clarkuse strict;
37563af33bc48281d19dce701398dbb88cb54fd7ecCary Clarkuse warnings;
38563af33bc48281d19dce701398dbb88cb54fd7ecCary Clark
39563af33bc48281d19dce701398dbb88cb54fd7ecCary Clarksub commify($);
40563af33bc48281d19dce701398dbb88cb54fd7ecCary Clark
41563af33bc48281d19dce701398dbb88cb54fd7ecCary Clarksub main()
42563af33bc48281d19dce701398dbb88cb54fd7ecCary Clark{
43563af33bc48281d19dce701398dbb88cb54fd7ecCary Clark    my $usage =
44563af33bc48281d19dce701398dbb88cb54fd7ecCary Clark        "Usage: " . basename($0) . " [options] malloc_history.txt\n" .
45563af33bc48281d19dce701398dbb88cb54fd7ecCary Clark        "  --grep-regexp        Include only call stacks that match this regular expression.\n" .
46563af33bc48281d19dce701398dbb88cb54fd7ecCary Clark        "  --byte-minimum       Include only call stacks with allocation sizes >= this value.\n" .
47563af33bc48281d19dce701398dbb88cb54fd7ecCary Clark        "  --merge-regexp       Merge all call stacks that match this regular expression.\n" .
48563af33bc48281d19dce701398dbb88cb54fd7ecCary Clark        "  --merge-depth        Merge all call stacks that match at this stack depth and above.\n";
49563af33bc48281d19dce701398dbb88cb54fd7ecCary Clark
50563af33bc48281d19dce701398dbb88cb54fd7ecCary Clark    my $grepRegexp = "";
51563af33bc48281d19dce701398dbb88cb54fd7ecCary Clark    my $byteMinimum = "";
52563af33bc48281d19dce701398dbb88cb54fd7ecCary Clark    my @mergeRegexps = ();
53563af33bc48281d19dce701398dbb88cb54fd7ecCary Clark    my $mergeDepth = "";
54563af33bc48281d19dce701398dbb88cb54fd7ecCary Clark    my $getOptionsResult = GetOptions(
55563af33bc48281d19dce701398dbb88cb54fd7ecCary Clark        "grep-regexp:s" => \$grepRegexp,
56563af33bc48281d19dce701398dbb88cb54fd7ecCary Clark        "byte-minimum:i" => \$byteMinimum,
57563af33bc48281d19dce701398dbb88cb54fd7ecCary Clark        "merge-regexp:s" => \@mergeRegexps,
58563af33bc48281d19dce701398dbb88cb54fd7ecCary Clark        "merge-depth:i" => \$mergeDepth
59563af33bc48281d19dce701398dbb88cb54fd7ecCary Clark    );
60563af33bc48281d19dce701398dbb88cb54fd7ecCary Clark    my $fileName = $ARGV[0];
61563af33bc48281d19dce701398dbb88cb54fd7ecCary Clark    die $usage if (!$getOptionsResult || !$fileName);
62563af33bc48281d19dce701398dbb88cb54fd7ecCary Clark
63563af33bc48281d19dce701398dbb88cb54fd7ecCary Clark    open FILE, "<$fileName" or die "bad file: $fileName";
64563af33bc48281d19dce701398dbb88cb54fd7ecCary Clark    my @file = <FILE>;
65563af33bc48281d19dce701398dbb88cb54fd7ecCary Clark    close FILE;
66563af33bc48281d19dce701398dbb88cb54fd7ecCary Clark
67563af33bc48281d19dce701398dbb88cb54fd7ecCary Clark    my %callstacks = ();
68563af33bc48281d19dce701398dbb88cb54fd7ecCary Clark    my $byteCountTotal = 0;
69563af33bc48281d19dce701398dbb88cb54fd7ecCary Clark
70563af33bc48281d19dce701398dbb88cb54fd7ecCary Clark    for (my $i = 0; $i < @file; $i++) {
71563af33bc48281d19dce701398dbb88cb54fd7ecCary Clark        my $line = $file[$i];
72563af33bc48281d19dce701398dbb88cb54fd7ecCary Clark        my ($callCount, $byteCount);
73563af33bc48281d19dce701398dbb88cb54fd7ecCary Clark
74563af33bc48281d19dce701398dbb88cb54fd7ecCary Clark        # First try malloc_history format
75563af33bc48281d19dce701398dbb88cb54fd7ecCary Clark        #   6 calls for 664 bytes thread_ffffffff |0x0 | start
76563af33bc48281d19dce701398dbb88cb54fd7ecCary Clark        ($callCount, $byteCount) = ($line =~ /(\d+) calls for (\d+) bytes/);
77563af33bc48281d19dce701398dbb88cb54fd7ecCary Clark        
78563af33bc48281d19dce701398dbb88cb54fd7ecCary Clark        # Then try leaks format
79563af33bc48281d19dce701398dbb88cb54fd7ecCary Clark        #   Leak: 0x0ac3ca40  size=48
80563af33bc48281d19dce701398dbb88cb54fd7ecCary Clark        #   0x00020001 0x00000001 0x00000000 0x00000000     ................
81563af33bc48281d19dce701398dbb88cb54fd7ecCary Clark        #   Call stack: [thread ffffffff]: | 0x0 | start
82563af33bc48281d19dce701398dbb88cb54fd7ecCary Clark        if (!$callCount || !$byteCount) {
83563af33bc48281d19dce701398dbb88cb54fd7ecCary Clark            $callCount = 1;
84563af33bc48281d19dce701398dbb88cb54fd7ecCary Clark            ($byteCount) = ($line =~ /Leak: [x[:xdigit:]]*  size=(\d+)/);
85563af33bc48281d19dce701398dbb88cb54fd7ecCary Clark
86563af33bc48281d19dce701398dbb88cb54fd7ecCary Clark            if ($byteCount) {
87563af33bc48281d19dce701398dbb88cb54fd7ecCary Clark                while (!($line =~ "Call stack: ")) {
88563af33bc48281d19dce701398dbb88cb54fd7ecCary Clark                    $i++;
89563af33bc48281d19dce701398dbb88cb54fd7ecCary Clark                    $line = $file[$i];
90563af33bc48281d19dce701398dbb88cb54fd7ecCary Clark                }
91563af33bc48281d19dce701398dbb88cb54fd7ecCary Clark            }
92563af33bc48281d19dce701398dbb88cb54fd7ecCary Clark        }
93563af33bc48281d19dce701398dbb88cb54fd7ecCary Clark        
94231d4e3152a9c27a73b6ac7badbe6be673aa3ddfSteve Block        # Then try LeakFinder format
95231d4e3152a9c27a73b6ac7badbe6be673aa3ddfSteve Block        # --------------- Key: 213813, 84 bytes ---------
96231d4e3152a9c27a73b6ac7badbe6be673aa3ddfSteve Block        # c:\cygwin\home\buildbot\webkit\opensource\webcore\rendering\renderarena.cpp(78): WebCore::RenderArena::allocate
97231d4e3152a9c27a73b6ac7badbe6be673aa3ddfSteve Block        # c:\cygwin\home\buildbot\webkit\opensource\webcore\rendering\renderobject.cpp(82): WebCore::RenderObject::operator new
98231d4e3152a9c27a73b6ac7badbe6be673aa3ddfSteve Block        if (!$callCount || !$byteCount) {
99231d4e3152a9c27a73b6ac7badbe6be673aa3ddfSteve Block            $callCount = 1;
100231d4e3152a9c27a73b6ac7badbe6be673aa3ddfSteve Block            ($byteCount) = ($line =~ /Key: (?:\d+), (\d+) bytes/);
101231d4e3152a9c27a73b6ac7badbe6be673aa3ddfSteve Block            if ($byteCount) {
102231d4e3152a9c27a73b6ac7badbe6be673aa3ddfSteve Block                $line = $file[++$i];
103231d4e3152a9c27a73b6ac7badbe6be673aa3ddfSteve Block                my @tempStack;
104231d4e3152a9c27a73b6ac7badbe6be673aa3ddfSteve Block                while ($file[$i+1] !~ /^(?:-|\d)/) {
105231d4e3152a9c27a73b6ac7badbe6be673aa3ddfSteve Block                    if ($line =~ /\): (.*)$/) {
106231d4e3152a9c27a73b6ac7badbe6be673aa3ddfSteve Block                        my $call = $1;
107231d4e3152a9c27a73b6ac7badbe6be673aa3ddfSteve Block                        $call =~ s/\r$//;
108231d4e3152a9c27a73b6ac7badbe6be673aa3ddfSteve Block                        unshift(@tempStack, $call);
109231d4e3152a9c27a73b6ac7badbe6be673aa3ddfSteve Block                    }
110231d4e3152a9c27a73b6ac7badbe6be673aa3ddfSteve Block                    $line = $file[++$i];
111231d4e3152a9c27a73b6ac7badbe6be673aa3ddfSteve Block                }            
112231d4e3152a9c27a73b6ac7badbe6be673aa3ddfSteve Block                $line = join(" | ", @tempStack);
113231d4e3152a9c27a73b6ac7badbe6be673aa3ddfSteve Block            }
114231d4e3152a9c27a73b6ac7badbe6be673aa3ddfSteve Block        }
115231d4e3152a9c27a73b6ac7badbe6be673aa3ddfSteve Block        
116563af33bc48281d19dce701398dbb88cb54fd7ecCary Clark        # Then give up
117563af33bc48281d19dce701398dbb88cb54fd7ecCary Clark        next if (!$callCount || !$byteCount);
118563af33bc48281d19dce701398dbb88cb54fd7ecCary Clark        
119563af33bc48281d19dce701398dbb88cb54fd7ecCary Clark        $byteCountTotal += $byteCount;
120563af33bc48281d19dce701398dbb88cb54fd7ecCary Clark
121563af33bc48281d19dce701398dbb88cb54fd7ecCary Clark        next if ($grepRegexp && !($line =~ $grepRegexp));
122563af33bc48281d19dce701398dbb88cb54fd7ecCary Clark
123563af33bc48281d19dce701398dbb88cb54fd7ecCary Clark        my $callstackBegin = 0;
124563af33bc48281d19dce701398dbb88cb54fd7ecCary Clark        if ($mergeDepth) {
125563af33bc48281d19dce701398dbb88cb54fd7ecCary Clark            # count stack frames backwards from end of callstack
126563af33bc48281d19dce701398dbb88cb54fd7ecCary Clark            $callstackBegin = length($line);
127563af33bc48281d19dce701398dbb88cb54fd7ecCary Clark            for (my $pipeCount = 0; $pipeCount < $mergeDepth; $pipeCount++) {
128563af33bc48281d19dce701398dbb88cb54fd7ecCary Clark                my $rindexResult = rindex($line, "|", $callstackBegin - 1);
129563af33bc48281d19dce701398dbb88cb54fd7ecCary Clark                last if $rindexResult == -1;
130563af33bc48281d19dce701398dbb88cb54fd7ecCary Clark                $callstackBegin = $rindexResult;
131563af33bc48281d19dce701398dbb88cb54fd7ecCary Clark            }
132563af33bc48281d19dce701398dbb88cb54fd7ecCary Clark        } else {
133563af33bc48281d19dce701398dbb88cb54fd7ecCary Clark            # start at beginning of callstack
134563af33bc48281d19dce701398dbb88cb54fd7ecCary Clark            $callstackBegin = index($line, "|");
135563af33bc48281d19dce701398dbb88cb54fd7ecCary Clark        }
136563af33bc48281d19dce701398dbb88cb54fd7ecCary Clark
137563af33bc48281d19dce701398dbb88cb54fd7ecCary Clark        my $callstack = substr($line, $callstackBegin + 2); # + 2 skips "| "
138563af33bc48281d19dce701398dbb88cb54fd7ecCary Clark        for my $regexp (@mergeRegexps) {
139563af33bc48281d19dce701398dbb88cb54fd7ecCary Clark            if ($callstack =~ $regexp) {
140563af33bc48281d19dce701398dbb88cb54fd7ecCary Clark                $callstack = $regexp . "\n";
141563af33bc48281d19dce701398dbb88cb54fd7ecCary Clark                last;
142563af33bc48281d19dce701398dbb88cb54fd7ecCary Clark            }
143563af33bc48281d19dce701398dbb88cb54fd7ecCary Clark        }
144563af33bc48281d19dce701398dbb88cb54fd7ecCary Clark        
145563af33bc48281d19dce701398dbb88cb54fd7ecCary Clark        if (!$callstacks{$callstack}) {
146563af33bc48281d19dce701398dbb88cb54fd7ecCary Clark            $callstacks{$callstack} = {"callCount" => 0, "byteCount" => 0};
147563af33bc48281d19dce701398dbb88cb54fd7ecCary Clark        }
148563af33bc48281d19dce701398dbb88cb54fd7ecCary Clark
149563af33bc48281d19dce701398dbb88cb54fd7ecCary Clark        $callstacks{$callstack}{"callCount"} += $callCount;
150563af33bc48281d19dce701398dbb88cb54fd7ecCary Clark        $callstacks{$callstack}{"byteCount"} += $byteCount;
151563af33bc48281d19dce701398dbb88cb54fd7ecCary Clark    }
152563af33bc48281d19dce701398dbb88cb54fd7ecCary Clark
153563af33bc48281d19dce701398dbb88cb54fd7ecCary Clark    my $byteCountTotalReported = 0;
154563af33bc48281d19dce701398dbb88cb54fd7ecCary Clark    for my $callstack (sort { $callstacks{$b}{"byteCount"} <=> $callstacks{$a}{"byteCount"} } keys %callstacks) {
155563af33bc48281d19dce701398dbb88cb54fd7ecCary Clark        my $callCount = $callstacks{$callstack}{"callCount"};
156563af33bc48281d19dce701398dbb88cb54fd7ecCary Clark        my $byteCount = $callstacks{$callstack}{"byteCount"};
157563af33bc48281d19dce701398dbb88cb54fd7ecCary Clark        last if ($byteMinimum && $byteCount < $byteMinimum);
158563af33bc48281d19dce701398dbb88cb54fd7ecCary Clark
159563af33bc48281d19dce701398dbb88cb54fd7ecCary Clark        $byteCountTotalReported += $byteCount;
160563af33bc48281d19dce701398dbb88cb54fd7ecCary Clark        print commify($callCount) . " calls for " . commify($byteCount) . " bytes: $callstack\n";
161563af33bc48281d19dce701398dbb88cb54fd7ecCary Clark    }
162563af33bc48281d19dce701398dbb88cb54fd7ecCary Clark
163563af33bc48281d19dce701398dbb88cb54fd7ecCary Clark    print "total: " . commify($byteCountTotalReported) . " bytes (" . commify($byteCountTotal - $byteCountTotalReported) . " bytes excluded).\n";
164563af33bc48281d19dce701398dbb88cb54fd7ecCary Clark}
165563af33bc48281d19dce701398dbb88cb54fd7ecCary Clark
166563af33bc48281d19dce701398dbb88cb54fd7ecCary Clarkexit(main());
167563af33bc48281d19dce701398dbb88cb54fd7ecCary Clark
168563af33bc48281d19dce701398dbb88cb54fd7ecCary Clark# Copied from perldoc -- please excuse the style
169563af33bc48281d19dce701398dbb88cb54fd7ecCary Clarksub commify($)
170563af33bc48281d19dce701398dbb88cb54fd7ecCary Clark{
171563af33bc48281d19dce701398dbb88cb54fd7ecCary Clark    local $_  = shift;
172563af33bc48281d19dce701398dbb88cb54fd7ecCary Clark    1 while s/^([-+]?\d+)(\d{3})/$1,$2/;
173563af33bc48281d19dce701398dbb88cb54fd7ecCary Clark    return $_;
174563af33bc48281d19dce701398dbb88cb54fd7ecCary Clark}
175