| OLD | NEW |
| 1 #!/usr/bin/env python | 1 #!/usr/bin/env python |
| 2 # Copyright 2014 The Chromium Authors. All rights reserved. | 2 # Copyright 2014 The Chromium Authors. All rights reserved. |
| 3 # Use of this source code is governed by a BSD-style license that can be | 3 # Use of this source code is governed by a BSD-style license that can be |
| 4 # found in the LICENSE file. | 4 # found in the LICENSE file. |
| 5 | 5 |
| 6 import argparse | 6 import argparse |
| 7 import contextlib | 7 import contextlib |
| 8 import cStringIO | 8 import cStringIO |
| 9 import datetime | 9 import datetime |
| 10 import gzip | 10 import gzip |
| 11 import json | 11 import json |
| 12 import logging | 12 import logging |
| 13 import multiprocessing | 13 import multiprocessing |
| 14 import os | 14 import os |
| 15 import sys | 15 import sys |
| 16 import threading | 16 import threading |
| 17 import traceback | 17 import traceback |
| 18 | 18 |
| 19 import requests | 19 import requests |
| 20 import requests_cache | 20 import requests_cache |
| 21 | 21 |
| 22 from infra_libs import logs | 22 from infra_libs import logs |
| 23 from infra_libs import ts_mon | |
| 24 from infra.libs.service_utils import outer_loop | 23 from infra.libs.service_utils import outer_loop |
| 25 | 24 |
| 26 from infra.services.builder_alerts import alert_builder | 25 from infra.services.builder_alerts import alert_builder |
| 27 from infra.services.builder_alerts import analysis | 26 from infra.services.builder_alerts import analysis |
| 28 from infra.services.builder_alerts import buildbot | 27 from infra.services.builder_alerts import buildbot |
| 29 from infra.services.builder_alerts import crbug_issues | 28 from infra.services.builder_alerts import crbug_issues |
| 30 from infra.services.builder_alerts import gatekeeper_extras | 29 from infra.services.builder_alerts import gatekeeper_extras |
| 31 from infra.services.builder_alerts import string_helpers | 30 from infra.services.builder_alerts import string_helpers |
| 32 | 31 |
| 33 | 32 |
| (...skipping 20 matching lines...) Expand all Loading... |
| 54 class SubProcess(object): | 53 class SubProcess(object): |
| 55 | 54 |
| 56 def __init__(self, cache, old_alerts, builder_filter, jobs): | 55 def __init__(self, cache, old_alerts, builder_filter, jobs): |
| 57 super(SubProcess, self).__init__() | 56 super(SubProcess, self).__init__() |
| 58 self._cache = cache | 57 self._cache = cache |
| 59 self._old_alerts = old_alerts | 58 self._old_alerts = old_alerts |
| 60 self._builder_filter = builder_filter | 59 self._builder_filter = builder_filter |
| 61 self._jobs = jobs | 60 self._jobs = jobs |
| 62 | 61 |
| 63 def __call__(self, master_url): | 62 def __call__(self, master_url): |
| 64 logging.debug('Thread for master %s has started', master_url) | |
| 65 try: | 63 try: |
| 66 master_json = buildbot.fetch_master_json(master_url) | 64 master_json = buildbot.fetch_master_json(master_url) |
| 67 if not master_json: | 65 if not master_json: |
| 68 return (None, None, None, master_url) | 66 return (None, None, None, master_url) |
| 69 | 67 |
| 70 master_alerts, stale_master_alert = alert_builder.alerts_for_master( | 68 master_alerts, stale_master_alert = alert_builder.alerts_for_master( |
| 71 self._cache, master_url, master_json, self._old_alerts, | 69 self._cache, master_url, master_json, self._old_alerts, |
| 72 self._builder_filter, self._jobs) | 70 self._builder_filter, self._jobs) |
| 73 | 71 |
| 74 # FIXME: The builder info doesn't really belong here. The builder | 72 # FIXME: The builder info doesn't really belong here. The builder |
| 75 # revisions tool uses this and we happen to have the builder json cached | 73 # revisions tool uses this and we happen to have the builder json cached |
| 76 # at this point so it's cheap to compute, but it should be moved | 74 # at this point so it's cheap to compute, but it should be moved |
| 77 # to a different feed. | 75 # to a different feed. |
| 78 data, stale_builder_alerts = ( | 76 data, stale_builder_alerts = ( |
| 79 buildbot.latest_builder_info_and_alerts_for_master( | 77 buildbot.latest_builder_info_and_alerts_for_master( |
| 80 self._cache, master_url, master_json)) | 78 self._cache, master_url, master_json)) |
| 81 if stale_master_alert: | 79 if stale_master_alert: |
| 82 stale_builder_alerts.append(stale_master_alert) | 80 stale_builder_alerts.append(stale_master_alert) |
| 83 return (master_alerts, data, stale_builder_alerts, master_url) | 81 return (master_alerts, data, stale_builder_alerts, master_url) |
| 84 except: | 82 except: |
| 85 # Put all exception text into an exception and raise that so it doesn't | 83 # Put all exception text into an exception and raise that so it doesn't |
| 86 # get eaten by the multiprocessing code. | 84 # get eaten by the multiprocessing code. |
| 87 msg = '%s for master url %s' % ( | 85 msg = '%s for master url %s' % ( |
| 88 ''.join(traceback.format_exception(*sys.exc_info())), | 86 ''.join(traceback.format_exception(*sys.exc_info())), |
| 89 master_url, | 87 master_url, |
| 90 ) | 88 ) |
| 91 raise Exception(msg) | 89 raise Exception(msg) |
| 92 finally: | |
| 93 logging.debug('Thread for master %s has finished', master_url) | |
| 94 | 90 |
| 95 | 91 |
| 96 def query_findit(findit_api_url, alerts): | 92 def query_findit(findit_api_url, alerts): |
| 97 """Get analysis results from Findit for failures in the given alerts. | 93 """Get analysis results from Findit for failures in the given alerts. |
| 98 | 94 |
| 99 Args: | 95 Args: |
| 100 findit_api_url (str): The URL to findit's api for build failure analysis. | 96 findit_api_url (str): The URL to findit's api for build failure analysis. |
| 101 alerts (list): A non-empty list of failure alerts. | 97 alerts (list): A non-empty list of failure alerts. |
| 102 | 98 |
| 103 Returns: | 99 Returns: |
| (...skipping 82 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 186 | 182 |
| 187 | 183 |
| 188 def gzipped(data): | 184 def gzipped(data): |
| 189 s = cStringIO.StringIO() | 185 s = cStringIO.StringIO() |
| 190 with contextlib.closing(gzip.GzipFile(fileobj=s, mode='w')) as g: | 186 with contextlib.closing(gzip.GzipFile(fileobj=s, mode='w')) as g: |
| 191 g.write(data) | 187 g.write(data) |
| 192 return s.getvalue() | 188 return s.getvalue() |
| 193 | 189 |
| 194 | 190 |
| 195 def inner_loop(args): | 191 def inner_loop(args): |
| 196 logging.debug('Starting inner loop') | |
| 197 old_api_endpoint = string_helpers.slash_join(args.api_endpoint_prefix, | 192 old_api_endpoint = string_helpers.slash_join(args.api_endpoint_prefix, |
| 198 args.old_api_path) if args.old_api_path else None | 193 args.old_api_path) if args.old_api_path else None |
| 199 if not old_api_endpoint: | 194 if not old_api_endpoint: |
| 200 logging.warn( | 195 logging.warn( |
| 201 'No /data url passed, will write to builder_alerts.json. JSON posted ' | 196 'No /data url passed, will write to builder_alerts.json. JSON posted ' |
| 202 'to new API endpoints will be written to builder_alerts_<tree>.json ' | 197 'to new API endpoints will be written to builder_alerts_<tree>.json ' |
| 203 'files.') | 198 'files.') |
| 204 | 199 |
| 205 if args.use_cache: | 200 if args.use_cache: |
| 206 requests_cache.install_cache('failure_stats') | 201 requests_cache.install_cache('failure_stats') |
| (...skipping 40 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 247 ' %s' % (master, builder, step, reason)) | 242 ' %s' % (master, builder, step, reason)) |
| 248 | 243 |
| 249 old_alerts[alert_key] = alert | 244 old_alerts[alert_key] = alert |
| 250 | 245 |
| 251 latest_builder_info = {} | 246 latest_builder_info = {} |
| 252 stale_builder_alerts = [] | 247 stale_builder_alerts = [] |
| 253 missing_masters = [] | 248 missing_masters = [] |
| 254 alerts = [] | 249 alerts = [] |
| 255 suspected_cls = [] | 250 suspected_cls = [] |
| 256 | 251 |
| 257 logging.debug('Processing all masters via process pool') | |
| 258 pool = multiprocessing.Pool(processes=args.processes) | 252 pool = multiprocessing.Pool(processes=args.processes) |
| 259 master_datas = pool.map(SubProcess(cache, old_alerts, args.builder_filter, | 253 master_datas = pool.map(SubProcess(cache, old_alerts, args.builder_filter, |
| 260 args.jobs), master_urls) | 254 args.jobs), master_urls) |
| 261 logging.debug('Closing all threads in master process pool') | |
| 262 pool.close() | 255 pool.close() |
| 263 pool.join() | 256 pool.join() |
| 264 logging.debug('Joined all threads in master process pool') | |
| 265 | 257 |
| 266 for data in master_datas: | 258 for data in master_datas: |
| 267 # TODO(ojan): We should put an alert in the JSON for this master so | 259 # TODO(ojan): We should put an alert in the JSON for this master so |
| 268 # we can show that the master is down in the sheriff-o-matic UI. | 260 # we can show that the master is down in the sheriff-o-matic UI. |
| 269 if not data[0]: | 261 if not data[0]: |
| 270 missing_masters.extend([data[3]]) | 262 missing_masters.extend([data[3]]) |
| 271 continue | 263 continue |
| 272 alerts.extend(data[0]) | 264 alerts.extend(data[0]) |
| 273 latest_builder_info.update(data[1]) | 265 latest_builder_info.update(data[1]) |
| 274 stale_builder_alerts.extend(data[2]) | 266 stale_builder_alerts.extend(data[2]) |
| (...skipping 70 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 345 resp.status_code, resp.reason, resp.content) | 337 resp.status_code, resp.reason, resp.content) |
| 346 ret = False | 338 ret = False |
| 347 else: | 339 else: |
| 348 with open('builder_alerts_%s.json' % tree, 'w') as f: | 340 with open('builder_alerts_%s.json' % tree, 'w') as f: |
| 349 f.write(json.dumps(json_data, indent=1)) | 341 f.write(json.dumps(json_data, indent=1)) |
| 350 else: | 342 else: |
| 351 logging.error( | 343 logging.error( |
| 352 '--crbug-service-account was not specified, can not get crbug issues') | 344 '--crbug-service-account was not specified, can not get crbug issues') |
| 353 ret = False | 345 ret = False |
| 354 | 346 |
| 355 logging.debug('Returning from inner loop') | |
| 356 return ret | 347 return ret |
| 357 | 348 |
| 358 | 349 |
| 359 def main(args): | 350 def main(args): |
| 360 parser = argparse.ArgumentParser(prog='run.py %s' % __package__) | 351 parser = argparse.ArgumentParser(prog='run.py %s' % __package__) |
| 361 parser.add_argument('data_url', action='store', nargs='*') # Deprecated | 352 parser.add_argument('data_url', action='store', nargs='*') # Deprecated |
| 362 parser.add_argument('--use-cache', action='store_true') | 353 parser.add_argument('--use-cache', action='store_true') |
| 363 parser.add_argument('--master-filter', action='store') | 354 parser.add_argument('--master-filter', action='store') |
| 364 parser.add_argument('--builder-filter', action='store') | 355 parser.add_argument('--builder-filter', action='store') |
| 365 parser.add_argument('--processes', default=PARALLEL_TASKS, action='store', | 356 parser.add_argument('--processes', default=PARALLEL_TASKS, action='store', |
| 366 type=int) | 357 type=int) |
| 367 parser.add_argument('--jobs', default=CONCURRENT_TASKS, action='store', | 358 parser.add_argument('--jobs', default=CONCURRENT_TASKS, action='store', |
| 368 type=int) | 359 type=int) |
| 369 logs.add_argparse_options(parser) | 360 logs.add_argparse_options(parser) |
| 370 outer_loop.add_argparse_options(parser) | 361 outer_loop.add_argparse_options(parser) |
| 371 | 362 |
| 372 ts_mon.add_argparse_options(parser) | |
| 373 parser.set_defaults( | |
| 374 ts_mon_target_type='task', | |
| 375 ts_mon_task_service_name='builder-alerts', | |
| 376 ts_mon_task_job_name='builder-alerts', | |
| 377 ) | |
| 378 | |
| 379 gatekeeper_json = os.path.join(build_scripts_dir, 'slave', 'gatekeeper.json') | 363 gatekeeper_json = os.path.join(build_scripts_dir, 'slave', 'gatekeeper.json') |
| 380 parser.add_argument('--gatekeeper', action='store', default=gatekeeper_json) | 364 parser.add_argument('--gatekeeper', action='store', default=gatekeeper_json) |
| 381 gatekeeper_trees_json = os.path.join(build_scripts_dir, 'slave', | 365 gatekeeper_trees_json = os.path.join(build_scripts_dir, 'slave', |
| 382 'gatekeeper_trees.json') | 366 'gatekeeper_trees.json') |
| 383 parser.add_argument('--gatekeeper-trees', action='store', | 367 parser.add_argument('--gatekeeper-trees', action='store', |
| 384 default=gatekeeper_trees_json) | 368 default=gatekeeper_trees_json) |
| 385 | 369 |
| 386 parser.add_argument('--findit-api-url', | 370 parser.add_argument('--findit-api-url', |
| 387 help='Query findit results from this url.') | 371 help='Query findit results from this url.') |
| 388 parser.add_argument('--crbug-service-account', | 372 parser.add_argument('--crbug-service-account', |
| 389 help='Path to a service account JSON file to be used to ' | 373 help='Path to a service account JSON file to be used to ' |
| 390 'search for relevant issues on crbug.com.') | 374 'search for relevant issues on crbug.com.') |
| 391 parser.add_argument('--api-endpoint-prefix', | 375 parser.add_argument('--api-endpoint-prefix', |
| 392 help='Endpoint prefix for posting alerts. Old API ' | 376 help='Endpoint prefix for posting alerts. Old API ' |
| 393 'endpoint will be formed by adding value specified ' | 377 'endpoint will be formed by adding value specified ' |
| 394 'in --old-api-path to the prefix, new API endpoints ' | 378 'in --old-api-path to the prefix, new API endpoints ' |
| 395 'will be formed by adding ' | 379 'will be formed by adding ' |
| 396 '/api/v1/alerts/<tree_name>.') | 380 '/api/v1/alerts/<tree_name>.') |
| 397 parser.add_argument('--old-api-path', | 381 parser.add_argument('--old-api-path', |
| 398 help='Path to be appended to --api-endpoint-prefix to ' | 382 help='Path to be appended to --api-endpoint-prefix to ' |
| 399 'form old API endpoint.') | 383 'form old API endpoint.') |
| 400 | 384 |
| 401 args = parser.parse_args(args) | 385 args = parser.parse_args(args) |
| 402 logs.process_argparse_options(args) | 386 logs.process_argparse_options(args) |
| 403 loop_args = outer_loop.process_argparse_options(args) | 387 loop_args = outer_loop.process_argparse_options(args) |
| 404 ts_mon.process_argparse_options(args) | |
| 405 | 388 |
| 406 # TODO(sergiyb): Remove support for data_url when builder_alerts recipes are | 389 # TODO(sergiyb): Remove support for data_url when builder_alerts recipes are |
| 407 # updated and using new syntax to call this script. | 390 # updated and using new syntax to call this script. |
| 408 if args.data_url: | 391 if args.data_url: |
| 409 if (len(args.data_url) == 1 and args.data_url[0].endswith('alerts') and | 392 if (len(args.data_url) == 1 and args.data_url[0].endswith('alerts') and |
| 410 not args.api_endpoint_prefix and not args.old_api_path): | 393 not args.api_endpoint_prefix and not args.old_api_path): |
| 411 logging.warn( | 394 logging.warn( |
| 412 'You are using positional argument to specify URL to post updates ' | 395 'You are using positional argument to specify URL to post updates ' |
| 413 'to. Please use --api-endpoint-prefix and --old-api-path instead.') | 396 'to. Please use --api-endpoint-prefix and --old-api-path instead.') |
| 414 slash_index = args.data_url[0].rindex('/') | 397 slash_index = args.data_url[0].rindex('/') |
| (...skipping 15 matching lines...) Expand all Loading... |
| 430 def filter(record): | 413 def filter(record): |
| 431 if record.levelno == logging.INFO: | 414 if record.levelno == logging.INFO: |
| 432 return False | 415 return False |
| 433 return True | 416 return True |
| 434 logging.getLogger('requests.packages.urllib3.connectionpool').addFilter( | 417 logging.getLogger('requests.packages.urllib3.connectionpool').addFilter( |
| 435 _ConnectionpoolFilter()) | 418 _ConnectionpoolFilter()) |
| 436 | 419 |
| 437 def outer_loop_iteration(): | 420 def outer_loop_iteration(): |
| 438 return inner_loop(args) | 421 return inner_loop(args) |
| 439 | 422 |
| 440 logging.debug('Starting outer loop') | |
| 441 loop_results = outer_loop.loop( | 423 loop_results = outer_loop.loop( |
| 442 task=outer_loop_iteration, | 424 task=outer_loop_iteration, |
| 443 sleep_timeout=lambda: 5, | 425 sleep_timeout=lambda: 5, |
| 444 **loop_args) | 426 **loop_args) |
| 445 logging.debug('Finished outer loop') | |
| 446 | 427 |
| 447 logging.debug('Flushing ts_mon starting') | |
| 448 ts_mon.flush() | |
| 449 logging.debug('Flushing ts_mon completed') | |
| 450 return 0 if loop_results.success else 1 | 428 return 0 if loop_results.success else 1 |
| 451 | 429 |
| 452 | 430 |
| 453 if __name__ == '__main__': | 431 if __name__ == '__main__': |
| 454 logging.debug('Started main') | 432 sys.exit(main(sys.argv[1:])) |
| 455 retcode = main(sys.argv[1:]) | |
| 456 current_thread_descriptions = [t.name + (' (daemon)' if t.isDaemon() else '') | |
| 457 for t in threading.enumerate() | |
| 458 if t is not threading.current_thread()] | |
| 459 logging.debug( | |
| 460 'Leaving main. Threads: %s', ', '.join(current_thread_descriptions)) | |
| 461 sys.exit(retcode) | |
| OLD | NEW |