Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(1730)

Unified Diff: Tools/AutoSheriff/buildbot.py

Issue 398823008: WIP: Add auto-sheriff.appspot.com code to Blink Base URL: svn://svn.chromium.org/blink/trunk
Patch Set: Created 6 years, 5 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
Index: Tools/AutoSheriff/buildbot.py
diff --git a/Tools/AutoSheriff/buildbot.py b/Tools/AutoSheriff/buildbot.py
new file mode 100644
index 0000000000000000000000000000000000000000..852ae990d63049561bd80bfe72ec317545040bba
--- /dev/null
+++ b/Tools/AutoSheriff/buildbot.py
@@ -0,0 +1,178 @@
+# Copyright 2014 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 collections
+import json
+import logging
+import operator
+import os
+import requests
+import urlparse
+import string_helpers
+
+
+# Python logging is stupidly verbose to configure.
ojan 2014/07/22 02:01:24 Not really a useful comment.
+def setup_logging():
+ logger = logging.getLogger(__name__)
+ logger.setLevel(logging.DEBUG)
+ handler = logging.StreamHandler()
+ handler.setLevel(logging.DEBUG)
+ formatter = logging.Formatter('%(levelname)s: %(message)s')
+ handler.setFormatter(formatter)
+ logger.addHandler(handler)
+ return logger, handler
+
+
+log, logging_handler = setup_logging()
+
+
+CBE_BASE = 'https://chrome-build-extract.appspot.com'
+
+# Unclear if this should be specific to builds.
+class BuildCache(object):
+ def __init__(self, root_path):
+ self.root_path = root_path
+
+ # Could be in operator.
+ def has(self, key):
+ path = os.path.join(self.root_path, key)
+ return os.path.exists(path)
+
+ # Could be attr getter.
+ def get(self, key):
+ path = os.path.join(self.root_path, key)
+ if not self.has(path):
+ return None
+ with open(path) as cached:
+ return json.load(cached)
+
+ # Could be attr setter.
+ def set(self, key, json_object):
+ path = os.path.join(self.root_path, key)
+ cache_dir = os.path.dirname(path)
+ if not os.path.exists(cache_dir):
+ os.makedirs(cache_dir)
+ with open(path, 'w') as cached:
+ cached.write(json.dumps(json_object))
+
+
+def master_name_from_url(master_url):
+ return urlparse.urlparse(master_url).path.split('/')[-1]
+
+
+def cache_key_for_build(master_url, builder_name, build_number):
+ master_name = master_name_from_url(master_url)
+ return os.path.join(master_name, builder_name, "%s.json" % build_number)
+
+
+def fetch_master_json(master_url):
+ master_name = master_name_from_url(master_url)
+ url = '%s/get_master/%s' % (CBE_BASE, master_name)
+ return requests.get(url).json()
+
+
+def prefill_builds_cache(cache, master_url, builder_name):
+ master_name = master_name_from_url(master_url)
+ builds_url = '%s/get_builds' % CBE_BASE
+ params = { 'master': master_name, 'builder': builder_name }
+ response = requests.get(builds_url, params=params)
+ builds = response.json()['builds']
+ for build in builds:
+ if not build.get('number'):
+ index = builds.index(build)
+ log.error('build at index %s in %s missing number?' % (index, response.url))
+ continue
+ build_number = build['number']
+ key = cache_key_for_build(master_url, builder_name, build_number)
+ cache.set(key, build)
+ build_numbers = map(operator.itemgetter('number'), builds)
+ log.debug('Prefilled (%.1fs) %s for %s %s' %
+ (response.elapsed.total_seconds(),
+ string_helpers.re_range(build_numbers),
+ master_name, builder_name))
+ return build_numbers
+
+
+def fetch_and_cache_build(cache, url, cache_key):
+ log.debug('Fetching %s.' % url)
+ try:
+ build = requests.get(url).json()
+ # Don't cache builds which are just errors?
+ if build.get('number'):
+ if build.get('eta') is None:
+ cache.set(cache_key, build)
+ else:
+ log.debug('Not caching in-progress build from %s.')
+ return build
+ except ValueError, e:
+ log.error('Not caching invalid json: %s: %s' % (url, e))
+
+
+def fetch_build_json(cache, master_url, builder_name, build_number):
+ cache_key = cache_key_for_build(master_url, builder_name, build_number)
+ build = cache.get(cache_key)
+ # I accidentally stored some error builds and incomplete builds before.
+ if build and (not build.get('number') or build.get('eta')):
+ log.warn('Refetching %s %s %s' % (master_url, builder_name, build_number))
+ build = None
+
+ master_name = master_name_from_url(master_url)
+
+ cbe_url = "https://chrome-build-extract.appspot.com/p/%s/builders/%s/builds/%s?json=1" % (
+ master_name, builder_name, build_number)
+ if not build:
+ build = fetch_and_cache_build(cache, cbe_url, cache_key)
+
+ if not build:
+ log.warn("CBE failed, failover to buildbot %s" % cbe_url)
+ buildbot_url = "https://build.chromium.org/p/%s/json/builders/%s/builds/%s" % (
+ master_name, builder_name, build_number)
+ build = fetch_and_cache_build(cache, buildbot_url, cache_key)
+
+ return build
+
+
+# This effectively extracts the 'configuration' of the build
+# we could extend this beyond repo versions in the future.
+def revisions_from_build(build_json):
+ def _property_value(build_json, property_name):
+ for prop_tuple in build_json['properties']:
+ if prop_tuple[0] == property_name:
+ return prop_tuple[1]
+
+ REVISION_VARIABLES = [
+ ('chromium', 'got_revision'),
+ ('blink', 'got_webkit_revision'),
+ ('v8', 'got_v8_revision'),
+ ('nacl', 'got_nacl_revision'),
+ # Skia, for whatever reason, isn't exposed in the buildbot properties so
+ # don't bother to include it here.
+ ]
+
+ revisions = {}
+ for repo_name, buildbot_property in REVISION_VARIABLES:
+ # This is epicly stupid: 'tester' builders have the wrong
+ # revision for 'got_foo_revision' and we have to use
+ # parent_got_foo_revision instead, but non-tester builders
+ # don't have the parent_ versions, so we have to fall back
+ # to got_foo_revision in those cases!
+ # Don't even think about using 'revision' that's wrong too.
ojan 2014/07/22 02:01:24 Lol! This should perhaps be a FIXME? Theoreticall
+ revision = _property_value(build_json, 'parent_' + buildbot_property)
+ if not revision:
+ revision = _property_value(build_json, buildbot_property)
+ revisions[repo_name] = revision
+ return revisions
+
+
+def latest_revisions_for_master(cache, master_url, master_json):
+ latest_revisions = collections.defaultdict(dict)
+ master_name = master_name_from_url(master_url)
+ for builder_name, builder_json in master_json['builders'].items():
+ # recent_builds can include current builds
+ recent_builds = set(builder_json['cachedBuilds'])
+ active_builds = set(builder_json['currentBuilds'])
+ last_finished_id = sorted(recent_builds - active_builds, reverse=True)[0]
+ last_build = fetch_build_json(cache, master_url, builder_name, last_finished_id)
+ latest_revisions[master_name][builder_name] = revisions_from_build(last_build)
+ return latest_revisions

Powered by Google App Engine
This is Rietveld 408576698