You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@qpid.apache.org by gm...@apache.org on 2018/11/13 18:05:51 UTC
qpid-dispatch git commit: NO-JIRA: Add a tool for post-processing
valgrind test results. This closes #417
Repository: qpid-dispatch
Updated Branches:
refs/heads/master 3c09709ba -> e94f12579
NO-JIRA: Add a tool for post-processing valgrind test results. This closes #417
Project: http://git-wip-us.apache.org/repos/asf/qpid-dispatch/repo
Commit: http://git-wip-us.apache.org/repos/asf/qpid-dispatch/commit/e94f1257
Tree: http://git-wip-us.apache.org/repos/asf/qpid-dispatch/tree/e94f1257
Diff: http://git-wip-us.apache.org/repos/asf/qpid-dispatch/diff/e94f1257
Branch: refs/heads/master
Commit: e94f1257977a3b1fdba4c71054c700822cb92925
Parents: 3c09709
Author: Kenneth Giusti <kg...@apache.org>
Authored: Fri Nov 9 10:59:45 2018 -0500
Committer: Ganesh Murthy <gm...@redhat.com>
Committed: Tue Nov 13 13:04:47 2018 -0500
----------------------------------------------------------------------
CMakeLists.txt | 4 +
bin/grinder | 370 ++++++++++++++++++++++++++++++++++++++++++++++++++++
2 files changed, 374 insertions(+)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/qpid-dispatch/blob/e94f1257/CMakeLists.txt
----------------------------------------------------------------------
diff --git a/CMakeLists.txt b/CMakeLists.txt
index bfe2c4a..61cbd9b 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -137,12 +137,16 @@ find_program(VALGRIND_EXECUTABLE valgrind DOC "Location of the valgrind program"
mark_as_advanced(VALGRIND_EXECUTABLE)
find_package_handle_standard_args(VALGRIND DEFAULT_MSG VALGRIND_EXECUTABLE)
option(USE_VALGRIND "Use valgrind when running tests" OFF)
+option(VALGRIND_XML "Write valgrind output as XML" OFF)
if (USE_VALGRIND)
if (CMAKE_BUILD_TYPE MATCHES "Coverage")
message(WARNING "Building for coverage analysis; disabling valgrind run-time error detection")
else ()
set(TEST_RUNNER "${VALGRIND_EXECUTABLE} --quiet --leak-check=full --show-leak-kinds=definite --errors-for-leak-kinds=definite --error-exitcode=42 --suppressions=${CMAKE_SOURCE_DIR}/tests/valgrind.supp")
+ if (VALGRIND_XML)
+ set(TEST_RUNNER "${TEST_RUNNER} --xml=yes --xml-file=valgrind-%p.xml")
+ endif()
endif ()
endif()
http://git-wip-us.apache.org/repos/asf/qpid-dispatch/blob/e94f1257/bin/grinder
----------------------------------------------------------------------
diff --git a/bin/grinder b/bin/grinder
new file mode 100755
index 0000000..3066498
--- /dev/null
+++ b/bin/grinder
@@ -0,0 +1,370 @@
+#!/usr/bin/python
+#
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+#
+
+#
+# a tool for post-processing Valgrind output from the unit tests
+# Use:
+# 1) configure the build to use valgrind and output xml
+# $ cmake .. -DUSE_VALGRIND=Yes -DVALGRIND_XML=Yes -DUSE_MEMORY_POOL=No
+# 2) build and run the unit tests
+# $ make && make test
+# 3) run grinder from your build directory. It will look for valgrind xml
+# files named "valgrind-*.xml in the current directory and all
+# subdirectories and process them. Output is sent to stdout
+# $ ../bin/grinder
+#
+# Note: be sure to clean the build directory before running the unit tests
+# to remove old valgrind-*.xml files
+#
+
+import logging
+import os
+import re
+import sys
+import xml.etree.ElementTree as ET
+from xml.etree.ElementTree import ParseError
+
+
+class Frame(object):
+ """
+ Represents info for a single stack frame
+ """
+ FIELDS = ["fn", "dir", "file", "line"]
+ def __init__(self, frame):
+ self._fields = dict()
+ for tag in self.FIELDS:
+ _ = frame.find(tag)
+ self._fields[tag] = _.text if _ is not None else "<none>"
+
+ def __str__(self):
+ return ("(%s) %s/%s:%s" %
+ (self._fields['fn'],
+ self._fields['dir'],
+ self._fields['file'],
+ self._fields['line']))
+
+ def __hash__(self):
+ return hash(self.__str__())
+
+
+class ErrorBase(object):
+ """
+ Base class representing a single valgrind error
+ """
+ def __init__(self, kind):
+ self.kind = kind
+ self.count = 1
+
+ def __hash__(self):
+ return hash(self.kind)
+
+ def __str__(self):
+ return "kind = %s (count=%d)" % (self.kind, self.count)
+
+ def merge(self, other):
+ self.count += other.count
+
+ def __lt__(self, other):
+ return self.count < other.count
+ def __le__(self, other):
+ return self.count <= other.count
+ def __eq__(self, other):
+ return self.count == other.count
+ def __gt__(self, other):
+ return self.count > other.count
+ def __ge__(self, other):
+ return self.count >= other.count
+
+
+class GeneralError(ErrorBase):
+ """
+ For simple single stack errors
+ """
+ def __init__(self, error_xml):
+ kind = error_xml.find("kind").text
+ super(GeneralError, self).__init__(kind)
+ w = error_xml.find("what")
+ self._what = w.text if w is not None else "<none>"
+
+ # stack
+ self._stack = list()
+ s = error_xml.find("stack")
+ for frame in s.findall("frame"):
+ self._stack.append(Frame(frame))
+
+ def __hash__(self):
+ h = super(GeneralError, self).__hash__()
+ for f in self._stack:
+ h += hash(f)
+ return h
+
+ def __str__(self):
+ s = super(GeneralError, self).__str__() + "\n"
+ if self._what:
+ s += self._what + "\n"
+ s += "Stack:"
+ for frame in self._stack:
+ s += "\n %s" % str(frame)
+ return s
+
+
+class LeakError(ErrorBase):
+ def __init__(self, error_xml):
+ kind = error_xml.find("kind").text
+ assert(kind.startswith("Leak_"))
+ super(LeakError, self).__init__(kind)
+ self._leaked_bytes = 0
+ self._leaked_blocks = 0
+ self._stack = list()
+
+ # xwhat:
+ # leakedbytes
+ # leakedblocks
+ lb = error_xml.find("xwhat/leakedbytes")
+ if lb is not None:
+ self._leaked_bytes = int(lb.text)
+ lb = error_xml.find("xwhat/leakedblocks")
+ if lb is not None:
+ self._leaked_blocks = int(lb.text)
+
+ # stack
+ s = error_xml.find("stack")
+ for frame in s.findall("frame"):
+ self._stack.append(Frame(frame))
+
+ def merge(self, other):
+ super(LeakError, self).merge(other)
+ self._leaked_bytes += other._leaked_bytes
+ self._leaked_blocks += other._leaked_blocks
+
+ def __hash__(self):
+ h = super(LeakError, self).__hash__()
+ for f in self._stack:
+ h += hash(f)
+ return h
+
+ def __str__(self):
+ s = super(LeakError, self).__str__() + "\n"
+ s += "Leaked Bytes = %d Blocks = %d\n" % (self._leaked_bytes,
+ self._leaked_blocks)
+ s += "Stack:"
+ for frame in self._stack:
+ s += "\n %s" % str(frame)
+ return s
+
+
+class InvalidMemRWError(ErrorBase):
+ def __init__(self, error_xml):
+ kind = error_xml.find("kind").text
+ assert(kind in ['InvalidRead', 'InvalidWrite'])
+ super(InvalidMemRWError, self).__init__(kind)
+ # expect
+ # what
+ # stack (invalid access)
+ # followed by zero or more:
+ # aux what (aux stack description)
+ # aux stack (where alloced, freed)
+ self._what = "<none>"
+ self._stack = None
+ self._auxwhat = list()
+ self._aux_stacks = list()
+ for child in error_xml:
+ if child.tag == "what":
+ self._what = child.text
+ if child.tag == "auxwhat":
+ self._auxwhat.append(child.text)
+ if child.tag == "stack":
+ stack = list()
+ for frame in child.findall("frame"):
+ stack.append(Frame(frame))
+ if self._stack == None:
+ self._stack = stack
+ else:
+ self._aux_stacks.append(stack)
+
+ def __hash__(self):
+ # for now don't include what/auxwhat as it may
+ # be different for the same codepath
+ h = super(InvalidMemRWError, self).__hash__()
+ for f in self._stack:
+ h += hash(f)
+ for s in self._aux_stacks:
+ for f in s:
+ h += hash(f)
+ return h
+
+ def __str__(self):
+ s = super(InvalidMemRWError, self).__str__() + "\n"
+ s += "%s\n" % self._what
+ s += "Stack:"
+ for frame in self._stack:
+ s += "\n %s" % str(frame)
+
+ for what, stack in zip(self._auxwhat, self._aux_stacks):
+ s += "\n %s" % what
+ for frame in stack:
+ s += "\n %s" % str(frame)
+ return s
+
+
+class SignalError(ErrorBase):
+ def __init__(self, error_xml):
+ super(SignalError, self).__init__("FatalSignal")
+ # expects:
+ # signo
+ # signame
+ # stack
+ self._signo = "<none>"
+ sn = error_xml.find("signo")
+ if sn is not None:
+ self._signo = sn.text
+ self._signame = "<none>"
+ sn = error_xml.find("signame")
+ if sn is not None:
+ self._signame = sn.text
+
+ self._stack = list()
+ s = error_xml.find("stack")
+ for frame in s.findall("frame"):
+ self._stack.append(Frame(frame))
+
+ def __hash__(self):
+ # for now don't include what/auxwhat as it may
+ # be different for the same codepath
+ h = super(SignalError, self).__hash__()
+ h += hash(self._signo)
+ h += hash(self._signame)
+ for f in self._stack:
+ h += hash(f)
+ return h
+
+ def __str__(self):
+ s = super(SignalError, self).__str__() + "\n"
+ s += "Signal %s (%s)\n" % (self._signo, self._signame)
+ s += "Stack:"
+ for frame in self._stack:
+ s += "\n %s" % str(frame)
+ return s
+
+
+_ERROR_CLASSES = {
+ 'InvalidRead': InvalidMemRWError,
+ 'InvalidWrite': InvalidMemRWError,
+ 'Leak_DefinitelyLost': LeakError,
+ 'Leak_IndirectlyLost': LeakError,
+ 'Leak_PossiblyLost': LeakError,
+ 'Leak_StillReachable': LeakError,
+ 'UninitCondition': GeneralError,
+ 'SyscallParam': GeneralError,
+ # TBD:
+ 'InvalidFree': None,
+ 'InvalidJump': None,
+ 'UninitValue': None,
+}
+
+
+def parse_error(error_xml):
+ """
+ Factory that returns an Error instance
+ """
+ kind = error_xml.find("kind").text
+ e_cls = _ERROR_CLASSES.get(kind)
+ if e_cls:
+ return e_cls(error_xml)
+ raise Exception("Unsupported error type %s, please update grinder"
+ " to handle it" % kind)
+
+
+def parse_xml_file(filename, exe_name='qdrouterd'):
+ """
+ Parse out errors from a valgrind output xml file
+ """
+ logging.debug("Parsing %s", filename)
+ error_list = list()
+ try:
+ root = ET.parse(filename).getroot()
+ except ParseError as exc:
+ if "no element found" not in str(exc):
+ logging.warning("Error parsing %s: %s - skipping",
+ filename, str(exc))
+ else:
+ logging.debug("No errors found in: %s - skipping",
+ filename)
+ return error_list
+
+ pv = root.find('protocolversion')
+ if pv is None or not "4" == pv.text:
+ # unsupported xml format version
+ logging.warning("Unsupported format version for %s, skipping...",
+ filename)
+ return error_list
+
+ pt = root.find('protocoltool')
+ if pt is None or not "memcheck" == pt.text:
+ logging.warning("Not a memcheck file %s, skipping...",
+ filename)
+ return error_list
+
+ if not exe_name in root.find('args/argv/exe').text:
+ # not from the target executable, skip
+ logging.debug("file %s is not generated from %s, skipping...",
+ filename, exe_name)
+ return error_list
+
+ for error in root.findall('error'):
+ error_list.append(parse_error(error))
+
+ # sigabort, etc classified as fatal_signal
+ for signal in root.findall("fatal_signal"):
+ error_list.append(SignalError(signal))
+ return error_list
+
+
+def main():
+ errors_map = dict()
+ file_name = re.compile("valgrind-[0-9]+\.xml")
+ for dp, dn, fn in os.walk("."):
+ for name in fn:
+ if file_name.match(name):
+ errors = parse_xml_file(os.path.join(dp, name))
+ for e in errors:
+ h = hash(e)
+ if h in errors_map:
+ # coalesce duplicate errors
+ errors_map[h].merge(e)
+ else:
+ errors_map[h] = e
+
+ # sort by # of occurances
+ error_list = sorted([e for e in errors_map.values()], reverse=True)
+
+ if error_list:
+ for e in error_list:
+ print("\n-----")
+ print("%s" % str(e))
+ print("\n\n-----")
+ print("----- %s total issues detected" % len(error_list))
+ print("-----")
+ else:
+ print("No Valgrind errors found! Congratulations ;)")
+
+
+if __name__ == "__main__":
+ sys.exit(main())
---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@qpid.apache.org
For additional commands, e-mail: commits-help@qpid.apache.org