#!/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("Windows%20", url) for url in all_reports[r]]): # Include win32 suppressions if the report is only present on Windows cur_supp += supp['win_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:])