Chromium Code Reviews| OLD | NEW |
|---|---|
| (Empty) | |
| 1 # Copyright 2014 The Chromium Authors. All rights reserved. | |
| 2 # Use of this source code is governed by a BSD-style license that can be | |
| 3 # found in the LICENSE file. | |
| 4 | |
| 5 import collections | |
| 6 import json | |
| 7 import logging | |
| 8 import operator | |
| 9 import os | |
| 10 import requests | |
| 11 import urlparse | |
| 12 import string_helpers | |
| 13 | |
| 14 | |
| 15 # Python logging is stupidly verbose to configure. | |
|
ojan
2014/07/22 02:01:24
Not really a useful comment.
| |
| 16 def setup_logging(): | |
| 17 logger = logging.getLogger(__name__) | |
| 18 logger.setLevel(logging.DEBUG) | |
| 19 handler = logging.StreamHandler() | |
| 20 handler.setLevel(logging.DEBUG) | |
| 21 formatter = logging.Formatter('%(levelname)s: %(message)s') | |
| 22 handler.setFormatter(formatter) | |
| 23 logger.addHandler(handler) | |
| 24 return logger, handler | |
| 25 | |
| 26 | |
| 27 log, logging_handler = setup_logging() | |
| 28 | |
| 29 | |
| 30 CBE_BASE = 'https://chrome-build-extract.appspot.com' | |
| 31 | |
| 32 # Unclear if this should be specific to builds. | |
| 33 class BuildCache(object): | |
| 34 def __init__(self, root_path): | |
| 35 self.root_path = root_path | |
| 36 | |
| 37 # Could be in operator. | |
| 38 def has(self, key): | |
| 39 path = os.path.join(self.root_path, key) | |
| 40 return os.path.exists(path) | |
| 41 | |
| 42 # Could be attr getter. | |
| 43 def get(self, key): | |
| 44 path = os.path.join(self.root_path, key) | |
| 45 if not self.has(path): | |
| 46 return None | |
| 47 with open(path) as cached: | |
| 48 return json.load(cached) | |
| 49 | |
| 50 # Could be attr setter. | |
| 51 def set(self, key, json_object): | |
| 52 path = os.path.join(self.root_path, key) | |
| 53 cache_dir = os.path.dirname(path) | |
| 54 if not os.path.exists(cache_dir): | |
| 55 os.makedirs(cache_dir) | |
| 56 with open(path, 'w') as cached: | |
| 57 cached.write(json.dumps(json_object)) | |
| 58 | |
| 59 | |
| 60 def master_name_from_url(master_url): | |
| 61 return urlparse.urlparse(master_url).path.split('/')[-1] | |
| 62 | |
| 63 | |
| 64 def cache_key_for_build(master_url, builder_name, build_number): | |
| 65 master_name = master_name_from_url(master_url) | |
| 66 return os.path.join(master_name, builder_name, "%s.json" % build_number) | |
| 67 | |
| 68 | |
| 69 def fetch_master_json(master_url): | |
| 70 master_name = master_name_from_url(master_url) | |
| 71 url = '%s/get_master/%s' % (CBE_BASE, master_name) | |
| 72 return requests.get(url).json() | |
| 73 | |
| 74 | |
| 75 def prefill_builds_cache(cache, master_url, builder_name): | |
| 76 master_name = master_name_from_url(master_url) | |
| 77 builds_url = '%s/get_builds' % CBE_BASE | |
| 78 params = { 'master': master_name, 'builder': builder_name } | |
| 79 response = requests.get(builds_url, params=params) | |
| 80 builds = response.json()['builds'] | |
| 81 for build in builds: | |
| 82 if not build.get('number'): | |
| 83 index = builds.index(build) | |
| 84 log.error('build at index %s in %s missing number?' % (index, respon se.url)) | |
| 85 continue | |
| 86 build_number = build['number'] | |
| 87 key = cache_key_for_build(master_url, builder_name, build_number) | |
| 88 cache.set(key, build) | |
| 89 build_numbers = map(operator.itemgetter('number'), builds) | |
| 90 log.debug('Prefilled (%.1fs) %s for %s %s' % | |
| 91 (response.elapsed.total_seconds(), | |
| 92 string_helpers.re_range(build_numbers), | |
| 93 master_name, builder_name)) | |
| 94 return build_numbers | |
| 95 | |
| 96 | |
| 97 def fetch_and_cache_build(cache, url, cache_key): | |
| 98 log.debug('Fetching %s.' % url) | |
| 99 try: | |
| 100 build = requests.get(url).json() | |
| 101 # Don't cache builds which are just errors? | |
| 102 if build.get('number'): | |
| 103 if build.get('eta') is None: | |
| 104 cache.set(cache_key, build) | |
| 105 else: | |
| 106 log.debug('Not caching in-progress build from %s.') | |
| 107 return build | |
| 108 except ValueError, e: | |
| 109 log.error('Not caching invalid json: %s: %s' % (url, e)) | |
| 110 | |
| 111 | |
| 112 def fetch_build_json(cache, master_url, builder_name, build_number): | |
| 113 cache_key = cache_key_for_build(master_url, builder_name, build_number) | |
| 114 build = cache.get(cache_key) | |
| 115 # I accidentally stored some error builds and incomplete builds before. | |
| 116 if build and (not build.get('number') or build.get('eta')): | |
| 117 log.warn('Refetching %s %s %s' % (master_url, builder_name, build_number)) | |
| 118 build = None | |
| 119 | |
| 120 master_name = master_name_from_url(master_url) | |
| 121 | |
| 122 cbe_url = "https://chrome-build-extract.appspot.com/p/%s/builders/%s/builds/%s ?json=1" % ( | |
| 123 master_name, builder_name, build_number) | |
| 124 if not build: | |
| 125 build = fetch_and_cache_build(cache, cbe_url, cache_key) | |
| 126 | |
| 127 if not build: | |
| 128 log.warn("CBE failed, failover to buildbot %s" % cbe_url) | |
| 129 buildbot_url = "https://build.chromium.org/p/%s/json/builders/%s/builds/%s" % ( | |
| 130 master_name, builder_name, build_number) | |
| 131 build = fetch_and_cache_build(cache, buildbot_url, cache_key) | |
| 132 | |
| 133 return build | |
| 134 | |
| 135 | |
| 136 # This effectively extracts the 'configuration' of the build | |
| 137 # we could extend this beyond repo versions in the future. | |
| 138 def revisions_from_build(build_json): | |
| 139 def _property_value(build_json, property_name): | |
| 140 for prop_tuple in build_json['properties']: | |
| 141 if prop_tuple[0] == property_name: | |
| 142 return prop_tuple[1] | |
| 143 | |
| 144 REVISION_VARIABLES = [ | |
| 145 ('chromium', 'got_revision'), | |
| 146 ('blink', 'got_webkit_revision'), | |
| 147 ('v8', 'got_v8_revision'), | |
| 148 ('nacl', 'got_nacl_revision'), | |
| 149 # Skia, for whatever reason, isn't exposed in the buildbot properties so | |
| 150 # don't bother to include it here. | |
| 151 ] | |
| 152 | |
| 153 revisions = {} | |
| 154 for repo_name, buildbot_property in REVISION_VARIABLES: | |
| 155 # This is epicly stupid: 'tester' builders have the wrong | |
| 156 # revision for 'got_foo_revision' and we have to use | |
| 157 # parent_got_foo_revision instead, but non-tester builders | |
| 158 # don't have the parent_ versions, so we have to fall back | |
| 159 # to got_foo_revision in those cases! | |
| 160 # 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
| |
| 161 revision = _property_value(build_json, 'parent_' + buildbot_property) | |
| 162 if not revision: | |
| 163 revision = _property_value(build_json, buildbot_property) | |
| 164 revisions[repo_name] = revision | |
| 165 return revisions | |
| 166 | |
| 167 | |
| 168 def latest_revisions_for_master(cache, master_url, master_json): | |
| 169 latest_revisions = collections.defaultdict(dict) | |
| 170 master_name = master_name_from_url(master_url) | |
| 171 for builder_name, builder_json in master_json['builders'].items(): | |
| 172 # recent_builds can include current builds | |
| 173 recent_builds = set(builder_json['cachedBuilds']) | |
| 174 active_builds = set(builder_json['currentBuilds']) | |
| 175 last_finished_id = sorted(recent_builds - active_builds, reverse=True)[0] | |
| 176 last_build = fetch_build_json(cache, master_url, builder_name, last_finished _id) | |
| 177 latest_revisions[master_name][builder_name] = revisions_from_build(last_buil d) | |
| 178 return latest_revisions | |
| OLD | NEW |