qpid-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From gmur...@apache.org
Subject qpid-dispatch git commit: NO-JIRA: Add a tool for post-processing valgrind test results. This closes #417
Date Tue, 13 Nov 2018 18:05:51 GMT
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 <kgiusti@apache.org>
Authored: Fri Nov 9 10:59:45 2018 -0500
Committer: Ganesh Murthy <gmurthy@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


Mime
View raw message