Temporary allocation of 20Mb before and after call to ServiceProcessControl::GetHisto...
[chromium-blink-merge.git] / tools / valgrind / scan-build.py
blobb58b6cc447e52871011ab3208fdd27c0fa9da766
1 #!/usr/bin/env python
2 # Copyright (c) 2013 The Chromium Authors. All rights reserved.
3 # Use of this source code is governed by a BSD-style license that can be
4 # found in the LICENSE file.
6 import argparse
7 import errno
8 import os
9 import re
10 import sys
11 import urllib
12 import urllib2
14 # Where all the data lives.
15 ROOT_URL = "http://build.chromium.org/p/chromium.memory.fyi/builders"
17 # TODO(groby) - support multi-line search from the command line. Useful when
18 # scanning for classes of failures, see below.
19 SEARCH_STRING = """<p class=\"failure result\">
20 Failed memory test: content
21 </p>"""
23 # Location of the log cache.
24 CACHE_DIR = "buildlogs.tmp"
26 # If we don't find anything after searching |CUTOFF| logs, we're probably done.
27 CUTOFF = 100
29 def EnsurePath(path):
30 """Makes sure |path| does exist, tries to create it if it doesn't."""
31 try:
32 os.makedirs(path)
33 except OSError as exception:
34 if exception.errno != errno.EEXIST:
35 raise
38 class Cache(object):
39 def __init__(self, root_dir):
40 self._root_dir = os.path.abspath(root_dir)
42 def _LocalName(self, name):
43 """If name is a relative path, treat it as relative to cache root.
44 If it is absolute and under cache root, pass it through.
45 Otherwise, raise error.
46 """
47 if os.path.isabs(name):
48 assert os.path.commonprefix([name, self._root_dir]) == self._root_dir
49 else:
50 name = os.path.join(self._root_dir, name)
51 return name
53 def _FetchLocal(self, local_name):
54 local_name = self._LocalName(local_name)
55 EnsurePath(os.path.dirname(local_name))
56 if os.path.exists(local_name):
57 f = open(local_name, 'r')
58 return f.readlines();
59 return None
61 def _FetchRemote(self, remote_name):
62 try:
63 response = urllib2.urlopen(remote_name)
64 except:
65 print "Could not fetch", remote_name
66 raise
67 return response.read()
69 def Update(self, local_name, remote_name):
70 local_name = self._LocalName(local_name)
71 EnsurePath(os.path.dirname(local_name))
72 blob = self._FetchRemote(remote_name)
73 f = open(local_name, "w")
74 f.write(blob)
75 return blob.splitlines()
77 def FetchData(self, local_name, remote_name):
78 result = self._FetchLocal(local_name)
79 if result:
80 return result
81 # If we get here, the local cache does not exist yet. Fetch, and store.
82 return self.Update(local_name, remote_name)
85 class Builder(object):
86 def __init__(self, waterfall, name):
87 self._name = name
88 self._waterfall = waterfall
90 def Name(self):
91 return self._name
93 def LatestBuild(self):
94 return self._waterfall.GetLatestBuild(self._name)
96 def GetBuildPath(self, build_num):
97 return "%s/%s/builds/%d" % (
98 self._waterfall._root_url, urllib.quote(self._name), build_num)
100 def _FetchBuildLog(self, build_num):
101 local_build_path = "builds/%s" % self._name
102 local_build_file = os.path.join(local_build_path, "%d.log" % build_num)
103 return self._waterfall._cache.FetchData(local_build_file,
104 self.GetBuildPath(build_num))
106 def _CheckLog(self, build_num, tester):
107 log_lines = self._FetchBuildLog(build_num)
108 return any(tester(line) for line in log_lines)
110 def ScanLogs(self, tester):
111 occurrences = []
112 build = self.LatestBuild()
113 no_results = 0
114 while build != 0 and no_results < CUTOFF:
115 if self._CheckLog(build, tester):
116 occurrences.append(build)
117 else:
118 no_results = no_results + 1
119 build = build - 1
120 return occurrences
123 class Waterfall(object):
124 def __init__(self, root_url, cache_dir):
125 self._root_url = root_url
126 self._builders = {}
127 self._top_revision = {}
128 self._cache = Cache(cache_dir)
130 def Builders(self):
131 return self._builders.values()
133 def Update(self):
134 self._cache.Update("builders", self._root_url)
135 self.FetchInfo()
137 def FetchInfo(self):
138 if self._top_revision:
139 return
141 html = self._cache.FetchData("builders", self._root_url)
143 """ Search for both builders and latest build number in HTML
144 <td class="box"><a href="builders/<builder-name>"> identifies a builder
145 <a href="builders/<builder-name>/builds/<build-num>"> is the latest build.
147 box_matcher = re.compile('.*a href[^>]*>([^<]*)\<')
148 build_matcher = re.compile('.*a href=\"builders/(.*)/builds/([0-9]+)\".*')
149 last_builder = ""
150 for line in html:
151 if 'a href="builders/' in line:
152 if 'td class="box"' in line:
153 last_builder = box_matcher.match(line).group(1)
154 self._builders[last_builder] = Builder(self, last_builder)
155 else:
156 result = build_matcher.match(line)
157 builder = result.group(1)
158 assert builder == urllib.quote(last_builder)
159 self._top_revision[last_builder] = int(result.group(2))
161 def GetLatestBuild(self, name):
162 self.FetchInfo()
163 assert self._top_revision
164 return self._top_revision[name]
167 class MultiLineChange(object):
168 def __init__(self, lines):
169 self._tracked_lines = lines
170 self._current = 0
172 def __call__(self, line):
173 """ Test a single line against multi-line change.
175 If it matches the currently active line, advance one line.
176 If the current line is the last line, report a match.
178 if self._tracked_lines[self._current] in line:
179 self._current = self._current + 1
180 if self._current == len(self._tracked_lines):
181 self._current = 0
182 return True
183 else:
184 self._current = 0
185 return False
188 def main(argv):
189 # Create argument parser.
190 parser = argparse.ArgumentParser()
191 commands = parser.add_mutually_exclusive_group(required=True)
192 commands.add_argument("--update", action='store_true')
193 commands.add_argument("--find", metavar='search term')
194 args = parser.parse_args()
196 path = os.path.abspath(os.path.dirname(argv[0]))
197 cache_path = os.path.join(path, CACHE_DIR)
199 fyi = Waterfall(ROOT_URL, cache_path)
201 if args.update:
202 fyi.Update()
203 for builder in fyi.Builders():
204 print "Updating", builder.Name()
205 builder.ScanLogs(lambda x:False)
207 if args.find:
208 tester = MultiLineChange(args.find.splitlines())
209 fyi.FetchInfo()
211 print "SCANNING FOR ", args.find
212 for builder in fyi.Builders():
213 print "Scanning", builder.Name()
214 occurrences = builder.ScanLogs(tester)
215 if occurrences:
216 min_build = min(occurrences)
217 path = builder.GetBuildPath(min_build)
218 print "Earliest occurrence in build %d" % min_build
219 print "Latest occurrence in build %d" % max(occurrences)
220 print "Latest build: %d" % builder.LatestBuild()
221 print path
222 print "%d total" % len(occurrences)
225 if __name__ == "__main__":
226 sys.exit(main(sys.argv))