| Index: tools/valgrind/waterfall_suppressions_used.py
|
| diff --git a/tools/valgrind/waterfall_suppressions_used.py b/tools/valgrind/waterfall_suppressions_used.py
|
| new file mode 100755
|
| index 0000000000000000000000000000000000000000..c6359a77b0b2d756cf31e37cd6f5b530fe39cc39
|
| --- /dev/null
|
| +++ b/tools/valgrind/waterfall_suppressions_used.py
|
| @@ -0,0 +1,182 @@
|
| +#!/usr/bin/env python
|
| +# Copyright (c) 2012 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.
|
| +
|
| +# This script reports which suppressions are being used by the valgrind and
|
| +# heapcheck bots.
|
| +
|
| +import json, urllib, urlparse, httplib
|
| +import sys
|
| +
|
| +def Fetch(url):
|
| + """ Fetch JSON from |url|. """
|
| + return json.loads(urllib.urlopen(url).read())
|
| +
|
| +
|
| +def FetchTail(url, tail=4096):
|
| + """ Fetch the last |tail| bytes of |url|. """
|
| + urlsplit = urlparse.urlsplit(url)
|
| + conn = httplib.HTTPConnection(urlsplit.netloc)
|
| + conn.request('HEAD', urlsplit.path)
|
| + response = conn.getresponse()
|
| + if response.status != 200:
|
| + raise Exception('Could not fetch %s -- got %s' % (url, response.status))
|
| + length = int(response.getheader('Content-Length'))
|
| + response.read() # Must be done before next request.
|
| + conn.request('GET', urlsplit.path,
|
| + headers={'Range': 'bytes=%d-%d' % (length - tail, length)})
|
| + response = conn.getresponse()
|
| + if response.status not in [200, 206]:
|
| + raise Exception('Could not fetch %s -- got %s' % (url, response.status))
|
| + response_body = response.read()
|
| + # Often buildbot will ignore our range request and send the whole data.
|
| + return response_body[-tail:]
|
| +
|
| +
|
| +def FindMatchingBuilders(waterfall_url, category):
|
| + """ Return builders which match the given |category|. """
|
| + print >> sys.stderr, 'Finding %s bots' % (category)
|
| + builders = Fetch('%s/json/builders' % waterfall_url)
|
| + matching_builders = []
|
| + for name, info in builders.iteritems():
|
| + if category in info['category']:
|
| + matching_builders.append(name)
|
| + return matching_builders
|
| +
|
| +
|
| +def ParseMemoryTestLog(log):
|
| + """ Parse memory test log for suppressions used. """
|
| + suppressions_used = []
|
| + log = log[log.find('Suppressions used:'):]
|
| + for line in log.splitlines()[2:]:
|
| + if line.startswith('-----'):
|
| + break
|
| + count, name = line.strip().split(None, 1)
|
| + suppressions_used.append([name, int(count)])
|
| + return suppressions_used
|
| +
|
| +
|
| +def ParseHeapcheckTestLog(log):
|
| + """ Parse heapcheck test log for suppressions used. """
|
| + suppressions_used = []
|
| + log = log[log.find('Suppressions used:'):]
|
| + for line in log.splitlines()[2:]:
|
| + if line.startswith('-----'):
|
| + break
|
| + count, _, _, name = line.strip().split(None, 3)
|
| + suppressions_used.append([name, int(count)])
|
| + return suppressions_used
|
| +
|
| +
|
| +def FetchBuildSuppressions(steps, step_type, log_parser):
|
| + """ Fetch suppressions used in |steps| matching |step_type|.
|
| + Result is { suppression_name : [(step_name, count)] }.
|
| + """
|
| + suppressions = {}
|
| + for step in steps:
|
| + if step_type in step['name']:
|
| + stdio_log = None
|
| + for log_name, log_url in step['logs']:
|
| + if log_name == 'stdio':
|
| + stdio_log = log_url
|
| + break
|
| + if stdio_log is None:
|
| + print >> sys.stderr, 'WARNING: no stdio log in %s' % step['name']
|
| + continue
|
| + try:
|
| + for name, count in log_parser(FetchTail('%s/text' % stdio_log)):
|
| + suppressions.setdefault(name, []).append((step['name'], count))
|
| + except Exception, e:
|
| + print >> sys.stderr, 'WARNING: Could not parse %s' % stdio_log
|
| + print >> sys.stderr, e
|
| + return suppressions
|
| +
|
| +
|
| +def FetchBuilderSuppressions(builds, step_type, log_parser):
|
| + """ Fetches aggregate suppression stats for all |builds|.
|
| + Result is { suppression_name : { step_name : [avg_count, max_count] } }.
|
| + """
|
| + suppressions = {}
|
| + for build_id, build_info in builds.iteritems():
|
| + build_suppressions = FetchBuildSuppressions(build_info['steps'], step_type,
|
| + log_parser)
|
| + for name, stats in build_suppressions.iteritems():
|
| + totals = suppressions.setdefault(name, {})
|
| + for step_name, count in stats:
|
| + step_totals = totals.setdefault(step_name, [0, 0])
|
| + step_totals[0] += count
|
| + step_totals[1] = max(count, step_totals[1])
|
| + # Normalize averages.
|
| + for name, stats in suppressions.iteritems():
|
| + for step_name, step_totals in stats.iteritems():
|
| + step_totals[0] /= float(len(builds))
|
| + return suppressions
|
| +
|
| +
|
| +def FetchSuppressions(waterfall_url, builder_type, verbose=False, count=3):
|
| + """ Fetch suppression stats for |builder_type| from last |count| builds. """
|
| + builder_category, step_type, log_parser = builder_type
|
| + # The last one is still building, so skip it.
|
| + select = '&'.join(['select=%d' % (-i-2) for i in range(count)])
|
| +
|
| + suppressions = {}
|
| + builders = FindMatchingBuilders(waterfall_url, builder_category)
|
| +
|
| + for builder in builders:
|
| + print >> sys.stderr, 'Fetching suppressions from "%s"' % (builder)
|
| + builds = Fetch('%s/json/builders/%s/builds?%s' % (waterfall_url, builder,
|
| + select))
|
| + builder_suppressions = FetchBuilderSuppressions(builds, step_type,
|
| + log_parser)
|
| + for name, builder_stats in builder_suppressions.iteritems():
|
| + suppressions.setdefault(name, []).append((builder, builder_stats))
|
| +
|
| + # Output.
|
| + for name, stats in sorted(suppressions.items()):
|
| + if verbose:
|
| + print name
|
| + # Compute totals across builders.
|
| + global_avg = 0
|
| + for builder, builder_stats in sorted(stats):
|
| + if verbose:
|
| + print ' %s' % builder
|
| + for step_name, [avg_count, max_count] in builder_stats.iteritems():
|
| + if verbose:
|
| + print ' %-30s %8.1f %8d' % (step_name, avg_count, max_count)
|
| + global_avg += avg_count
|
| + global_avg /= float(len(stats))
|
| + if verbose:
|
| + print ' %30s %8.1f' % ('TOTAL', global_avg)
|
| + else:
|
| + print '%8.1f %s' % (global_avg, name)
|
| +
|
| +
|
| +WATERFALL_URL = 'http://build.chromium.org/p/chromium.memory.fyi'
|
| +
|
| +BUILDER_TYPES = {
|
| + # type is (builder category, step type, log parser)
|
| + 'valgrind' : ('memory_tester', 'memory test', ParseMemoryTestLog),
|
| + 'heapcheck' : ('heapcheck_tester', 'heapcheck test', ParseHeapcheckTestLog),
|
| +}
|
| +
|
| +if __name__ == '__main__':
|
| + import sys
|
| + import getopt # No argparse before python 2.6
|
| +
|
| + count = 3
|
| + verbose = False
|
| + try:
|
| + optlist, args = getopt.getopt(sys.argv[1:], 'vc:')
|
| + for opt, val in optlist:
|
| + if opt == '-c':
|
| + count = int(val)
|
| + if opt == '-v':
|
| + verbose = True
|
| + builder_type = BUILDER_TYPES[args[0]]
|
| + except:
|
| + print >> sys.stderr, 'Usage: %s [-c build_count] [-v] (%s)' % (
|
| + sys.argv[0], '|'.join(BUILDER_TYPES.keys()))
|
| + sys.exit(2)
|
| +
|
| + FetchSuppressions(WATERFALL_URL, builder_type, count=count, verbose=verbose)
|
|
|