summaryrefslogtreecommitdiff
path: root/tools/drmemory/scripts/drmemory_analyze.py
diff options
context:
space:
mode:
Diffstat (limited to 'tools/drmemory/scripts/drmemory_analyze.py')
-rw-r--r--tools/drmemory/scripts/drmemory_analyze.py202
1 files changed, 202 insertions, 0 deletions
diff --git a/tools/drmemory/scripts/drmemory_analyze.py b/tools/drmemory/scripts/drmemory_analyze.py
new file mode 100644
index 0000000000..29fc0ed4b0
--- /dev/null
+++ b/tools/drmemory/scripts/drmemory_analyze.py
@@ -0,0 +1,202 @@
+#!/usr/bin/env python
+# Copyright (c) 2011 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.
+
+# drmemory_analyze.py
+
+''' Given a Dr. Memory output file, parses errors and uniques them.'''
+
+from collections import defaultdict
+import common
+import hashlib
+import logging
+import optparse
+import os
+import re
+import subprocess
+import sys
+import time
+
+class DrMemoryError:
+ def __init__(self, report, suppression, testcase):
+ self._report = report
+ self._testcase = testcase
+
+ # Chromium-specific transformations of the suppressions:
+ # Replace 'any_test.exe' and 'chrome.dll' with '*', then remove the
+ # Dr.Memory-generated error ids from the name= lines as they don't
+ # make sense in a multiprocess report.
+ supp_lines = suppression.split("\n")
+ for l in xrange(len(supp_lines)):
+ if supp_lines[l].startswith("name="):
+ supp_lines[l] = "name=<insert_a_suppression_name_here>"
+ if supp_lines[l].startswith("chrome.dll!"):
+ supp_lines[l] = supp_lines[l].replace("chrome.dll!", "*!")
+ bang_index = supp_lines[l].find("!")
+ d_exe_index = supp_lines[l].find(".exe!")
+ if bang_index >= 4 and d_exe_index + 4 == bang_index:
+ supp_lines[l] = "*" + supp_lines[l][bang_index:]
+ self._suppression = "\n".join(supp_lines)
+
+ def __str__(self):
+ output = ""
+ output += "### BEGIN MEMORY TOOL REPORT (error hash=#%016X#)\n" % \
+ self.ErrorHash()
+ output += self._report + "\n"
+ if self._testcase:
+ output += "The report came from the `%s` test.\n" % self._testcase
+ output += "Suppression (error hash=#%016X#):\n" % self.ErrorHash()
+ output += (" For more info on using suppressions see "
+ "http://dev.chromium.org/developers/how-tos/using-drmemory#TOC-Suppressing-error-reports-from-the-\n")
+ output += "{\n%s\n}\n" % self._suppression
+ output += "### END MEMORY TOOL REPORT (error hash=#%016X#)\n" % \
+ self.ErrorHash()
+ return output
+
+ # This is a device-independent hash identifying the suppression.
+ # By printing out this hash we can find duplicate reports between tests and
+ # different shards running on multiple buildbots
+ def ErrorHash(self):
+ return int(hashlib.md5(self._suppression).hexdigest()[:16], 16)
+
+ def __hash__(self):
+ return hash(self._suppression)
+
+ def __eq__(self, rhs):
+ return self._suppression == rhs
+
+
+class DrMemoryAnalyzer:
+ ''' Given a set of Dr.Memory output files, parse all the errors out of
+ them, unique them and output the results.'''
+
+ def __init__(self):
+ self.known_errors = set()
+ self.error_count = 0;
+
+ def ReadLine(self):
+ self.line_ = self.cur_fd_.readline()
+
+ def ReadSection(self):
+ result = [self.line_]
+ self.ReadLine()
+ while len(self.line_.strip()) > 0:
+ result.append(self.line_)
+ self.ReadLine()
+ return result
+
+ def ParseReportFile(self, filename, testcase):
+ ret = []
+
+ # First, read the generated suppressions file so we can easily lookup a
+ # suppression for a given error.
+ supp_fd = open(filename.replace("results", "suppress"), 'r')
+ generated_suppressions = {} # Key -> Error #, Value -> Suppression text.
+ for line in supp_fd:
+ # NOTE: this regexp looks fragile. Might break if the generated
+ # suppression format slightly changes.
+ m = re.search("# Suppression for Error #([0-9]+)", line.strip())
+ if not m:
+ continue
+ error_id = int(m.groups()[0])
+ assert error_id not in generated_suppressions
+ # OK, now read the next suppression:
+ cur_supp = ""
+ for supp_line in supp_fd:
+ if supp_line.startswith("#") or supp_line.strip() == "":
+ break
+ cur_supp += supp_line
+ generated_suppressions[error_id] = cur_supp.strip()
+ supp_fd.close()
+
+ self.cur_fd_ = open(filename, 'r')
+ while True:
+ self.ReadLine()
+ if (self.line_ == ''): break
+
+ match = re.search("^Error #([0-9]+): (.*)", self.line_)
+ if match:
+ error_id = int(match.groups()[0])
+ self.line_ = match.groups()[1].strip() + "\n"
+ report = "".join(self.ReadSection()).strip()
+ suppression = generated_suppressions[error_id]
+ ret.append(DrMemoryError(report, suppression, testcase))
+
+ if re.search("SUPPRESSIONS USED:", self.line_):
+ self.ReadLine()
+ while self.line_.strip() != "":
+ line = self.line_.strip()
+ (count, name) = re.match(" *([0-9\?]+)x(?: \(.*?\))?: (.*)",
+ line).groups()
+ if (count == "?"):
+ # Whole-module have no count available: assume 1
+ count = 1
+ else:
+ count = int(count)
+ self.used_suppressions[name] += count
+ self.ReadLine()
+
+ if self.line_.startswith("ASSERT FAILURE"):
+ ret.append(self.line_.strip())
+
+ self.cur_fd_.close()
+ return ret
+
+ def Report(self, filenames, testcase, check_sanity):
+ sys.stdout.flush()
+ # TODO(timurrrr): support positive tests / check_sanity==True
+ self.used_suppressions = defaultdict(int)
+
+ to_report = []
+ reports_for_this_test = set()
+ for f in filenames:
+ cur_reports = self.ParseReportFile(f, testcase)
+
+ # Filter out the reports that were there in previous tests.
+ for r in cur_reports:
+ if r in reports_for_this_test:
+ # A similar report is about to be printed for this test.
+ pass
+ elif r in self.known_errors:
+ # A similar report has already been printed in one of the prev tests.
+ to_report.append("This error was already printed in some "
+ "other test, see 'hash=#%016X#'" % r.ErrorHash())
+ reports_for_this_test.add(r)
+ else:
+ self.known_errors.add(r)
+ reports_for_this_test.add(r)
+ to_report.append(r)
+
+ common.PrintUsedSuppressionsList(self.used_suppressions)
+
+ if not to_report:
+ logging.info("PASS: No error reports found")
+ return 0
+
+ sys.stdout.flush()
+ sys.stderr.flush()
+ logging.info("Found %i error reports" % len(to_report))
+ for report in to_report:
+ self.error_count += 1
+ logging.info("Report #%d\n%s" % (self.error_count, report))
+ logging.info("Total: %i error reports" % len(to_report))
+ sys.stdout.flush()
+ return -1
+
+
+def main():
+ '''For testing only. The DrMemoryAnalyze class should be imported instead.'''
+ parser = optparse.OptionParser("usage: %prog <files to analyze>")
+
+ (options, args) = parser.parse_args()
+ if len(args) == 0:
+ parser.error("no filename specified")
+ filenames = args
+
+ logging.getLogger().setLevel(logging.INFO)
+ return DrMemoryAnalyzer().Report(filenames, None, False)
+
+
+if __name__ == '__main__':
+ sys.exit(main())