| OLD | NEW |
| 1 #!/usr/bin/env python | 1 #!/usr/bin/env python |
| 2 # Copyright (c) 2013 The Chromium Authors. All rights reserved. | 2 # Copyright (c) 2013 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 """Performance Test Bisect Tool | 6 """Performance Test Bisect Tool |
| 7 | 7 |
| 8 This script bisects a series of changelists using binary search. It starts at | 8 This script bisects a series of changelists using binary search. It starts at |
| 9 a bad revision where a performance metric has regressed, and asks for a last | 9 a bad revision where a performance metric has regressed, and asks for a last |
| 10 known-good revision. It will then binary search across this revision range by | 10 known-good revision. It will then binary search across this revision range by |
| (...skipping 34 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 45 import shlex | 45 import shlex |
| 46 import shutil | 46 import shutil |
| 47 import StringIO | 47 import StringIO |
| 48 import sys | 48 import sys |
| 49 import time | 49 import time |
| 50 import zipfile | 50 import zipfile |
| 51 | 51 |
| 52 sys.path.append(os.path.join(os.path.dirname(__file__), 'telemetry')) | 52 sys.path.append(os.path.join(os.path.dirname(__file__), 'telemetry')) |
| 53 | 53 |
| 54 from auto_bisect import bisect_utils | 54 from auto_bisect import bisect_utils |
| 55 from auto_bisect import builder |
| 55 from auto_bisect import math_utils | 56 from auto_bisect import math_utils |
| 56 from auto_bisect import post_perf_builder_job as bisect_builder | 57 from auto_bisect import post_perf_builder_job as bisect_builder |
| 57 from auto_bisect import source_control as source_control_module | 58 from auto_bisect import source_control as source_control_module |
| 58 from auto_bisect import ttest | 59 from auto_bisect import ttest |
| 59 from telemetry.util import cloud_storage | 60 from telemetry.util import cloud_storage |
| 60 | 61 |
| 61 # Below is the map of "depot" names to information about each depot. Each depot | 62 # Below is the map of "depot" names to information about each depot. Each depot |
| 62 # is a repository, and in the process of bisecting, revision ranges in these | 63 # is a repository, and in the process of bisecting, revision ranges in these |
| 63 # repositories may also be bisected. | 64 # repositories may also be bisected. |
| 64 # | 65 # |
| (...skipping 79 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 144 'svn': 'http://skia.googlecode.com/svn/trunk/gyp', | 145 'svn': 'http://skia.googlecode.com/svn/trunk/gyp', |
| 145 'depends': None, | 146 'depends': None, |
| 146 'from': ['chromium'], | 147 'from': ['chromium'], |
| 147 'viewvc': 'https://code.google.com/p/skia/source/detail?r=', | 148 'viewvc': 'https://code.google.com/p/skia/source/detail?r=', |
| 148 'deps_var': 'None' | 149 'deps_var': 'None' |
| 149 } | 150 } |
| 150 } | 151 } |
| 151 | 152 |
| 152 DEPOT_NAMES = DEPOT_DEPS_NAME.keys() | 153 DEPOT_NAMES = DEPOT_DEPS_NAME.keys() |
| 153 | 154 |
| 154 CROS_SDK_PATH = os.path.join('..', 'cros', 'chromite', 'bin', 'cros_sdk') | |
| 155 CROS_CHROMEOS_PATTERN = 'chromeos-base/chromeos-chrome' | 155 CROS_CHROMEOS_PATTERN = 'chromeos-base/chromeos-chrome' |
| 156 CROS_TEST_KEY_PATH = os.path.join('..', 'cros', 'chromite', 'ssh_keys', | |
| 157 'testing_rsa') | |
| 158 CROS_SCRIPT_KEY_PATH = os.path.join('..', 'cros', 'src', 'scripts', | |
| 159 'mod_for_test_scripts', 'ssh_keys', | |
| 160 'testing_rsa') | |
| 161 | 156 |
| 162 # Possible return values from BisectPerformanceMetrics.SyncBuildAndRunRevision. | 157 # Possible return values from BisectPerformanceMetrics.SyncBuildAndRunRevision. |
| 163 BUILD_RESULT_SUCCEED = 0 | 158 BUILD_RESULT_SUCCEED = 0 |
| 164 BUILD_RESULT_FAIL = 1 | 159 BUILD_RESULT_FAIL = 1 |
| 165 BUILD_RESULT_SKIPPED = 2 | 160 BUILD_RESULT_SKIPPED = 2 |
| 166 | 161 |
| 167 # Maximum time in seconds to wait after posting build request to tryserver. | 162 # Maximum time in seconds to wait after posting build request to tryserver. |
| 168 # TODO: Change these values based on the actual time taken by buildbots on | 163 # TODO: Change these values based on the actual time taken by buildbots on |
| 169 # the tryserver. | 164 # the tryserver. |
| 170 MAX_MAC_BUILD_TIME = 14400 | 165 MAX_MAC_BUILD_TIME = 14400 |
| (...skipping 266 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 437 for name in zf.namelist(): | 432 for name in zf.namelist(): |
| 438 if verbose: | 433 if verbose: |
| 439 print 'Extracting %s' % name | 434 print 'Extracting %s' % name |
| 440 zf.extract(name, output_dir) | 435 zf.extract(name, output_dir) |
| 441 if bisect_utils.IsMacHost(): | 436 if bisect_utils.IsMacHost(): |
| 442 # Restore permission bits. | 437 # Restore permission bits. |
| 443 os.chmod(os.path.join(output_dir, name), | 438 os.chmod(os.path.join(output_dir, name), |
| 444 zf.getinfo(name).external_attr >> 16L) | 439 zf.getinfo(name).external_attr >> 16L) |
| 445 | 440 |
| 446 | 441 |
| 447 def SetBuildSystemDefault(build_system, use_goma, goma_dir): | |
| 448 """Sets up any environment variables needed to build with the specified build | |
| 449 system. | |
| 450 | |
| 451 Args: | |
| 452 build_system: A string specifying build system. Currently only 'ninja' or | |
| 453 'make' are supported. | |
| 454 """ | |
| 455 if build_system == 'ninja': | |
| 456 gyp_var = os.getenv('GYP_GENERATORS', default='') | |
| 457 | |
| 458 if not gyp_var or not 'ninja' in gyp_var: | |
| 459 if gyp_var: | |
| 460 os.environ['GYP_GENERATORS'] = gyp_var + ',ninja' | |
| 461 else: | |
| 462 os.environ['GYP_GENERATORS'] = 'ninja' | |
| 463 | |
| 464 if bisect_utils.IsWindowsHost(): | |
| 465 os.environ['GYP_DEFINES'] = ('component=shared_library ' | |
| 466 'incremental_chrome_dll=1 ' | |
| 467 'disable_nacl=1 fastbuild=1 ' | |
| 468 'chromium_win_pch=0') | |
| 469 | |
| 470 elif build_system == 'make': | |
| 471 os.environ['GYP_GENERATORS'] = 'make' | |
| 472 else: | |
| 473 raise RuntimeError('%s build not supported.' % build_system) | |
| 474 | |
| 475 if use_goma: | |
| 476 os.environ['GYP_DEFINES'] = '%s %s' % (os.getenv('GYP_DEFINES', default=''), | |
| 477 'use_goma=1') | |
| 478 if goma_dir: | |
| 479 os.environ['GYP_DEFINES'] += ' gomadir=%s' % goma_dir | |
| 480 | |
| 481 | |
| 482 def BuildWithMake(threads, targets, build_type='Release'): | |
| 483 cmd = ['make', 'BUILDTYPE=%s' % build_type] | |
| 484 | |
| 485 if threads: | |
| 486 cmd.append('-j%d' % threads) | |
| 487 | |
| 488 cmd += targets | |
| 489 | |
| 490 return_code = bisect_utils.RunProcess(cmd) | |
| 491 | |
| 492 return not return_code | |
| 493 | |
| 494 | |
| 495 def BuildWithNinja(threads, targets, build_type='Release'): | |
| 496 cmd = ['ninja', '-C', os.path.join('out', build_type)] | |
| 497 | |
| 498 if threads: | |
| 499 cmd.append('-j%d' % threads) | |
| 500 | |
| 501 cmd += targets | |
| 502 | |
| 503 return_code = bisect_utils.RunProcess(cmd) | |
| 504 | |
| 505 return not return_code | |
| 506 | |
| 507 | |
| 508 def BuildWithVisualStudio(targets, build_type='Release'): | |
| 509 path_to_devenv = os.path.abspath( | |
| 510 os.path.join(os.environ['VS100COMNTOOLS'], '..', 'IDE', 'devenv.com')) | |
| 511 path_to_sln = os.path.join(os.getcwd(), 'chrome', 'chrome.sln') | |
| 512 cmd = [path_to_devenv, '/build', build_type, path_to_sln] | |
| 513 | |
| 514 for t in targets: | |
| 515 cmd.extend(['/Project', t]) | |
| 516 | |
| 517 return_code = bisect_utils.RunProcess(cmd) | |
| 518 | |
| 519 return not return_code | |
| 520 | |
| 521 | |
| 522 def WriteStringToFile(text, file_name): | 442 def WriteStringToFile(text, file_name): |
| 523 try: | 443 try: |
| 524 with open(file_name, 'wb') as f: | 444 with open(file_name, 'wb') as f: |
| 525 f.write(text) | 445 f.write(text) |
| 526 except IOError: | 446 except IOError: |
| 527 raise RuntimeError('Error writing to file [%s]' % file_name ) | 447 raise RuntimeError('Error writing to file [%s]' % file_name ) |
| 528 | 448 |
| 529 | 449 |
| 530 def ReadStringFromFile(file_name): | 450 def ReadStringFromFile(file_name): |
| 531 try: | 451 try: |
| 532 with open(file_name) as f: | 452 with open(file_name) as f: |
| 533 return f.read() | 453 return f.read() |
| 534 except IOError: | 454 except IOError: |
| 535 raise RuntimeError('Error reading file [%s]' % file_name ) | 455 raise RuntimeError('Error reading file [%s]' % file_name ) |
| 536 | 456 |
| 537 | 457 |
| 538 def ChangeBackslashToSlashInPatch(diff_text): | 458 def ChangeBackslashToSlashInPatch(diff_text): |
| 539 """Formats file paths in the given text to unix-style paths.""" | 459 """Formats file paths in the given text to unix-style paths.""" |
| 540 if diff_text: | 460 if diff_text: |
| 541 diff_lines = diff_text.split('\n') | 461 diff_lines = diff_text.split('\n') |
| 542 for i in range(len(diff_lines)): | 462 for i in range(len(diff_lines)): |
| 543 if (diff_lines[i].startswith('--- ') or | 463 if (diff_lines[i].startswith('--- ') or |
| 544 diff_lines[i].startswith('+++ ')): | 464 diff_lines[i].startswith('+++ ')): |
| 545 diff_lines[i] = diff_lines[i].replace('\\', '/') | 465 diff_lines[i] = diff_lines[i].replace('\\', '/') |
| 546 return '\n'.join(diff_lines) | 466 return '\n'.join(diff_lines) |
| 547 return None | 467 return None |
| 548 | 468 |
| 549 | 469 |
| 550 class Builder(object): | |
| 551 """Builder is used by the bisect script to build relevant targets and deploy. | |
| 552 """ | |
| 553 def __init__(self, opts): | |
| 554 """Performs setup for building with target build system. | |
| 555 | |
| 556 Args: | |
| 557 opts: Options parsed from command line. | |
| 558 """ | |
| 559 if bisect_utils.IsWindowsHost(): | |
| 560 if not opts.build_preference: | |
| 561 opts.build_preference = 'msvs' | |
| 562 | |
| 563 if opts.build_preference == 'msvs': | |
| 564 if not os.getenv('VS100COMNTOOLS'): | |
| 565 raise RuntimeError( | |
| 566 'Path to visual studio could not be determined.') | |
| 567 else: | |
| 568 SetBuildSystemDefault(opts.build_preference, opts.use_goma, | |
| 569 opts.goma_dir) | |
| 570 else: | |
| 571 if not opts.build_preference: | |
| 572 if 'ninja' in os.getenv('GYP_GENERATORS', default=''): | |
| 573 opts.build_preference = 'ninja' | |
| 574 else: | |
| 575 opts.build_preference = 'make' | |
| 576 | |
| 577 SetBuildSystemDefault(opts.build_preference, opts.use_goma, opts.goma_dir) | |
| 578 | |
| 579 if not bisect_utils.SetupPlatformBuildEnvironment(opts): | |
| 580 raise RuntimeError('Failed to set platform environment.') | |
| 581 | |
| 582 @staticmethod | |
| 583 def FromOpts(opts): | |
| 584 builder = None | |
| 585 if opts.target_platform == 'cros': | |
| 586 builder = CrosBuilder(opts) | |
| 587 elif opts.target_platform == 'android': | |
| 588 builder = AndroidBuilder(opts) | |
| 589 elif opts.target_platform == 'android-chrome': | |
| 590 builder = AndroidChromeBuilder(opts) | |
| 591 else: | |
| 592 builder = DesktopBuilder(opts) | |
| 593 return builder | |
| 594 | |
| 595 def Build(self, depot, opts): | |
| 596 raise NotImplementedError() | |
| 597 | |
| 598 def GetBuildOutputDirectory(self, opts, src_dir=None): | |
| 599 """Returns the path to the build directory, relative to the checkout root. | |
| 600 | |
| 601 Assumes that the current working directory is the checkout root. | |
| 602 """ | |
| 603 src_dir = src_dir or 'src' | |
| 604 if opts.build_preference == 'ninja' or bisect_utils.IsLinuxHost(): | |
| 605 return os.path.join(src_dir, 'out') | |
| 606 if bisect_utils.IsMacHost(): | |
| 607 return os.path.join(src_dir, 'xcodebuild') | |
| 608 if bisect_utils.IsWindowsHost(): | |
| 609 return os.path.join(src_dir, 'build') | |
| 610 raise NotImplementedError('Unexpected platform %s' % sys.platform) | |
| 611 | |
| 612 | |
| 613 class DesktopBuilder(Builder): | |
| 614 """DesktopBuilder is used to build Chromium on linux/mac/windows.""" | |
| 615 def __init__(self, opts): | |
| 616 super(DesktopBuilder, self).__init__(opts) | |
| 617 | |
| 618 def Build(self, depot, opts): | |
| 619 """Builds chromium_builder_perf target using options passed into | |
| 620 the script. | |
| 621 | |
| 622 Args: | |
| 623 depot: Current depot being bisected. | |
| 624 opts: The options parsed from the command line. | |
| 625 | |
| 626 Returns: | |
| 627 True if build was successful. | |
| 628 """ | |
| 629 targets = ['chromium_builder_perf'] | |
| 630 | |
| 631 threads = None | |
| 632 if opts.use_goma: | |
| 633 threads = 64 | |
| 634 | |
| 635 build_success = False | |
| 636 if opts.build_preference == 'make': | |
| 637 build_success = BuildWithMake(threads, targets, opts.target_build_type) | |
| 638 elif opts.build_preference == 'ninja': | |
| 639 build_success = BuildWithNinja(threads, targets, opts.target_build_type) | |
| 640 elif opts.build_preference == 'msvs': | |
| 641 assert bisect_utils.IsWindowsHost(), 'msvs is only supported on Windows.' | |
| 642 build_success = BuildWithVisualStudio(targets, opts.target_build_type) | |
| 643 else: | |
| 644 assert False, 'No build system defined.' | |
| 645 return build_success | |
| 646 | |
| 647 | |
| 648 class AndroidBuilder(Builder): | |
| 649 """AndroidBuilder is used to build on android.""" | |
| 650 def __init__(self, opts): | |
| 651 super(AndroidBuilder, self).__init__(opts) | |
| 652 | |
| 653 def _GetTargets(self): | |
| 654 return ['chrome_shell_apk', 'cc_perftests_apk', 'android_tools'] | |
| 655 | |
| 656 def Build(self, depot, opts): | |
| 657 """Builds the android content shell and other necessary tools using options | |
| 658 passed into the script. | |
| 659 | |
| 660 Args: | |
| 661 depot: Current depot being bisected. | |
| 662 opts: The options parsed from the command line. | |
| 663 | |
| 664 Returns: | |
| 665 True if build was successful. | |
| 666 """ | |
| 667 threads = None | |
| 668 if opts.use_goma: | |
| 669 threads = 64 | |
| 670 | |
| 671 build_success = False | |
| 672 if opts.build_preference == 'ninja': | |
| 673 build_success = BuildWithNinja( | |
| 674 threads, self._GetTargets(), opts.target_build_type) | |
| 675 else: | |
| 676 assert False, 'No build system defined.' | |
| 677 | |
| 678 return build_success | |
| 679 | |
| 680 | |
| 681 class AndroidChromeBuilder(AndroidBuilder): | |
| 682 """AndroidBuilder is used to build on android's chrome.""" | |
| 683 def __init__(self, opts): | |
| 684 super(AndroidChromeBuilder, self).__init__(opts) | |
| 685 | |
| 686 def _GetTargets(self): | |
| 687 return AndroidBuilder._GetTargets(self) + ['chrome_apk'] | |
| 688 | |
| 689 | |
| 690 class CrosBuilder(Builder): | |
| 691 """CrosBuilder is used to build and image ChromeOS/Chromium when cros is the | |
| 692 target platform.""" | |
| 693 def __init__(self, opts): | |
| 694 super(CrosBuilder, self).__init__(opts) | |
| 695 | |
| 696 def ImageToTarget(self, opts): | |
| 697 """Installs latest image to target specified by opts.cros_remote_ip. | |
| 698 | |
| 699 Args: | |
| 700 opts: Program options containing cros_board and cros_remote_ip. | |
| 701 | |
| 702 Returns: | |
| 703 True if successful. | |
| 704 """ | |
| 705 try: | |
| 706 # Keys will most likely be set to 0640 after wiping the chroot. | |
| 707 os.chmod(CROS_SCRIPT_KEY_PATH, 0600) | |
| 708 os.chmod(CROS_TEST_KEY_PATH, 0600) | |
| 709 cmd = [CROS_SDK_PATH, '--', './bin/cros_image_to_target.py', | |
| 710 '--remote=%s' % opts.cros_remote_ip, | |
| 711 '--board=%s' % opts.cros_board, '--test', '--verbose'] | |
| 712 | |
| 713 return_code = bisect_utils.RunProcess(cmd) | |
| 714 return not return_code | |
| 715 except OSError: | |
| 716 return False | |
| 717 | |
| 718 def BuildPackages(self, opts, depot): | |
| 719 """Builds packages for cros. | |
| 720 | |
| 721 Args: | |
| 722 opts: Program options containing cros_board. | |
| 723 depot: The depot being bisected. | |
| 724 | |
| 725 Returns: | |
| 726 True if successful. | |
| 727 """ | |
| 728 cmd = [CROS_SDK_PATH] | |
| 729 | |
| 730 if depot != 'cros': | |
| 731 path_to_chrome = os.path.join(os.getcwd(), '..') | |
| 732 cmd += ['--chrome_root=%s' % path_to_chrome] | |
| 733 | |
| 734 cmd += ['--'] | |
| 735 | |
| 736 if depot != 'cros': | |
| 737 cmd += ['CHROME_ORIGIN=LOCAL_SOURCE'] | |
| 738 | |
| 739 cmd += ['BUILDTYPE=%s' % opts.target_build_type, './build_packages', | |
| 740 '--board=%s' % opts.cros_board] | |
| 741 return_code = bisect_utils.RunProcess(cmd) | |
| 742 | |
| 743 return not return_code | |
| 744 | |
| 745 def BuildImage(self, opts, depot): | |
| 746 """Builds test image for cros. | |
| 747 | |
| 748 Args: | |
| 749 opts: Program options containing cros_board. | |
| 750 depot: The depot being bisected. | |
| 751 | |
| 752 Returns: | |
| 753 True if successful. | |
| 754 """ | |
| 755 cmd = [CROS_SDK_PATH] | |
| 756 | |
| 757 if depot != 'cros': | |
| 758 path_to_chrome = os.path.join(os.getcwd(), '..') | |
| 759 cmd += ['--chrome_root=%s' % path_to_chrome] | |
| 760 | |
| 761 cmd += ['--'] | |
| 762 | |
| 763 if depot != 'cros': | |
| 764 cmd += ['CHROME_ORIGIN=LOCAL_SOURCE'] | |
| 765 | |
| 766 cmd += ['BUILDTYPE=%s' % opts.target_build_type, '--', './build_image', | |
| 767 '--board=%s' % opts.cros_board, 'test'] | |
| 768 | |
| 769 return_code = bisect_utils.RunProcess(cmd) | |
| 770 | |
| 771 return not return_code | |
| 772 | |
| 773 def Build(self, depot, opts): | |
| 774 """Builds targets using options passed into the script. | |
| 775 | |
| 776 Args: | |
| 777 depot: Current depot being bisected. | |
| 778 opts: The options parsed from the command line. | |
| 779 | |
| 780 Returns: | |
| 781 True if build was successful. | |
| 782 """ | |
| 783 if self.BuildPackages(opts, depot): | |
| 784 if self.BuildImage(opts, depot): | |
| 785 return self.ImageToTarget(opts) | |
| 786 return False | |
| 787 | |
| 788 | |
| 789 def _ParseRevisionsFromDEPSFileManually(deps_file_contents): | 470 def _ParseRevisionsFromDEPSFileManually(deps_file_contents): |
| 790 """Parses the vars section of the DEPS file with regex. | 471 """Parses the vars section of the DEPS file with regex. |
| 791 | 472 |
| 792 Args: | 473 Args: |
| 793 deps_file_contents: The DEPS file contents as a string. | 474 deps_file_contents: The DEPS file contents as a string. |
| 794 | 475 |
| 795 Returns: | 476 Returns: |
| 796 A dict in the format {depot:revision} if successful, otherwise None. | 477 A dict in the format {depot:revision} if successful, otherwise None. |
| 797 """ | 478 """ |
| 798 # We'll parse the "vars" section of the DEPS file. | 479 # We'll parse the "vars" section of the DEPS file. |
| (...skipping 365 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 1164 step_count += 1 | 845 step_count += 1 |
| 1165 if step_count: | 846 if step_count: |
| 1166 step_perf_time_avg = step_perf_time_avg / step_count | 847 step_perf_time_avg = step_perf_time_avg / step_count |
| 1167 step_build_time_avg = step_build_time_avg / step_count | 848 step_build_time_avg = step_build_time_avg / step_count |
| 1168 print | 849 print |
| 1169 print 'Average build time : %s' % datetime.timedelta( | 850 print 'Average build time : %s' % datetime.timedelta( |
| 1170 seconds=int(step_build_time_avg)) | 851 seconds=int(step_build_time_avg)) |
| 1171 print 'Average test time : %s' % datetime.timedelta( | 852 print 'Average test time : %s' % datetime.timedelta( |
| 1172 seconds=int(step_perf_time_avg)) | 853 seconds=int(step_perf_time_avg)) |
| 1173 | 854 |
| 855 |
| 1174 def _FindOtherRegressions(revision_data_sorted, bad_greater_than_good): | 856 def _FindOtherRegressions(revision_data_sorted, bad_greater_than_good): |
| 1175 """Compiles a list of other possible regressions from the revision data. | 857 """Compiles a list of other possible regressions from the revision data. |
| 1176 | 858 |
| 1177 Args: | 859 Args: |
| 1178 revision_data_sorted: Sorted list of (revision, revision data dict) pairs. | 860 revision_data_sorted: Sorted list of (revision, revision data dict) pairs. |
| 1179 bad_greater_than_good: Whether the result value at the "bad" revision is | 861 bad_greater_than_good: Whether the result value at the "bad" revision is |
| 1180 numerically greater than the result value at the "good" revision. | 862 numerically greater than the result value at the "good" revision. |
| 1181 | 863 |
| 1182 Returns: | 864 Returns: |
| 1183 A list of [current_rev, previous_rev, confidence] for other places where | 865 A list of [current_rev, previous_rev, confidence] for other places where |
| (...skipping 19 matching lines...) Expand all Loading... |
| 1203 is_same_direction = (prev_less_than_current if | 885 is_same_direction = (prev_less_than_current if |
| 1204 bad_greater_than_good else not prev_less_than_current) | 886 bad_greater_than_good else not prev_less_than_current) |
| 1205 | 887 |
| 1206 # Only report potential regressions with high confidence. | 888 # Only report potential regressions with high confidence. |
| 1207 if is_same_direction and confidence > 50: | 889 if is_same_direction and confidence > 50: |
| 1208 other_regressions.append([current_id, previous_id, confidence]) | 890 other_regressions.append([current_id, previous_id, confidence]) |
| 1209 previous_values.append(current_values) | 891 previous_values.append(current_values) |
| 1210 previous_id = current_id | 892 previous_id = current_id |
| 1211 return other_regressions | 893 return other_regressions |
| 1212 | 894 |
| 895 |
| 1213 class BisectPerformanceMetrics(object): | 896 class BisectPerformanceMetrics(object): |
| 1214 """This class contains functionality to perform a bisection of a range of | 897 """This class contains functionality to perform a bisection of a range of |
| 1215 revisions to narrow down where performance regressions may have occurred. | 898 revisions to narrow down where performance regressions may have occurred. |
| 1216 | 899 |
| 1217 The main entry-point is the Run method. | 900 The main entry-point is the Run method. |
| 1218 """ | 901 """ |
| 1219 | 902 |
| 1220 def __init__(self, source_control, opts): | 903 def __init__(self, source_control, opts): |
| 1221 super(BisectPerformanceMetrics, self).__init__() | 904 super(BisectPerformanceMetrics, self).__init__() |
| 1222 | 905 |
| 1223 self.opts = opts | 906 self.opts = opts |
| 1224 self.source_control = source_control | 907 self.source_control = source_control |
| 1225 self.src_cwd = os.getcwd() | 908 self.src_cwd = os.getcwd() |
| 1226 self.cros_cwd = os.path.join(os.getcwd(), '..', 'cros') | 909 self.cros_cwd = os.path.join(os.getcwd(), '..', 'cros') |
| 1227 self.depot_cwd = {} | 910 self.depot_cwd = {} |
| 1228 self.cleanup_commands = [] | 911 self.cleanup_commands = [] |
| 1229 self.warnings = [] | 912 self.warnings = [] |
| 1230 self.builder = Builder.FromOpts(opts) | 913 self.builder = builder.Builder.FromOpts(opts) |
| 1231 | 914 |
| 1232 # This always starts true since the script grabs latest first. | 915 # This always starts true since the script grabs latest first. |
| 1233 self.was_blink = True | 916 self.was_blink = True |
| 1234 | 917 |
| 1235 for d in DEPOT_NAMES: | 918 for d in DEPOT_NAMES: |
| 1236 # The working directory of each depot is just the path to the depot, but | 919 # The working directory of each depot is just the path to the depot, but |
| 1237 # since we're already in 'src', we can skip that part. | 920 # since we're already in 'src', we can skip that part. |
| 1238 | 921 |
| 1239 self.depot_cwd[d] = os.path.join( | 922 self.depot_cwd[d] = os.path.join( |
| 1240 self.src_cwd, DEPOT_DEPS_NAME[d]['src'][4:]) | 923 self.src_cwd, DEPOT_DEPS_NAME[d]['src'][4:]) |
| (...skipping 192 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 1433 A dict in the format {depot:revision} if successful, otherwise None. | 1116 A dict in the format {depot:revision} if successful, otherwise None. |
| 1434 """ | 1117 """ |
| 1435 cwd = os.getcwd() | 1118 cwd = os.getcwd() |
| 1436 self.ChangeToDepotWorkingDirectory(depot) | 1119 self.ChangeToDepotWorkingDirectory(depot) |
| 1437 | 1120 |
| 1438 results = {} | 1121 results = {} |
| 1439 | 1122 |
| 1440 if depot == 'chromium' or depot == 'android-chrome': | 1123 if depot == 'chromium' or depot == 'android-chrome': |
| 1441 results = self._ParseRevisionsFromDEPSFile(depot) | 1124 results = self._ParseRevisionsFromDEPSFile(depot) |
| 1442 os.chdir(cwd) | 1125 os.chdir(cwd) |
| 1443 elif depot == 'cros': | 1126 |
| 1444 cmd = [CROS_SDK_PATH, '--', 'portageq-%s' % self.opts.cros_board, | 1127 if depot == 'cros': |
| 1445 'best_visible', '/build/%s' % self.opts.cros_board, 'ebuild', | 1128 cmd = [ |
| 1446 CROS_CHROMEOS_PATTERN] | 1129 bisect_utils.CROS_SDK_PATH, |
| 1130 '--', |
| 1131 'portageq-%s' % self.opts.cros_board, |
| 1132 'best_visible', |
| 1133 '/build/%s' % self.opts.cros_board, |
| 1134 'ebuild', |
| 1135 CROS_CHROMEOS_PATTERN |
| 1136 ] |
| 1447 output, return_code = bisect_utils.RunProcessAndRetrieveOutput(cmd) | 1137 output, return_code = bisect_utils.RunProcessAndRetrieveOutput(cmd) |
| 1448 | 1138 |
| 1449 assert not return_code, ('An error occurred while running ' | 1139 assert not return_code, ('An error occurred while running ' |
| 1450 '"%s"' % ' '.join(cmd)) | 1140 '"%s"' % ' '.join(cmd)) |
| 1451 | 1141 |
| 1452 if len(output) > CROS_CHROMEOS_PATTERN: | 1142 if len(output) > CROS_CHROMEOS_PATTERN: |
| 1453 output = output[len(CROS_CHROMEOS_PATTERN):] | 1143 output = output[len(CROS_CHROMEOS_PATTERN):] |
| 1454 | 1144 |
| 1455 if len(output) > 1: | 1145 if len(output) > 1: |
| 1456 output = output.split('_')[0] | 1146 output = output.split('_')[0] |
| (...skipping 11 matching lines...) Expand all Loading... |
| 1468 | 1158 |
| 1469 cwd = os.getcwd() | 1159 cwd = os.getcwd() |
| 1470 self.ChangeToDepotWorkingDirectory('chromium') | 1160 self.ChangeToDepotWorkingDirectory('chromium') |
| 1471 cmd = ['log', '-1', '--format=%H', | 1161 cmd = ['log', '-1', '--format=%H', |
| 1472 '--author=chrome-release@google.com', | 1162 '--author=chrome-release@google.com', |
| 1473 '--grep=to %s' % version, 'origin/master'] | 1163 '--grep=to %s' % version, 'origin/master'] |
| 1474 return_code = bisect_utils.CheckRunGit(cmd) | 1164 return_code = bisect_utils.CheckRunGit(cmd) |
| 1475 os.chdir(cwd) | 1165 os.chdir(cwd) |
| 1476 | 1166 |
| 1477 results['chromium'] = output.strip() | 1167 results['chromium'] = output.strip() |
| 1478 elif depot == 'v8': | 1168 |
| 1169 if depot == 'v8': |
| 1479 # We can't try to map the trunk revision to bleeding edge yet, because | 1170 # We can't try to map the trunk revision to bleeding edge yet, because |
| 1480 # we don't know which direction to try to search in. Have to wait until | 1171 # we don't know which direction to try to search in. Have to wait until |
| 1481 # the bisect has narrowed the results down to 2 v8 rolls. | 1172 # the bisect has narrowed the results down to 2 v8 rolls. |
| 1482 results['v8_bleeding_edge'] = None | 1173 results['v8_bleeding_edge'] = None |
| 1483 | 1174 |
| 1484 return results | 1175 return results |
| 1485 | 1176 |
| 1486 def BackupOrRestoreOutputdirectory(self, restore=False, build_type='Release'): | 1177 def BackupOrRestoreOutputdirectory(self, restore=False, build_type='Release'): |
| 1487 """Backs up or restores build output directory based on restore argument. | 1178 """Backs up or restores build output directory based on restore argument. |
| 1488 | 1179 |
| 1489 Args: | 1180 Args: |
| 1490 restore: Indicates whether to restore or backup. Default is False(Backup) | 1181 restore: Indicates whether to restore or backup. Default is False(Backup) |
| 1491 build_type: Target build type ('Release', 'Debug', 'Release_x64' etc.) | 1182 build_type: Target build type ('Release', 'Debug', 'Release_x64' etc.) |
| 1492 | 1183 |
| 1493 Returns: | 1184 Returns: |
| 1494 Path to backup or restored location as string. otherwise None if it fails. | 1185 Path to backup or restored location as string. otherwise None if it fails. |
| 1495 """ | 1186 """ |
| 1496 build_dir = os.path.abspath( | 1187 build_dir = os.path.abspath( |
| 1497 self.builder.GetBuildOutputDirectory(self.opts, self.src_cwd)) | 1188 builder.GetBuildOutputDirectory(self.opts, self.src_cwd)) |
| 1498 source_dir = os.path.join(build_dir, build_type) | 1189 source_dir = os.path.join(build_dir, build_type) |
| 1499 destination_dir = os.path.join(build_dir, '%s.bak' % build_type) | 1190 destination_dir = os.path.join(build_dir, '%s.bak' % build_type) |
| 1500 if restore: | 1191 if restore: |
| 1501 source_dir, destination_dir = destination_dir, source_dir | 1192 source_dir, destination_dir = destination_dir, source_dir |
| 1502 if os.path.exists(source_dir): | 1193 if os.path.exists(source_dir): |
| 1503 RmTreeAndMkDir(destination_dir, skip_makedir=True) | 1194 RmTreeAndMkDir(destination_dir, skip_makedir=True) |
| 1504 shutil.move(source_dir, destination_dir) | 1195 shutil.move(source_dir, destination_dir) |
| 1505 return destination_dir | 1196 return destination_dir |
| 1506 return None | 1197 return None |
| 1507 | 1198 |
| (...skipping 43 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 1551 if patch: | 1242 if patch: |
| 1552 # Get the SHA of the DEPS changes patch. | 1243 # Get the SHA of the DEPS changes patch. |
| 1553 patch_sha = GetSHA1HexDigest(patch) | 1244 patch_sha = GetSHA1HexDigest(patch) |
| 1554 | 1245 |
| 1555 # Update the DEPS changes patch with a patch to create a new file named | 1246 # Update the DEPS changes patch with a patch to create a new file named |
| 1556 # 'DEPS.sha' and add patch_sha evaluated above to it. | 1247 # 'DEPS.sha' and add patch_sha evaluated above to it. |
| 1557 patch = '%s\n%s' % (patch, DEPS_SHA_PATCH % {'deps_sha': patch_sha}) | 1248 patch = '%s\n%s' % (patch, DEPS_SHA_PATCH % {'deps_sha': patch_sha}) |
| 1558 | 1249 |
| 1559 # Get Build output directory | 1250 # Get Build output directory |
| 1560 abs_build_dir = os.path.abspath( | 1251 abs_build_dir = os.path.abspath( |
| 1561 self.builder.GetBuildOutputDirectory(self.opts, self.src_cwd)) | 1252 builder.GetBuildOutputDirectory(self.opts, self.src_cwd)) |
| 1562 | 1253 |
| 1563 fetch_build_func = lambda: self.GetBuildArchiveForRevision( | 1254 fetch_build_func = lambda: self.GetBuildArchiveForRevision( |
| 1564 revision, self.opts.gs_bucket, self.opts.target_arch, | 1255 revision, self.opts.gs_bucket, self.opts.target_arch, |
| 1565 patch_sha, abs_build_dir) | 1256 patch_sha, abs_build_dir) |
| 1566 | 1257 |
| 1567 # Downloaded archive file path, downloads build archive for given revision. | 1258 # Downloaded archive file path, downloads build archive for given revision. |
| 1568 downloaded_file = fetch_build_func() | 1259 downloaded_file = fetch_build_func() |
| 1569 | 1260 |
| 1570 # When build archive doesn't exists, post a build request to tryserver | 1261 # When build archive doesn't exists, post a build request to tryserver |
| 1571 # and wait for the build to be produced. | 1262 # and wait for the build to be produced. |
| (...skipping 337 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 1909 | 1600 |
| 1910 if not _GenerateProfileIfNecessary(args): | 1601 if not _GenerateProfileIfNecessary(args): |
| 1911 err_text = 'Failed to generate profile for performance test.' | 1602 err_text = 'Failed to generate profile for performance test.' |
| 1912 return (err_text, failure_code) | 1603 return (err_text, failure_code) |
| 1913 | 1604 |
| 1914 # If running a Telemetry test for Chrome OS, insert the remote IP and | 1605 # If running a Telemetry test for Chrome OS, insert the remote IP and |
| 1915 # identity parameters. | 1606 # identity parameters. |
| 1916 is_telemetry = bisect_utils.IsTelemetryCommand(command_to_run) | 1607 is_telemetry = bisect_utils.IsTelemetryCommand(command_to_run) |
| 1917 if self.opts.target_platform == 'cros' and is_telemetry: | 1608 if self.opts.target_platform == 'cros' and is_telemetry: |
| 1918 args.append('--remote=%s' % self.opts.cros_remote_ip) | 1609 args.append('--remote=%s' % self.opts.cros_remote_ip) |
| 1919 args.append('--identity=%s' % CROS_TEST_KEY_PATH) | 1610 args.append('--identity=%s' % bisect_utils.CROS_TEST_KEY_PATH) |
| 1920 | 1611 |
| 1921 start_time = time.time() | 1612 start_time = time.time() |
| 1922 | 1613 |
| 1923 metric_values = [] | 1614 metric_values = [] |
| 1924 output_of_all_runs = '' | 1615 output_of_all_runs = '' |
| 1925 for i in xrange(self.opts.repeat_test_count): | 1616 for i in xrange(self.opts.repeat_test_count): |
| 1926 # Can ignore the return code since if the tests fail, it won't return 0. | 1617 # Can ignore the return code since if the tests fail, it won't return 0. |
| 1927 current_args = copy.copy(args) | 1618 current_args = copy.copy(args) |
| 1928 if is_telemetry: | 1619 if is_telemetry: |
| 1929 if i == 0 and reset_on_first_run: | 1620 if i == 0 and reset_on_first_run: |
| (...skipping 173 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 2103 return True | 1794 return True |
| 2104 | 1795 |
| 2105 def PerformCrosChrootCleanup(self): | 1796 def PerformCrosChrootCleanup(self): |
| 2106 """Deletes the chroot. | 1797 """Deletes the chroot. |
| 2107 | 1798 |
| 2108 Returns: | 1799 Returns: |
| 2109 True if successful. | 1800 True if successful. |
| 2110 """ | 1801 """ |
| 2111 cwd = os.getcwd() | 1802 cwd = os.getcwd() |
| 2112 self.ChangeToDepotWorkingDirectory('cros') | 1803 self.ChangeToDepotWorkingDirectory('cros') |
| 2113 cmd = [CROS_SDK_PATH, '--delete'] | 1804 cmd = [bisect_utils.CROS_SDK_PATH, '--delete'] |
| 2114 return_code = bisect_utils.RunProcess(cmd) | 1805 return_code = bisect_utils.RunProcess(cmd) |
| 2115 os.chdir(cwd) | 1806 os.chdir(cwd) |
| 2116 return not return_code | 1807 return not return_code |
| 2117 | 1808 |
| 2118 def CreateCrosChroot(self): | 1809 def CreateCrosChroot(self): |
| 2119 """Creates a new chroot. | 1810 """Creates a new chroot. |
| 2120 | 1811 |
| 2121 Returns: | 1812 Returns: |
| 2122 True if successful. | 1813 True if successful. |
| 2123 """ | 1814 """ |
| 2124 cwd = os.getcwd() | 1815 cwd = os.getcwd() |
| 2125 self.ChangeToDepotWorkingDirectory('cros') | 1816 self.ChangeToDepotWorkingDirectory('cros') |
| 2126 cmd = [CROS_SDK_PATH, '--create'] | 1817 cmd = [bisect_utils.CROS_SDK_PATH, '--create'] |
| 2127 return_code = bisect_utils.RunProcess(cmd) | 1818 return_code = bisect_utils.RunProcess(cmd) |
| 2128 os.chdir(cwd) | 1819 os.chdir(cwd) |
| 2129 return not return_code | 1820 return not return_code |
| 2130 | 1821 |
| 2131 def PerformPreSyncCleanup(self, revision, depot): | 1822 def PerformPreSyncCleanup(self, revision, depot): |
| 2132 """Performs any necessary cleanup before syncing. | 1823 """Performs any necessary cleanup before syncing. |
| 2133 | 1824 |
| 2134 Returns: | 1825 Returns: |
| 2135 True if successful. | 1826 True if successful. |
| 2136 """ | 1827 """ |
| (...skipping 1528 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 3665 # bugs. If you change this, please update the perf dashboard as well. | 3356 # bugs. If you change this, please update the perf dashboard as well. |
| 3666 bisect_utils.OutputAnnotationStepStart('Results') | 3357 bisect_utils.OutputAnnotationStepStart('Results') |
| 3667 print 'Error: %s' % e.message | 3358 print 'Error: %s' % e.message |
| 3668 if opts.output_buildbot_annotations: | 3359 if opts.output_buildbot_annotations: |
| 3669 bisect_utils.OutputAnnotationStepClosed() | 3360 bisect_utils.OutputAnnotationStepClosed() |
| 3670 return 1 | 3361 return 1 |
| 3671 | 3362 |
| 3672 | 3363 |
| 3673 if __name__ == '__main__': | 3364 if __name__ == '__main__': |
| 3674 sys.exit(main()) | 3365 sys.exit(main()) |
| OLD | NEW |