| #!/usr/bin/env python |
| # Copyright (c) 2012 The Chromium Authors. All rights reserved. |
| # Use of this source code is governed by a BSD-style license that can be |
| # found in the LICENSE file. |
| |
| import argparse |
| from collections import defaultdict |
| import json |
| import os |
| import re |
| import subprocess |
| import sys |
| |
| import suppressions |
| |
| |
| def ReadReportsFromFile(filename): |
| """ Returns a list of (report_hash, report) and the URL of the report on the |
| waterfall. |
| """ |
| input_file = file(filename, 'r') |
| # reports is a list of (error hash, report) pairs. |
| reports = [] |
| in_suppression = False |
| cur_supp = [] |
| # This stores the last error hash found while reading the file. |
| last_hash = "" |
| for line in input_file: |
| line = line.strip() |
| line = line.replace("</span><span class=\"stdout\">", "") |
| line = line.replace("</span><span class=\"stderr\">", "") |
| line = line.replace("<", "<") |
| line = line.replace(">", ">") |
| if in_suppression: |
| if line == "}": |
| cur_supp += ["}"] |
| reports += [[last_hash, "\n".join(cur_supp)]] |
| in_suppression = False |
| cur_supp = [] |
| last_hash = "" |
| else: |
| cur_supp += [" "*3 + line] |
| elif line == "{": |
| in_suppression = True |
| cur_supp = ["{"] |
| elif line.find("Suppression (error hash=#") == 0: |
| last_hash = line[25:41] |
| # The line at the end of the file is assumed to store the URL of the report. |
| return reports,line |
| |
| def Demangle(names): |
| """ Demangle a list of C++ symbols, return a list of human-readable symbols. |
| """ |
| # -n is not the default on Mac. |
| args = ['c++filt', '-n'] |
| pipe = subprocess.Popen(args, stdin=subprocess.PIPE, stdout=subprocess.PIPE) |
| stdout, _ = pipe.communicate(input='\n'.join(names)) |
| demangled = stdout.split("\n") |
| # Each line ends with a newline, so the final entry of the split output |
| # will always be ''. |
| assert len(demangled) == len(names) |
| return demangled |
| |
| def GetSymbolsFromReport(report): |
| """Extract all symbols from a suppression report.""" |
| symbols = [] |
| prefix = "fun:" |
| prefix_len = len(prefix) |
| for line in report.splitlines(): |
| index = line.find(prefix) |
| if index != -1: |
| symbols.append(line[index + prefix_len:]) |
| return symbols |
| |
| def PrintTopSymbols(symbol_reports, top_count): |
| """Print the |top_count| symbols with the most occurrences.""" |
| boring_symbols=['malloc', '_Znw*', 'TestBody'] |
| sorted_reports = sorted(filter(lambda x:x[0] not in boring_symbols, |
| symbol_reports.iteritems()), |
| key=lambda x:len(x[1]), reverse=True) |
| symbols = symbol_reports.keys() |
| demangled = Demangle(symbols) |
| assert len(demangled) == len(symbols) |
| symboltable = dict(zip(symbols, demangled)) |
| |
| print "\n" |
| print "Top %d symbols" % top_count |
| for (symbol, suppressions) in sorted_reports[:top_count]: |
| print "%4d occurrences : %s" % (len(suppressions), symboltable[symbol]) |
| |
| def ReadHashExclusions(exclusions): |
| input_file = file(exclusions, 'r') |
| contents = json.load(input_file) |
| return contents['hashes'] |
| |
| |
| def main(argv): |
| supp = suppressions.GetSuppressions() |
| |
| # all_reports is a map {report: list of urls containing this report} |
| all_reports = defaultdict(list) |
| report_hashes = {} |
| symbol_reports = defaultdict(list) |
| |
| # Create argument parser. |
| parser = argparse.ArgumentParser() |
| parser.add_argument('--top-symbols', type=int, default=0, |
| help='Print a list of the top <n> symbols') |
| parser.add_argument('--symbol-filter', action='append', |
| help='Filter out all suppressions not containing the specified symbol(s). ' |
| 'Matches against the mangled names.') |
| parser.add_argument('--exclude-symbol', action='append', |
| help='Filter out all suppressions containing the specified symbol(s). ' |
| 'Matches against the mangled names.') |
| parser.add_argument('--exclude-hashes', action='append', |
| help='Specify a .json file with a list of hashes to exclude.') |
| |
| parser.add_argument('reports', metavar='report file', nargs='+', |
| help='List of report files') |
| args = parser.parse_args(argv) |
| |
| # exclude_hashes is a list of strings, each string an error hash. |
| exclude_hashes = [] |
| |
| exclude_hashes = [] |
| if args.exclude_hashes: |
| for excl in args.exclude_hashes: |
| print "reading exclusion", excl |
| exclude_hashes += ReadHashExclusions(excl) |
| |
| for f in args.reports: |
| f_reports, url = ReadReportsFromFile(f) |
| for (hash, report) in f_reports: |
| if hash in exclude_hashes: |
| continue |
| all_reports[report] += [url] |
| report_hashes[report] = hash |
| |
| reports_count = 0 |
| for r in all_reports: |
| cur_supp = supp['common_suppressions'] |
| if all([re.search("%20Mac%20|mac_valgrind", url) |
| for url in all_reports[r]]): |
| # Include mac suppressions if the report is only present on Mac |
| cur_supp += supp['mac_suppressions'] |
| elif all([re.search("Linux%20", url) for url in all_reports[r]]): |
| cur_supp += supp['linux_suppressions'] |
| if all(["DrMemory" in url for url in all_reports[r]]): |
| cur_supp += supp['drmem_suppressions'] |
| if all(["DrMemory%20full" in url for url in all_reports[r]]): |
| cur_supp += supp['drmem_full_suppressions'] |
| |
| # Test if this report is already suppressed |
| skip = False |
| for s in cur_supp: |
| if s.Match(r.split("\n")): |
| skip = True |
| break |
| |
| # Skip reports if none of the symbols are in the report. |
| if args.symbol_filter and all(not s in r for s in args.symbol_filter): |
| skip = True |
| if args.exclude_symbol and any(s in r for s in args.exclude_symbol): |
| skip = True |
| |
| if not skip: |
| reports_count += 1 |
| print "===================================" |
| print "This report observed at" |
| for url in all_reports[r]: |
| print " %s" % url |
| print "didn't match any suppressions:" |
| print "Suppression (error hash=#%s#):" % (report_hashes[r]) |
| print r |
| print "===================================" |
| |
| if args.top_symbols > 0: |
| symbols = GetSymbolsFromReport(r) |
| for symbol in symbols: |
| symbol_reports[symbol].append(report_hashes[r]) |
| |
| if reports_count > 0: |
| print ("%d unique reports don't match any of the suppressions" % |
| reports_count) |
| if args.top_symbols > 0: |
| PrintTopSymbols(symbol_reports, args.top_symbols) |
| |
| else: |
| print "Congratulations! All reports are suppressed!" |
| # TODO(timurrrr): also make sure none of the old suppressions |
| # were narrowed too much. |
| |
| |
| if __name__ == "__main__": |
| main(sys.argv[1:]) |