summaryrefslogtreecommitdiffstats
path: root/tools/valgrind/memcheck_analyze.py
diff options
context:
space:
mode:
authortimurrrr@chromium.org <timurrrr@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2010-07-29 11:31:50 +0000
committertimurrrr@chromium.org <timurrrr@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2010-07-29 11:31:50 +0000
commit72477d8668af94974138a84be34c031c7bc0a2e1 (patch)
tree5765320b7b984547ff545bef80a9357e57d58e85 /tools/valgrind/memcheck_analyze.py
parentb0db42f4873718dbc15443868777feeb25ba3fa4 (diff)
downloadchromium_src-72477d8668af94974138a84be34c031c7bc0a2e1.zip
chromium_src-72477d8668af94974138a84be34c031c7bc0a2e1.tar.gz
chromium_src-72477d8668af94974138a84be34c031c7bc0a2e1.tar.bz2
Avoid duplicate error reports / suppressions when UI test reports are analyzed separately
Review URL: http://codereview.chromium.org/3056025 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@54118 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'tools/valgrind/memcheck_analyze.py')
-rwxr-xr-xtools/valgrind/memcheck_analyze.py163
1 files changed, 91 insertions, 72 deletions
diff --git a/tools/valgrind/memcheck_analyze.py b/tools/valgrind/memcheck_analyze.py
index 32ac22e8..1b5a339 100755
--- a/tools/valgrind/memcheck_analyze.py
+++ b/tools/valgrind/memcheck_analyze.py
@@ -24,16 +24,6 @@ import common
# Global symbol table (yuck)
TheAddressTable = None
-# Contains the time when the we started analyzing the first log file.
-# This variable is used to skip incomplete logs after some timeout.
-# TODO(timurrrr): Currently, this needs to be a global variable
-# because analyzer can be called multiple times (e.g. ui_tests)
-# unless we re-factor the analyze system to avoid creating multiple analyzers.
-AnalyzeStartTime = None
-
-# Max time to wait for memcheck logs to complete.
-LOG_COMPLETION_TIMEOUT = 180.0
-
# These are functions (using C++ mangled names) that we look for in stack
# traces. We don't show stack frames while pretty printing when they are below
# any of the following:
@@ -219,10 +209,11 @@ class ValgrindError:
def __str__(self):
''' Pretty print the type and backtrace(s) of this specific error,
including suppression (which is just a mangled backtrace).'''
- output = self._kind + "\n"
+ output = ""
if (self._commandline):
output += self._commandline + "\n"
+ output += self._kind + "\n"
for backtrace in self._backtraces:
output += backtrace[0] + "\n"
filter = subprocess.Popen("c++filt -n", stdin=subprocess.PIPE,
@@ -255,37 +246,32 @@ class ValgrindError:
output += " (" + frame[OBJECT_FILE] + ")"
output += "\n"
- # TODO(dank): stop synthesizing suppressions once everyone has
- # valgrind-3.5 and we can rely on xml
- if (self._suppression == None):
- output += "Suppression:\n"
- for frame in backtrace[1]:
- output += " fun:" + (frame[FUNCTION_NAME] or "*") + "\n"
-
- if (self._suppression != None):
- output += "Suppression:"
- # Widen suppression slightly to make portable between mac and linux
- supp = self._suppression;
- supp = supp.replace("fun:_Znwj", "fun:_Znw*")
- supp = supp.replace("fun:_Znwm", "fun:_Znw*")
- # Split into lines so we can enforce length limits
- supplines = supp.split("\n")
-
- # Truncate at line 26 (VG_MAX_SUPP_CALLERS plus 2 for name and type)
- # or at the first 'boring' caller.
- # (https://bugs.kde.org/show_bug.cgi?id=199468 proposes raising
- # VG_MAX_SUPP_CALLERS, but we're probably fine with it as is.)
- # TODO(dkegel): add more boring callers
- newlen = 26;
- try:
- newlen = min(newlen, supplines.index(" fun:_ZN11MessageLoop3RunEv"))
- except ValueError:
- pass
- if (len(supplines) > newlen):
- supplines = supplines[0:newlen]
- supplines.append("}")
-
- output += "\n".join(supplines) + "\n"
+ assert self._suppression != None, "Your Valgrind doesn't generate " \
+ "suppressions - is it too old?"
+
+ output += "Suppression (error hash=#%X#):" % self.__hash__()
+ # Widen suppression slightly to make portable between mac and linux
+ supp = self._suppression;
+ supp = supp.replace("fun:_Znwj", "fun:_Znw*")
+ supp = supp.replace("fun:_Znwm", "fun:_Znw*")
+ # Split into lines so we can enforce length limits
+ supplines = supp.split("\n")
+
+ # Truncate at line 26 (VG_MAX_SUPP_CALLERS plus 2 for name and type)
+ # or at the first 'boring' caller.
+ # (https://bugs.kde.org/show_bug.cgi?id=199468 proposes raising
+ # VG_MAX_SUPP_CALLERS, but we're probably fine with it as is.)
+ # TODO(dkegel): add more boring callers
+ newlen = 26;
+ try:
+ newlen = min(newlen, supplines.index(" fun:_ZN11MessageLoop3RunEv"))
+ except ValueError:
+ pass
+ if (len(supplines) > newlen):
+ supplines = supplines[0:newlen]
+ supplines.append("}")
+
+ output += "\n".join(supplines) + "\n"
return output
@@ -320,7 +306,7 @@ def find_and_truncate(f):
f.truncate()
return True
-class MemcheckAnalyze:
+class MemcheckAnalyzer:
''' Given a set of Valgrind XML files, parse all the errors out of them,
unique them and output the results.'''
@@ -354,15 +340,37 @@ class MemcheckAnalyze:
"bug_49253 Memcheck sanity test 08 (new/write left) or Memcheck sanity test 09 (new/write right) on Mac.": 2,
}
- def __init__(self, source_dir, files, show_all_leaks=False, use_gdb=False):
- '''Reads in a set of files.
+ # Max time to wait for memcheck logs to complete.
+ LOG_COMPLETION_TIMEOUT = 180.0
+
+ def __init__(self, source_dir, show_all_leaks=False, use_gdb=False):
+ '''Create a parser for Memcheck logs.
Args:
source_dir: Path to top of source tree for this build
- files: A list of filenames.
- show_all_leaks: whether to show even less important leaks
+ show_all_leaks: Whether to show even less important leaks
+ use_gdb: Whether to use gdb to resolve source filenames and line numbers
+ in the report stacktraces
'''
+ self._source_dir = source_dir
+ self._show_all_leaks = show_all_leaks
+ self._use_gdb = use_gdb
+
+ # Contains the set of unique errors
+ self._errors = set()
+
+ # Contains the time when the we started analyzing the first log file.
+ # This variable is used to skip incomplete logs after some timeout.
+ self._analyze_start_time = None
+
+ def Report(self, files, check_sanity=False):
+ '''Reads in a set of files and prints Memcheck report.
+
+ Args:
+ files: A list of filenames.
+ check_sanity: if true, search for SANITY_TEST_SUPPRESSIONS
+ '''
# Beyond the detailed errors parsed by ValgrindError above,
# the xml file contain records describing suppressions that were used:
# <suppcounts>
@@ -384,16 +392,19 @@ class MemcheckAnalyze:
# into the process.
global TheAddressTable
- if use_gdb:
+ if self._use_gdb:
TheAddressTable = gdb_helper.AddressTable()
- self._errors = set()
- self._suppcounts = {}
+ else:
+ TheAddressTable = None
+ cur_report_errors = set()
+ suppcounts = {}
badfiles = set()
- global AnalyzeStartTime
- if AnalyzeStartTime == None:
- AnalyzeStartTime = time.time()
- self._parse_failed = False
+ if self._analyze_start_time == None:
+ self._analyze_start_time = time.time()
+ start_time = self._analyze_start_time
+
+ parse_failed = False
for file in files:
# Wait up to three minutes for valgrind to finish writing all files,
# but after that, just skip incomplete files and warn.
@@ -407,7 +418,7 @@ class MemcheckAnalyze:
origsize = os.path.getsize(file)
while (running and not found and
(firstrun or
- ((time.time() - AnalyzeStartTime) < LOG_COMPLETION_TIMEOUT))):
+ ((time.time() - start_time) < self.LOG_COMPLETION_TIMEOUT))):
firstrun = False
f.seek(0)
if pid:
@@ -435,7 +446,7 @@ class MemcheckAnalyze:
try:
parsed_file = parse(file);
except ExpatError, e:
- self._parse_failed = True
+ parse_failed = True
logging.warn("could not parse %s: %s" % (file, e))
lineno = e.lineno - 1
context_lines = 5
@@ -471,10 +482,20 @@ class MemcheckAnalyze:
raw_errors = parsed_file.getElementsByTagName("error")
for raw_error in raw_errors:
# Ignore "possible" leaks for now by default.
- if (show_all_leaks or
+ if (self._show_all_leaks or
getTextOf(raw_error, "kind") != "Leak_PossiblyLost"):
- error = ValgrindError(source_dir, raw_error, commandline)
- self._errors.add(error)
+ error = ValgrindError(self._source_dir, raw_error, commandline)
+ if error not in cur_report_errors:
+ # We haven't seen such errors doing this report yet...
+ if error in self._errors:
+ # ... but we saw it in earlier reports, e.g. previous UI test
+ cur_report_errors.add("This error was already printed "
+ "in some other test, see 'hash=#%X#'" % \
+ error.__hash__())
+ else:
+ # ... and we haven't seen it in other tests as well
+ self._errors.add(error)
+ cur_report_errors.add(error)
suppcountlist = parsed_file.getElementsByTagName("suppcounts")
if len(suppcountlist) > 0:
@@ -482,10 +503,10 @@ class MemcheckAnalyze:
for node in suppcountlist.getElementsByTagName("pair"):
count = getTextOf(node, "count");
name = getTextOf(node, "name");
- if name in self._suppcounts:
- self._suppcounts[name] += int(count)
+ if name in suppcounts:
+ suppcounts[name] += int(count)
else:
- self._suppcounts[name] = int(count)
+ suppcounts[name] = int(count)
if len(badfiles) > 0:
logging.warn("valgrind didn't finish writing %d files?!" % len(badfiles))
@@ -493,8 +514,7 @@ class MemcheckAnalyze:
logging.warn("Last 20 lines of %s :" % file)
os.system("tail -n 20 '%s' 1>&2" % file)
- def Report(self, check_sanity=False):
- if self._parse_failed:
+ if parse_failed:
logging.error("FAIL! Couldn't parse Valgrind output file")
return -2
@@ -504,15 +524,15 @@ class MemcheckAnalyze:
print " count name"
if common.IsLinux():
- remaining_sanity_supp = MemcheckAnalyze.SANITY_TEST_SUPPRESSIONS_LINUX
+ remaining_sanity_supp = MemcheckAnalyzer.SANITY_TEST_SUPPRESSIONS_LINUX
elif common.IsMac():
- remaining_sanity_supp = MemcheckAnalyze.SANITY_TEST_SUPPRESSIONS_MAC
+ remaining_sanity_supp = MemcheckAnalyzer.SANITY_TEST_SUPPRESSIONS_MAC
else:
remaining_sanity_supp = {}
if check_sanity:
logging.warn("No sanity test list for platform %s", sys.platform)
- for (name, count) in sorted(self._suppcounts.items(),
+ for (name, count) in sorted(suppcounts.items(),
key=lambda (k,v): (v,k)):
print "%7d %s" % (count, name)
if name in remaining_sanity_supp and remaining_sanity_supp[name] == count:
@@ -526,11 +546,10 @@ class MemcheckAnalyze:
if self._errors:
logging.error("FAIL! There were %s errors: " % len(self._errors))
- global TheAddressTable
if TheAddressTable != None:
TheAddressTable.ResolveAll()
- for error in self._errors:
+ for error in cur_report_errors:
logging.error(error)
retcode = -1
@@ -551,7 +570,7 @@ class MemcheckAnalyze:
return 0
def _main():
- '''For testing only. The MemcheckAnalyze class should be imported instead.'''
+ '''For testing only. The MemcheckAnalyzer class should be imported instead.'''
retcode = 0
parser = optparse.OptionParser("usage: %prog [options] <files to analyze>")
parser.add_option("", "--source_dir",
@@ -563,8 +582,8 @@ def _main():
parser.error("no filename specified")
filenames = args
- analyzer = MemcheckAnalyze(options.source_dir, filenames, use_gdb=True)
- retcode = analyzer.Report()
+ analyzer = MemcheckAnalyzer(options.source_dir, use_gdb=True)
+ retcode = analyzer.Report(filenames)
sys.exit(retcode)