1#!/usr/bin/perl -w
2# This Source Code Form is subject to the terms of the Mozilla Public
3# License, v. 2.0. If a copy of the MPL was not distributed with this
4# file, You can obtain one at http://mozilla.org/MPL/2.0/.
5
6
7################################################################################
8
9sub usage() {
10    print <<EOUSAGE;
11# bloatdiff.pl - munges the output from
12#   XPCOM_MEM_BLOAT_LOG=1
13#   firefox-bin -P default resource:///res/bloatcycle.html
14# so that it does some summary and stats stuff.
15#
16# To show leak test results for a set of changes, do something like this:
17#
18#   XPCOM_MEM_BLOAT_LOG=1
19#   firefox-bin -P default resource:///res/bloatcycle.html > a.out
20#     **make change**
21#   firefox-bin -P default resource:///res/bloatcycle.html > b.out
22#   bloatdiff.pl a.out b.out
23
24EOUSAGE
25}
26
27$OLDFILE = $ARGV[0];
28$NEWFILE = $ARGV[1];
29#$LABEL   = $ARGV[2];
30
31if (!$OLDFILE or
32    ! -e $OLDFILE or
33    -z $OLDFILE) {
34    print "\nError: Previous log file not specified, does not exist, or is empty.\n\n";
35    &usage();
36    exit 1;
37}
38
39if (!$NEWFILE or
40    ! -e $NEWFILE or
41    -z $NEWFILE) {
42    print "\nError: Current log file not specified, does not exist, or is empty.\n\n";
43    &usage();
44    exit 1;
45}
46
47sub processFile {
48    my ($filename, $map, $prevMap) = @_;
49    open(FH, $filename);
50    while (<FH>) {
51        if (m{
52              ^\s*(\d+)\s          # Line number
53              ([\w:]+)\s+          # Name
54              (-?\d+)\s+           # Size
55              (-?\d+)\s+           # Leaked
56              (-?\d+)\s+           # Objects Total
57              (-?\d+)\s+           # Objects Rem
58              \(\s*(-?[\d.]+)\s+   # Objects Mean
59                 \+/-\s+
60              ([\w.]+)\)\s+        # Objects StdDev
61              (-?\d+)\s+           # Reference Total
62              (-?\d+)\s+           # Reference Rem
63              \(\s*(-?[\d.]+)\s+   # Reference Mean
64                 \+/-\s+
65              ([\w\.]+)\)          # Reference StdDev
66             }x) {
67          $$map{$2} = { name => $2,
68                        size => $3,
69                        leaked => $4,
70                        objTotal => $5,
71                        objRem => $6,
72                        objMean => $7,
73                        objStdDev => $8,
74                        refTotal => $9,
75                        refRem => $10,
76                        refMean => $11,
77                        refStdDev => $12,
78                        bloat => $3 * $5 # size * objTotal
79                      };
80        } else {
81#            print "failed to parse: $_\n";
82        }
83    }
84    close(FH);
85}
86
87%oldMap = ();
88processFile($OLDFILE, \%oldMap);
89
90%newMap = ();
91processFile($NEWFILE, \%newMap);
92
93################################################################################
94
95$inf = 9999999.99;
96
97sub getLeaksDelta {
98    my ($key) = @_;
99    my $oldLeaks = $oldMap{$key}{leaked} || 0;
100    my $newLeaks = $newMap{$key}{leaked};
101    my $percentLeaks = 0;
102    if ($oldLeaks == 0) {
103        if ($newLeaks != 0) {
104            # there weren't any leaks before, but now there are!
105            $percentLeaks = $inf;
106        }
107    }
108    else {
109        $percentLeaks = ($newLeaks - $oldLeaks) / $oldLeaks * 100;
110    }
111    # else we had no record of this class before
112    return ($newLeaks - $oldLeaks, $percentLeaks);
113}
114
115################################################################################
116
117sub getBloatDelta {
118    my ($key) = @_;
119    my $newBloat = $newMap{$key}{bloat};
120    my $percentBloat = 0;
121    my $oldSize = $oldMap{$key}{size} || 0;
122    my $oldTotal = $oldMap{$key}{objTotal} || 0;
123    my $oldBloat = $oldTotal * $oldSize;
124    if ($oldBloat == 0) {
125        if ($newBloat != 0) {
126            # this class wasn't used before, but now it is
127            $percentBloat = $inf;
128        }
129    }
130    else {
131        $percentBloat = ($newBloat - $oldBloat) / $oldBloat * 100;
132    }
133    # else we had no record of this class before
134    return ($newBloat - $oldBloat, $percentBloat);
135}
136
137################################################################################
138
139foreach $key (keys %newMap) {
140    my ($newLeaks, $percentLeaks) = getLeaksDelta($key);
141    my ($newBloat, $percentBloat) = getBloatDelta($key);
142    $newMap{$key}{leakDelta} = $newLeaks;
143    $newMap{$key}{leakPercent} = $percentLeaks;
144    $newMap{$key}{bloatDelta} = $newBloat;
145    $newMap{$key}{bloatPercent} = $percentBloat;
146}
147
148################################################################################
149
150# Print a value of bytes out in a reasonable
151# KB, MB, or GB form.  Copied from build-seamonkey-util.pl, sorry.  -mcafee
152sub PrintSize($) {
153
154    # print a number with 3 significant figures
155    sub PrintNum($) {
156        my ($num) = @_;
157        my $rv;
158        if ($num < 1) {
159            $rv = sprintf "%.3f", ($num);
160        } elsif ($num < 10) {
161            $rv = sprintf "%.2f", ($num);
162        } elsif ($num < 100) {
163            $rv = sprintf "%.1f", ($num);
164        } else {
165            $rv = sprintf "%d", ($num);
166        }
167    }
168
169    my ($size) = @_;
170    my $rv;
171    if ($size > 1000000000) {
172        $rv = PrintNum($size / 1000000000.0) . "G";
173    } elsif ($size > 1000000) {
174        $rv = PrintNum($size / 1000000.0) . "M";
175    } elsif ($size > 1000) {
176        $rv = PrintNum($size / 1000.0) . "K";
177    } else {
178        $rv = PrintNum($size);
179    }
180}
181
182
183print "Bloat/Leak Delta Report\n";
184print "--------------------------------------------------------------------------------------\n";
185print "Current file:  $NEWFILE\n";
186print "Previous file: $OLDFILE\n";
187print "----------------------------------------------leaks------leaks%------bloat------bloat%\n";
188
189    if (! $newMap{"TOTAL"} or
190        ! $newMap{"TOTAL"}{bloat}) {
191        # It's OK if leaked or leakPercent are 0 (in fact, that would be good).
192        # If bloatPercent is zero, it is also OK, because we may have just had
193        # two runs exactly the same or with no new bloat.
194        print "\nError: unable to calculate bloat/leak data.\n";
195        print "There is no data present.\n\n";
196        print "HINT - Did your test run complete successfully?\n";
197        print "HINT - Are you pointing at the right log files?\n\n";
198        &usage();
199        exit 1;
200    }
201
202printf "%-40s %10s %10.2f%% %10s %10.2f%%\n",
203       ("TOTAL",
204        $newMap{"TOTAL"}{leaked}, $newMap{"TOTAL"}{leakPercent},
205        $newMap{"TOTAL"}{bloat}, $newMap{"TOTAL"}{bloatPercent});
206
207################################################################################
208
209sub percentStr {
210    my ($p) = @_;
211    if ($p == $inf) {
212        return "-";
213    }
214    else {
215        return sprintf "%10.2f%%", $p;
216    }
217}
218
219# NEW LEAKS
220@keys = sort { $newMap{$b}{leakPercent} <=> $newMap{$a}{leakPercent} } keys %newMap;
221my $needsHeading = 1;
222my $total = 0;
223foreach $key (@keys) {
224    my $percentLeaks = $newMap{$key}{leakPercent};
225    my $leaks = $newMap{$key}{leaked};
226    if ($percentLeaks > 0 && $key !~ /TOTAL/) {
227        if ($needsHeading) {
228            printf "--NEW-LEAKS-----------------------------------leaks------leaks%%-----------------------\n";
229            $needsHeading = 0;
230        }
231        printf "%-40s %10s %10s\n", ($key, $leaks, percentStr($percentLeaks));
232        $total += $leaks;
233    }
234}
235if (!$needsHeading) {
236    printf "%-40s %10s\n", ("TOTAL", $total);
237}
238
239# FIXED LEAKS
240@keys = sort { $newMap{$b}{leakPercent} <=> $newMap{$a}{leakPercent} } keys %newMap;
241$needsHeading = 1;
242$total = 0;
243foreach $key (@keys) {
244    my $percentLeaks = $newMap{$key}{leakPercent};
245    my $leaks = $newMap{$key}{leaked};
246    if ($percentLeaks < 0 && $key !~ /TOTAL/) {
247        if ($needsHeading) {
248            printf "--FIXED-LEAKS---------------------------------leaks------leaks%%-----------------------\n";
249            $needsHeading = 0;
250        }
251        printf "%-40s %10s %10s\n", ($key, $leaks, percentStr($percentLeaks));
252        $total += $leaks;
253    }
254}
255if (!$needsHeading) {
256    printf "%-40s %10s\n", ("TOTAL", $total);
257}
258
259# NEW BLOAT
260@keys = sort { $newMap{$b}{bloatPercent} <=> $newMap{$a}{bloatPercent} } keys %newMap;
261$needsHeading = 1;
262$total = 0;
263foreach $key (@keys) {
264    my $percentBloat = $newMap{$key}{bloatPercent};
265    my $bloat = $newMap{$key}{bloat};
266    if ($percentBloat > 0  && $key !~ /TOTAL/) {
267        if ($needsHeading) {
268            printf "--NEW-BLOAT-----------------------------------bloat------bloat%%-----------------------\n";
269            $needsHeading = 0;
270        }
271        printf "%-40s %10s %10s\n", ($key, $bloat, percentStr($percentBloat));
272        $total += $bloat;
273    }
274}
275if (!$needsHeading) {
276    printf "%-40s %10s\n", ("TOTAL", $total);
277}
278
279# ALL LEAKS
280@keys = sort { $newMap{$b}{leaked} <=> $newMap{$a}{leaked} } keys %newMap;
281$needsHeading = 1;
282$total = 0;
283foreach $key (@keys) {
284    my $leaks = $newMap{$key}{leaked};
285    my $percentLeaks = $newMap{$key}{leakPercent};
286    if ($leaks > 0) {
287        if ($needsHeading) {
288            printf "--ALL-LEAKS-----------------------------------leaks------leaks%%-----------------------\n";
289            $needsHeading = 0;
290        }
291        printf "%-40s %10s %10s\n", ($key, $leaks, percentStr($percentLeaks));
292        if ($key !~ /TOTAL/) {
293            $total += $leaks;
294        }
295    }
296}
297if (!$needsHeading) {
298#    printf "%-40s %10s\n", ("TOTAL", $total);
299}
300
301# ALL BLOAT
302@keys = sort { $newMap{$b}{bloat} <=> $newMap{$a}{bloat} } keys %newMap;
303$needsHeading = 1;
304$total = 0;
305foreach $key (@keys) {
306    my $bloat = $newMap{$key}{bloat};
307    my $percentBloat = $newMap{$key}{bloatPercent};
308    if ($bloat > 0) {
309        if ($needsHeading) {
310            printf "--ALL-BLOAT-----------------------------------bloat------bloat%%-----------------------\n";
311            $needsHeading = 0;
312        }
313        printf "%-40s %10s %10s\n", ($key, $bloat, percentStr($percentBloat));
314        if ($key !~ /TOTAL/) {
315            $total += $bloat;
316        }
317    }
318}
319if (!$needsHeading) {
320#    printf "%-40s %10s\n", ("TOTAL", $total);
321}
322
323# NEW CLASSES
324@keys = sort { $newMap{$b}{bloatDelta} <=> $newMap{$a}{bloatDelta} } keys %newMap;
325$needsHeading = 1;
326my $ltotal = 0;
327my $btotal = 0;
328foreach $key (@keys) {
329    my $leaks = $newMap{$key}{leaked};
330    my $bloat = $newMap{$key}{bloat};
331    my $percentBloat = $newMap{$key}{bloatPercent};
332    if ($percentBloat == $inf && $key !~ /TOTAL/) {
333        if ($needsHeading) {
334            printf "--CLASSES-NOT-REPORTED-LAST-TIME--------------leaks------bloat------------------------\n";
335            $needsHeading = 0;
336        }
337        printf "%-40s %10s %10s\n", ($key, $leaks, $bloat);
338        if ($key !~ /TOTAL/) {
339            $ltotal += $leaks;
340            $btotal += $bloat;
341        }
342    }
343}
344if (!$needsHeading) {
345    printf "%-40s %10s %10s\n", ("TOTAL", $ltotal, $btotal);
346}
347
348# OLD CLASSES
349@keys = sort { ($oldMap{$b}{bloat} || 0) <=> ($oldMap{$a}{bloat} || 0) } keys %oldMap;
350$needsHeading = 1;
351$ltotal = 0;
352$btotal = 0;
353foreach $key (@keys) {
354    if (!defined($newMap{$key})) {
355        my $leaks = $oldMap{$key}{leaked};
356        my $bloat = $oldMap{$key}{bloat};
357        if ($needsHeading) {
358            printf "--CLASSES-THAT-WENT-AWAY----------------------leaks------bloat------------------------\n";
359            $needsHeading = 0;
360        }
361        printf "%-40s %10s %10s\n", ($key, $leaks, $bloat);
362        if ($key !~ /TOTAL/) {
363            $ltotal += $leaks;
364            $btotal += $bloat;
365        }
366    }
367}
368if (!$needsHeading) {
369    printf "%-40s %10s %10s\n", ("TOTAL", $ltotal, $btotal);
370}
371
372print "--------------------------------------------------------------------------------------\n";
373