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