| OLD | NEW |
| 1 # Copyright (C) 2013 Google Inc. All rights reserved. | 1 # Copyright (C) 2013 Google Inc. All rights reserved. |
| 2 # | 2 # |
| 3 # Redistribution and use in source and binary forms, with or without | 3 # Redistribution and use in source and binary forms, with or without |
| 4 # modification, are permitted provided that the following conditions are | 4 # modification, are permitted provided that the following conditions are |
| 5 # met: | 5 # met: |
| 6 # | 6 # |
| 7 # * Redistributions of source code must retain the above copyright | 7 # * Redistributions of source code must retain the above copyright |
| 8 # notice, this list of conditions and the following disclaimer. | 8 # notice, this list of conditions and the following disclaimer. |
| 9 # * Redistributions in binary form must reproduce the above | 9 # * Redistributions in binary form must reproduce the above |
| 10 # copyright notice, this list of conditions and the following disclaimer | 10 # copyright notice, this list of conditions and the following disclaimer |
| (...skipping 20 matching lines...) Expand all Loading... |
| 31 import logging | 31 import logging |
| 32 import re | 32 import re |
| 33 import sys | 33 import sys |
| 34 import traceback | 34 import traceback |
| 35 import urllib2 | 35 import urllib2 |
| 36 import webapp2 | 36 import webapp2 |
| 37 | 37 |
| 38 from google.appengine.api import memcache | 38 from google.appengine.api import memcache |
| 39 | 39 |
| 40 MASTERS = [ | 40 MASTERS = [ |
| 41 {'name': 'ChromiumWin', 'url': 'http://build.chromium.org/p/chromium.win', '
groups': ['@ToT Chromium']}, | 41 {'name': 'ChromiumWin', 'url_name': 'chromium.win', 'groups': ['@ToT Chromiu
m']}, |
| 42 {'name': 'ChromiumMac', 'url': 'http://build.chromium.org/p/chromium.mac', '
groups': ['@ToT Chromium']}, | 42 {'name': 'ChromiumMac', 'url_name': 'chromium.mac', 'groups': ['@ToT Chromiu
m']}, |
| 43 {'name': 'ChromiumLinux', 'url': 'http://build.chromium.org/p/chromium.linux
', 'groups': ['@ToT Chromium']}, | 43 {'name': 'ChromiumLinux', 'url_name': 'chromium.linux', 'groups': ['@ToT Chr
omium']}, |
| 44 {'name': 'ChromiumChromiumOS', 'url': 'http://build.chromium.org/p/chromium.
chromiumos', 'groups': ['@ToT ChromeOS']}, | 44 {'name': 'ChromiumChromiumOS', 'url_name': 'chromium.chromiumos', 'groups':
['@ToT ChromeOS']}, |
| 45 {'name': 'ChromiumGPU', 'url': 'http://build.chromium.org/p/chromium.gpu', '
groups': ['@ToT Chromium']}, | 45 {'name': 'ChromiumGPU', 'url_name': 'chromium.gpu', 'groups': ['@ToT Chromiu
m']}, |
| 46 {'name': 'ChromiumGPUFYI', 'url': 'http://build.chromium.org/p/chromium.gpu.
fyi', 'groups': ['@ToT Chromium FYI']}, | 46 {'name': 'ChromiumGPUFYI', 'url_name': 'chromium.gpu.fyi', 'groups': ['@ToT
Chromium FYI']}, |
| 47 {'name': 'ChromiumWebkit', 'url': 'http://build.chromium.org/p/chromium.webk
it', 'groups': ['@ToT Chromium', '@ToT Blink']}, | 47 {'name': 'ChromiumWebkit', 'url_name': 'chromium.webkit', 'groups': ['@ToT C
hromium', '@ToT Blink']}, |
| 48 {'name': 'ChromiumFYI', 'url': 'http://build.chromium.org/p/chromium.fyi', '
groups': ['@ToT Chromium FYI']}, | 48 {'name': 'ChromiumFYI', 'url_name': 'chromium.fyi', 'groups': ['@ToT Chromiu
m FYI']}, |
| 49 {'name': 'V8', 'url': 'http://build.chromium.org/p/client.v8', 'groups': ['@
ToT V8']}, | 49 {'name': 'V8', 'url_name': 'client.v8', 'groups': ['@ToT V8']}, |
| 50 ] | 50 ] |
| 51 | 51 |
| 52 # Buildbot steps that have test in the name, but don't run tests. | 52 # Buildbot steps that have test in the name, but don't run tests. |
| 53 NON_TEST_STEP_NAMES = [ | 53 NON_TEST_STEP_NAMES = [ |
| 54 'archive', | 54 'archive', |
| 55 'Run tests', | 55 'Run tests', |
| 56 'find isolated tests', | 56 'find isolated tests', |
| 57 'read test spec', | 57 'read test spec', |
| 58 'Download latest chromedriver', | 58 'Download latest chromedriver', |
| 59 'compile tests', | 59 'compile tests', |
| (...skipping 10 matching lines...) Expand all Loading... |
| 70 'python_tests(chrome', | 70 'python_tests(chrome', |
| 71 'run_all_tests.py', | 71 'run_all_tests.py', |
| 72 'test_report', | 72 'test_report', |
| 73 'test CronetSample', | 73 'test CronetSample', |
| 74 'test_mini_installer', | 74 'test_mini_installer', |
| 75 'telemetry_unittests', | 75 'telemetry_unittests', |
| 76 'webkit_python_tests', | 76 'webkit_python_tests', |
| 77 'webkit_unit_tests', | 77 'webkit_unit_tests', |
| 78 ] | 78 ] |
| 79 | 79 |
| 80 class FetchBuildersException(Exception): pass | 80 BUILDS_URL = 'http://chrome-build-extract.appspot.com/get_builds?builder=%s&mast
er=%s&num_builds=1' |
| 81 MASTER_URL = 'http://chrome-build-extract.appspot.com/get_master/%s' |
| 81 | 82 |
| 82 | 83 |
| 83 def master_json_url(master_url): | 84 class FetchBuildersException(Exception): |
| 84 return master_url + '/json/builders' | 85 pass |
| 85 | |
| 86 | |
| 87 def builder_json_url(master_url, builder): | |
| 88 return master_json_url(master_url) + '/' + urllib2.quote(builder) | |
| 89 | |
| 90 | |
| 91 def cached_build_json_url(master_url, builder, build_number): | |
| 92 return builder_json_url(master_url, builder) + '/builds/' + str(build_number
) | |
| 93 | 86 |
| 94 | 87 |
| 95 def fetch_json(url): | 88 def fetch_json(url): |
| 96 logging.debug('Fetching %s' % url) | 89 logging.debug('Fetching %s' % url) |
| 97 fetched_json = {} | 90 fetched_json = {} |
| 98 try: | 91 try: |
| 99 resp = urllib2.urlopen(url) | 92 resp = urllib2.urlopen(url) |
| 100 except: | 93 except: |
| 101 exc_info = sys.exc_info() | 94 exc_info = sys.exc_info() |
| 102 logging.warning('Error while fetching %s: %s', url, exc_info[1]) | 95 logging.warning('Error while fetching %s: %s', url, exc_info[1]) |
| 103 return fetched_json | 96 return fetched_json |
| 104 | 97 |
| 105 try: | 98 try: |
| 106 fetched_json = json.load(resp) | 99 fetched_json = json.load(resp) |
| 107 except: | 100 except: |
| 108 exc_info = sys.exc_info() | 101 exc_info = sys.exc_info() |
| 109 logging.warning('Unable to parse JSON response from %s: %s', url, exc_in
fo[1]) | 102 logging.warning('Unable to parse JSON response from %s: %s', url, exc_in
fo[1]) |
| 110 | 103 |
| 111 return fetched_json | 104 return fetched_json |
| 112 | 105 |
| 113 | 106 |
| 114 def get_latest_build(build_data): | |
| 115 cached_builds = [] | |
| 116 if 'cachedBuilds' in build_data: | |
| 117 cached_builds = build_data['cachedBuilds'] | |
| 118 | |
| 119 current_builds = build_data['currentBuilds'] | |
| 120 | |
| 121 latest_cached_builds = set(cached_builds) - set(current_builds) | |
| 122 if len(latest_cached_builds) != 0: | |
| 123 latest_cached_builds = sorted(list(latest_cached_builds)) | |
| 124 latest_build = latest_cached_builds[-1] | |
| 125 elif len(current_builds) != 0: | |
| 126 latest_build = current_builds[0] | |
| 127 else: | |
| 128 basedir = build_data['basedir'] if 'basedir' in build_data else 'current
builder' | |
| 129 logging.info('No cached or current builds for %s', basedir) | |
| 130 return None | |
| 131 | |
| 132 return latest_build | |
| 133 | |
| 134 | |
| 135 def dump_json(data): | 107 def dump_json(data): |
| 136 return json.dumps(data, separators=(',', ':'), sort_keys=True) | 108 return json.dumps(data, separators=(',', ':'), sort_keys=True) |
| 137 | 109 |
| 138 | 110 |
| 139 def fetch_buildbot_data(masters, force_update=False): | 111 def fetch_buildbot_data(masters): |
| 140 if force_update: | |
| 141 logging.info('Starting a forced buildbot update. Failure to fetch a mast
er\'s data will not abort the fetch.') | |
| 142 | |
| 143 start_time = datetime.datetime.now() | 112 start_time = datetime.datetime.now() |
| 144 master_data = masters[:] | 113 master_data = masters[:] |
| 145 for master in master_data: | 114 for master in master_data: |
| 146 master_url = master['url'] | 115 master_url = MASTER_URL % master['url_name'] |
| 116 builders = fetch_json(master_url) |
| 117 if not builders: |
| 118 msg = 'Aborting fetch. Could not fetch builders from master "%s": %s
.' % (master['name'], master_url) |
| 119 logging.warning(msg) |
| 120 raise FetchBuildersException(msg) |
| 121 |
| 147 tests_object = master.setdefault('tests', {}) | 122 tests_object = master.setdefault('tests', {}) |
| 148 master['tests'] = tests_object | |
| 149 | 123 |
| 150 builders = fetch_json(master_json_url(master_url)) | 124 for builder in builders['builders'].keys(): |
| 151 if not builders: | 125 build = fetch_json(BUILDS_URL % (urllib2.quote(builder), master['url
_name'])) |
| 152 msg = 'Could not fetch builders from master "%s": %s.' % (master['na
me'], master_url) | 126 if not build: |
| 153 logging.warning(msg) | 127 logging.info('Skipping builder %s on master %s due to empty data
.', builder, master['url_name']) |
| 154 if force_update: | |
| 155 continue | |
| 156 else: | |
| 157 logging.warning('Aborting fetch.') | |
| 158 raise FetchBuildersException(msg) | |
| 159 | |
| 160 for builder in builders: | |
| 161 build_data = fetch_json(builder_json_url(master_url, builder)) | |
| 162 | |
| 163 latest_build = get_latest_build(build_data) | |
| 164 if not latest_build: | |
| 165 logging.info('Skipping builder %s because it lacked cached or cu
rrent builds.', builder) | |
| 166 continue | 128 continue |
| 167 | 129 |
| 168 build = fetch_json(cached_build_json_url(master_url, builder, latest
_build)) | 130 if not build['builds']: |
| 169 if not build: | 131 logging.info('Skipping builder %s on master %s due to empty buil
ds list.', builder, master['url_name']) |
| 170 logging.info('Skipping build %s on builder %s due to empty data'
, latest_build, builder) | 132 continue |
| 171 for step in build['steps']: | 133 |
| 134 for step in build['builds'][0]['steps']: |
| 172 step_name = step['name'] | 135 step_name = step['name'] |
| 173 | 136 |
| 174 if not 'test' in step_name: | 137 if not 'test' in step_name: |
| 175 continue | 138 continue |
| 176 | 139 |
| 177 if any(name in step_name for name in NON_TEST_STEP_NAMES): | 140 if any(name in step_name for name in NON_TEST_STEP_NAMES): |
| 178 continue | 141 continue |
| 179 | 142 |
| 180 if re.search('_only|_ignore|_perf$', step_name): | 143 if re.search('_only|_ignore|_perf$', step_name): |
| 181 continue | 144 continue |
| (...skipping 12 matching lines...) Expand all Loading... |
| 194 delta = datetime.datetime.now() - start_time | 157 delta = datetime.datetime.now() - start_time |
| 195 | 158 |
| 196 logging.info('Fetched buildbot data in %s seconds.', delta.seconds) | 159 logging.info('Fetched buildbot data in %s seconds.', delta.seconds) |
| 197 | 160 |
| 198 return dump_json(output_data) | 161 return dump_json(output_data) |
| 199 | 162 |
| 200 | 163 |
| 201 class UpdateBuilders(webapp2.RequestHandler): | 164 class UpdateBuilders(webapp2.RequestHandler): |
| 202 """Fetch and update the cached buildbot data.""" | 165 """Fetch and update the cached buildbot data.""" |
| 203 def get(self): | 166 def get(self): |
| 204 force_update = True if self.request.get('force') else False | |
| 205 try: | 167 try: |
| 206 buildbot_data = fetch_buildbot_data(MASTERS, force_update) | 168 buildbot_data = fetch_buildbot_data(MASTERS) |
| 207 memcache.set('buildbot_data', buildbot_data) | 169 memcache.set('buildbot_data', buildbot_data) |
| 208 self.response.set_status(200) | 170 self.response.set_status(200) |
| 209 self.response.out.write("ok") | 171 self.response.out.write("ok") |
| 210 except FetchBuildersException, ex: | 172 except FetchBuildersException, ex: |
| 211 logging.error('Not updating builders because fetch failed: %s', str(
ex)) | 173 logging.error('Not updating builders because fetch failed: %s', str(
ex)) |
| 212 self.response.set_status(500) | 174 self.response.set_status(500) |
| 213 self.response.out.write(ex.message) | 175 self.response.out.write(ex.message) |
| 214 | 176 |
| 215 | 177 |
| 216 | |
| 217 class GetBuilders(webapp2.RequestHandler): | 178 class GetBuilders(webapp2.RequestHandler): |
| 218 """Return a list of masters mapped to their respective builders, possibly us
ing cached data.""" | 179 """Return a list of masters mapped to their respective builders, possibly us
ing cached data.""" |
| 219 def get(self): | 180 def get(self): |
| 220 callback = self.request.get('callback') | |
| 221 | |
| 222 buildbot_data = memcache.get('buildbot_data') | 181 buildbot_data = memcache.get('buildbot_data') |
| 223 | 182 |
| 224 if not buildbot_data: | 183 if not buildbot_data: |
| 225 logging.warning('No buildbot data in memcache. If this message repea
ts, something is probably wrong with memcache.') | 184 logging.warning('No buildbot data in memcache. If this message repea
ts, something is probably wrong with memcache.') |
| 185 try: |
| 186 buildbot_data = fetch_buildbot_data(MASTERS) |
| 187 memcache.set('buildbot_data', buildbot_data) |
| 188 except FetchBuildersException, ex: |
| 189 logging.error('Builders fetch failed: %s', str(ex)) |
| 190 self.response.set_status(500) |
| 191 self.response.out.write(ex.message) |
| 192 return |
| 226 | 193 |
| 227 # Since we have no cached buildbot data, we would rather have missin
g masters than no data at all. | 194 callback = self.request.get('callback') |
| 228 buildbot_data = fetch_buildbot_data(MASTERS, True) | |
| 229 try: | |
| 230 memcache.set('buildbot_data', buildbot_data) | |
| 231 except ValueError, err: | |
| 232 logging.error(str(err)) | |
| 233 | |
| 234 if callback: | 195 if callback: |
| 235 buildbot_data = callback + '(' + buildbot_data + ');' | 196 buildbot_data = callback + '(' + buildbot_data + ');' |
| 236 | 197 |
| 237 self.response.out.write(buildbot_data) | 198 self.response.out.write(buildbot_data) |
| OLD | NEW |