summaryrefslogtreecommitdiff
path: root/chromium/tools/valgrind/scan-build.py
diff options
context:
space:
mode:
authorZeno Albisser <zeno.albisser@digia.com>2013-08-15 21:46:11 +0200
committerZeno Albisser <zeno.albisser@digia.com>2013-08-15 21:46:11 +0200
commit679147eead574d186ebf3069647b4c23e8ccace6 (patch)
treefc247a0ac8ff119f7c8550879ebb6d3dd8d1ff69 /chromium/tools/valgrind/scan-build.py
downloadqtwebengine-chromium-679147eead574d186ebf3069647b4c23e8ccace6.tar.gz
Initial import.
Diffstat (limited to 'chromium/tools/valgrind/scan-build.py')
-rwxr-xr-xchromium/tools/valgrind/scan-build.py227
1 files changed, 227 insertions, 0 deletions
diff --git a/chromium/tools/valgrind/scan-build.py b/chromium/tools/valgrind/scan-build.py
new file mode 100755
index 00000000000..b58b6cc447e
--- /dev/null
+++ b/chromium/tools/valgrind/scan-build.py
@@ -0,0 +1,227 @@
+#!/usr/bin/env python
+# Copyright (c) 2013 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.
+
+import argparse
+import errno
+import os
+import re
+import sys
+import urllib
+import urllib2
+
+# Where all the data lives.
+ROOT_URL = "http://build.chromium.org/p/chromium.memory.fyi/builders"
+
+# TODO(groby) - support multi-line search from the command line. Useful when
+# scanning for classes of failures, see below.
+SEARCH_STRING = """<p class=\"failure result\">
+Failed memory test: content
+</p>"""
+
+# Location of the log cache.
+CACHE_DIR = "buildlogs.tmp"
+
+# If we don't find anything after searching |CUTOFF| logs, we're probably done.
+CUTOFF = 100
+
+def EnsurePath(path):
+ """Makes sure |path| does exist, tries to create it if it doesn't."""
+ try:
+ os.makedirs(path)
+ except OSError as exception:
+ if exception.errno != errno.EEXIST:
+ raise
+
+
+class Cache(object):
+ def __init__(self, root_dir):
+ self._root_dir = os.path.abspath(root_dir)
+
+ def _LocalName(self, name):
+ """If name is a relative path, treat it as relative to cache root.
+ If it is absolute and under cache root, pass it through.
+ Otherwise, raise error.
+ """
+ if os.path.isabs(name):
+ assert os.path.commonprefix([name, self._root_dir]) == self._root_dir
+ else:
+ name = os.path.join(self._root_dir, name)
+ return name
+
+ def _FetchLocal(self, local_name):
+ local_name = self._LocalName(local_name)
+ EnsurePath(os.path.dirname(local_name))
+ if os.path.exists(local_name):
+ f = open(local_name, 'r')
+ return f.readlines();
+ return None
+
+ def _FetchRemote(self, remote_name):
+ try:
+ response = urllib2.urlopen(remote_name)
+ except:
+ print "Could not fetch", remote_name
+ raise
+ return response.read()
+
+ def Update(self, local_name, remote_name):
+ local_name = self._LocalName(local_name)
+ EnsurePath(os.path.dirname(local_name))
+ blob = self._FetchRemote(remote_name)
+ f = open(local_name, "w")
+ f.write(blob)
+ return blob.splitlines()
+
+ def FetchData(self, local_name, remote_name):
+ result = self._FetchLocal(local_name)
+ if result:
+ return result
+ # If we get here, the local cache does not exist yet. Fetch, and store.
+ return self.Update(local_name, remote_name)
+
+
+class Builder(object):
+ def __init__(self, waterfall, name):
+ self._name = name
+ self._waterfall = waterfall
+
+ def Name(self):
+ return self._name
+
+ def LatestBuild(self):
+ return self._waterfall.GetLatestBuild(self._name)
+
+ def GetBuildPath(self, build_num):
+ return "%s/%s/builds/%d" % (
+ self._waterfall._root_url, urllib.quote(self._name), build_num)
+
+ def _FetchBuildLog(self, build_num):
+ local_build_path = "builds/%s" % self._name
+ local_build_file = os.path.join(local_build_path, "%d.log" % build_num)
+ return self._waterfall._cache.FetchData(local_build_file,
+ self.GetBuildPath(build_num))
+
+ def _CheckLog(self, build_num, tester):
+ log_lines = self._FetchBuildLog(build_num)
+ return any(tester(line) for line in log_lines)
+
+ def ScanLogs(self, tester):
+ occurrences = []
+ build = self.LatestBuild()
+ no_results = 0
+ while build != 0 and no_results < CUTOFF:
+ if self._CheckLog(build, tester):
+ occurrences.append(build)
+ else:
+ no_results = no_results + 1
+ build = build - 1
+ return occurrences
+
+
+class Waterfall(object):
+ def __init__(self, root_url, cache_dir):
+ self._root_url = root_url
+ self._builders = {}
+ self._top_revision = {}
+ self._cache = Cache(cache_dir)
+
+ def Builders(self):
+ return self._builders.values()
+
+ def Update(self):
+ self._cache.Update("builders", self._root_url)
+ self.FetchInfo()
+
+ def FetchInfo(self):
+ if self._top_revision:
+ return
+
+ html = self._cache.FetchData("builders", self._root_url)
+
+ """ Search for both builders and latest build number in HTML
+ <td class="box"><a href="builders/<builder-name>"> identifies a builder
+ <a href="builders/<builder-name>/builds/<build-num>"> is the latest build.
+ """
+ box_matcher = re.compile('.*a href[^>]*>([^<]*)\<')
+ build_matcher = re.compile('.*a href=\"builders/(.*)/builds/([0-9]+)\".*')
+ last_builder = ""
+ for line in html:
+ if 'a href="builders/' in line:
+ if 'td class="box"' in line:
+ last_builder = box_matcher.match(line).group(1)
+ self._builders[last_builder] = Builder(self, last_builder)
+ else:
+ result = build_matcher.match(line)
+ builder = result.group(1)
+ assert builder == urllib.quote(last_builder)
+ self._top_revision[last_builder] = int(result.group(2))
+
+ def GetLatestBuild(self, name):
+ self.FetchInfo()
+ assert self._top_revision
+ return self._top_revision[name]
+
+
+class MultiLineChange(object):
+ def __init__(self, lines):
+ self._tracked_lines = lines
+ self._current = 0
+
+ def __call__(self, line):
+ """ Test a single line against multi-line change.
+
+ If it matches the currently active line, advance one line.
+ If the current line is the last line, report a match.
+ """
+ if self._tracked_lines[self._current] in line:
+ self._current = self._current + 1
+ if self._current == len(self._tracked_lines):
+ self._current = 0
+ return True
+ else:
+ self._current = 0
+ return False
+
+
+def main(argv):
+ # Create argument parser.
+ parser = argparse.ArgumentParser()
+ commands = parser.add_mutually_exclusive_group(required=True)
+ commands.add_argument("--update", action='store_true')
+ commands.add_argument("--find", metavar='search term')
+ args = parser.parse_args()
+
+ path = os.path.abspath(os.path.dirname(argv[0]))
+ cache_path = os.path.join(path, CACHE_DIR)
+
+ fyi = Waterfall(ROOT_URL, cache_path)
+
+ if args.update:
+ fyi.Update()
+ for builder in fyi.Builders():
+ print "Updating", builder.Name()
+ builder.ScanLogs(lambda x:False)
+
+ if args.find:
+ tester = MultiLineChange(args.find.splitlines())
+ fyi.FetchInfo()
+
+ print "SCANNING FOR ", args.find
+ for builder in fyi.Builders():
+ print "Scanning", builder.Name()
+ occurrences = builder.ScanLogs(tester)
+ if occurrences:
+ min_build = min(occurrences)
+ path = builder.GetBuildPath(min_build)
+ print "Earliest occurrence in build %d" % min_build
+ print "Latest occurrence in build %d" % max(occurrences)
+ print "Latest build: %d" % builder.LatestBuild()
+ print path
+ print "%d total" % len(occurrences)
+
+
+if __name__ == "__main__":
+ sys.exit(main(sys.argv))
+