15821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#!/usr/bin/env python 25821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# Copyright (c) 2011 The Chromium Authors. All rights reserved. 35821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# Use of this source code is governed by a BSD-style license that can be 45821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# found in the LICENSE file. 55821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 65821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# drmemory_analyze.py 75821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 85821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)''' Given a Dr. Memory output file, parses errors and uniques them.''' 95821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 105821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)from collections import defaultdict 115821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)import common 125821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)import hashlib 135821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)import logging 145821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)import optparse 155821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)import os 165821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)import re 175821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)import subprocess 185821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)import sys 195821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)import time 205821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 215821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)class DrMemoryError: 225821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) def __init__(self, report, suppression, testcase): 235821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) self._report = report 245821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) self._testcase = testcase 255821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 265821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) # Chromium-specific transformations of the suppressions: 275821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) # Replace 'any_test.exe' and 'chrome.dll' with '*', then remove the 285821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) # Dr.Memory-generated error ids from the name= lines as they don't 295821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) # make sense in a multiprocess report. 305821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) supp_lines = suppression.split("\n") 315821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) for l in xrange(len(supp_lines)): 325821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) if supp_lines[l].startswith("name="): 335821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) supp_lines[l] = "name=<insert_a_suppression_name_here>" 345821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) if supp_lines[l].startswith("chrome.dll!"): 355821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) supp_lines[l] = supp_lines[l].replace("chrome.dll!", "*!") 365821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) bang_index = supp_lines[l].find("!") 375821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) d_exe_index = supp_lines[l].find(".exe!") 385821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) if bang_index >= 4 and d_exe_index + 4 == bang_index: 395821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) supp_lines[l] = "*" + supp_lines[l][bang_index:] 405821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) self._suppression = "\n".join(supp_lines) 415821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 425821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) def __str__(self): 435821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) output = self._report + "\n" 445821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) if self._testcase: 455821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) output += "The report came from the `%s` test.\n" % self._testcase 465821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) output += "Suppression (error hash=#%016X#):\n" % self.ErrorHash() 475821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) output += (" For more info on using suppressions see " 485821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) "http://dev.chromium.org/developers/how-tos/using-drmemory#TOC-Suppressing-error-reports-from-the-\n") 495821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) output += "{\n%s\n}\n" % self._suppression 505821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) return output 515821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 525821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) # This is a device-independent hash identifying the suppression. 535821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) # By printing out this hash we can find duplicate reports between tests and 545821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) # different shards running on multiple buildbots 555821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) def ErrorHash(self): 565821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) return int(hashlib.md5(self._suppression).hexdigest()[:16], 16) 575821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 585821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) def __hash__(self): 595821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) return hash(self._suppression) 605821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 615821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) def __eq__(self, rhs): 625821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) return self._suppression == rhs 635821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 645821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 655821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)class DrMemoryAnalyzer: 665821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) ''' Given a set of Dr.Memory output files, parse all the errors out of 675821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) them, unique them and output the results.''' 685821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 695821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) def __init__(self): 705821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) self.known_errors = set() 715821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) self.error_count = 0; 725821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 735821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) def ReadLine(self): 745821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) self.line_ = self.cur_fd_.readline() 755821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 765821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) def ReadSection(self): 775821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) result = [self.line_] 785821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) self.ReadLine() 795821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) while len(self.line_.strip()) > 0: 805821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) result.append(self.line_) 815821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) self.ReadLine() 825821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) return result 835821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 845821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) def ParseReportFile(self, filename, testcase): 855821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) ret = [] 865821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 875821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) # First, read the generated suppressions file so we can easily lookup a 885821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) # suppression for a given error. 895821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) supp_fd = open(filename.replace("results", "suppress"), 'r') 905821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) generated_suppressions = {} # Key -> Error #, Value -> Suppression text. 915821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) for line in supp_fd: 925821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) # NOTE: this regexp looks fragile. Might break if the generated 935821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) # suppression format slightly changes. 945821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) m = re.search("# Suppression for Error #([0-9]+)", line.strip()) 955821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) if not m: 965821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) continue 975821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) error_id = int(m.groups()[0]) 985821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) assert error_id not in generated_suppressions 995821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) # OK, now read the next suppression: 1005821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) cur_supp = "" 1015821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) for supp_line in supp_fd: 1025821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) if supp_line.startswith("#") or supp_line.strip() == "": 1035821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) break 1045821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) cur_supp += supp_line 1055821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) generated_suppressions[error_id] = cur_supp.strip() 1065821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) supp_fd.close() 1075821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 1085821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) self.cur_fd_ = open(filename, 'r') 1095821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) while True: 1105821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) self.ReadLine() 1115821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) if (self.line_ == ''): break 1125821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 1135821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) match = re.search("^Error #([0-9]+): (.*)", self.line_) 1145821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) if match: 1155821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) error_id = int(match.groups()[0]) 1165821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) self.line_ = match.groups()[1].strip() + "\n" 1175821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) report = "".join(self.ReadSection()).strip() 1185821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) suppression = generated_suppressions[error_id] 1195821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) ret.append(DrMemoryError(report, suppression, testcase)) 1205821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 1215821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) if re.search("SUPPRESSIONS USED:", self.line_): 1225821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) self.ReadLine() 1235821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) while self.line_.strip() != "": 1245821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) line = self.line_.strip() 1255c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu (count, name) = re.match(" *([0-9\?]+)x(?: \(.*?\))?: (.*)", 1265821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) line).groups() 1275c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu if (count == "?"): 1285c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu # Whole-module have no count available: assume 1 1295c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu count = 1 1305c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu else: 1315c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu count = int(count) 1325821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) self.used_suppressions[name] += count 1335821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) self.ReadLine() 1345821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 1355821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) if self.line_.startswith("ASSERT FAILURE"): 1365821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) ret.append(self.line_.strip()) 1375821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 1385821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) self.cur_fd_.close() 1395821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) return ret 1405821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 1415821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) def Report(self, filenames, testcase, check_sanity): 1425821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) sys.stdout.flush() 1435821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) # TODO(timurrrr): support positive tests / check_sanity==True 1445821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) self.used_suppressions = defaultdict(int) 1455821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 1465821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) to_report = [] 1475821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) reports_for_this_test = set() 1485821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) for f in filenames: 1495821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) cur_reports = self.ParseReportFile(f, testcase) 1505821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 1515821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) # Filter out the reports that were there in previous tests. 1525821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) for r in cur_reports: 1535821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) if r in reports_for_this_test: 1545821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) # A similar report is about to be printed for this test. 1555821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) pass 1565821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) elif r in self.known_errors: 1575821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) # A similar report has already been printed in one of the prev tests. 1585821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) to_report.append("This error was already printed in some " 1595821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) "other test, see 'hash=#%016X#'" % r.ErrorHash()) 1605821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) reports_for_this_test.add(r) 1615821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) else: 1625821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) self.known_errors.add(r) 1635821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) reports_for_this_test.add(r) 1645821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) to_report.append(r) 1655821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 1665821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) common.PrintUsedSuppressionsList(self.used_suppressions) 1675821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 1685821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) if not to_report: 1695821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) logging.info("PASS: No error reports found") 1705821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) return 0 1715821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 1725821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) sys.stdout.flush() 1735821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) sys.stderr.flush() 1745821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) logging.info("Found %i error reports" % len(to_report)) 1755821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) for report in to_report: 1765821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) self.error_count += 1 1775821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) logging.info("Report #%d\n%s" % (self.error_count, report)) 1785821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) logging.info("Total: %i error reports" % len(to_report)) 1795821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) sys.stdout.flush() 1805821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) return -1 1815821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 1825821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 1835821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)def main(): 1845821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) '''For testing only. The DrMemoryAnalyze class should be imported instead.''' 1854e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles) parser = optparse.OptionParser("usage: %prog <files to analyze>") 1865821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 1875821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) (options, args) = parser.parse_args() 1885821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) if len(args) == 0: 1895821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) parser.error("no filename specified") 1905821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) filenames = args 1915821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 1925821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) logging.getLogger().setLevel(logging.INFO) 1935821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) return DrMemoryAnalyzer().Report(filenames, None, False) 1945821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 1955821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 1965821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)if __name__ == '__main__': 1975821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) sys.exit(main()) 198