Changeset 225937 in webkit


Ignore:
Timestamp:
Dec 14, 2017 3:26:12 PM (6 years ago)
Author:
ap@apple.com
Message:

Improve leaks detector output
https://bugs.webkit.org/show_bug.cgi?id=180828

Reviewed by Joseph Pecoraro.

Fixing two issues:

  1. run-leaks omits many lines from leaks tool output, making it incompatible with

other tools. Notably, symbolication cannot be performed.

  1. run-leaks output goes to "run-webkit-tests --debug-rwt-logging" output. This

makes it much longer than needed, sometimes even overloading buildbot. We don't
need full output in test log, as separate files are created for each of these.

  • Scripts/run-leaks: Represent each line in leaks output when parsing, and print

everything except for explicitly excluded leaks. From my testing and reading
the code, it looks like none of our tools should be broken by this change.

  • Scripts/webkitpy/port/leakdetector.py: I couldn't find a way to run a helper tool

without dumping all of its output to debug log, so switched to using a file for leaks.

  • Scripts/webkitperl/run-leaks_unittest/run-leaks-report-v1.0.pl:
  • Scripts/webkitperl/run-leaks_unittest/run-leaks-report-v2.0-new.pl:
  • Scripts/webkitperl/run-leaks_unittest/run-leaks-report-v2.0-old.pl:
  • Scripts/webkitpy/port/leakdetector_unittest.py:

Updated tests for new behavior.

Location:
trunk/Tools
Files:
7 edited

Legend:

Unmodified
Added
Removed
  • trunk/Tools/ChangeLog

    r225935 r225937  
     12017-12-14  Alexey Proskuryakov  <ap@apple.com>
     2
     3        Improve leaks detector output
     4        https://bugs.webkit.org/show_bug.cgi?id=180828
     5
     6        Reviewed by Joseph Pecoraro.
     7
     8        Fixing two issues:
     9        1. run-leaks omits many lines from leaks tool output, making it incompatible with
     10        other tools. Notably, symbolication cannot be performed.
     11        2. run-leaks output goes to "run-webkit-tests --debug-rwt-logging" output. This
     12        makes it much longer than needed, sometimes even overloading buildbot. We don't
     13        need full output in test log, as separate files are created for each of these.
     14
     15        * Scripts/run-leaks: Represent each line in leaks output when parsing, and print
     16        everything except for explicitly excluded leaks. From my testing and reading
     17        the code, it looks like none of our tools should be broken by this change.
     18
     19        * Scripts/webkitpy/port/leakdetector.py: I couldn't find a way to run a helper tool
     20        without dumping all of its output to debug log, so switched to using a file for leaks.
     21
     22        * Scripts/webkitperl/run-leaks_unittest/run-leaks-report-v1.0.pl:
     23        * Scripts/webkitperl/run-leaks_unittest/run-leaks-report-v2.0-new.pl:
     24        * Scripts/webkitperl/run-leaks_unittest/run-leaks-report-v2.0-old.pl:
     25        * Scripts/webkitpy/port/leakdetector_unittest.py:
     26        Updated tests for new behavior.
     27
    1282017-12-14  Brady Eidson  <beidson@apple.com>
    229
  • trunk/Tools/Scripts/run-leaks

    r165676 r225937  
    4747        "  --exclude-callstack regexp   Exclude leaks whose call stacks match the regular expression 'regexp'.\n" .
    4848        "  --exclude-type regexp        Exclude leaks whose data types match the regular expression 'regexp'.\n" .
     49        "  --output-file path           Store result in a file. If not specified, standard output is used.\n" .
    4950        "  --help                       Show this help message.\n";
    5051
    5152    my @callStacksToExclude = ();
    5253    my @typesToExclude = ();
     54    my $outputPath;
    5355    my $help = 0;
    5456
     
    5658        'exclude-callstack:s' => \@callStacksToExclude,
    5759        'exclude-type:s' => \@typesToExclude,
     60        'output-file:s' => \$outputPath,
    5861        'help' => \$help
    5962    );
     
    7780    }
    7881
    79     my $leakList = parseLeaksOutput(@$leaksOutput);
    80     if (!$leakList) {
     82    my $parsedOutput = parseLeaksOutput(@$leaksOutput);
     83    if (!$parsedOutput) {
    8184        return 1;
    8285    }
    8386
    8487    # Filter output.
    85     my $leakCount = @$leakList;
    86     removeMatchingRecords(@$leakList, "callStack", @callStacksToExclude);
    87     removeMatchingRecords(@$leakList, "type", @typesToExclude);
    88     my $excludeCount = $leakCount - @$leakList;
    89 
    90     # Dump results.
    91     print $leaksOutput->[0];
    92     print $leaksOutput->[1];
    93     foreach my $leak (@$leakList) {
    94         print $leak->{"leaksOutput"};
     88    # FIXME: Adjust the overall leak count in the output accordingly. This will affect callers, notably leakdetector.py.
     89    my $parsedLineCount = @$parsedOutput;
     90    removeMatchingRecords(@$parsedOutput, "callStack", @callStacksToExclude);
     91    removeMatchingRecords(@$parsedOutput, "type", @typesToExclude);
     92    my $excludeCount = $parsedLineCount - @$parsedOutput;
     93
     94    my $outputFH;
     95    if (defined $outputPath) {
     96        open($outputFH , '>', $outputPath) or die "Could not open $outputPath for writing";
     97    } else {
     98        $outputFH = *STDOUT;
     99    }
     100
     101    foreach my $leak (@$parsedOutput) {
     102        print $outputFH $leak->{"leaksOutput"};
    95103    }
    96104
    97105    if ($excludeCount) {
    98         print "$excludeCount leaks excluded (not printed)\n";
     106        print $outputFH "$excludeCount leaks excluded (not printed)\n";
    99107    }
    100108
     
    133141    #   We treat every line except for  Process 00000: and Leak: as optional
    134142
    135     # Skip header section until the first two "Process " lines.
    136     # FIXME: In the future we may wish to propagate the header section through to our output.
    137     until ($leaksOutput->[0] =~ /^Process /) {
    138         shift @$leaksOutput;
    139     }
    140 
    141     my ($leakCount) = ($leaksOutput->[1] =~ /[[:blank:]]+([0-9]+)[[:blank:]]+leaks?/);
    142     if (!defined($leakCount)) {
    143         reportError("Could not parse leak count reported by leaks tool.");
    144         return;
    145     }
    146 
    147     my @leakList = ();
     143    my $leakCount;
     144    my @parsedOutput = ();
     145    my $parsingLeak = 0;
     146    my $parsedLeakCount = 0;
    148147    for my $line (@$leaksOutput) {
    149         next if $line =~ /^Process/;
    150         next if $line =~ /^node buffer added/;
    151        
     148        if ($line =~ /^Process \d+: (\d+) leaks?/) {
     149            $leakCount = $1;
     150        }
     151
     152        if ($line eq "\n") {
     153            $parsingLeak = 0;
     154        }
     155
    152156        if ($line =~ /^Leak: /) {
     157            $parsingLeak = 1;
     158            $parsedLeakCount++;
    153159            my ($address) = ($line =~ /Leak: ([[:xdigit:]x]+)/);
    154160            if (!defined($address)) {
     
    163169            }
    164170
     171            # FIXME: This code seems wrong, the leaks tool doesn't actually use single quotes.
     172            # We should reconcile with other format changes that happened since, such as the
     173            # addition of zone information.
    165174            my ($type) = ($line =~ /'([^']+)'/); #'
    166175            if (!defined($type)) {
     
    175184                "leaksOutput" => $line
    176185            );
    177             push(@leakList, \%leak);
     186            push(@parsedOutput, \%leak);
     187        } elsif ($parsingLeak) {
     188            $parsedOutput[$#parsedOutput]->{"leaksOutput"} .= $line;
     189            if ($line =~ /Call stack:/) {
     190                $parsedOutput[$#parsedOutput]->{"callStack"} = $line;
     191            }
    178192        } else {
    179             $leakList[$#leakList]->{"leaksOutput"} .= $line;
    180             if ($line =~ /Call stack:/) {
    181                 $leakList[$#leakList]->{"callStack"} = $line;
    182             }
    183         }
    184     }
    185    
    186     if (@leakList != $leakCount) {
    187         my $parsedLeakCount = @leakList;
    188         reportError("Parsed leak count($parsedLeakCount) does not match leak count reported by leaks tool($leakCount).");
     193            my %nonLeakLine = (
     194                "leaksOutput" => $line
     195            );
     196            push(@parsedOutput, \%nonLeakLine);
     197        }
     198    }
     199   
     200    if ($parsedLeakCount != $leakCount) {
     201        reportError("Parsed leak count ($parsedLeakCount) does not match leak count reported by leaks tool ($leakCount).");
    189202        return;
    190203    }
    191204
    192     return \@leakList;
     205    return \@parsedOutput;
    193206}
    194207
     
    201214
    202215        foreach my $regexp (@$regexpList) {
    203             if ($record->{$key} =~ $regexp) {
     216            if (defined $record->{$key} and $record->{$key} =~ $regexp) {
    204217                splice(@$recordList, $i, 1);
    205218                next RECORD;
  • trunk/Tools/Scripts/webkitperl/run-leaks_unittest/run-leaks-report-v1.0.pl

    r103338 r225937  
    6363my $expectedOutput =
    6464[
     65  {
     66    'leaksOutput' => 'Process 1602: 86671 nodes malloced for 13261 KB'
     67  },
     68  {
     69    'leaksOutput' => 'Process 1602: 8 leaks for 160 total leaked bytes.'
     70  },
    6571  {
    6672    'leaksOutput' => join('', split(/\n/, <<EOF)),
  • trunk/Tools/Scripts/webkitperl/run-leaks_unittest/run-leaks-report-v2.0-new.pl

    r103338 r225937  
    7979[
    8080  {
     81    'leaksOutput' => 'Process:         DumpRenderTree [29903]'
     82  },
     83  {
     84    'leaksOutput' => 'Path:            /Volumes/Data/Build/Debug/DumpRenderTree'
     85  },
     86  {
     87    'leaksOutput' => 'Load Address:    0x102116000'
     88  },
     89  {
     90    'leaksOutput' => 'Identifier:      DumpRenderTree'
     91  },
     92  {
     93    'leaksOutput' => 'Version:         ??? (???)'
     94  },
     95  {
     96    'leaksOutput' => 'Code Type:       X86-64 (Native)'
     97  },
     98  {
     99    'leaksOutput' => 'Parent Process:  Python [29892]'
     100  },
     101  {
     102    'leaksOutput' => ''
     103  },
     104  {
     105    'leaksOutput' => 'Date/Time:       2011-11-14 11:12:45.706 -0800'
     106  },
     107  {
     108    'leaksOutput' => 'OS Version:      Mac OS X 10.7.2 (11C74)'
     109  },
     110  {
     111    'leaksOutput' => 'Report Version:  7'
     112  },
     113  {
     114    'leaksOutput' => ''
     115  },
     116  {
     117    'leaksOutput' => 'leaks Report Version:  2.0'
     118  },
     119  {
     120    'leaksOutput' => 'leaks(12871,0xacdfa2c0) malloc: process 89617 no longer exists, stack logs deleted from /tmp/stack-logs.89617.DumpRenderTree.A2giy6.index'
     121  },
     122  {
     123    'leaksOutput' => 'Process 29903: 60015 nodes malloced for 7290 KB'
     124  },
     125  {
     126    'leaksOutput' => 'Process 29903: 2 leaks for 1008 total leaked bytes.'
     127  },
     128  {
    81129    'leaksOutput' => join('', split(/\n/, <<EOF)),
    82130Leak: 0x7f9a3a612810  size=576  zone: DefaultMallocZone_0x10227b000   URLConnectionLoader::LoaderConnectionEventQueue  C++  CFNetwork
  • trunk/Tools/Scripts/webkitperl/run-leaks_unittest/run-leaks-report-v2.0-old.pl

    r103338 r225937  
    5959[
    6060  {
     61    'leaksOutput' => 'leaks Report Version:  2.0'
     62  },
     63  {
     64    'leaksOutput' => 'Process:         Safari [53606]'
     65  },
     66  {
     67    'leaksOutput' => 'Path:            /Applications/Safari.app/Contents/MacOS/Safari'
     68  },
     69  {
     70    'leaksOutput' => 'Load Address:    0x100000000'
     71  },
     72  {
     73    'leaksOutput' => 'Identifier:      com.apple.Safari'
     74  },
     75  {
     76    'leaksOutput' => 'Version:         5.0 (6533.9)'
     77  },
     78  {
     79    'leaksOutput' => 'Build Info:      WebBrowser-75330900~1'
     80  },
     81  {
     82    'leaksOutput' => 'Code Type:       X86-64 (Native)'
     83  },
     84  {
     85    'leaksOutput' => 'Parent Process:  perl5.10.0 [53599]'
     86  },
     87  {
     88    'leaksOutput' => ''
     89  },
     90  {
     91    'leaksOutput' => 'Date/Time:       2010-05-27 11:42:27.356 -0700'
     92  },
     93  {
     94    'leaksOutput' => 'OS Version:      Mac OS X 10.6.3 (10D571)'
     95  },
     96  {
     97    'leaksOutput' => 'Report Version:  6'
     98  },
     99  {
     100    'leaksOutput' => ''
     101  },
     102  {
     103    'leaksOutput' => 'Process 53606: 112295 nodes malloced for 22367 KB'
     104  },
     105  {
     106    'leaksOutput' => 'Process 53606: 1 leak for 32 total leaked bytes.'
     107  },
     108  {
    61109    'leaksOutput' => join('', split(/\n/, <<EOF)),
    62110Leak: 0x1118c0e60  size=32  zone: DefaultMallocZone_0x105a92000 string 'com.apple.quarantine'
  • trunk/Tools/Scripts/webkitpy/port/leakdetector.py

    r225733 r225937  
    6363        return callstacks
    6464
    65     def _leaks_args(self, pid):
     65    def _leaks_args(self, pid, leaks_output_path):
    6666        leaks_args = []
    6767        for callstack in self._callstacks_to_exclude_from_leaks():
     
    6969        for excluded_type in self._types_to_exclude_from_leaks():
    7070            leaks_args += ['--exclude-type=%s' % excluded_type]
     71        leaks_args += ['--output-file=%s' % leaks_output_path]
    7172        leaks_args.append(pid)
    7273        return leaks_args
     
    114115    def check_for_leaks(self, process_name, process_pid):
    115116        _log.debug("Checking for leaks in %s" % process_name)
     117        leaks_filename = self.leaks_file_name(process_name, process_pid)
     118        leaks_output_path = self._filesystem.join(self._port.results_directory(), leaks_filename)
    116119        try:
    117120            # Oddly enough, run-leaks (or the underlying leaks tool) does not seem to always output utf-8,
    118121            # thus we pass decode_output=False.  Without this code we've seen errors like:
    119122            # "UnicodeDecodeError: 'utf8' codec can't decode byte 0x88 in position 779874: unexpected code byte"
    120             leaks_output = self._port._run_script("run-leaks", self._leaks_args(process_pid), include_configuration_arguments=False, decode_output=False)
     123            self._port._run_script("run-leaks", self._leaks_args(process_pid, leaks_output_path), include_configuration_arguments=False, decode_output=False)
     124            leaks_output = self._filesystem.read_binary_file(leaks_output_path)
    121125        except ScriptError as e:
    122126            _log.warn("Failed to run leaks tool: %s" % e.message_with_output())
     
    127131        adjusted_count = count - excluded
    128132        if not adjusted_count:
     133            self._filesystem.remove(leaks_output_path)
    129134            return
    130 
    131         leaks_filename = self.leaks_file_name(process_name, process_pid)
    132         leaks_output_path = self._filesystem.join(self._port.results_directory(), leaks_filename)
    133         self._filesystem.write_binary_file(leaks_output_path, leaks_output)
    134135
    135136        # FIXME: Ideally we would not be logging from the worker process, but rather pass the leak
  • trunk/Tools/Scripts/webkitpy/port/leakdetector_unittest.py

    r225698 r225937  
    5151        detector._callstacks_to_exclude_from_leaks = lambda: ['foo bar', 'BAZ']
    5252        detector._types_to_exclude_from_leaks = lambda: ['abcdefg', 'hi jklmno']
    53         expected_args = ['--exclude-callstack=foo bar', '--exclude-callstack=BAZ', '--exclude-type=abcdefg', '--exclude-type=hi jklmno', 1234]
    54         self.assertEqual(detector._leaks_args(1234), expected_args)
     53        expected_args = ['--exclude-callstack=foo bar', '--exclude-callstack=BAZ', '--exclude-type=abcdefg', '--exclude-type=hi jklmno', '--output-file=leaks.txt', 1234]
     54        self.assertEqual(detector._leaks_args(1234, 'leaks.txt'), expected_args)
    5555
    5656    example_leaks_output = """Process 5122: 663744 nodes malloced for 78683 KB
Note: See TracChangeset for help on using the changeset viewer.