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