| OLD | NEW |
| 1 # Copyright (c) 2015 The Chromium Authors. All rights reserved. | 1 # Copyright (c) 2015 The Chromium Authors. All rights reserved. |
| 2 # Use of this source code is governed by a BSD-style license that can be | 2 # Use of this source code is governed by a BSD-style license that can be |
| 3 # found in the LICENSE file. | 3 # found in the LICENSE file. |
| 4 | 4 |
| 5 """Functions specific to handle goma related info. | 5 """Functions specific to handle goma related info. |
| 6 """ | 6 """ |
| 7 | 7 |
| 8 import base64 | 8 import base64 |
| 9 import datetime | 9 import datetime |
| 10 import getpass | 10 import getpass |
| 11 import glob | 11 import glob |
| 12 import gzip | 12 import gzip |
| 13 import json | 13 import json |
| 14 import multiprocessing |
| 14 import os | 15 import os |
| 15 import re | 16 import re |
| 16 import shutil | 17 import shutil |
| 17 import socket | 18 import socket |
| 19 import subprocess |
| 18 import sys | 20 import sys |
| 19 import tempfile | 21 import tempfile |
| 20 import time | 22 import time |
| 21 | 23 |
| 22 from common import chromium_utils | 24 from common import chromium_utils |
| 23 from slave import slave_utils | 25 from slave import slave_utils |
| 24 | 26 |
| 25 # The Google Cloud Storage bucket to store logs related to goma. | 27 # The Google Cloud Storage bucket to store logs related to goma. |
| 26 GOMA_LOG_GS_BUCKET = 'chrome-goma-log' | 28 GOMA_LOG_GS_BUCKET = 'chrome-goma-log' |
| 27 | 29 |
| 28 # Platform dependent location of run command. | 30 # Platform dependent location of run command. |
| 29 PLATFORM_RUN_CMD = { | 31 PLATFORM_RUN_CMD = { |
| 30 # os.name: run_cmd to use. | 32 # os.name: run_cmd to use. |
| 31 'nt': 'C:\\infra-python\\run.py', | 33 'nt': 'C:\\infra-python\\run.py', |
| 32 'posix': '/opt/infra-python/run.py', | 34 'posix': '/opt/infra-python/run.py', |
| 33 } | 35 } |
| 34 | 36 |
| 35 TIMESTAMP_PATTERN = re.compile('(\d{4}/\d{2}/\d{2} \d{2}:\d{2}:\d{2})') | 37 TIMESTAMP_PATTERN = re.compile('(\d{4}/\d{2}/\d{2} \d{2}:\d{2}:\d{2})') |
| 36 TIMESTAMP_FORMAT = '%Y/%m/%d %H:%M:%S' | 38 TIMESTAMP_FORMAT = '%Y/%m/%d %H:%M:%S' |
| 37 | 39 |
| 40 # Define a bunch of directory paths (same as bot_update.py) |
| 41 CURRENT_DIR = os.path.abspath(os.getcwd()) |
| 42 BUILDER_DIR = os.path.dirname(CURRENT_DIR) |
| 43 SLAVE_DIR = os.path.dirname(BUILDER_DIR) |
| 44 # GOMA_CACHE_DIR used for caching long-term data. |
| 45 |
| 46 DEFAULT_GOMA_CACHE_DIR = os.path.join(SLAVE_DIR, 'goma_cache') |
| 47 # Path of the scripts/slave/ checkout on the slave, found by looking at the |
| 48 # current compile.py script's path's dirname(). |
| 49 SLAVE_SCRIPTS_DIR = os.path.dirname(os.path.abspath(__file__)) |
| 50 # Path of the build/ checkout on the slave, found relative to the |
| 51 # scripts/slave/ directory. |
| 52 BUILD_DIR = os.path.dirname(os.path.dirname(SLAVE_SCRIPTS_DIR)) |
| 53 |
| 54 |
| 38 | 55 |
| 39 def GetShortHostname(): | 56 def GetShortHostname(): |
| 40 """Get this machine's short hostname in lower case.""" | 57 """Get this machine's short hostname in lower case.""" |
| 41 return socket.gethostname().split('.')[0].lower() | 58 return socket.gethostname().split('.')[0].lower() |
| 42 | 59 |
| 43 | 60 |
| 44 def GetGomaTmpDirectory(): | 61 def GetGomaTmpDirectory(): |
| 45 """Get goma's temp directory.""" | 62 """Get goma's temp directory.""" |
| 46 candidates = ['GOMA_TMP_DIR', 'TEST_TMPDIR', 'TMPDIR', 'TMP'] | 63 candidates = ['GOMA_TMP_DIR', 'TEST_TMPDIR', 'TMPDIR', 'TMP'] |
| 47 for candidate in candidates: | 64 for candidate in candidates: |
| (...skipping 300 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 348 retcode = chromium_utils.RunCommand( | 365 retcode = chromium_utils.RunCommand( |
| 349 cmd, filter_obj=cmd_filter, | 366 cmd, filter_obj=cmd_filter, |
| 350 max_time=30) | 367 max_time=30) |
| 351 if retcode: | 368 if retcode: |
| 352 print('Execution of send_ts_mon_values failed with code %s' | 369 print('Execution of send_ts_mon_values failed with code %s' |
| 353 % retcode) | 370 % retcode) |
| 354 print '\n'.join(cmd_filter.text) | 371 print '\n'.join(cmd_filter.text) |
| 355 | 372 |
| 356 except Exception as ex: | 373 except Exception as ex: |
| 357 print('error while sending ts mon json_file=%s: %s' % (json_file, ex)) | 374 print('error while sending ts mon json_file=%s: %s' % (json_file, ex)) |
| 375 |
| 376 |
| 377 def UpdateWindowsEnvironment(envfile_dir, env): |
| 378 """Update windows environment in environment.{x86,x64}. |
| 379 |
| 380 Args: |
| 381 envfile_dir: a directory name environment.{x86,x64} are stored. |
| 382 env: an instance of EchoDict that represents environment. |
| 383 """ |
| 384 # envvars_to_save come from _ExtractImportantEnvironment in |
| 385 # https://chromium.googlesource.com/external/gyp/+/\ |
| 386 # master/pylib/gyp/msvs_emuation.py |
| 387 # You must update this when the original code is updated. |
| 388 envvars_to_save = ( |
| 389 'goma_.*', # TODO(scottmg): This is ugly, but needed for goma. |
| 390 'include', |
| 391 'lib', |
| 392 'libpath', |
| 393 'path', |
| 394 'pathext', |
| 395 'systemroot', |
| 396 'temp', |
| 397 'tmp', |
| 398 ) |
| 399 env_to_store = {} |
| 400 for envvar in envvars_to_save: |
| 401 compiled = re.compile(envvar, re.IGNORECASE) |
| 402 for key in env.overrides: |
| 403 if compiled.match(key): |
| 404 if envvar == 'path': |
| 405 env_to_store[key] = (os.path.dirname(sys.executable) + |
| 406 os.pathsep + env[key]) |
| 407 else: |
| 408 env_to_store[key] = env[key] |
| 409 |
| 410 if not env_to_store: |
| 411 return |
| 412 |
| 413 nul = '\0' |
| 414 for arch in ['x86', 'x64']: |
| 415 path = os.path.join(envfile_dir, 'environment.%s' % arch) |
| 416 print '%s will be updated with %s.' % (path, env_to_store) |
| 417 env_in_file = {} |
| 418 with open(path) as f: |
| 419 for entry in f.read().split(nul): |
| 420 if not entry: |
| 421 continue |
| 422 key, value = entry.split('=', 1) |
| 423 env_in_file[key] = value |
| 424 env_in_file.update(env_to_store) |
| 425 with open(path, 'wb') as f: |
| 426 f.write(nul.join(['%s=%s' % (k, v) for k, v in env_in_file.iteritems()])) |
| 427 f.write(nul * 2) |
| 428 |
| 429 |
| 430 def goma_setup(options, env): |
| 431 """Sets up goma if necessary. |
| 432 |
| 433 If using the Goma compiler, first call goma_ctl to ensure the proxy is |
| 434 available, and returns (True, instance of cloudtail subprocess). |
| 435 If it failed to start up compiler_proxy, modify options.compiler and |
| 436 options.goma_dir and returns (False, None) |
| 437 |
| 438 Args: |
| 439 options (Option): options for goma |
| 440 env (dict): env for compiler_proxy |
| 441 |
| 442 Returns: |
| 443 (bool, subprocess.Popen): |
| 444 first element represents whether compiler_proxy starts successfully |
| 445 second element is subprocess instance of cloudtail |
| 446 |
| 447 """ |
| 448 if options.compiler not in ('goma', 'goma-clang'): |
| 449 # Unset goma_dir to make sure we'll not use goma. |
| 450 options.goma_dir = None |
| 451 return False, None |
| 452 |
| 453 hostname = GetShortHostname() |
| 454 # HACK(shinyak, yyanagisawa, goma): Windows NO_NACL_GOMA (crbug.com/390764) |
| 455 # Building NaCl untrusted code using goma brings large performance |
| 456 # improvement but it sometimes cause build failure by race condition. |
| 457 # Let me enable goma build on goma canary buildslaves to confirm the issue |
| 458 # has been fixed by a workaround. |
| 459 # vm*-m4 are trybots. build*-m1 and vm*-m1 are all goma canary bots. |
| 460 if hostname in ['build28-m1', 'build58-m1', 'vm191-m1', 'vm480-m1', |
| 461 'vm820-m1', 'vm821-m1', 'vm848-m1']: |
| 462 env['NO_NACL_GOMA'] = 'false' |
| 463 |
| 464 if options.goma_fail_fast: |
| 465 # startup fails when initial ping failed. |
| 466 env['GOMA_FAIL_FAST'] = 'true' |
| 467 else: |
| 468 # If a network error continues 30 minutes, compiler_proxy make the compile |
| 469 # failed. When people use goma, they expect using goma is faster than |
| 470 # compile locally. If goma cannot guarantee that, let it make compile |
| 471 # as error. |
| 472 env['GOMA_ALLOWED_NETWORK_ERROR_DURATION'] = '1800' |
| 473 |
| 474 # HACK(yyanagisawa): reduce GOMA_BURST_MAX_PROCS crbug.com/592306 |
| 475 # Recently, I sometimes see buildbot slave time out, one possibility I come |
| 476 # up with is burst mode use up resource. |
| 477 # Let me temporary set small values to GOMA_BURST_MAX_PROCS to confirm |
| 478 # the possibility is true or false. |
| 479 max_subprocs = '3' |
| 480 max_heavy_subprocs = '1' |
| 481 number_of_processors = 0 |
| 482 try: |
| 483 number_of_processors = multiprocessing.cpu_count() |
| 484 except NotImplementedError: |
| 485 print 'cpu_count() is not implemented, using default value.' |
| 486 number_of_processors = 1 |
| 487 if number_of_processors > 3: |
| 488 max_subprocs = str(number_of_processors - 1) |
| 489 max_heavy_subprocs = str(number_of_processors / 2) |
| 490 env['GOMA_BURST_MAX_SUBPROCS'] = max_subprocs |
| 491 env['GOMA_BURST_MAX_SUBPROCS_LOW'] = max_subprocs |
| 492 env['GOMA_BURST_MAX_SUBPROCS_HEAVY'] = max_heavy_subprocs |
| 493 |
| 494 # Caches CRLs in GOMA_CACHE_DIR. |
| 495 # Since downloading CRLs is usually slow, caching them may improves |
| 496 # compiler_proxy start time. |
| 497 if not os.path.exists(options.goma_cache_dir): |
| 498 os.mkdir(options.goma_cache_dir, 0700) |
| 499 env['GOMA_CACHE_DIR'] = options.goma_cache_dir |
| 500 |
| 501 # Enable DepsCache. DepsCache caches the list of files to send goma server. |
| 502 # This will greatly improve build speed when cache is warmed. |
| 503 # The cache file is stored in the target output directory. |
| 504 env['GOMA_DEPS_CACHE_DIR'] = ( |
| 505 options.goma_deps_cache_dir or options.target_output_dir) |
| 506 |
| 507 if not env.get('GOMA_HERMETIC'): |
| 508 env['GOMA_HERMETIC'] = options.goma_hermetic |
| 509 if options.goma_enable_remote_link: |
| 510 env['GOMA_ENABLE_REMOTE_LINK'] = options.goma_enable_remote_link |
| 511 if options.goma_store_local_run_output: |
| 512 env['GOMA_STORE_LOCAL_RUN_OUTPUT'] = options.goma_store_local_run_output |
| 513 if options.goma_enable_compiler_info_cache: |
| 514 # Will be stored in GOMA_CACHE_DIR. |
| 515 env['GOMA_COMPILER_INFO_CACHE_FILE'] = 'goma-compiler-info.cache' |
| 516 |
| 517 if options.build_data_dir: |
| 518 env['GOMA_DUMP_STATS_FILE'] = os.path.join(options.build_data_dir, |
| 519 'goma_stats_proto') |
| 520 |
| 521 # goma is requested. |
| 522 goma_key = os.path.join(options.goma_dir, 'goma.key') |
| 523 if os.path.exists(goma_key): |
| 524 env['GOMA_API_KEY_FILE'] = goma_key |
| 525 if options.goma_service_account_json_file: |
| 526 env['GOMA_SERVICE_ACCOUNT_JSON_FILE'] = \ |
| 527 options.goma_service_account_json_file |
| 528 if chromium_utils.IsWindows(): |
| 529 env['GOMA_RPC_EXTRA_PARAMS'] = '?win' |
| 530 |
| 531 # HACK(yyanagisawa): update environment files on |env| update. |
| 532 # For compiling on Windows, environment in environment files are used. |
| 533 # It means even if enviroment such as GOMA_DISABLED is updated in |
| 534 # compile.py, the update will be ignored. |
| 535 # We need to update environment files to reflect the update. |
| 536 if chromium_utils.IsWindows() and NeedEnvFileUpdateOnWin(env): |
| 537 print 'Updating environment.{x86,x64} files.' |
| 538 UpdateWindowsEnvironment(options.target_output_dir, env) |
| 539 |
| 540 |
| 541 goma_start_command = ['restart'] |
| 542 goma_ctl_cmd = [sys.executable, |
| 543 os.path.join(options.goma_dir, 'goma_ctl.py')] |
| 544 result = chromium_utils.RunCommand(goma_ctl_cmd + goma_start_command, env=env) |
| 545 if not result: |
| 546 # goma started sucessfully. |
| 547 # Making cloudtail to upload the latest log. |
| 548 # TODO(yyanagisawa): install cloudtail from CIPD. |
| 549 cloudtail_path = '/opt/infra-tools/cloudtail' |
| 550 if chromium_utils.IsWindows(): |
| 551 cloudtail_path = 'C:\\infra-tools\\cloudtail' |
| 552 try: |
| 553 cloudtail_proc = subprocess.Popen( |
| 554 [cloudtail_path, 'tail', '--log-id', 'goma_compiler_proxy', '--path', |
| 555 GetLatestGomaCompilerProxyInfo()]) |
| 556 except Exception as e: |
| 557 print 'failed to invoke cloudtail: %s' % e |
| 558 return True, None |
| 559 return True, cloudtail_proc |
| 560 |
| 561 if options.goma_jsonstatus: |
| 562 chromium_utils.RunCommand( |
| 563 goma_ctl_cmd + ['jsonstatus', options.goma_jsonstatus], env=env) |
| 564 SendGomaTsMon(options.goma_jsonstatus, -1) |
| 565 |
| 566 # Try to stop compiler_proxy so that it flushes logs and stores |
| 567 # GomaStats. |
| 568 if options.build_data_dir: |
| 569 env['GOMACTL_CRASH_REPORT_ID_FILE'] = os.path.join(options.build_data_dir, |
| 570 'crash_report_id_file') |
| 571 chromium_utils.RunCommand(goma_ctl_cmd + ['stop'], env=env) |
| 572 |
| 573 override_gsutil = None |
| 574 if options.gsutil_py_path: |
| 575 override_gsutil = [sys.executable, options.gsutil_py_path] |
| 576 |
| 577 # Upload compiler_proxy.INFO to investigate the reason of compiler_proxy |
| 578 # start-up failure. |
| 579 UploadGomaCompilerProxyInfo(override_gsutil=override_gsutil) |
| 580 # Upload GomaStats to make it monitored. |
| 581 if env.get('GOMA_DUMP_STATS_FILE'): |
| 582 SendGomaStats(env['GOMA_DUMP_STATS_FILE'], |
| 583 env.get('GOMACTL_CRASH_REPORT_ID_FILE'), |
| 584 options.build_data_dir) |
| 585 |
| 586 if options.goma_disable_local_fallback: |
| 587 print 'error: failed to start goma; fallback has been disabled' |
| 588 raise Exception('failed to start goma') |
| 589 |
| 590 print 'warning: failed to start goma. falling back to non-goma' |
| 591 # Drop goma from options.compiler |
| 592 options.compiler = options.compiler.replace('goma-', '') |
| 593 if options.compiler == 'goma': |
| 594 options.compiler = None |
| 595 # Reset options.goma_dir. |
| 596 options.goma_dir = None |
| 597 env['GOMA_DISABLED'] = '1' |
| 598 return False, None |
| 599 |
| 600 |
| 601 def goma_teardown(options, env, exit_status, cloudtail_proc): |
| 602 """Tears down goma if necessary. """ |
| 603 if (options.compiler in ('goma', 'goma-clang') and |
| 604 options.goma_dir): |
| 605 override_gsutil = None |
| 606 if options.gsutil_py_path: |
| 607 override_gsutil = [sys.executable, options.gsutil_py_path] |
| 608 |
| 609 # If goma compiler_proxy crashes during the build, there could be crash |
| 610 # dump. |
| 611 if options.build_data_dir: |
| 612 env['GOMACTL_CRASH_REPORT_ID_FILE'] = os.path.join(options.build_data_dir, |
| 613 'crash_report_id_file') |
| 614 goma_ctl_cmd = [sys.executable, |
| 615 os.path.join(options.goma_dir, 'goma_ctl.py')] |
| 616 if options.goma_jsonstatus: |
| 617 chromium_utils.RunCommand( |
| 618 goma_ctl_cmd + ['jsonstatus', options.goma_jsonstatus], env=env) |
| 619 SendGomaTsMon(options.goma_jsonstatus, exit_status) |
| 620 # Always stop the proxy for now to allow in-place update. |
| 621 chromium_utils.RunCommand(goma_ctl_cmd + ['stop'], env=env) |
| 622 UploadGomaCompilerProxyInfo(override_gsutil=override_gsutil) |
| 623 if env.get('GOMA_DUMP_STATS_FILE'): |
| 624 SendGomaStats(env['GOMA_DUMP_STATS_FILE'], |
| 625 env.get('GOMACTL_CRASH_REPORT_ID_FILE'), |
| 626 options.build_data_dir) |
| 627 if cloudtail_proc: |
| 628 cloudtail_proc.terminate() |
| 629 cloudtail_proc.wait() |
| 630 |
| 631 |
| 632 def determine_goma_jobs(): |
| 633 # We would like to speed up build on Windows a bit, since it is slowest. |
| 634 number_of_processors = 0 |
| 635 try: |
| 636 number_of_processors = multiprocessing.cpu_count() |
| 637 except NotImplementedError: |
| 638 print 'cpu_count() is not implemented, using default value 50.' |
| 639 return 50 |
| 640 |
| 641 assert number_of_processors > 0 |
| 642 |
| 643 # When goma is used, 10 * number_of_processors is basically good in |
| 644 # various situations according to our measurement. Build speed won't |
| 645 # be improved if -j is larger than that. |
| 646 # |
| 647 # Since Mac had process number limitation before, we had to set |
| 648 # the upper limit to 50. Now that the process number limitation is 2000, |
| 649 # so we would be able to use 10 * number_of_processors. |
| 650 # For the safety, we'd like to set the upper limit to 200. |
| 651 # |
| 652 # Note that currently most try-bot build slaves have 8 processors. |
| 653 if chromium_utils.IsMac() or chromium_utils.IsWindows(): |
| 654 return min(10 * number_of_processors, 200) |
| 655 |
| 656 # For Linux, we also would like to use 10 * cpu. However, not sure |
| 657 # backend resource is enough, so let me set Linux and Linux x64 builder |
| 658 # only for now. |
| 659 hostname = GetShortHostname() |
| 660 if hostname in ( |
| 661 ['build14-m1', 'build48-m1'] + |
| 662 # Also increasing cpus for v8/blink trybots. |
| 663 ['build%d-m4' % x for x in xrange(45, 48)] + |
| 664 # Also increasing cpus for LTO buildbots. |
| 665 ['slave%d-c1' % x for x in [20, 33] + range(78, 108)]): |
| 666 return min(10 * number_of_processors, 200) |
| 667 |
| 668 return 50 |
| 669 |
| 670 |
| 671 def NeedEnvFileUpdateOnWin(env): |
| 672 """Returns true if environment file need to be updated.""" |
| 673 # Following GOMA_* are applied to compiler_proxy not gomacc, |
| 674 # you do not need to update environment files. |
| 675 ignore_envs = ( |
| 676 'GOMA_API_KEY_FILE', |
| 677 'GOMA_DEPS_CACHE_DIR', |
| 678 'GOMA_HERMETIC', |
| 679 'GOMA_RPC_EXTRA_PARAMS', |
| 680 'GOMA_ALLOWED_NETWORK_ERROR_DURATION' |
| 681 ) |
| 682 for key in env.overrides: |
| 683 if key not in ignore_envs: |
| 684 return True |
| 685 return False |
| 686 |
| 687 |
| 688 def construct_optionparser(option_parser): |
| 689 option_parser.add_option('--goma-cache-dir', |
| 690 default=DEFAULT_GOMA_CACHE_DIR, |
| 691 help='specify goma cache directory') |
| 692 option_parser.add_option('--goma-deps-cache-dir', |
| 693 help='specify goma deps cache directory') |
| 694 option_parser.add_option('--goma-hermetic', default='error', |
| 695 help='Set goma hermetic mode') |
| 696 option_parser.add_option('--goma-enable-remote-link', default=None, |
| 697 help='Enable goma remote link.') |
| 698 option_parser.add_option('--goma-enable-compiler-info-cache', |
| 699 action='store_true', |
| 700 help='Enable goma CompilerInfo cache') |
| 701 option_parser.add_option('--goma-store-local-run-output', default=None, |
| 702 help='Store local run output to goma servers.') |
| 703 option_parser.add_option('--goma-fail-fast', action='store_true') |
| 704 option_parser.add_option('--goma-disable-local-fallback', action='store_true') |
| 705 option_parser.add_option('--goma-jsonstatus', |
| 706 help='Specify a file to dump goma_ctl jsonstatus.') |
| 707 option_parser.add_option('--goma-service-account-json-file', |
| 708 help='Specify a file containing goma service account' |
| 709 ' credentials') |
| 710 option_parser.add_option('--build-data-dir', action='store', |
| 711 help='specify a build data directory.') |
| 712 |
| 713 if not option_parser.has_option('--goma-dir'): |
| 714 option_parser.add_option('--goma-dir', |
| 715 default=os.path.join(BUILD_DIR, 'goma'), |
| 716 help='specify goma directory') |
| 717 |
| 718 if not option_parser.has_option('--goma-jobs'): |
| 719 option_parser.add_option('--goma-jobs', default=None, |
| 720 help='The number of jobs for ninja -j.') |
| 721 |
| 722 if not option_parser.has_option('--gsutil-py-path'): |
| 723 option_parser.add_option('--gsutil-py-path', |
| 724 help='Specify path to gsutil.py script.') |
| 725 |
| 726 if not option_parser.has_option('--compiler'): |
| 727 option_parser.add_option('--compiler', default=None, |
| 728 help='specify alternative compiler (e.g. clang)') |
| 729 |
| 730 return option_parser |
| OLD | NEW |