| 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 18 matching lines...) Expand all Loading... |
| 29 "out/Release/performance_ui_tests --gtest_filter=ShutdownTest.SimpleUserQuit"\ | 29 "out/Release/performance_ui_tests --gtest_filter=ShutdownTest.SimpleUserQuit"\ |
| 30 -g 1f6e67861535121c5c819c16a666f2436c207e7b\ | 30 -g 1f6e67861535121c5c819c16a666f2436c207e7b\ |
| 31 -b b732f23b4f81c382db0b23b9035f3dadc7d925bb\ | 31 -b b732f23b4f81c382db0b23b9035f3dadc7d925bb\ |
| 32 -m shutdown/simple-user-quit | 32 -m shutdown/simple-user-quit |
| 33 """ | 33 """ |
| 34 | 34 |
| 35 import copy | 35 import copy |
| 36 import errno | 36 import errno |
| 37 import hashlib | 37 import hashlib |
| 38 import logging | 38 import logging |
| 39 import optparse | 39 import argparse |
| 40 import os | 40 import os |
| 41 import re | 41 import re |
| 42 import shlex | 42 import shlex |
| 43 import shutil | 43 import shutil |
| 44 import StringIO | 44 import StringIO |
| 45 import sys | 45 import sys |
| 46 import time | 46 import time |
| 47 import zipfile | 47 import zipfile |
| 48 | 48 |
| 49 sys.path.append(os.path.join( | 49 sys.path.append(os.path.join( |
| 50 os.path.dirname(__file__), os.path.pardir, 'telemetry')) | 50 os.path.dirname(__file__), os.path.pardir, 'telemetry')) |
| 51 | 51 |
| 52 from bisect_printer import BisectPrinter | 52 from bisect_printer import BisectPrinter |
| 53 from bisect_results import BisectResults | 53 from bisect_results import BisectResults |
| 54 from bisect_state import BisectState | 54 from bisect_state import BisectState |
| 55 import bisect_utils | 55 import bisect_utils |
| 56 import builder | 56 import builder |
| 57 import math_utils | 57 import math_utils |
| 58 import request_build | 58 import request_build |
| 59 import source_control | 59 import source_control |
| 60 from telemetry.util import cloud_storage | 60 from telemetry.util import cloud_storage |
| 61 | 61 |
| 62 # The script is in chromium/src/tools/auto_bisect. Throughout this script, | 62 # The script is in chromium/src/tools/auto_bisect. Throughout this script, |
| 63 # we use paths to other things in the chromium/src repository. | 63 # we use paths to other things in the chromium/src repository. |
| 64 | 64 |
| 65 CROS_CHROMEOS_PATTERN = 'chromeos-base/chromeos-chrome' | |
| 66 | |
| 67 # Possible return values from BisectPerformanceMetrics.RunTest. | 65 # Possible return values from BisectPerformanceMetrics.RunTest. |
| 68 BUILD_RESULT_SUCCEED = 0 | 66 BUILD_RESULT_SUCCEED = 0 |
| 69 BUILD_RESULT_FAIL = 1 | 67 BUILD_RESULT_FAIL = 1 |
| 70 BUILD_RESULT_SKIPPED = 2 | 68 BUILD_RESULT_SKIPPED = 2 |
| 71 | 69 |
| 72 # Maximum time in seconds to wait after posting build request to the try server. | 70 # Maximum time in seconds to wait after posting build request to the try server. |
| 73 # TODO: Change these values based on the actual time taken by buildbots on | 71 # TODO: Change these values based on the actual time taken by buildbots on |
| 74 # the try server. | 72 # the try server. |
| 75 MAX_MAC_BUILD_TIME = 14400 | 73 MAX_MAC_BUILD_TIME = 14400 |
| 76 MAX_WIN_BUILD_TIME = 14400 | 74 MAX_WIN_BUILD_TIME = 14400 |
| (...skipping 579 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 656 | 654 |
| 657 def __init__(self, src_cwd): | 655 def __init__(self, src_cwd): |
| 658 self.depot_cwd = {} | 656 self.depot_cwd = {} |
| 659 for depot in bisect_utils.DEPOT_NAMES: | 657 for depot in bisect_utils.DEPOT_NAMES: |
| 660 # The working directory of each depot is just the path to the depot, but | 658 # The working directory of each depot is just the path to the depot, but |
| 661 # since we're already in 'src', we can skip that part. | 659 # since we're already in 'src', we can skip that part. |
| 662 path_in_src = bisect_utils.DEPOT_DEPS_NAME[depot]['src'][4:] | 660 path_in_src = bisect_utils.DEPOT_DEPS_NAME[depot]['src'][4:] |
| 663 self.SetDepotDir(depot, os.path.join(src_cwd, path_in_src)) | 661 self.SetDepotDir(depot, os.path.join(src_cwd, path_in_src)) |
| 664 | 662 |
| 665 self.SetDepotDir('chromium', src_cwd) | 663 self.SetDepotDir('chromium', src_cwd) |
| 666 self.SetDepotDir('cros', os.path.join(src_cwd, 'tools', 'cros')) | |
| 667 | 664 |
| 668 def SetDepotDir(self, depot_name, depot_dir): | 665 def SetDepotDir(self, depot_name, depot_dir): |
| 669 self.depot_cwd[depot_name] = depot_dir | 666 self.depot_cwd[depot_name] = depot_dir |
| 670 | 667 |
| 671 def GetDepotDir(self, depot_name): | 668 def GetDepotDir(self, depot_name): |
| 672 if depot_name in self.depot_cwd: | 669 if depot_name in self.depot_cwd: |
| 673 return self.depot_cwd[depot_name] | 670 return self.depot_cwd[depot_name] |
| 674 else: | 671 else: |
| 675 assert False, ('Unknown depot [ %s ] encountered. Possibly a new one ' | 672 assert False, ('Unknown depot [ %s ] encountered. Possibly a new one ' |
| 676 'was added without proper support?' % depot_name) | 673 'was added without proper support?' % depot_name) |
| (...skipping 96 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 773 bisect_utils.RunGit(['branch', '-D', BISECT_TRYJOB_BRANCH]) | 770 bisect_utils.RunGit(['branch', '-D', BISECT_TRYJOB_BRANCH]) |
| 774 | 771 |
| 775 | 772 |
| 776 class BisectPerformanceMetrics(object): | 773 class BisectPerformanceMetrics(object): |
| 777 """This class contains functionality to perform a bisection of a range of | 774 """This class contains functionality to perform a bisection of a range of |
| 778 revisions to narrow down where performance regressions may have occurred. | 775 revisions to narrow down where performance regressions may have occurred. |
| 779 | 776 |
| 780 The main entry-point is the Run method. | 777 The main entry-point is the Run method. |
| 781 """ | 778 """ |
| 782 | 779 |
| 783 def __init__(self, opts): | 780 def __init__(self, opts, src_cwd): |
| 781 """Constructs a BisectPerformancesMetrics object. |
| 782 |
| 783 Args: |
| 784 opts: BisectOptions object containing parsed options. |
| 785 src_cwd: Root src/ directory of the test repository (inside bisect/ dir). |
| 786 """ |
| 784 super(BisectPerformanceMetrics, self).__init__() | 787 super(BisectPerformanceMetrics, self).__init__() |
| 785 | 788 |
| 786 self.opts = opts | 789 self.opts = opts |
| 787 | 790 self.src_cwd = src_cwd |
| 788 # The src directory here is NOT the src/ directory for the repository | |
| 789 # where the bisect script is running from. Instead, it's the src/ directory | |
| 790 # inside the bisect/ directory which is created before running. | |
| 791 self.src_cwd = os.getcwd() | |
| 792 | |
| 793 self.depot_registry = DepotDirectoryRegistry(self.src_cwd) | 791 self.depot_registry = DepotDirectoryRegistry(self.src_cwd) |
| 792 self.printer = BisectPrinter(self.opts, self.depot_registry) |
| 794 self.cleanup_commands = [] | 793 self.cleanup_commands = [] |
| 795 self.warnings = [] | 794 self.warnings = [] |
| 796 self.builder = builder.Builder.FromOpts(opts) | 795 self.builder = builder.Builder.FromOpts(opts) |
| 797 | 796 |
| 798 def PerformCleanup(self): | 797 def PerformCleanup(self): |
| 799 """Performs cleanup when script is finished.""" | 798 """Performs cleanup when script is finished.""" |
| 800 os.chdir(self.src_cwd) | 799 os.chdir(self.src_cwd) |
| 801 for c in self.cleanup_commands: | 800 for c in self.cleanup_commands: |
| 802 if c[0] == 'mv': | 801 if c[0] == 'mv': |
| 803 shutil.move(c[1], c[2]) | 802 shutil.move(c[1], c[2]) |
| 804 else: | 803 else: |
| 805 assert False, 'Invalid cleanup command.' | 804 assert False, 'Invalid cleanup command.' |
| 806 | 805 |
| 807 def GetRevisionList(self, depot, bad_revision, good_revision): | 806 def GetRevisionList(self, depot, bad_revision, good_revision): |
| 808 """Retrieves a list of all the commits between the bad revision and | 807 """Retrieves a list of all the commits between the bad revision and |
| 809 last known good revision.""" | 808 last known good revision.""" |
| 810 | 809 |
| 811 revision_work_list = [] | 810 cwd = self.depot_registry.GetDepotDir(depot) |
| 812 | 811 return source_control.GetRevisionList(bad_revision, good_revision, cwd=cwd) |
| 813 if depot == 'cros': | |
| 814 revision_range_start = good_revision | |
| 815 revision_range_end = bad_revision | |
| 816 | |
| 817 cwd = os.getcwd() | |
| 818 self.depot_registry.ChangeToDepotDir('cros') | |
| 819 | |
| 820 # Print the commit timestamps for every commit in the revision time | |
| 821 # range. We'll sort them and bisect by that. There is a remote chance that | |
| 822 # 2 (or more) commits will share the exact same timestamp, but it's | |
| 823 # probably safe to ignore that case. | |
| 824 cmd = ['repo', 'forall', '-c', | |
| 825 'git log --format=%%ct --before=%d --after=%d' % ( | |
| 826 revision_range_end, revision_range_start)] | |
| 827 output, return_code = bisect_utils.RunProcessAndRetrieveOutput(cmd) | |
| 828 | |
| 829 assert not return_code, ('An error occurred while running ' | |
| 830 '"%s"' % ' '.join(cmd)) | |
| 831 | |
| 832 os.chdir(cwd) | |
| 833 | |
| 834 revision_work_list = list(set( | |
| 835 [int(o) for o in output.split('\n') if bisect_utils.IsStringInt(o)])) | |
| 836 revision_work_list = sorted(revision_work_list, reverse=True) | |
| 837 else: | |
| 838 cwd = self.depot_registry.GetDepotDir(depot) | |
| 839 revision_work_list = source_control.GetRevisionList(bad_revision, | |
| 840 good_revision, cwd=cwd) | |
| 841 | |
| 842 return revision_work_list | |
| 843 | 812 |
| 844 def _ParseRevisionsFromDEPSFile(self, depot): | 813 def _ParseRevisionsFromDEPSFile(self, depot): |
| 845 """Parses the local DEPS file to determine blink/skia/v8 revisions which may | 814 """Parses the local DEPS file to determine blink/skia/v8 revisions which may |
| 846 be needed if the bisect recurses into those depots later. | 815 be needed if the bisect recurses into those depots later. |
| 847 | 816 |
| 848 Args: | 817 Args: |
| 849 depot: Name of depot being bisected. | 818 depot: Name of depot being bisected. |
| 850 | 819 |
| 851 Returns: | 820 Returns: |
| 852 A dict in the format {depot:revision} if successful, otherwise None. | 821 A dict in the format {depot:revision} if successful, otherwise None. |
| (...skipping 60 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 913 """ | 882 """ |
| 914 cwd = os.getcwd() | 883 cwd = os.getcwd() |
| 915 self.depot_registry.ChangeToDepotDir(depot) | 884 self.depot_registry.ChangeToDepotDir(depot) |
| 916 | 885 |
| 917 results = {} | 886 results = {} |
| 918 | 887 |
| 919 if depot == 'chromium' or depot == 'android-chrome': | 888 if depot == 'chromium' or depot == 'android-chrome': |
| 920 results = self._ParseRevisionsFromDEPSFile(depot) | 889 results = self._ParseRevisionsFromDEPSFile(depot) |
| 921 os.chdir(cwd) | 890 os.chdir(cwd) |
| 922 | 891 |
| 923 if depot == 'cros': | |
| 924 cmd = [ | |
| 925 bisect_utils.CROS_SDK_PATH, | |
| 926 '--', | |
| 927 'portageq-%s' % self.opts.cros_board, | |
| 928 'best_visible', | |
| 929 '/build/%s' % self.opts.cros_board, | |
| 930 'ebuild', | |
| 931 CROS_CHROMEOS_PATTERN | |
| 932 ] | |
| 933 output, return_code = bisect_utils.RunProcessAndRetrieveOutput(cmd) | |
| 934 | |
| 935 assert not return_code, ('An error occurred while running ' | |
| 936 '"%s"' % ' '.join(cmd)) | |
| 937 | |
| 938 if len(output) > CROS_CHROMEOS_PATTERN: | |
| 939 output = output[len(CROS_CHROMEOS_PATTERN):] | |
| 940 | |
| 941 if len(output) > 1: | |
| 942 output = output.split('_')[0] | |
| 943 | |
| 944 if len(output) > 3: | |
| 945 contents = output.split('.') | |
| 946 | |
| 947 version = contents[2] | |
| 948 | |
| 949 if contents[3] != '0': | |
| 950 warningText = ('Chrome version: %s.%s but using %s.0 to bisect.' % | |
| 951 (version, contents[3], version)) | |
| 952 if not warningText in self.warnings: | |
| 953 self.warnings.append(warningText) | |
| 954 | |
| 955 cwd = os.getcwd() | |
| 956 self.depot_registry.ChangeToDepotDir('chromium') | |
| 957 cmd = ['log', '-1', '--format=%H', | |
| 958 '--author=chrome-release@google.com', | |
| 959 '--grep=to %s' % version, 'origin/master'] | |
| 960 return_code = bisect_utils.CheckRunGit(cmd) | |
| 961 os.chdir(cwd) | |
| 962 | |
| 963 results['chromium'] = output.strip() | |
| 964 | |
| 965 if depot == 'v8': | 892 if depot == 'v8': |
| 966 # We can't try to map the trunk revision to bleeding edge yet, because | 893 # We can't try to map the trunk revision to bleeding edge yet, because |
| 967 # we don't know which direction to try to search in. Have to wait until | 894 # we don't know which direction to try to search in. Have to wait until |
| 968 # the bisect has narrowed the results down to 2 v8 rolls. | 895 # the bisect has narrowed the results down to 2 v8 rolls. |
| 969 results['v8_bleeding_edge'] = None | 896 results['v8_bleeding_edge'] = None |
| 970 | 897 |
| 971 return results | 898 return results |
| 972 | 899 |
| 973 def BackupOrRestoreOutputDirectory(self, restore=False, build_type='Release'): | 900 def BackupOrRestoreOutputDirectory(self, restore=False, build_type='Release'): |
| 974 """Backs up or restores build output directory based on restore argument. | 901 """Backs up or restores build output directory based on restore argument. |
| (...skipping 497 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 1472 # refer to http://bugs.python.org/issue1724822. By default posix=True. | 1399 # refer to http://bugs.python.org/issue1724822. By default posix=True. |
| 1473 args = shlex.split(command_to_run, posix=not bisect_utils.IsWindowsHost()) | 1400 args = shlex.split(command_to_run, posix=not bisect_utils.IsWindowsHost()) |
| 1474 | 1401 |
| 1475 if not _GenerateProfileIfNecessary(args): | 1402 if not _GenerateProfileIfNecessary(args): |
| 1476 err_text = 'Failed to generate profile for performance test.' | 1403 err_text = 'Failed to generate profile for performance test.' |
| 1477 return (err_text, failure_code) | 1404 return (err_text, failure_code) |
| 1478 | 1405 |
| 1479 # If running a Telemetry test for Chrome OS, insert the remote IP and | 1406 # If running a Telemetry test for Chrome OS, insert the remote IP and |
| 1480 # identity parameters. | 1407 # identity parameters. |
| 1481 is_telemetry = bisect_utils.IsTelemetryCommand(command_to_run) | 1408 is_telemetry = bisect_utils.IsTelemetryCommand(command_to_run) |
| 1482 if self.opts.target_platform == 'cros' and is_telemetry: | |
| 1483 args.append('--remote=%s' % self.opts.cros_remote_ip) | |
| 1484 args.append('--identity=%s' % bisect_utils.CROS_TEST_KEY_PATH) | |
| 1485 | 1409 |
| 1486 start_time = time.time() | 1410 start_time = time.time() |
| 1487 | 1411 |
| 1488 metric_values = [] | 1412 metric_values = [] |
| 1489 output_of_all_runs = '' | 1413 output_of_all_runs = '' |
| 1490 for i in xrange(self.opts.repeat_test_count): | 1414 for i in xrange(self.opts.repeat_test_count): |
| 1491 # Can ignore the return code since if the tests fail, it won't return 0. | 1415 # Can ignore the return code since if the tests fail, it won't return 0. |
| 1492 current_args = copy.copy(args) | 1416 current_args = copy.copy(args) |
| 1493 if is_telemetry: | 1417 if is_telemetry: |
| 1494 if i == 0 and reset_on_first_run: | 1418 if i == 0 and reset_on_first_run: |
| (...skipping 93 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 1588 | 1512 |
| 1589 Args: | 1513 Args: |
| 1590 revision: The revision to sync to. | 1514 revision: The revision to sync to. |
| 1591 depot: The depot in use at the moment (probably skia). | 1515 depot: The depot in use at the moment (probably skia). |
| 1592 | 1516 |
| 1593 Returns: | 1517 Returns: |
| 1594 A list of [depot, revision] pairs that need to be synced. | 1518 A list of [depot, revision] pairs that need to be synced. |
| 1595 """ | 1519 """ |
| 1596 revisions_to_sync = [[depot, revision]] | 1520 revisions_to_sync = [[depot, revision]] |
| 1597 | 1521 |
| 1598 is_base = ((depot == 'chromium') or (depot == 'cros') or | 1522 is_base = ((depot == 'chromium') or (depot == 'android-chrome')) |
| 1599 (depot == 'android-chrome')) | |
| 1600 | 1523 |
| 1601 # Some SVN depots were split into multiple git depots, so we need to | 1524 # Some SVN depots were split into multiple git depots, so we need to |
| 1602 # figure out for each mirror which git revision to grab. There's no | 1525 # figure out for each mirror which git revision to grab. There's no |
| 1603 # guarantee that the SVN revision will exist for each of the dependent | 1526 # guarantee that the SVN revision will exist for each of the dependent |
| 1604 # depots, so we have to grep the git logs and grab the next earlier one. | 1527 # depots, so we have to grep the git logs and grab the next earlier one. |
| 1605 if not is_base and bisect_utils.DEPOT_DEPS_NAME[depot]['depends']: | 1528 if not is_base and bisect_utils.DEPOT_DEPS_NAME[depot]['depends']: |
| 1606 commit_position = source_control.GetCommitPosition(revision) | 1529 commit_position = source_control.GetCommitPosition(revision) |
| 1607 | 1530 |
| 1608 for d in bisect_utils.DEPOT_DEPS_NAME[depot]['depends']: | 1531 for d in bisect_utils.DEPOT_DEPS_NAME[depot]['depends']: |
| 1609 self.depot_registry.ChangeToDepotDir(d) | 1532 self.depot_registry.ChangeToDepotDir(d) |
| (...skipping 19 matching lines...) Expand all Loading... |
| 1629 print 'Cleaning up between runs.' | 1552 print 'Cleaning up between runs.' |
| 1630 print | 1553 print |
| 1631 | 1554 |
| 1632 # Leaving these .pyc files around between runs may disrupt some perf tests. | 1555 # Leaving these .pyc files around between runs may disrupt some perf tests. |
| 1633 for (path, _, files) in os.walk(self.src_cwd): | 1556 for (path, _, files) in os.walk(self.src_cwd): |
| 1634 for cur_file in files: | 1557 for cur_file in files: |
| 1635 if cur_file.endswith('.pyc'): | 1558 if cur_file.endswith('.pyc'): |
| 1636 path_to_file = os.path.join(path, cur_file) | 1559 path_to_file = os.path.join(path, cur_file) |
| 1637 os.remove(path_to_file) | 1560 os.remove(path_to_file) |
| 1638 | 1561 |
| 1639 def PerformCrosChrootCleanup(self): | 1562 def _RunPostSync(self, _depot): |
| 1640 """Deletes the chroot. | |
| 1641 | |
| 1642 Returns: | |
| 1643 True if successful. | |
| 1644 """ | |
| 1645 cwd = os.getcwd() | |
| 1646 self.depot_registry.ChangeToDepotDir('cros') | |
| 1647 cmd = [bisect_utils.CROS_SDK_PATH, '--delete'] | |
| 1648 return_code = bisect_utils.RunProcess(cmd) | |
| 1649 os.chdir(cwd) | |
| 1650 return not return_code | |
| 1651 | |
| 1652 def CreateCrosChroot(self): | |
| 1653 """Creates a new chroot. | |
| 1654 | |
| 1655 Returns: | |
| 1656 True if successful. | |
| 1657 """ | |
| 1658 cwd = os.getcwd() | |
| 1659 self.depot_registry.ChangeToDepotDir('cros') | |
| 1660 cmd = [bisect_utils.CROS_SDK_PATH, '--create'] | |
| 1661 return_code = bisect_utils.RunProcess(cmd) | |
| 1662 os.chdir(cwd) | |
| 1663 return not return_code | |
| 1664 | |
| 1665 def _PerformPreSyncCleanup(self, depot): | |
| 1666 """Performs any necessary cleanup before syncing. | |
| 1667 | |
| 1668 Args: | |
| 1669 depot: Depot name. | |
| 1670 | |
| 1671 Returns: | |
| 1672 True if successful. | |
| 1673 """ | |
| 1674 if depot == 'cros': | |
| 1675 return self.PerformCrosChrootCleanup() | |
| 1676 return True | |
| 1677 | |
| 1678 def _RunPostSync(self, depot): | |
| 1679 """Performs any work after syncing. | 1563 """Performs any work after syncing. |
| 1680 | 1564 |
| 1681 Args: | 1565 Args: |
| 1682 depot: Depot name. | 1566 depot: Depot name. |
| 1683 | 1567 |
| 1684 Returns: | 1568 Returns: |
| 1685 True if successful. | 1569 True if successful. |
| 1686 """ | 1570 """ |
| 1687 if self.opts.target_platform == 'android': | 1571 if self.opts.target_platform == 'android': |
| 1688 if not builder.SetupAndroidBuildEnvironment(self.opts, | 1572 if not builder.SetupAndroidBuildEnvironment(self.opts, |
| 1689 path_to_src=self.src_cwd): | 1573 path_to_src=self.src_cwd): |
| 1690 return False | 1574 return False |
| 1691 | 1575 |
| 1692 if depot == 'cros': | 1576 return self.RunGClientHooks() |
| 1693 return self.CreateCrosChroot() | |
| 1694 else: | |
| 1695 return self.RunGClientHooks() | |
| 1696 return True | |
| 1697 | 1577 |
| 1698 @staticmethod | 1578 @staticmethod |
| 1699 def ShouldSkipRevision(depot, revision): | 1579 def ShouldSkipRevision(depot, revision): |
| 1700 """Checks whether a particular revision can be safely skipped. | 1580 """Checks whether a particular revision can be safely skipped. |
| 1701 | 1581 |
| 1702 Some commits can be safely skipped (such as a DEPS roll), since the tool | 1582 Some commits can be safely skipped (such as a DEPS roll), since the tool |
| 1703 is git based those changes would have no effect. | 1583 is git based those changes would have no effect. |
| 1704 | 1584 |
| 1705 Args: | 1585 Args: |
| 1706 depot: The depot being bisected. | 1586 depot: The depot being bisected. |
| (...skipping 23 matching lines...) Expand all Loading... |
| 1730 metric: The performance metric being tested. | 1610 metric: The performance metric being tested. |
| 1731 | 1611 |
| 1732 Returns: | 1612 Returns: |
| 1733 On success, a tuple containing the results of the performance test. | 1613 On success, a tuple containing the results of the performance test. |
| 1734 Otherwise, a tuple with the error message. | 1614 Otherwise, a tuple with the error message. |
| 1735 """ | 1615 """ |
| 1736 # Decide which sync program to use. | 1616 # Decide which sync program to use. |
| 1737 sync_client = None | 1617 sync_client = None |
| 1738 if depot == 'chromium' or depot == 'android-chrome': | 1618 if depot == 'chromium' or depot == 'android-chrome': |
| 1739 sync_client = 'gclient' | 1619 sync_client = 'gclient' |
| 1740 elif depot == 'cros': | |
| 1741 sync_client = 'repo' | |
| 1742 | 1620 |
| 1743 # Decide what depots will need to be synced to what revisions. | 1621 # Decide what depots will need to be synced to what revisions. |
| 1744 revisions_to_sync = self._FindAllRevisionsToSync(revision, depot) | 1622 revisions_to_sync = self._FindAllRevisionsToSync(revision, depot) |
| 1745 if not revisions_to_sync: | 1623 if not revisions_to_sync: |
| 1746 return ('Failed to resolve dependent depots.', BUILD_RESULT_FAIL) | 1624 return ('Failed to resolve dependent depots.', BUILD_RESULT_FAIL) |
| 1747 | 1625 |
| 1748 if not self._PerformPreSyncCleanup(depot): | |
| 1749 return ('Failed to perform pre-sync cleanup.', BUILD_RESULT_FAIL) | |
| 1750 | |
| 1751 # Do the syncing for all depots. | 1626 # Do the syncing for all depots. |
| 1752 if not self.opts.debug_ignore_sync: | 1627 if not self.opts.debug_ignore_sync: |
| 1753 if not self._SyncAllRevisions(revisions_to_sync, sync_client): | 1628 if not self._SyncAllRevisions(revisions_to_sync, sync_client): |
| 1754 return ('Failed to sync: [%s]' % str(revision), BUILD_RESULT_FAIL) | 1629 return ('Failed to sync: [%s]' % str(revision), BUILD_RESULT_FAIL) |
| 1755 | 1630 |
| 1756 # Try to do any post-sync steps. This may include "gclient runhooks". | 1631 # Try to do any post-sync steps. This may include "gclient runhooks". |
| 1757 if not self._RunPostSync(depot): | 1632 if not self._RunPostSync(depot): |
| 1758 return ('Failed to run [gclient runhooks].', BUILD_RESULT_FAIL) | 1633 return ('Failed to run [gclient runhooks].', BUILD_RESULT_FAIL) |
| 1759 | 1634 |
| 1760 # Skip this revision if it can be skipped. | 1635 # Skip this revision if it can be skipped. |
| (...skipping 34 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 1795 start_build_time) | 1670 start_build_time) |
| 1796 else: | 1671 else: |
| 1797 return ('Failed to parse DEPS file for external revisions.', | 1672 return ('Failed to parse DEPS file for external revisions.', |
| 1798 BUILD_RESULT_FAIL) | 1673 BUILD_RESULT_FAIL) |
| 1799 | 1674 |
| 1800 def _SyncAllRevisions(self, revisions_to_sync, sync_client): | 1675 def _SyncAllRevisions(self, revisions_to_sync, sync_client): |
| 1801 """Syncs multiple depots to particular revisions. | 1676 """Syncs multiple depots to particular revisions. |
| 1802 | 1677 |
| 1803 Args: | 1678 Args: |
| 1804 revisions_to_sync: A list of (depot, revision) pairs to be synced. | 1679 revisions_to_sync: A list of (depot, revision) pairs to be synced. |
| 1805 sync_client: Program used to sync, e.g. "gclient", "repo". Can be None. | 1680 sync_client: Program used to sync, e.g. "gclient". Can be None. |
| 1806 | 1681 |
| 1807 Returns: | 1682 Returns: |
| 1808 True if successful, False otherwise. | 1683 True if successful, False otherwise. |
| 1809 """ | 1684 """ |
| 1810 for depot, revision in revisions_to_sync: | 1685 for depot, revision in revisions_to_sync: |
| 1811 self.depot_registry.ChangeToDepotDir(depot) | 1686 self.depot_registry.ChangeToDepotDir(depot) |
| 1812 | 1687 |
| 1813 if sync_client: | 1688 if sync_client: |
| 1814 self.PerformPreBuildCleanup() | 1689 self.PerformPreBuildCleanup() |
| 1815 | 1690 |
| (...skipping 386 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 2202 self, target_depot, good_revision, bad_revision): | 2077 self, target_depot, good_revision, bad_revision): |
| 2203 """Checks that |good_revision| is an earlier revision than |bad_revision|. | 2078 """Checks that |good_revision| is an earlier revision than |bad_revision|. |
| 2204 | 2079 |
| 2205 Args: | 2080 Args: |
| 2206 good_revision: Number/tag of the known good revision. | 2081 good_revision: Number/tag of the known good revision. |
| 2207 bad_revision: Number/tag of the known bad revision. | 2082 bad_revision: Number/tag of the known bad revision. |
| 2208 | 2083 |
| 2209 Returns: | 2084 Returns: |
| 2210 True if the revisions are in the proper order (good earlier than bad). | 2085 True if the revisions are in the proper order (good earlier than bad). |
| 2211 """ | 2086 """ |
| 2212 if target_depot != 'cros': | 2087 cwd = self.depot_registry.GetDepotDir(target_depot) |
| 2213 cwd = self.depot_registry.GetDepotDir(target_depot) | 2088 good_position = source_control.GetCommitPosition(good_revision, cwd) |
| 2214 good_position = source_control.GetCommitPosition(good_revision, cwd) | 2089 bad_position = source_control.GetCommitPosition(bad_revision, cwd) |
| 2215 bad_position = source_control.GetCommitPosition(bad_revision, cwd) | |
| 2216 else: | |
| 2217 # CrOS and SVN use integers. | |
| 2218 good_position = int(good_revision) | |
| 2219 bad_position = int(bad_revision) | |
| 2220 | 2090 |
| 2221 return good_position <= bad_position | 2091 return good_position <= bad_position |
| 2222 | 2092 |
| 2223 def CanPerformBisect(self, good_revision, bad_revision): | 2093 def CanPerformBisect(self, good_revision, bad_revision): |
| 2224 """Checks whether a given revision is bisectable. | 2094 """Checks whether a given revision is bisectable. |
| 2225 | 2095 |
| 2226 Checks for following: | 2096 Checks for following: |
| 2227 1. Non-bisectable revsions for android bots (refer to crbug.com/385324). | 2097 1. Non-bisectable revsions for android bots (refer to crbug.com/385324). |
| 2228 2. Non-bisectable revsions for Windows bots (refer to crbug.com/405274). | 2098 2. Non-bisectable revsions for Windows bots (refer to crbug.com/405274). |
| 2229 | 2099 |
| (...skipping 39 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 2269 command_to_run: Specify the command to execute the performance test. | 2139 command_to_run: Specify the command to execute the performance test. |
| 2270 good_revision: Number/tag of the known good revision. | 2140 good_revision: Number/tag of the known good revision. |
| 2271 bad_revision: Number/tag of the known bad revision. | 2141 bad_revision: Number/tag of the known bad revision. |
| 2272 metric: The performance metric to monitor. | 2142 metric: The performance metric to monitor. |
| 2273 | 2143 |
| 2274 Returns: | 2144 Returns: |
| 2275 A BisectResults object. | 2145 A BisectResults object. |
| 2276 """ | 2146 """ |
| 2277 # Choose depot to bisect first | 2147 # Choose depot to bisect first |
| 2278 target_depot = 'chromium' | 2148 target_depot = 'chromium' |
| 2279 if self.opts.target_platform == 'cros': | 2149 if self.opts.target_platform == 'android-chrome': |
| 2280 target_depot = 'cros' | |
| 2281 elif self.opts.target_platform == 'android-chrome': | |
| 2282 target_depot = 'android-chrome' | 2150 target_depot = 'android-chrome' |
| 2283 | 2151 |
| 2284 cwd = os.getcwd() | 2152 cwd = os.getcwd() |
| 2285 self.depot_registry.ChangeToDepotDir(target_depot) | 2153 self.depot_registry.ChangeToDepotDir(target_depot) |
| 2286 | 2154 |
| 2287 # If they passed SVN revisions, we can try match them to git SHA1 hashes. | 2155 # If they passed SVN revisions, we can try match them to git SHA1 hashes. |
| 2288 bad_revision = source_control.ResolveToRevision( | 2156 bad_revision = source_control.ResolveToRevision( |
| 2289 bad_revision_in, target_depot, bisect_utils.DEPOT_DEPS_NAME, 100) | 2157 bad_revision_in, target_depot, bisect_utils.DEPOT_DEPS_NAME, 100) |
| 2290 good_revision = source_control.ResolveToRevision( | 2158 good_revision = source_control.ResolveToRevision( |
| 2291 good_revision_in, target_depot, bisect_utils.DEPOT_DEPS_NAME, -100) | 2159 good_revision_in, target_depot, bisect_utils.DEPOT_DEPS_NAME, -100) |
| (...skipping 106 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 2398 bad_revision_state.passed = False | 2266 bad_revision_state.passed = False |
| 2399 bad_revision_state.value = known_bad_value | 2267 bad_revision_state.value = known_bad_value |
| 2400 | 2268 |
| 2401 good_revision_state = revision_states[max_revision] | 2269 good_revision_state = revision_states[max_revision] |
| 2402 good_revision_state.external = good_results[2] | 2270 good_revision_state.external = good_results[2] |
| 2403 good_revision_state.perf_time = good_results[3] | 2271 good_revision_state.perf_time = good_results[3] |
| 2404 good_revision_state.build_time = good_results[4] | 2272 good_revision_state.build_time = good_results[4] |
| 2405 good_revision_state.passed = True | 2273 good_revision_state.passed = True |
| 2406 good_revision_state.value = known_good_value | 2274 good_revision_state.value = known_good_value |
| 2407 | 2275 |
| 2408 bisect_printer = BisectPrinter(self.opts, self.depot_registry) | |
| 2409 | |
| 2410 # Check how likely it is that the good and bad results are different | 2276 # Check how likely it is that the good and bad results are different |
| 2411 # beyond chance-induced variation. | 2277 # beyond chance-induced variation. |
| 2412 confidence_error = False | 2278 confidence_error = False |
| 2413 if not self.opts.debug_ignore_regression_confidence: | 2279 if not self.opts.debug_ignore_regression_confidence: |
| 2414 confidence_error = _CheckRegressionConfidenceError(good_revision, | 2280 confidence_error = _CheckRegressionConfidenceError(good_revision, |
| 2415 bad_revision, | 2281 bad_revision, |
| 2416 known_good_value, | 2282 known_good_value, |
| 2417 known_bad_value) | 2283 known_bad_value) |
| 2418 if confidence_error: | 2284 if confidence_error: |
| 2419 self.warnings.append(confidence_error) | 2285 self.warnings.append(confidence_error) |
| 2420 bad_revision_state.passed = True # Marking the 'bad' revision as good. | 2286 bad_revision_state.passed = True # Marking the 'bad' revision as good. |
| 2421 return BisectResults(bisect_state, self.depot_registry, self.opts, | 2287 return BisectResults(bisect_state, self.depot_registry, self.opts, |
| 2422 self.warnings) | 2288 self.warnings) |
| 2423 | 2289 |
| 2424 while True: | 2290 while True: |
| 2425 if not revision_states: | 2291 if not revision_states: |
| 2426 break | 2292 break |
| 2427 | 2293 |
| 2428 if max_revision - min_revision <= 1: | 2294 if max_revision - min_revision <= 1: |
| 2429 min_revision_state = revision_states[min_revision] | 2295 min_revision_state = revision_states[min_revision] |
| 2430 max_revision_state = revision_states[max_revision] | 2296 max_revision_state = revision_states[max_revision] |
| 2431 current_depot = min_revision_state.depot | 2297 current_depot = min_revision_state.depot |
| 2432 # TODO(sergiyb): Under which conditions can first two branches be hit? | 2298 # TODO(sergiyb): Under which conditions can first two branches be hit? |
| 2433 if min_revision_state.passed == '?': | 2299 if min_revision_state.passed == '?': |
| 2434 next_revision_index = min_revision | 2300 next_revision_index = min_revision |
| 2435 elif max_revision_state.passed == '?': | 2301 elif max_revision_state.passed == '?': |
| 2436 next_revision_index = max_revision | 2302 next_revision_index = max_revision |
| 2437 elif current_depot in ['android-chrome', 'cros', 'chromium', 'v8']: | 2303 elif current_depot in ['android-chrome', 'chromium', 'v8']: |
| 2438 previous_revision = revision_states[min_revision].revision | 2304 previous_revision = revision_states[min_revision].revision |
| 2439 # If there were changes to any of the external libraries we track, | 2305 # If there were changes to any of the external libraries we track, |
| 2440 # should bisect the changes there as well. | 2306 # should bisect the changes there as well. |
| 2441 external_depot = self._FindNextDepotToBisect( | 2307 external_depot = self._FindNextDepotToBisect( |
| 2442 current_depot, min_revision_state, max_revision_state) | 2308 current_depot, min_revision_state, max_revision_state) |
| 2443 # If there was no change in any of the external depots, the search | 2309 # If there was no change in any of the external depots, the search |
| 2444 # is over. | 2310 # is over. |
| 2445 if not external_depot: | 2311 if not external_depot: |
| 2446 if current_depot == 'v8': | 2312 if current_depot == 'v8': |
| 2447 self.warnings.append('Unfortunately, V8 bisection couldn\'t ' | 2313 self.warnings.append('Unfortunately, V8 bisection couldn\'t ' |
| (...skipping 76 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 2524 next_revision_state.passed = 'Build Failed' | 2390 next_revision_state.passed = 'Build Failed' |
| 2525 | 2391 |
| 2526 print run_results[0] | 2392 print run_results[0] |
| 2527 | 2393 |
| 2528 # If the build is broken, remove it and redo search. | 2394 # If the build is broken, remove it and redo search. |
| 2529 revision_states.pop(next_revision_index) | 2395 revision_states.pop(next_revision_index) |
| 2530 | 2396 |
| 2531 max_revision -= 1 | 2397 max_revision -= 1 |
| 2532 | 2398 |
| 2533 if self.opts.output_buildbot_annotations: | 2399 if self.opts.output_buildbot_annotations: |
| 2534 bisect_printer.PrintPartialResults(bisect_state) | 2400 self.printer.PrintPartialResults(bisect_state) |
| 2535 bisect_utils.OutputAnnotationStepClosed() | 2401 bisect_utils.OutputAnnotationStepClosed() |
| 2536 | 2402 |
| 2537 return BisectResults(bisect_state, self.depot_registry, self.opts, | 2403 return BisectResults(bisect_state, self.depot_registry, self.opts, |
| 2538 self.warnings) | 2404 self.warnings) |
| 2539 else: | 2405 else: |
| 2540 # Weren't able to sync and retrieve the revision range. | 2406 # Weren't able to sync and retrieve the revision range. |
| 2541 error = ('An error occurred attempting to retrieve revision range: ' | 2407 error = ('An error occurred attempting to retrieve revision range: ' |
| 2542 '[%s..%s]' % (good_revision, bad_revision)) | 2408 '[%s..%s]' % (good_revision, bad_revision)) |
| 2543 return BisectResults(error=error) | 2409 return BisectResults(error=error) |
| 2544 | 2410 |
| (...skipping 45 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 2590 def __init__(self): | 2456 def __init__(self): |
| 2591 super(BisectOptions, self).__init__() | 2457 super(BisectOptions, self).__init__() |
| 2592 | 2458 |
| 2593 self.target_platform = 'chromium' | 2459 self.target_platform = 'chromium' |
| 2594 self.build_preference = None | 2460 self.build_preference = None |
| 2595 self.good_revision = None | 2461 self.good_revision = None |
| 2596 self.bad_revision = None | 2462 self.bad_revision = None |
| 2597 self.use_goma = None | 2463 self.use_goma = None |
| 2598 self.goma_dir = None | 2464 self.goma_dir = None |
| 2599 self.goma_threads = 64 | 2465 self.goma_threads = 64 |
| 2600 self.cros_board = None | |
| 2601 self.cros_remote_ip = None | |
| 2602 self.repeat_test_count = 20 | 2466 self.repeat_test_count = 20 |
| 2603 self.truncate_percent = 25 | 2467 self.truncate_percent = 25 |
| 2604 self.max_time_minutes = 20 | 2468 self.max_time_minutes = 20 |
| 2605 self.metric = None | 2469 self.metric = None |
| 2606 self.command = None | 2470 self.command = None |
| 2607 self.output_buildbot_annotations = None | 2471 self.output_buildbot_annotations = None |
| 2608 self.no_custom_deps = False | 2472 self.no_custom_deps = False |
| 2609 self.working_directory = None | 2473 self.working_directory = None |
| 2610 self.extra_src = None | 2474 self.extra_src = None |
| 2611 self.debug_ignore_build = None | 2475 self.debug_ignore_build = None |
| 2612 self.debug_ignore_sync = None | 2476 self.debug_ignore_sync = None |
| 2613 self.debug_ignore_perf_test = None | 2477 self.debug_ignore_perf_test = None |
| 2614 self.debug_ignore_regression_confidence = None | 2478 self.debug_ignore_regression_confidence = None |
| 2615 self.debug_fake_first_test_mean = 0 | 2479 self.debug_fake_first_test_mean = 0 |
| 2616 self.gs_bucket = None | 2480 self.gs_bucket = None |
| 2617 self.target_arch = 'ia32' | 2481 self.target_arch = 'ia32' |
| 2618 self.target_build_type = 'Release' | 2482 self.target_build_type = 'Release' |
| 2619 self.builder_host = None | 2483 self.builder_host = None |
| 2620 self.builder_port = None | 2484 self.builder_port = None |
| 2621 self.bisect_mode = bisect_utils.BISECT_MODE_MEAN | 2485 self.bisect_mode = bisect_utils.BISECT_MODE_MEAN |
| 2622 self.improvement_direction = 0 | 2486 self.improvement_direction = 0 |
| 2623 | 2487 |
| 2624 @staticmethod | 2488 @staticmethod |
| 2625 def _CreateCommandLineParser(): | 2489 def _AddBisectOptionsGroup(parser): |
| 2490 group = parser.add_argument_group('Bisect options') |
| 2491 group.add_argument('-c', '--command', required=True, |
| 2492 help='A command to execute your performance test at ' |
| 2493 'each point in the bisection.') |
| 2494 group.add_argument('-b', '--bad_revision', required=True, |
| 2495 help='A bad revision to start bisection. Must be later ' |
| 2496 'than good revision. May be either a git or svn ' |
| 2497 'revision.') |
| 2498 group.add_argument('-g', '--good_revision', required=True, |
| 2499 help='A revision to start bisection where performance ' |
| 2500 'test is known to pass. Must be earlier than the ' |
| 2501 'bad revision. May be either a git or a svn ' |
| 2502 'revision.') |
| 2503 group.add_argument('-m', '--metric', |
| 2504 help='The desired metric to bisect on. For example ' |
| 2505 '"vm_rss_final_b/vm_rss_f_b"') |
| 2506 group.add_argument('-d', '--improvement_direction', type=int, default=0, |
| 2507 help='An integer number representing the direction of ' |
| 2508 'improvement. 1 for higher is better, -1 for lower ' |
| 2509 'is better, 0 for ignore (default).') |
| 2510 group.add_argument('-r', '--repeat_test_count', type=int, default=20, |
| 2511 choices=range(1, 101), |
| 2512 help='The number of times to repeat the performance ' |
| 2513 'test. Values will be clamped to range [1, 100]. ' |
| 2514 'Default value is 20.') |
| 2515 group.add_argument('--max_time_minutes', type=int, default=20, |
| 2516 choices=range(1, 61), |
| 2517 help='The maximum time (in minutes) to take running the ' |
| 2518 'performance tests. The script will run the ' |
| 2519 'performance tests according to ' |
| 2520 '--repeat_test_count, so long as it doesn\'t exceed' |
| 2521 ' --max_time_minutes. Values will be clamped to ' |
| 2522 'range [1, 60]. Default value is 20.') |
| 2523 group.add_argument('-t', '--truncate_percent', type=int, default=25, |
| 2524 help='The highest/lowest % are discarded to form a ' |
| 2525 'truncated mean. Values will be clamped to range ' |
| 2526 '[0, 25]. Default value is 25 (highest/lowest 25% ' |
| 2527 'will be discarded).') |
| 2528 group.add_argument('--bisect_mode', default=bisect_utils.BISECT_MODE_MEAN, |
| 2529 choices=[bisect_utils.BISECT_MODE_MEAN, |
| 2530 bisect_utils.BISECT_MODE_STD_DEV, |
| 2531 bisect_utils.BISECT_MODE_RETURN_CODE], |
| 2532 help='The bisect mode. Choices are to bisect on the ' |
| 2533 'difference in mean, std_dev, or return_code.') |
| 2534 |
| 2535 @staticmethod |
| 2536 def _AddBuildOptionsGroup(parser): |
| 2537 group = parser.add_argument_group('Build options') |
| 2538 group.add_argument('-w', '--working_directory', |
| 2539 help='Path to the working directory where the script ' |
| 2540 'will do an initial checkout of the chromium depot. The ' |
| 2541 'files will be placed in a subdirectory "bisect" under ' |
| 2542 'working_directory and that will be used to perform the ' |
| 2543 'bisection. This parameter is optional, if it is not ' |
| 2544 'supplied, the script will work from the current depot.') |
| 2545 group.add_argument('--build_preference', type='choice', |
| 2546 choices=['msvs', 'ninja', 'make'], |
| 2547 help='The preferred build system to use. On linux/mac ' |
| 2548 'the options are make/ninja. On Windows, the ' |
| 2549 'options are msvs/ninja.') |
| 2550 group.add_argument('--target_platform', type='choice', default='chromium', |
| 2551 choices=['chromium', 'android', 'android-chrome'], |
| 2552 help='The target platform. Choices are "chromium" ' |
| 2553 '(current platform), or "android". If you specify ' |
| 2554 'something other than "chromium", you must be ' |
| 2555 'properly set up to build that platform.') |
| 2556 group.add_argument('--no_custom_deps', dest='no_custom_deps', |
| 2557 action='store_true', default=False, |
| 2558 help='Run the script with custom_deps or not.') |
| 2559 group.add_argument('--extra_src', |
| 2560 help='Path to a script which can be used to modify the ' |
| 2561 'bisect script\'s behavior.') |
| 2562 group.add_argument('--use_goma', action='store_true', |
| 2563 help='Add a bunch of extra threads for goma, and enable ' |
| 2564 'goma') |
| 2565 group.add_argument('--goma_dir', |
| 2566 help='Path to goma tools (or system default if not ' |
| 2567 'specified).') |
| 2568 group.add_argument('--goma_threads', type=int, default='64', |
| 2569 help='Number of threads for goma, only if using goma.') |
| 2570 group.add_argument('--output_buildbot_annotations', action='store_true', |
| 2571 help='Add extra annotation output for buildbot.') |
| 2572 group.add_argument('--gs_bucket', default='', dest='gs_bucket', |
| 2573 help='Name of Google Storage bucket to upload or ' |
| 2574 'download build. e.g., chrome-perf') |
| 2575 group.add_argument('--target_arch', type='choice', default='ia32', |
| 2576 dest='target_arch', choices=['ia32', 'x64', 'arm'], |
| 2577 help='The target build architecture. Choices are "ia32" ' |
| 2578 '(default), "x64" or "arm".') |
| 2579 group.add_argument('--target_build_type', type='choice', default='Release', |
| 2580 choices=['Release', 'Debug'], |
| 2581 help='The target build type. Choices are "Release" ' |
| 2582 '(default), or "Debug".') |
| 2583 group.add_argument('--builder_host', dest='builder_host', |
| 2584 help='Host address of server to produce build by ' |
| 2585 'posting try job request.') |
| 2586 group.add_argument('--builder_port', dest='builder_port', type='int', |
| 2587 help='HTTP port of the server to produce build by ' |
| 2588 'posting try job request.') |
| 2589 |
| 2590 @staticmethod |
| 2591 def _AddDebugOptionsGroup(parser): |
| 2592 group = parser.add_argument_group('Debug options') |
| 2593 group.add_argument('--debug_ignore_build', action='store_true', |
| 2594 help='DEBUG: Don\'t perform builds.') |
| 2595 group.add_argument('--debug_ignore_sync', action='store_true', |
| 2596 help='DEBUG: Don\'t perform syncs.') |
| 2597 group.add_argument('--debug_ignore_perf_test', action='store_true', |
| 2598 help='DEBUG: Don\'t perform performance tests.') |
| 2599 group.add_argument('--debug_ignore_regression_confidence', |
| 2600 action='store_true', |
| 2601 help='DEBUG: Don\'t score the confidence of the initial ' |
| 2602 'good and bad revisions\' test results.') |
| 2603 group.add_argument('--debug_fake_first_test_mean', type='int', default='0', |
| 2604 help='DEBUG: When faking performance tests, return this ' |
| 2605 'value as the mean of the first performance test, ' |
| 2606 'and return a mean of 0.0 for further tests.') |
| 2607 return group |
| 2608 |
| 2609 @classmethod |
| 2610 def _CreateCommandLineParser(cls): |
| 2626 """Creates a parser with bisect options. | 2611 """Creates a parser with bisect options. |
| 2627 | 2612 |
| 2628 Returns: | 2613 Returns: |
| 2629 An instance of optparse.OptionParser. | 2614 An instance of optparse.OptionParser. |
| 2630 """ | 2615 """ |
| 2631 usage = ('%prog [options] [-- chromium-options]\n' | 2616 usage = ('%prog [options] [-- chromium-options]\n' |
| 2632 'Perform binary search on revision history to find a minimal ' | 2617 'Perform binary search on revision history to find a minimal ' |
| 2633 'range of revisions where a performance metric regressed.\n') | 2618 'range of revisions where a performance metric regressed.\n') |
| 2634 | 2619 |
| 2635 parser = optparse.OptionParser(usage=usage) | 2620 parser = argparse.ArgumentParser(usage=usage) |
| 2636 | 2621 cls._AddBisectOptionsGroup(parser) |
| 2637 group = optparse.OptionGroup(parser, 'Bisect options') | 2622 cls._AddBuildOptionsGroup(parser) |
| 2638 group.add_option('-c', '--command', | 2623 cls._AddDebugOptionsGroup(parser) |
| 2639 type='str', | |
| 2640 help='A command to execute your performance test at' + | |
| 2641 ' each point in the bisection.') | |
| 2642 group.add_option('-b', '--bad_revision', | |
| 2643 type='str', | |
| 2644 help='A bad revision to start bisection. ' + | |
| 2645 'Must be later than good revision. May be either a git' + | |
| 2646 ' or svn revision.') | |
| 2647 group.add_option('-g', '--good_revision', | |
| 2648 type='str', | |
| 2649 help='A revision to start bisection where performance' + | |
| 2650 ' test is known to pass. Must be earlier than the ' + | |
| 2651 'bad revision. May be either a git or svn revision.') | |
| 2652 group.add_option('-m', '--metric', | |
| 2653 type='str', | |
| 2654 help='The desired metric to bisect on. For example ' + | |
| 2655 '"vm_rss_final_b/vm_rss_f_b"') | |
| 2656 group.add_option('-d', '--improvement_direction', | |
| 2657 type='int', | |
| 2658 default=0, | |
| 2659 help='An integer number representing the direction of ' + | |
| 2660 'improvement. 1 for higher is better, -1 for lower is ' + | |
| 2661 'better, 0 for ignore (default).') | |
| 2662 group.add_option('-r', '--repeat_test_count', | |
| 2663 type='int', | |
| 2664 default=20, | |
| 2665 help='The number of times to repeat the performance ' | |
| 2666 'test. Values will be clamped to range [1, 100]. ' | |
| 2667 'Default value is 20.') | |
| 2668 group.add_option('--max_time_minutes', | |
| 2669 type='int', | |
| 2670 default=20, | |
| 2671 help='The maximum time (in minutes) to take running the ' | |
| 2672 'performance tests. The script will run the performance ' | |
| 2673 'tests according to --repeat_test_count, so long as it ' | |
| 2674 'doesn\'t exceed --max_time_minutes. Values will be ' | |
| 2675 'clamped to range [1, 60].' | |
| 2676 'Default value is 20.') | |
| 2677 group.add_option('-t', '--truncate_percent', | |
| 2678 type='int', | |
| 2679 default=25, | |
| 2680 help='The highest/lowest % are discarded to form a ' | |
| 2681 'truncated mean. Values will be clamped to range [0, ' | |
| 2682 '25]. Default value is 25 (highest/lowest 25% will be ' | |
| 2683 'discarded).') | |
| 2684 group.add_option('--bisect_mode', | |
| 2685 type='choice', | |
| 2686 choices=[bisect_utils.BISECT_MODE_MEAN, | |
| 2687 bisect_utils.BISECT_MODE_STD_DEV, | |
| 2688 bisect_utils.BISECT_MODE_RETURN_CODE], | |
| 2689 default=bisect_utils.BISECT_MODE_MEAN, | |
| 2690 help='The bisect mode. Choices are to bisect on the ' | |
| 2691 'difference in mean, std_dev, or return_code.') | |
| 2692 parser.add_option_group(group) | |
| 2693 | |
| 2694 group = optparse.OptionGroup(parser, 'Build options') | |
| 2695 group.add_option('-w', '--working_directory', | |
| 2696 type='str', | |
| 2697 help='Path to the working directory where the script ' | |
| 2698 'will do an initial checkout of the chromium depot. The ' | |
| 2699 'files will be placed in a subdirectory "bisect" under ' | |
| 2700 'working_directory and that will be used to perform the ' | |
| 2701 'bisection. This parameter is optional, if it is not ' | |
| 2702 'supplied, the script will work from the current depot.') | |
| 2703 group.add_option('--build_preference', | |
| 2704 type='choice', | |
| 2705 choices=['msvs', 'ninja', 'make'], | |
| 2706 help='The preferred build system to use. On linux/mac ' | |
| 2707 'the options are make/ninja. On Windows, the options ' | |
| 2708 'are msvs/ninja.') | |
| 2709 group.add_option('--target_platform', | |
| 2710 type='choice', | |
| 2711 choices=['chromium', 'cros', 'android', 'android-chrome'], | |
| 2712 default='chromium', | |
| 2713 help='The target platform. Choices are "chromium" ' | |
| 2714 '(current platform), "cros", or "android". If you ' | |
| 2715 'specify something other than "chromium", you must be ' | |
| 2716 'properly set up to build that platform.') | |
| 2717 group.add_option('--no_custom_deps', | |
| 2718 dest='no_custom_deps', | |
| 2719 action='store_true', | |
| 2720 default=False, | |
| 2721 help='Run the script with custom_deps or not.') | |
| 2722 group.add_option('--extra_src', | |
| 2723 type='str', | |
| 2724 help='Path to a script which can be used to modify ' | |
| 2725 'the bisect script\'s behavior.') | |
| 2726 group.add_option('--cros_board', | |
| 2727 type='str', | |
| 2728 help='The cros board type to build.') | |
| 2729 group.add_option('--cros_remote_ip', | |
| 2730 type='str', | |
| 2731 help='The remote machine to image to.') | |
| 2732 group.add_option('--use_goma', | |
| 2733 action='store_true', | |
| 2734 help='Add a bunch of extra threads for goma, and enable ' | |
| 2735 'goma') | |
| 2736 group.add_option('--goma_dir', | |
| 2737 help='Path to goma tools (or system default if not ' | |
| 2738 'specified).') | |
| 2739 group.add_option('--goma_threads', | |
| 2740 type='int', | |
| 2741 default='64', | |
| 2742 help='Number of threads for goma, only if using goma.') | |
| 2743 group.add_option('--output_buildbot_annotations', | |
| 2744 action='store_true', | |
| 2745 help='Add extra annotation output for buildbot.') | |
| 2746 group.add_option('--gs_bucket', | |
| 2747 default='', | |
| 2748 dest='gs_bucket', | |
| 2749 type='str', | |
| 2750 help=('Name of Google Storage bucket to upload or ' | |
| 2751 'download build. e.g., chrome-perf')) | |
| 2752 group.add_option('--target_arch', | |
| 2753 type='choice', | |
| 2754 choices=['ia32', 'x64', 'arm'], | |
| 2755 default='ia32', | |
| 2756 dest='target_arch', | |
| 2757 help=('The target build architecture. Choices are "ia32" ' | |
| 2758 '(default), "x64" or "arm".')) | |
| 2759 group.add_option('--target_build_type', | |
| 2760 type='choice', | |
| 2761 choices=['Release', 'Debug'], | |
| 2762 default='Release', | |
| 2763 help='The target build type. Choices are "Release" ' | |
| 2764 '(default), or "Debug".') | |
| 2765 group.add_option('--builder_host', | |
| 2766 dest='builder_host', | |
| 2767 type='str', | |
| 2768 help=('Host address of server to produce build by posting' | |
| 2769 ' try job request.')) | |
| 2770 group.add_option('--builder_port', | |
| 2771 dest='builder_port', | |
| 2772 type='int', | |
| 2773 help=('HTTP port of the server to produce build by posting' | |
| 2774 ' try job request.')) | |
| 2775 parser.add_option_group(group) | |
| 2776 | |
| 2777 group = optparse.OptionGroup(parser, 'Debug options') | |
| 2778 group.add_option('--debug_ignore_build', | |
| 2779 action='store_true', | |
| 2780 help='DEBUG: Don\'t perform builds.') | |
| 2781 group.add_option('--debug_ignore_sync', | |
| 2782 action='store_true', | |
| 2783 help='DEBUG: Don\'t perform syncs.') | |
| 2784 group.add_option('--debug_ignore_perf_test', | |
| 2785 action='store_true', | |
| 2786 help='DEBUG: Don\'t perform performance tests.') | |
| 2787 group.add_option('--debug_ignore_regression_confidence', | |
| 2788 action='store_true', | |
| 2789 help='DEBUG: Don\'t score the confidence of the initial ' | |
| 2790 'good and bad revisions\' test results.') | |
| 2791 group.add_option('--debug_fake_first_test_mean', | |
| 2792 type='int', | |
| 2793 default='0', | |
| 2794 help=('DEBUG: When faking performance tests, return this ' | |
| 2795 'value as the mean of the first performance test, ' | |
| 2796 'and return a mean of 0.0 for further tests.')) | |
| 2797 parser.add_option_group(group) | |
| 2798 return parser | 2624 return parser |
| 2799 | 2625 |
| 2800 def ParseCommandLine(self): | 2626 def ParseCommandLine(self): |
| 2801 """Parses the command line for bisect options.""" | 2627 """Parses the command line for bisect options.""" |
| 2802 parser = self._CreateCommandLineParser() | 2628 parser = self._CreateCommandLineParser() |
| 2803 opts, _ = parser.parse_args() | 2629 opts, _ = parser.parse_args() |
| 2804 | 2630 |
| 2805 try: | 2631 try: |
| 2806 if not opts.command: | |
| 2807 raise RuntimeError('missing required parameter: --command') | |
| 2808 | |
| 2809 if not opts.good_revision: | |
| 2810 raise RuntimeError('missing required parameter: --good_revision') | |
| 2811 | |
| 2812 if not opts.bad_revision: | |
| 2813 raise RuntimeError('missing required parameter: --bad_revision') | |
| 2814 | |
| 2815 if (not opts.metric and | 2632 if (not opts.metric and |
| 2816 opts.bisect_mode != bisect_utils.BISECT_MODE_RETURN_CODE): | 2633 opts.bisect_mode != bisect_utils.BISECT_MODE_RETURN_CODE): |
| 2817 raise RuntimeError('missing required parameter: --metric') | 2634 raise RuntimeError('missing required parameter: --metric') |
| 2818 | 2635 |
| 2819 if opts.gs_bucket: | 2636 if opts.gs_bucket: |
| 2820 if not cloud_storage.List(opts.gs_bucket): | 2637 if not cloud_storage.List(opts.gs_bucket): |
| 2821 raise RuntimeError('Invalid Google Storage: gs://%s' % opts.gs_bucket) | 2638 raise RuntimeError('Invalid Google Storage: gs://%s' % opts.gs_bucket) |
| 2822 if not opts.builder_host: | 2639 if not opts.builder_host: |
| 2823 raise RuntimeError('Must specify try server host name using ' | 2640 raise RuntimeError('Must specify try server host name using ' |
| 2824 '--builder_host when gs_bucket is used.') | 2641 '--builder_host when gs_bucket is used.') |
| 2825 if not opts.builder_port: | 2642 if not opts.builder_port: |
| 2826 raise RuntimeError('Must specify try server port number using ' | 2643 raise RuntimeError('Must specify try server port number using ' |
| 2827 '--builder_port when gs_bucket is used.') | 2644 '--builder_port when gs_bucket is used.') |
| 2828 if opts.target_platform == 'cros': | |
| 2829 # Run sudo up front to make sure credentials are cached for later. | |
| 2830 print 'Sudo is required to build cros:' | |
| 2831 print | |
| 2832 bisect_utils.RunProcess(['sudo', 'true']) | |
| 2833 | |
| 2834 if not opts.cros_board: | |
| 2835 raise RuntimeError('missing required parameter: --cros_board') | |
| 2836 | |
| 2837 if not opts.cros_remote_ip: | |
| 2838 raise RuntimeError('missing required parameter: --cros_remote_ip') | |
| 2839 | |
| 2840 if not opts.working_directory: | |
| 2841 raise RuntimeError('missing required parameter: --working_directory') | |
| 2842 | 2645 |
| 2843 if opts.bisect_mode != bisect_utils.BISECT_MODE_RETURN_CODE: | 2646 if opts.bisect_mode != bisect_utils.BISECT_MODE_RETURN_CODE: |
| 2844 metric_values = opts.metric.split('/') | 2647 metric_values = opts.metric.split('/') |
| 2845 if len(metric_values) != 2: | 2648 if len(metric_values) != 2: |
| 2846 raise RuntimeError('Invalid metric specified: [%s]' % opts.metric) | 2649 raise RuntimeError('Invalid metric specified: [%s]' % opts.metric) |
| 2847 opts.metric = metric_values | 2650 opts.metric = metric_values |
| 2848 | 2651 |
| 2849 opts.repeat_test_count = min(max(opts.repeat_test_count, 1), 100) | 2652 opts.truncate_percent = min(max(opts.truncate_percent, 0), 25) / 100.0 |
| 2850 opts.max_time_minutes = min(max(opts.max_time_minutes, 1), 60) | |
| 2851 opts.truncate_percent = min(max(opts.truncate_percent, 0), 25) | |
| 2852 opts.truncate_percent = opts.truncate_percent / 100.0 | |
| 2853 | 2653 |
| 2854 for k, v in opts.__dict__.iteritems(): | 2654 for k, v in opts.__dict__.iteritems(): |
| 2855 assert hasattr(self, k), 'Invalid %s attribute in BisectOptions.' % k | 2655 assert hasattr(self, k), 'Invalid %s attribute in BisectOptions.' % k |
| 2856 setattr(self, k, v) | 2656 setattr(self, k, v) |
| 2857 except RuntimeError, e: | 2657 except RuntimeError, e: |
| 2858 output_string = StringIO.StringIO() | 2658 output_string = StringIO.StringIO() |
| 2859 parser.print_help(file=output_string) | 2659 parser.print_help(file=output_string) |
| 2860 error_message = '%s\n\n%s' % (e.message, output_string.getvalue()) | 2660 error_message = '%s\n\n%s' % (e.message, output_string.getvalue()) |
| 2861 output_string.close() | 2661 output_string.close() |
| 2862 raise RuntimeError(error_message) | 2662 raise RuntimeError(error_message) |
| (...skipping 66 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 2929 | 2729 |
| 2930 if not source_control.IsInGitRepository(): | 2730 if not source_control.IsInGitRepository(): |
| 2931 raise RuntimeError( | 2731 raise RuntimeError( |
| 2932 'Sorry, only the git workflow is supported at the moment.') | 2732 'Sorry, only the git workflow is supported at the moment.') |
| 2933 | 2733 |
| 2934 # gClient sync seems to fail if you're not in master branch. | 2734 # gClient sync seems to fail if you're not in master branch. |
| 2935 if (not source_control.IsInProperBranch() and | 2735 if (not source_control.IsInProperBranch() and |
| 2936 not opts.debug_ignore_sync and | 2736 not opts.debug_ignore_sync and |
| 2937 not opts.working_directory): | 2737 not opts.working_directory): |
| 2938 raise RuntimeError('You must switch to master branch to run bisection.') | 2738 raise RuntimeError('You must switch to master branch to run bisection.') |
| 2939 bisect_test = BisectPerformanceMetrics(opts) | 2739 bisect_test = BisectPerformanceMetrics(opts, os.getcwd()) |
| 2940 bisect_printer = BisectPrinter(opts, bisect_test.depot_registry) | |
| 2941 try: | 2740 try: |
| 2942 results = bisect_test.Run(opts.command, opts.bad_revision, | 2741 results = bisect_test.Run(opts.command, opts.bad_revision, |
| 2943 opts.good_revision, opts.metric) | 2742 opts.good_revision, opts.metric) |
| 2944 if results.error: | 2743 if results.error: |
| 2945 raise RuntimeError(results.error) | 2744 raise RuntimeError(results.error) |
| 2946 bisect_printer.FormatAndPrintResults(results) | 2745 bisect_test.printer.FormatAndPrintResults(results) |
| 2947 return 0 | 2746 return 0 |
| 2948 finally: | 2747 finally: |
| 2949 bisect_test.PerformCleanup() | 2748 bisect_test.PerformCleanup() |
| 2950 except RuntimeError, e: | 2749 except RuntimeError, e: |
| 2951 if opts.output_buildbot_annotations: | 2750 if opts.output_buildbot_annotations: |
| 2952 # The perf dashboard scrapes the "results" step in order to comment on | 2751 # The perf dashboard scrapes the "results" step in order to comment on |
| 2953 # bugs. If you change this, please update the perf dashboard as well. | 2752 # bugs. If you change this, please update the perf dashboard as well. |
| 2954 bisect_utils.OutputAnnotationStepStart('Results') | 2753 bisect_utils.OutputAnnotationStepStart('Results') |
| 2955 print 'Error: ', e.message | 2754 print 'Error: ', e.message |
| 2956 logging.warn('A RuntimeError was caught: %s', e.message) | 2755 logging.warn('A RuntimeError was caught: %s', e.message) |
| 2957 if opts.output_buildbot_annotations: | 2756 if opts.output_buildbot_annotations: |
| 2958 bisect_utils.OutputAnnotationStepClosed() | 2757 bisect_utils.OutputAnnotationStepClosed() |
| 2959 return 1 | 2758 return 1 |
| 2960 | 2759 |
| 2961 | 2760 |
| 2962 if __name__ == '__main__': | 2761 if __name__ == '__main__': |
| 2963 sys.exit(main()) | 2762 sys.exit(main()) |
| OLD | NEW |