Chromium Code Reviews| 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 850 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 861 if step_count: | 861 if step_count: |
| 862 step_perf_time_avg = step_perf_time_avg / step_count | 862 step_perf_time_avg = step_perf_time_avg / step_count |
| 863 step_build_time_avg = step_build_time_avg / step_count | 863 step_build_time_avg = step_build_time_avg / step_count |
| 864 print | 864 print |
| 865 print 'Average build time : %s' % datetime.timedelta( | 865 print 'Average build time : %s' % datetime.timedelta( |
| 866 seconds=int(step_build_time_avg)) | 866 seconds=int(step_build_time_avg)) |
| 867 print 'Average test time : %s' % datetime.timedelta( | 867 print 'Average test time : %s' % datetime.timedelta( |
| 868 seconds=int(step_perf_time_avg)) | 868 seconds=int(step_perf_time_avg)) |
| 869 | 869 |
| 870 | 870 |
| 871 def _FindOtherRegressions(revision_data_sorted, bad_greater_than_good): | 871 class BisectResults(object): |
|
ojan
2014/09/19 18:17:00
All these classes should do in their own files. It
Sergiy Byelozyorov
2014/09/22 19:30:12
Done.
| |
| 872 """Compiles a list of other possible regressions from the revision data. | 872 """This class holds the results of the bisect.""" |
|
ojan
2014/09/19 18:17:00
+1 to removing comments like this. This doesn't pr
Sergiy Byelozyorov
2014/09/22 19:30:12
Done.
| |
| 873 | 873 |
| 874 Args: | 874 def __init__(self, depot_manager, source_control): |
| 875 revision_data_sorted: Sorted list of (revision, revision data) pairs. | 875 self._depot_manager = depot_manager |
| 876 bad_greater_than_good: Whether the result value at the "bad" revision is | 876 self.revision_data = {} |
| 877 numerically greater than the result value at the "good" revision. | 877 self.error = None |
| 878 | 878 self._source_control = source_control |
| 879 Returns: | 879 |
| 880 A list of [current_rev, previous_rev, confidence] for other places where | 880 @staticmethod |
| 881 there may have been a regression. | 881 def _FindOtherRegressions(revision_data_sorted, bad_greater_than_good): |
| 882 """ | 882 """Compiles a list of other possible regressions from the revision data. |
| 883 other_regressions = [] | 883 |
| 884 previous_values = [] | 884 Args: |
| 885 previous_id = None | 885 revision_data_sorted: Sorted list of (revision, revision data) pairs. |
| 886 for current_id, current_data in revision_data_sorted: | 886 bad_greater_than_good: Whether the result value at the "bad" revision is |
| 887 current_values = current_data['value'] | 887 numerically greater than the result value at the "good" revision. |
| 888 if current_values: | 888 |
| 889 current_values = current_values['values'] | 889 Returns: |
| 890 if previous_values: | 890 A list of [current_rev, previous_rev, confidence] for other places where |
| 891 confidence = ConfidenceScore(previous_values, [current_values]) | 891 there may have been a regression. |
| 892 mean_of_prev_runs = math_utils.Mean(sum(previous_values, [])) | 892 """ |
| 893 mean_of_current_runs = math_utils.Mean(current_values) | 893 other_regressions = [] |
| 894 | 894 previous_values = [] |
| 895 # Check that the potential regression is in the same direction as | 895 previous_id = None |
| 896 # the overall regression. If the mean of the previous runs < the | 896 for current_id, current_data in revision_data_sorted: |
| 897 # mean of the current runs, this local regression is in same | 897 current_values = current_data['value'] |
| 898 # direction. | 898 if current_values: |
| 899 prev_less_than_current = mean_of_prev_runs < mean_of_current_runs | 899 current_values = current_values['values'] |
| 900 is_same_direction = (prev_less_than_current if | 900 if previous_values: |
| 901 bad_greater_than_good else not prev_less_than_current) | 901 confidence = ConfidenceScore(previous_values, [current_values]) |
| 902 | 902 mean_of_prev_runs = math_utils.Mean(sum(previous_values, [])) |
| 903 # Only report potential regressions with high confidence. | 903 mean_of_current_runs = math_utils.Mean(current_values) |
| 904 if is_same_direction and confidence > 50: | 904 |
| 905 other_regressions.append([current_id, previous_id, confidence]) | 905 # Check that the potential regression is in the same direction as |
| 906 previous_values.append(current_values) | 906 # the overall regression. If the mean of the previous runs < the |
| 907 previous_id = current_id | 907 # mean of the current runs, this local regression is in same |
| 908 return other_regressions | 908 # direction. |
| 909 prev_less_than_current = mean_of_prev_runs < mean_of_current_runs | |
| 910 is_same_direction = (prev_less_than_current if | |
| 911 bad_greater_than_good else not prev_less_than_current) | |
| 912 | |
| 913 # Only report potential regressions with high confidence. | |
| 914 if is_same_direction and confidence > 50: | |
| 915 other_regressions.append([current_id, previous_id, confidence]) | |
| 916 previous_values.append(current_values) | |
| 917 previous_id = current_id | |
| 918 return other_regressions | |
| 919 | |
| 920 def GetResultsDict(self): | |
| 921 """Prepares and returns information about the final resulsts as a dict. | |
| 922 | |
| 923 Returns: | |
| 924 A dictionary with the following fields | |
|
ojan
2014/09/19 18:17:00
In my experience, documentation like this almost i
Sergiy Byelozyorov
2014/09/22 19:30:12
It actually took me a while to gather this info an
| |
| 925 | |
| 926 'first_working_revision': First good revision. | |
| 927 'last_broken_revision': Last bad revision. | |
| 928 'culprit_revisions': A list of revisions, which contain the bad change | |
| 929 introducing the failure. | |
| 930 'other_regressions': A list of tuples representing other regressions, | |
| 931 which may have occured. | |
| 932 'regression_size': For performance bisects, this is a relative change of | |
| 933 the mean metric value. For other bisects this field always contains | |
| 934 'zero-to-nonzero'. | |
| 935 'regression_std_err': For performance bisects, it is a pooled standard | |
| 936 error for groups of good and bad runs. Not used for other bisects. | |
| 937 'confidence': For performance bisects, it is a confidence that the good | |
| 938 and bad runs are distinct groups. Not used for non-performance | |
| 939 bisects. | |
| 940 'revision_data_sorted': dict mapping revision ids to data about that | |
| 941 revision. Each piece of revision data consists of a dict with the | |
| 942 following keys: | |
| 943 | |
| 944 'passed': Represents whether the performance test was successful at | |
| 945 that revision. Possible values include: 1 (passed), 0 (failed), | |
| 946 '?' (skipped), 'F' (build failed). | |
| 947 'depot': The depot that this revision is from (i.e. WebKit) | |
| 948 'external': If the revision is a 'src' revision, 'external' contains | |
| 949 the revisions of each of the external libraries. | |
| 950 'sort': A sort value for sorting the dict in order of commits. | |
| 951 | |
| 952 For example: | |
| 953 { | |
| 954 'CL #1': | |
| 955 { | |
| 956 'passed': False, | |
| 957 'depot': 'chromium', | |
| 958 'external': None, | |
| 959 'sort': 0 | |
| 960 } | |
| 961 } | |
| 962 """ | |
| 963 revision_data_sorted = sorted(self.revision_data.iteritems(), | |
| 964 key = lambda x: x[1]['sort']) | |
| 965 | |
| 966 # Find range where it possibly broke. | |
| 967 first_working_revision = None | |
| 968 first_working_revision_index = -1 | |
| 969 last_broken_revision = None | |
| 970 last_broken_revision_index = -1 | |
| 971 | |
| 972 culprit_revisions = [] | |
| 973 other_regressions = [] | |
| 974 regression_size = 0.0 | |
| 975 regression_std_err = 0.0 | |
| 976 confidence = 0.0 | |
| 977 | |
| 978 for i in xrange(len(revision_data_sorted)): | |
| 979 k, v = revision_data_sorted[i] | |
| 980 if v['passed'] == 1: | |
| 981 if not first_working_revision: | |
| 982 first_working_revision = k | |
| 983 first_working_revision_index = i | |
| 984 | |
| 985 if not v['passed']: | |
| 986 last_broken_revision = k | |
| 987 last_broken_revision_index = i | |
| 988 | |
| 989 if last_broken_revision != None and first_working_revision != None: | |
| 990 broken_means = [] | |
| 991 for i in xrange(0, last_broken_revision_index + 1): | |
| 992 if revision_data_sorted[i][1]['value']: | |
| 993 broken_means.append(revision_data_sorted[i][1]['value']['values']) | |
| 994 | |
| 995 working_means = [] | |
| 996 for i in xrange(first_working_revision_index, len(revision_data_sorted)): | |
| 997 if revision_data_sorted[i][1]['value']: | |
| 998 working_means.append(revision_data_sorted[i][1]['value']['values']) | |
| 999 | |
| 1000 # Flatten the lists to calculate mean of all values. | |
| 1001 working_mean = sum(working_means, []) | |
| 1002 broken_mean = sum(broken_means, []) | |
| 1003 | |
| 1004 # Calculate the approximate size of the regression | |
| 1005 mean_of_bad_runs = math_utils.Mean(broken_mean) | |
| 1006 mean_of_good_runs = math_utils.Mean(working_mean) | |
| 1007 | |
| 1008 regression_size = 100 * math_utils.RelativeChange(mean_of_good_runs, | |
| 1009 mean_of_bad_runs) | |
| 1010 if math.isnan(regression_size): | |
| 1011 regression_size = 'zero-to-nonzero' | |
| 1012 | |
| 1013 regression_std_err = math.fabs(math_utils.PooledStandardError( | |
| 1014 [working_mean, broken_mean]) / | |
| 1015 max(0.0001, min(mean_of_good_runs, mean_of_bad_runs))) * 100.0 | |
| 1016 | |
| 1017 # Give a "confidence" in the bisect. At the moment we use how distinct the | |
| 1018 # values are before and after the last broken revision, and how noisy the | |
| 1019 # overall graph is. | |
| 1020 confidence = ConfidenceScore(working_means, broken_means) | |
| 1021 | |
| 1022 culprit_revisions = [] | |
| 1023 | |
| 1024 cwd = os.getcwd() | |
| 1025 self._depot_manager.ChangeToDepotDir( | |
| 1026 self.revision_data[last_broken_revision]['depot']) | |
| 1027 | |
| 1028 if self.revision_data[last_broken_revision]['depot'] == 'cros': | |
| 1029 # Want to get a list of all the commits and what depots they belong | |
| 1030 # to so that we can grab info about each. | |
| 1031 cmd = ['repo', 'forall', '-c', | |
| 1032 'pwd ; git log --pretty=oneline --before=%d --after=%d' % ( | |
| 1033 last_broken_revision, first_working_revision + 1)] | |
| 1034 output, return_code = bisect_utils.RunProcessAndRetrieveOutput(cmd) | |
| 1035 | |
| 1036 changes = [] | |
| 1037 assert not return_code, ('An error occurred while running ' | |
| 1038 '"%s"' % ' '.join(cmd)) | |
| 1039 last_depot = None | |
| 1040 cwd = os.getcwd() | |
| 1041 for l in output.split('\n'): | |
| 1042 if l: | |
| 1043 # Output will be in form: | |
| 1044 # /path_to_depot | |
| 1045 # /path_to_other_depot | |
| 1046 # <SHA1> | |
| 1047 # /path_again | |
| 1048 # <SHA1> | |
| 1049 # etc. | |
| 1050 if l[0] == '/': | |
| 1051 last_depot = l | |
| 1052 else: | |
| 1053 contents = l.split(' ') | |
| 1054 if len(contents) > 1: | |
| 1055 changes.append([last_depot, contents[0]]) | |
| 1056 for c in changes: | |
| 1057 os.chdir(c[0]) | |
| 1058 info = self._source_control.QueryRevisionInfo(c[1]) | |
| 1059 culprit_revisions.append((c[1], info, None)) | |
| 1060 else: | |
| 1061 for i in xrange(last_broken_revision_index, len(revision_data_sorted)): | |
| 1062 k, v = revision_data_sorted[i] | |
| 1063 if k == first_working_revision: | |
| 1064 break | |
| 1065 self._depot_manager.ChangeToDepotDir(v['depot']) | |
| 1066 info = self._source_control.QueryRevisionInfo(k) | |
| 1067 culprit_revisions.append((k, info, v['depot'])) | |
| 1068 os.chdir(cwd) | |
| 1069 | |
| 1070 # Check for any other possible regression ranges. | |
| 1071 other_regressions = self._FindOtherRegressions( | |
| 1072 revision_data_sorted, mean_of_bad_runs > mean_of_good_runs) | |
| 1073 | |
| 1074 return { | |
| 1075 'first_working_revision': first_working_revision, | |
| 1076 'last_broken_revision': last_broken_revision, | |
| 1077 'culprit_revisions': culprit_revisions, | |
| 1078 'other_regressions': other_regressions, | |
| 1079 'regression_size': regression_size, | |
| 1080 'regression_std_err': regression_std_err, | |
| 1081 'confidence': confidence, | |
| 1082 'revision_data_sorted': revision_data_sorted | |
| 1083 } | |
| 1084 | |
| 1085 | |
| 1086 class DepotManager(object): | |
|
ojan
2014/09/19 18:17:00
Don't call it a manager. A manager can literally d
Sergiy Byelozyorov
2014/09/22 19:30:12
How about DepotDirectoryRegistry? I'd like to keep
ojan
2014/09/26 17:34:30
SGTM. That's much more clear.
| |
| 1087 """Manages depots (code repositories) and their directories.""" | |
| 1088 | |
| 1089 def __init__(self): | |
| 1090 self.depot_cwd = {} | |
| 1091 self.cros_cwd = os.path.join(SRC_DIR, 'tools', 'cros') | |
| 1092 for depot in DEPOT_NAMES: | |
| 1093 # The working directory of each depot is just the path to the depot, but | |
| 1094 # since we're already in 'src', we can skip that part. | |
| 1095 self.depot_cwd[depot] = os.path.join( | |
| 1096 SRC_DIR, DEPOT_DEPS_NAME[depot]['src'][4:]) | |
| 1097 | |
| 1098 def AddDepot(self, depot_name, depot_dir): | |
| 1099 self.depot_cwd[depot_name] = depot_dir | |
| 1100 | |
| 1101 def GetDepotDir(self, depot_name): | |
| 1102 if depot_name == 'chromium': | |
| 1103 return SRC_DIR | |
| 1104 elif depot_name == 'cros': | |
| 1105 return self.cros_cwd | |
| 1106 elif depot_name in DEPOT_NAMES: | |
| 1107 return self.depot_cwd[depot_name] | |
| 1108 else: | |
| 1109 assert False, ('Unknown depot [ %s ] encountered. Possibly a new one ' | |
| 1110 'was added without proper support?' % depot_name) | |
| 1111 | |
| 1112 def ChangeToDepotDir(self, depot_name): | |
| 1113 """Given a depot, changes to the appropriate working directory. | |
| 1114 | |
| 1115 Args: | |
| 1116 depot_name: The name of the depot (see DEPOT_NAMES). | |
| 1117 """ | |
| 1118 os.chdir(self.GetDepotDir(depot_name)) | |
| 909 | 1119 |
| 910 | 1120 |
| 911 class BisectPerformanceMetrics(object): | 1121 class BisectPerformanceMetrics(object): |
| 912 """This class contains functionality to perform a bisection of a range of | 1122 """This class contains functionality to perform a bisection of a range of |
| 913 revisions to narrow down where performance regressions may have occurred. | 1123 revisions to narrow down where performance regressions may have occurred. |
| 914 | 1124 |
| 915 The main entry-point is the Run method. | 1125 The main entry-point is the Run method. |
| 916 """ | 1126 """ |
| 917 | 1127 |
| 918 def __init__(self, source_control, opts): | 1128 def __init__(self, source_control, opts): |
| 919 super(BisectPerformanceMetrics, self).__init__() | 1129 super(BisectPerformanceMetrics, self).__init__() |
| 920 | 1130 |
| 921 self.opts = opts | 1131 self.opts = opts |
| 922 self.source_control = source_control | 1132 self.source_control = source_control |
| 923 self.cros_cwd = os.path.join(SRC_DIR, 'tools', 'cros') | 1133 self.depot_manager = DepotManager() |
| 924 self.depot_cwd = {} | |
| 925 self.cleanup_commands = [] | 1134 self.cleanup_commands = [] |
| 926 self.warnings = [] | 1135 self.warnings = [] |
| 927 self.builder = builder.Builder.FromOpts(opts) | 1136 self.builder = builder.Builder.FromOpts(opts) |
| 928 | 1137 |
| 929 for depot in DEPOT_NAMES: | |
| 930 # The working directory of each depot is just the path to the depot, but | |
| 931 # since we're already in 'src', we can skip that part. | |
| 932 self.depot_cwd[depot] = os.path.join( | |
| 933 SRC_DIR, DEPOT_DEPS_NAME[depot]['src'][4:]) | |
| 934 | 1138 |
| 935 def PerformCleanup(self): | 1139 def PerformCleanup(self): |
| 936 """Performs cleanup when script is finished.""" | 1140 """Performs cleanup when script is finished.""" |
| 937 os.chdir(SRC_DIR) | 1141 os.chdir(SRC_DIR) |
| 938 for c in self.cleanup_commands: | 1142 for c in self.cleanup_commands: |
| 939 if c[0] == 'mv': | 1143 if c[0] == 'mv': |
| 940 shutil.move(c[1], c[2]) | 1144 shutil.move(c[1], c[2]) |
| 941 else: | 1145 else: |
| 942 assert False, 'Invalid cleanup command.' | 1146 assert False, 'Invalid cleanup command.' |
| 943 | 1147 |
| 944 def GetRevisionList(self, depot, bad_revision, good_revision): | 1148 def GetRevisionList(self, depot, bad_revision, good_revision): |
| 945 """Retrieves a list of all the commits between the bad revision and | 1149 """Retrieves a list of all the commits between the bad revision and |
| 946 last known good revision.""" | 1150 last known good revision.""" |
| 947 | 1151 |
| 948 revision_work_list = [] | 1152 revision_work_list = [] |
| 949 | 1153 |
| 950 if depot == 'cros': | 1154 if depot == 'cros': |
| 951 revision_range_start = good_revision | 1155 revision_range_start = good_revision |
| 952 revision_range_end = bad_revision | 1156 revision_range_end = bad_revision |
| 953 | 1157 |
| 954 cwd = os.getcwd() | 1158 cwd = os.getcwd() |
| 955 self.ChangeToDepotWorkingDirectory('cros') | 1159 self.depot_manager.ChangeToDepotDir('cros') |
| 956 | 1160 |
| 957 # Print the commit timestamps for every commit in the revision time | 1161 # Print the commit timestamps for every commit in the revision time |
| 958 # range. We'll sort them and bisect by that. There is a remote chance that | 1162 # range. We'll sort them and bisect by that. There is a remote chance that |
| 959 # 2 (or more) commits will share the exact same timestamp, but it's | 1163 # 2 (or more) commits will share the exact same timestamp, but it's |
| 960 # probably safe to ignore that case. | 1164 # probably safe to ignore that case. |
| 961 cmd = ['repo', 'forall', '-c', | 1165 cmd = ['repo', 'forall', '-c', |
| 962 'git log --format=%%ct --before=%d --after=%d' % ( | 1166 'git log --format=%%ct --before=%d --after=%d' % ( |
| 963 revision_range_end, revision_range_start)] | 1167 revision_range_end, revision_range_start)] |
| 964 output, return_code = bisect_utils.RunProcessAndRetrieveOutput(cmd) | 1168 output, return_code = bisect_utils.RunProcessAndRetrieveOutput(cmd) |
| 965 | 1169 |
| 966 assert not return_code, ('An error occurred while running ' | 1170 assert not return_code, ('An error occurred while running ' |
| 967 '"%s"' % ' '.join(cmd)) | 1171 '"%s"' % ' '.join(cmd)) |
| 968 | 1172 |
| 969 os.chdir(cwd) | 1173 os.chdir(cwd) |
| 970 | 1174 |
| 971 revision_work_list = list(set( | 1175 revision_work_list = list(set( |
| 972 [int(o) for o in output.split('\n') if bisect_utils.IsStringInt(o)])) | 1176 [int(o) for o in output.split('\n') if bisect_utils.IsStringInt(o)])) |
| 973 revision_work_list = sorted(revision_work_list, reverse=True) | 1177 revision_work_list = sorted(revision_work_list, reverse=True) |
| 974 else: | 1178 else: |
| 975 cwd = self._GetDepotDirectory(depot) | 1179 cwd = self.depot_manager.GetDepotDir(depot) |
| 976 revision_work_list = self.source_control.GetRevisionList(bad_revision, | 1180 revision_work_list = self.source_control.GetRevisionList(bad_revision, |
| 977 good_revision, cwd=cwd) | 1181 good_revision, cwd=cwd) |
| 978 | 1182 |
| 979 return revision_work_list | 1183 return revision_work_list |
| 980 | 1184 |
| 981 def _GetV8BleedingEdgeFromV8TrunkIfMappable(self, revision): | 1185 def _GetV8BleedingEdgeFromV8TrunkIfMappable(self, revision): |
| 982 commit_position = self.source_control.GetCommitPosition(revision) | 1186 commit_position = self.source_control.GetCommitPosition(revision) |
| 983 | 1187 |
| 984 if bisect_utils.IsStringInt(commit_position): | 1188 if bisect_utils.IsStringInt(commit_position): |
| 985 # V8 is tricky to bisect, in that there are only a few instances when | 1189 # V8 is tricky to bisect, in that there are only a few instances when |
| 986 # we can dive into bleeding_edge and get back a meaningful result. | 1190 # we can dive into bleeding_edge and get back a meaningful result. |
| 987 # Try to detect a V8 "business as usual" case, which is when: | 1191 # Try to detect a V8 "business as usual" case, which is when: |
| 988 # 1. trunk revision N has description "Version X.Y.Z" | 1192 # 1. trunk revision N has description "Version X.Y.Z" |
| 989 # 2. bleeding_edge revision (N-1) has description "Prepare push to | 1193 # 2. bleeding_edge revision (N-1) has description "Prepare push to |
| 990 # trunk. Now working on X.Y.(Z+1)." | 1194 # trunk. Now working on X.Y.(Z+1)." |
| 991 # | 1195 # |
| 992 # As of 01/24/2014, V8 trunk descriptions are formatted: | 1196 # As of 01/24/2014, V8 trunk descriptions are formatted: |
| 993 # "Version 3.X.Y (based on bleeding_edge revision rZ)" | 1197 # "Version 3.X.Y (based on bleeding_edge revision rZ)" |
| 994 # So we can just try parsing that out first and fall back to the old way. | 1198 # So we can just try parsing that out first and fall back to the old way. |
| 995 v8_dir = self._GetDepotDirectory('v8') | 1199 v8_dir = self.depot_manager.GetDepotDir('v8') |
| 996 v8_bleeding_edge_dir = self._GetDepotDirectory('v8_bleeding_edge') | 1200 v8_bleeding_edge_dir = self.depot_manager.GetDepotDir('v8_bleeding_edge') |
| 997 | 1201 |
| 998 revision_info = self.source_control.QueryRevisionInfo(revision, | 1202 revision_info = self.source_control.QueryRevisionInfo(revision, |
| 999 cwd=v8_dir) | 1203 cwd=v8_dir) |
| 1000 | 1204 |
| 1001 version_re = re.compile("Version (?P<values>[0-9,.]+)") | 1205 version_re = re.compile("Version (?P<values>[0-9,.]+)") |
| 1002 | 1206 |
| 1003 regex_results = version_re.search(revision_info['subject']) | 1207 regex_results = version_re.search(revision_info['subject']) |
| 1004 | 1208 |
| 1005 if regex_results: | 1209 if regex_results: |
| 1006 git_revision = None | 1210 git_revision = None |
| (...skipping 19 matching lines...) Expand all Loading... | |
| 1026 | 1230 |
| 1027 if git_revision: | 1231 if git_revision: |
| 1028 revision_info = self.source_control.QueryRevisionInfo(git_revision, | 1232 revision_info = self.source_control.QueryRevisionInfo(git_revision, |
| 1029 cwd=v8_bleeding_edge_dir) | 1233 cwd=v8_bleeding_edge_dir) |
| 1030 | 1234 |
| 1031 if 'Prepare push to trunk' in revision_info['subject']: | 1235 if 'Prepare push to trunk' in revision_info['subject']: |
| 1032 return git_revision | 1236 return git_revision |
| 1033 return None | 1237 return None |
| 1034 | 1238 |
| 1035 def _GetNearestV8BleedingEdgeFromTrunk(self, revision, search_forward=True): | 1239 def _GetNearestV8BleedingEdgeFromTrunk(self, revision, search_forward=True): |
| 1036 cwd = self._GetDepotDirectory('v8') | 1240 cwd = self.depot_manager.GetDepotDir('v8') |
| 1037 cmd = ['log', '--format=%ct', '-1', revision] | 1241 cmd = ['log', '--format=%ct', '-1', revision] |
| 1038 output = bisect_utils.CheckRunGit(cmd, cwd=cwd) | 1242 output = bisect_utils.CheckRunGit(cmd, cwd=cwd) |
| 1039 commit_time = int(output) | 1243 commit_time = int(output) |
| 1040 commits = [] | 1244 commits = [] |
| 1041 | 1245 |
| 1042 if search_forward: | 1246 if search_forward: |
| 1043 cmd = ['log', '--format=%H', '-10', '--after=%d' % commit_time, | 1247 cmd = ['log', '--format=%H', '-10', '--after=%d' % commit_time, |
| 1044 'origin/master'] | 1248 'origin/master'] |
| 1045 output = bisect_utils.CheckRunGit(cmd, cwd=cwd) | 1249 output = bisect_utils.CheckRunGit(cmd, cwd=cwd) |
| 1046 output = output.split() | 1250 output = output.split() |
| (...skipping 41 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 1088 results = {} | 1292 results = {} |
| 1089 for depot_name, depot_data in DEPOT_DEPS_NAME.iteritems(): | 1293 for depot_name, depot_data in DEPOT_DEPS_NAME.iteritems(): |
| 1090 if (depot_data.get('platform') and | 1294 if (depot_data.get('platform') and |
| 1091 depot_data.get('platform') != os.name): | 1295 depot_data.get('platform') != os.name): |
| 1092 continue | 1296 continue |
| 1093 | 1297 |
| 1094 if (depot_data.get('recurse') and depot in depot_data.get('from')): | 1298 if (depot_data.get('recurse') and depot in depot_data.get('from')): |
| 1095 depot_data_src = depot_data.get('src') or depot_data.get('src_old') | 1299 depot_data_src = depot_data.get('src') or depot_data.get('src_old') |
| 1096 src_dir = deps_data.get(depot_data_src) | 1300 src_dir = deps_data.get(depot_data_src) |
| 1097 if src_dir: | 1301 if src_dir: |
| 1098 self.depot_cwd[depot_name] = os.path.join( | 1302 self.depot_manager.AddDepot( |
| 1099 SRC_DIR, depot_data_src[4:]) | 1303 depot_name, os.path.join(SRC_DIR, depot_data_src[4:])) |
| 1100 re_results = rxp.search(src_dir) | 1304 re_results = rxp.search(src_dir) |
| 1101 if re_results: | 1305 if re_results: |
| 1102 results[depot_name] = re_results.group('revision') | 1306 results[depot_name] = re_results.group('revision') |
| 1103 else: | 1307 else: |
| 1104 warning_text = ('Could not parse revision for %s while bisecting ' | 1308 warning_text = ('Could not parse revision for %s while bisecting ' |
| 1105 '%s' % (depot_name, depot)) | 1309 '%s' % (depot_name, depot)) |
| 1106 if not warning_text in self.warnings: | 1310 if not warning_text in self.warnings: |
| 1107 self.warnings.append(warning_text) | 1311 self.warnings.append(warning_text) |
| 1108 else: | 1312 else: |
| 1109 results[depot_name] = None | 1313 results[depot_name] = None |
| (...skipping 16 matching lines...) Expand all Loading... | |
| 1126 def _Get3rdPartyRevisions(self, depot): | 1330 def _Get3rdPartyRevisions(self, depot): |
| 1127 """Parses the DEPS file to determine WebKit/v8/etc... versions. | 1331 """Parses the DEPS file to determine WebKit/v8/etc... versions. |
| 1128 | 1332 |
| 1129 Args: | 1333 Args: |
| 1130 depot: A depot name. Should be in the DEPOT_NAMES list. | 1334 depot: A depot name. Should be in the DEPOT_NAMES list. |
| 1131 | 1335 |
| 1132 Returns: | 1336 Returns: |
| 1133 A dict in the format {depot: revision} if successful, otherwise None. | 1337 A dict in the format {depot: revision} if successful, otherwise None. |
| 1134 """ | 1338 """ |
| 1135 cwd = os.getcwd() | 1339 cwd = os.getcwd() |
| 1136 self.ChangeToDepotWorkingDirectory(depot) | 1340 self.depot_manager.ChangeToDepotDir(depot) |
| 1137 | 1341 |
| 1138 results = {} | 1342 results = {} |
| 1139 | 1343 |
| 1140 if depot == 'chromium' or depot == 'android-chrome': | 1344 if depot == 'chromium' or depot == 'android-chrome': |
| 1141 results = self._ParseRevisionsFromDEPSFile(depot) | 1345 results = self._ParseRevisionsFromDEPSFile(depot) |
| 1142 os.chdir(cwd) | 1346 os.chdir(cwd) |
| 1143 | 1347 |
| 1144 if depot == 'cros': | 1348 if depot == 'cros': |
| 1145 cmd = [ | 1349 cmd = [ |
| 1146 bisect_utils.CROS_SDK_PATH, | 1350 bisect_utils.CROS_SDK_PATH, |
| (...skipping 20 matching lines...) Expand all Loading... | |
| 1167 | 1371 |
| 1168 version = contents[2] | 1372 version = contents[2] |
| 1169 | 1373 |
| 1170 if contents[3] != '0': | 1374 if contents[3] != '0': |
| 1171 warningText = ('Chrome version: %s.%s but using %s.0 to bisect.' % | 1375 warningText = ('Chrome version: %s.%s but using %s.0 to bisect.' % |
| 1172 (version, contents[3], version)) | 1376 (version, contents[3], version)) |
| 1173 if not warningText in self.warnings: | 1377 if not warningText in self.warnings: |
| 1174 self.warnings.append(warningText) | 1378 self.warnings.append(warningText) |
| 1175 | 1379 |
| 1176 cwd = os.getcwd() | 1380 cwd = os.getcwd() |
| 1177 self.ChangeToDepotWorkingDirectory('chromium') | 1381 self.depot_manager.ChangeToDepotDir('chromium') |
| 1178 cmd = ['log', '-1', '--format=%H', | 1382 cmd = ['log', '-1', '--format=%H', |
| 1179 '--author=chrome-release@google.com', | 1383 '--author=chrome-release@google.com', |
| 1180 '--grep=to %s' % version, 'origin/master'] | 1384 '--grep=to %s' % version, 'origin/master'] |
| 1181 return_code = bisect_utils.CheckRunGit(cmd) | 1385 return_code = bisect_utils.CheckRunGit(cmd) |
| 1182 os.chdir(cwd) | 1386 os.chdir(cwd) |
| 1183 | 1387 |
| 1184 results['chromium'] = output.strip() | 1388 results['chromium'] = output.strip() |
| 1185 | 1389 |
| 1186 if depot == 'v8': | 1390 if depot == 'v8': |
| 1187 # We can't try to map the trunk revision to bleeding edge yet, because | 1391 # We can't try to map the trunk revision to bleeding edge yet, because |
| (...skipping 217 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 1405 Returns: | 1609 Returns: |
| 1406 Updated DEPS content as string if deps key is found, otherwise None. | 1610 Updated DEPS content as string if deps key is found, otherwise None. |
| 1407 """ | 1611 """ |
| 1408 # Check whether the depot and revision pattern in DEPS file vars | 1612 # Check whether the depot and revision pattern in DEPS file vars |
| 1409 # e.g. for webkit the format is "webkit_revision": "12345". | 1613 # e.g. for webkit the format is "webkit_revision": "12345". |
| 1410 deps_revision = re.compile(r'(?<="%s": ")([0-9]+)(?=")' % deps_key, | 1614 deps_revision = re.compile(r'(?<="%s": ")([0-9]+)(?=")' % deps_key, |
| 1411 re.MULTILINE) | 1615 re.MULTILINE) |
| 1412 new_data = None | 1616 new_data = None |
| 1413 if re.search(deps_revision, deps_contents): | 1617 if re.search(deps_revision, deps_contents): |
| 1414 commit_position = self.source_control.GetCommitPosition( | 1618 commit_position = self.source_control.GetCommitPosition( |
| 1415 git_revision, self._GetDepotDirectory(depot)) | 1619 git_revision, self.depot_manager.GetDepotDir(depot)) |
| 1416 if not commit_position: | 1620 if not commit_position: |
| 1417 print 'Could not determine commit position for %s' % git_revision | 1621 print 'Could not determine commit position for %s' % git_revision |
| 1418 return None | 1622 return None |
| 1419 # Update the revision information for the given depot | 1623 # Update the revision information for the given depot |
| 1420 new_data = re.sub(deps_revision, str(commit_position), deps_contents) | 1624 new_data = re.sub(deps_revision, str(commit_position), deps_contents) |
| 1421 else: | 1625 else: |
| 1422 # Check whether the depot and revision pattern in DEPS file vars | 1626 # Check whether the depot and revision pattern in DEPS file vars |
| 1423 # e.g. for webkit the format is "webkit_revision": "559a6d4ab7a84c539..". | 1627 # e.g. for webkit the format is "webkit_revision": "559a6d4ab7a84c539..". |
| 1424 deps_revision = re.compile( | 1628 deps_revision = re.compile( |
| 1425 r'(?<=["\']%s["\']: ["\'])([a-fA-F0-9]{40})(?=["\'])' % deps_key, | 1629 r'(?<=["\']%s["\']: ["\'])([a-fA-F0-9]{40})(?=["\'])' % deps_key, |
| (...skipping 343 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 1769 # Some SVN depots were split into multiple git depots, so we need to | 1973 # Some SVN depots were split into multiple git depots, so we need to |
| 1770 # figure out for each mirror which git revision to grab. There's no | 1974 # figure out for each mirror which git revision to grab. There's no |
| 1771 # guarantee that the SVN revision will exist for each of the dependent | 1975 # guarantee that the SVN revision will exist for each of the dependent |
| 1772 # depots, so we have to grep the git logs and grab the next earlier one. | 1976 # depots, so we have to grep the git logs and grab the next earlier one. |
| 1773 if (not is_base | 1977 if (not is_base |
| 1774 and DEPOT_DEPS_NAME[depot]['depends'] | 1978 and DEPOT_DEPS_NAME[depot]['depends'] |
| 1775 and self.source_control.IsGit()): | 1979 and self.source_control.IsGit()): |
| 1776 commit_position = self.source_control.GetCommitPosition(revision) | 1980 commit_position = self.source_control.GetCommitPosition(revision) |
| 1777 | 1981 |
| 1778 for d in DEPOT_DEPS_NAME[depot]['depends']: | 1982 for d in DEPOT_DEPS_NAME[depot]['depends']: |
| 1779 self.ChangeToDepotWorkingDirectory(d) | 1983 self.depot_manager.ChangeToDepotDir(d) |
| 1780 | 1984 |
| 1781 dependant_rev = self.source_control.ResolveToRevision( | 1985 dependant_rev = self.source_control.ResolveToRevision( |
| 1782 commit_position, d, DEPOT_DEPS_NAME, -1000) | 1986 commit_position, d, DEPOT_DEPS_NAME, -1000) |
| 1783 | 1987 |
| 1784 if dependant_rev: | 1988 if dependant_rev: |
| 1785 revisions_to_sync.append([d, dependant_rev]) | 1989 revisions_to_sync.append([d, dependant_rev]) |
| 1786 | 1990 |
| 1787 num_resolved = len(revisions_to_sync) | 1991 num_resolved = len(revisions_to_sync) |
| 1788 num_needed = len(DEPOT_DEPS_NAME[depot]['depends']) | 1992 num_needed = len(DEPOT_DEPS_NAME[depot]['depends']) |
| 1789 | 1993 |
| 1790 self.ChangeToDepotWorkingDirectory(depot) | 1994 self.depot_manager.ChangeToDepotDir(depot) |
| 1791 | 1995 |
| 1792 if not ((num_resolved - 1) == num_needed): | 1996 if not ((num_resolved - 1) == num_needed): |
| 1793 return None | 1997 return None |
| 1794 | 1998 |
| 1795 return revisions_to_sync | 1999 return revisions_to_sync |
| 1796 | 2000 |
| 1797 @staticmethod | 2001 @staticmethod |
| 1798 def PerformPreBuildCleanup(): | 2002 def PerformPreBuildCleanup(): |
| 1799 """Performs cleanup between runs.""" | 2003 """Performs cleanup between runs.""" |
| 1800 print 'Cleaning up between runs.' | 2004 print 'Cleaning up between runs.' |
| 1801 print | 2005 print |
| 1802 | 2006 |
| 1803 # Leaving these .pyc files around between runs may disrupt some perf tests. | 2007 # Leaving these .pyc files around between runs may disrupt some perf tests. |
| 1804 for (path, _, files) in os.walk(SRC_DIR): | 2008 for (path, _, files) in os.walk(SRC_DIR): |
| 1805 for cur_file in files: | 2009 for cur_file in files: |
| 1806 if cur_file.endswith('.pyc'): | 2010 if cur_file.endswith('.pyc'): |
| 1807 path_to_file = os.path.join(path, cur_file) | 2011 path_to_file = os.path.join(path, cur_file) |
| 1808 os.remove(path_to_file) | 2012 os.remove(path_to_file) |
| 1809 | 2013 |
| 1810 def PerformCrosChrootCleanup(self): | 2014 def PerformCrosChrootCleanup(self): |
| 1811 """Deletes the chroot. | 2015 """Deletes the chroot. |
| 1812 | 2016 |
| 1813 Returns: | 2017 Returns: |
| 1814 True if successful. | 2018 True if successful. |
| 1815 """ | 2019 """ |
| 1816 cwd = os.getcwd() | 2020 cwd = os.getcwd() |
| 1817 self.ChangeToDepotWorkingDirectory('cros') | 2021 self.depot_manager.ChangeToDepotDir('cros') |
| 1818 cmd = [bisect_utils.CROS_SDK_PATH, '--delete'] | 2022 cmd = [bisect_utils.CROS_SDK_PATH, '--delete'] |
| 1819 return_code = bisect_utils.RunProcess(cmd) | 2023 return_code = bisect_utils.RunProcess(cmd) |
| 1820 os.chdir(cwd) | 2024 os.chdir(cwd) |
| 1821 return not return_code | 2025 return not return_code |
| 1822 | 2026 |
| 1823 def CreateCrosChroot(self): | 2027 def CreateCrosChroot(self): |
| 1824 """Creates a new chroot. | 2028 """Creates a new chroot. |
| 1825 | 2029 |
| 1826 Returns: | 2030 Returns: |
| 1827 True if successful. | 2031 True if successful. |
| 1828 """ | 2032 """ |
| 1829 cwd = os.getcwd() | 2033 cwd = os.getcwd() |
| 1830 self.ChangeToDepotWorkingDirectory('cros') | 2034 self.depot_manager.ChangeToDepotDir('cros') |
| 1831 cmd = [bisect_utils.CROS_SDK_PATH, '--create'] | 2035 cmd = [bisect_utils.CROS_SDK_PATH, '--create'] |
| 1832 return_code = bisect_utils.RunProcess(cmd) | 2036 return_code = bisect_utils.RunProcess(cmd) |
| 1833 os.chdir(cwd) | 2037 os.chdir(cwd) |
| 1834 return not return_code | 2038 return not return_code |
| 1835 | 2039 |
| 1836 def _PerformPreSyncCleanup(self, depot): | 2040 def _PerformPreSyncCleanup(self, depot): |
| 1837 """Performs any necessary cleanup before syncing. | 2041 """Performs any necessary cleanup before syncing. |
| 1838 | 2042 |
| 1839 Args: | 2043 Args: |
| 1840 depot: Depot name. | 2044 depot: Depot name. |
| (...skipping 141 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 1982 """Syncs multiple depots to particular revisions. | 2186 """Syncs multiple depots to particular revisions. |
| 1983 | 2187 |
| 1984 Args: | 2188 Args: |
| 1985 revisions_to_sync: A list of (depot, revision) pairs to be synced. | 2189 revisions_to_sync: A list of (depot, revision) pairs to be synced. |
| 1986 sync_client: Program used to sync, e.g. "gclient", "repo". Can be None. | 2190 sync_client: Program used to sync, e.g. "gclient", "repo". Can be None. |
| 1987 | 2191 |
| 1988 Returns: | 2192 Returns: |
| 1989 True if successful, False otherwise. | 2193 True if successful, False otherwise. |
| 1990 """ | 2194 """ |
| 1991 for depot, revision in revisions_to_sync: | 2195 for depot, revision in revisions_to_sync: |
| 1992 self.ChangeToDepotWorkingDirectory(depot) | 2196 self.depot_manager.ChangeToDepotDir(depot) |
| 1993 | 2197 |
| 1994 if sync_client: | 2198 if sync_client: |
| 1995 self.PerformPreBuildCleanup() | 2199 self.PerformPreBuildCleanup() |
| 1996 | 2200 |
| 1997 # When using gclient to sync, you need to specify the depot you | 2201 # When using gclient to sync, you need to specify the depot you |
| 1998 # want so that all the dependencies sync properly as well. | 2202 # want so that all the dependencies sync properly as well. |
| 1999 # i.e. gclient sync src@<SHA1> | 2203 # i.e. gclient sync src@<SHA1> |
| 2000 if sync_client == 'gclient': | 2204 if sync_client == 'gclient': |
| 2001 revision = '%s@%s' % (DEPOT_DEPS_NAME[depot]['src'], revision) | 2205 revision = '%s@%s' % (DEPOT_DEPS_NAME[depot]['src'], revision) |
| 2002 | 2206 |
| (...skipping 20 matching lines...) Expand all Loading... | |
| 2023 dist_to_good_value = abs(current_value['std_dev'] - | 2227 dist_to_good_value = abs(current_value['std_dev'] - |
| 2024 known_good_value['std_dev']) | 2228 known_good_value['std_dev']) |
| 2025 dist_to_bad_value = abs(current_value['std_dev'] - | 2229 dist_to_bad_value = abs(current_value['std_dev'] - |
| 2026 known_bad_value['std_dev']) | 2230 known_bad_value['std_dev']) |
| 2027 else: | 2231 else: |
| 2028 dist_to_good_value = abs(current_value['mean'] - known_good_value['mean']) | 2232 dist_to_good_value = abs(current_value['mean'] - known_good_value['mean']) |
| 2029 dist_to_bad_value = abs(current_value['mean'] - known_bad_value['mean']) | 2233 dist_to_bad_value = abs(current_value['mean'] - known_bad_value['mean']) |
| 2030 | 2234 |
| 2031 return dist_to_good_value < dist_to_bad_value | 2235 return dist_to_good_value < dist_to_bad_value |
| 2032 | 2236 |
| 2033 def _GetDepotDirectory(self, depot_name): | |
| 2034 if depot_name == 'chromium': | |
| 2035 return SRC_DIR | |
| 2036 elif depot_name == 'cros': | |
| 2037 return self.cros_cwd | |
| 2038 elif depot_name in DEPOT_NAMES: | |
| 2039 return self.depot_cwd[depot_name] | |
| 2040 else: | |
| 2041 assert False, ('Unknown depot [ %s ] encountered. Possibly a new one ' | |
| 2042 'was added without proper support?' % depot_name) | |
| 2043 | |
| 2044 def ChangeToDepotWorkingDirectory(self, depot_name): | |
| 2045 """Given a depot, changes to the appropriate working directory. | |
| 2046 | |
| 2047 Args: | |
| 2048 depot_name: The name of the depot (see DEPOT_NAMES). | |
| 2049 """ | |
| 2050 os.chdir(self._GetDepotDirectory(depot_name)) | |
| 2051 | |
| 2052 def _FillInV8BleedingEdgeInfo(self, min_revision_data, max_revision_data): | 2237 def _FillInV8BleedingEdgeInfo(self, min_revision_data, max_revision_data): |
| 2053 r1 = self._GetNearestV8BleedingEdgeFromTrunk(min_revision_data['revision'], | 2238 r1 = self._GetNearestV8BleedingEdgeFromTrunk(min_revision_data['revision'], |
| 2054 search_forward=True) | 2239 search_forward=True) |
| 2055 r2 = self._GetNearestV8BleedingEdgeFromTrunk(max_revision_data['revision'], | 2240 r2 = self._GetNearestV8BleedingEdgeFromTrunk(max_revision_data['revision'], |
| 2056 search_forward=False) | 2241 search_forward=False) |
| 2057 min_revision_data['external']['v8_bleeding_edge'] = r1 | 2242 min_revision_data['external']['v8_bleeding_edge'] = r1 |
| 2058 max_revision_data['external']['v8_bleeding_edge'] = r2 | 2243 max_revision_data['external']['v8_bleeding_edge'] = r2 |
| 2059 | 2244 |
| 2060 if (not self._GetV8BleedingEdgeFromV8TrunkIfMappable( | 2245 if (not self._GetV8BleedingEdgeFromV8TrunkIfMappable( |
| 2061 min_revision_data['revision']) | 2246 min_revision_data['revision']) |
| (...skipping 55 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 2117 end_revision: End of the revision range. | 2302 end_revision: End of the revision range. |
| 2118 start_revision: Start of the revision range. | 2303 start_revision: Start of the revision range. |
| 2119 previous_revision: The last revision we synced to on |previous_depot|. | 2304 previous_revision: The last revision we synced to on |previous_depot|. |
| 2120 | 2305 |
| 2121 Returns: | 2306 Returns: |
| 2122 A list containing the revisions between |start_revision| and | 2307 A list containing the revisions between |start_revision| and |
| 2123 |end_revision| inclusive. | 2308 |end_revision| inclusive. |
| 2124 """ | 2309 """ |
| 2125 # Change into working directory of external library to run | 2310 # Change into working directory of external library to run |
| 2126 # subsequent commands. | 2311 # subsequent commands. |
| 2127 self.ChangeToDepotWorkingDirectory(current_depot) | 2312 self.depot_manager.ChangeToDepotDir(current_depot) |
| 2128 | 2313 |
| 2129 # V8 (and possibly others) is merged in periodically. Bisecting | 2314 # V8 (and possibly others) is merged in periodically. Bisecting |
| 2130 # this directory directly won't give much good info. | 2315 # this directory directly won't give much good info. |
| 2131 if DEPOT_DEPS_NAME[current_depot].has_key('custom_deps'): | 2316 if DEPOT_DEPS_NAME[current_depot].has_key('custom_deps'): |
| 2132 config_path = os.path.join(SRC_DIR, '..') | 2317 config_path = os.path.join(SRC_DIR, '..') |
| 2133 if bisect_utils.RunGClientAndCreateConfig(self.opts, | 2318 if bisect_utils.RunGClientAndCreateConfig(self.opts, |
| 2134 DEPOT_DEPS_NAME[current_depot]['custom_deps'], cwd=config_path): | 2319 DEPOT_DEPS_NAME[current_depot]['custom_deps'], cwd=config_path): |
| 2135 return [] | 2320 return [] |
| 2136 if bisect_utils.RunGClient( | 2321 if bisect_utils.RunGClient( |
| 2137 ['sync', '--revision', previous_revision], cwd=SRC_DIR): | 2322 ['sync', '--revision', previous_revision], cwd=SRC_DIR): |
| 2138 return [] | 2323 return [] |
| 2139 | 2324 |
| 2140 if current_depot == 'v8_bleeding_edge': | 2325 if current_depot == 'v8_bleeding_edge': |
| 2141 self.ChangeToDepotWorkingDirectory('chromium') | 2326 self.depot_manager.ChangeToDepotDir('chromium') |
| 2142 | 2327 |
| 2143 shutil.move('v8', 'v8.bak') | 2328 shutil.move('v8', 'v8.bak') |
| 2144 shutil.move('v8_bleeding_edge', 'v8') | 2329 shutil.move('v8_bleeding_edge', 'v8') |
| 2145 | 2330 |
| 2146 self.cleanup_commands.append(['mv', 'v8', 'v8_bleeding_edge']) | 2331 self.cleanup_commands.append(['mv', 'v8', 'v8_bleeding_edge']) |
| 2147 self.cleanup_commands.append(['mv', 'v8.bak', 'v8']) | 2332 self.cleanup_commands.append(['mv', 'v8.bak', 'v8']) |
| 2148 | 2333 |
| 2149 self.depot_cwd['v8_bleeding_edge'] = os.path.join(SRC_DIR, 'v8') | 2334 self.depot_manager.AddDepot( |
| 2150 self.depot_cwd['v8'] = os.path.join(SRC_DIR, 'v8.bak') | 2335 'v8_bleeding_edge', os.path.join(SRC_DIR, 'v8')) |
| 2336 self.depot_manager.AddDepot('v8', os.path.join(SRC_DIR, 'v8.bak')) | |
| 2151 | 2337 |
| 2152 self.ChangeToDepotWorkingDirectory(current_depot) | 2338 self.depot_manager.ChangeToDepotDir(current_depot) |
| 2153 | 2339 |
| 2154 depot_revision_list = self.GetRevisionList(current_depot, | 2340 depot_revision_list = self.GetRevisionList(current_depot, |
| 2155 end_revision, | 2341 end_revision, |
| 2156 start_revision) | 2342 start_revision) |
| 2157 | 2343 |
| 2158 self.ChangeToDepotWorkingDirectory('chromium') | 2344 self.depot_manager.ChangeToDepotDir('chromium') |
| 2159 | 2345 |
| 2160 return depot_revision_list | 2346 return depot_revision_list |
| 2161 | 2347 |
| 2162 def GatherReferenceValues(self, good_rev, bad_rev, cmd, metric, target_depot): | 2348 def GatherReferenceValues(self, good_rev, bad_rev, cmd, metric, target_depot): |
| 2163 """Gathers reference values by running the performance tests on the | 2349 """Gathers reference values by running the performance tests on the |
| 2164 known good and bad revisions. | 2350 known good and bad revisions. |
| 2165 | 2351 |
| 2166 Args: | 2352 Args: |
| 2167 good_rev: The last known good revision where the performance regression | 2353 good_rev: The last known good revision where the performance regression |
| 2168 has not occurred yet. | 2354 has not occurred yet. |
| (...skipping 86 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 2255 | 2441 |
| 2256 Args: | 2442 Args: |
| 2257 good_revision: Number/tag of the known good revision. | 2443 good_revision: Number/tag of the known good revision. |
| 2258 bad_revision: Number/tag of the known bad revision. | 2444 bad_revision: Number/tag of the known bad revision. |
| 2259 | 2445 |
| 2260 Returns: | 2446 Returns: |
| 2261 True if the revisions are in the proper order (good earlier than bad). | 2447 True if the revisions are in the proper order (good earlier than bad). |
| 2262 """ | 2448 """ |
| 2263 if self.source_control.IsGit() and target_depot != 'cros': | 2449 if self.source_control.IsGit() and target_depot != 'cros': |
| 2264 cmd = ['log', '--format=%ct', '-1', good_revision] | 2450 cmd = ['log', '--format=%ct', '-1', good_revision] |
| 2265 cwd = self._GetDepotDirectory(target_depot) | 2451 cwd = self.depot_manager.GetDepotDir(target_depot) |
| 2266 | 2452 |
| 2267 output = bisect_utils.CheckRunGit(cmd, cwd=cwd) | 2453 output = bisect_utils.CheckRunGit(cmd, cwd=cwd) |
| 2268 good_commit_time = int(output) | 2454 good_commit_time = int(output) |
| 2269 | 2455 |
| 2270 cmd = ['log', '--format=%ct', '-1', bad_revision] | 2456 cmd = ['log', '--format=%ct', '-1', bad_revision] |
| 2271 output = bisect_utils.CheckRunGit(cmd, cwd=cwd) | 2457 output = bisect_utils.CheckRunGit(cmd, cwd=cwd) |
| 2272 bad_commit_time = int(output) | 2458 bad_commit_time = int(output) |
| 2273 | 2459 |
| 2274 return good_commit_time <= bad_commit_time | 2460 return good_commit_time <= bad_commit_time |
| 2275 else: | 2461 else: |
| (...skipping 45 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 2321 intermediate revisions to determine the CL where the performance regression | 2507 intermediate revisions to determine the CL where the performance regression |
| 2322 occurred. | 2508 occurred. |
| 2323 | 2509 |
| 2324 Args: | 2510 Args: |
| 2325 command_to_run: Specify the command to execute the performance test. | 2511 command_to_run: Specify the command to execute the performance test. |
| 2326 good_revision: Number/tag of the known good revision. | 2512 good_revision: Number/tag of the known good revision. |
| 2327 bad_revision: Number/tag of the known bad revision. | 2513 bad_revision: Number/tag of the known bad revision. |
| 2328 metric: The performance metric to monitor. | 2514 metric: The performance metric to monitor. |
| 2329 | 2515 |
| 2330 Returns: | 2516 Returns: |
| 2331 A dict with 2 members, 'revision_data' and 'error'. On success, | 2517 A BisectResults object. |
| 2332 'revision_data' will contain a dict mapping revision ids to | |
| 2333 data about that revision. Each piece of revision data consists of a | |
| 2334 dict with the following keys: | |
| 2335 | |
| 2336 'passed': Represents whether the performance test was successful at | |
| 2337 that revision. Possible values include: 1 (passed), 0 (failed), | |
| 2338 '?' (skipped), 'F' (build failed). | |
| 2339 'depot': The depot that this revision is from (i.e. WebKit) | |
| 2340 'external': If the revision is a 'src' revision, 'external' contains | |
| 2341 the revisions of each of the external libraries. | |
| 2342 'sort': A sort value for sorting the dict in order of commits. | |
| 2343 | |
| 2344 For example: | |
| 2345 { | |
| 2346 'error':None, | |
| 2347 'revision_data': | |
| 2348 { | |
| 2349 'CL #1': | |
| 2350 { | |
| 2351 'passed': False, | |
| 2352 'depot': 'chromium', | |
| 2353 'external': None, | |
| 2354 'sort': 0 | |
| 2355 } | |
| 2356 } | |
| 2357 } | |
| 2358 | |
| 2359 If an error occurred, the 'error' field will contain the message and | |
| 2360 'revision_data' will be empty. | |
| 2361 """ | 2518 """ |
| 2362 results = { | 2519 results = BisectResults(self.depot_manager, self.source_control) |
| 2363 'revision_data' : {}, | |
| 2364 'error' : None, | |
| 2365 } | |
| 2366 | 2520 |
| 2367 # Choose depot to bisect first | 2521 # Choose depot to bisect first |
| 2368 target_depot = 'chromium' | 2522 target_depot = 'chromium' |
| 2369 if self.opts.target_platform == 'cros': | 2523 if self.opts.target_platform == 'cros': |
| 2370 target_depot = 'cros' | 2524 target_depot = 'cros' |
| 2371 elif self.opts.target_platform == 'android-chrome': | 2525 elif self.opts.target_platform == 'android-chrome': |
| 2372 target_depot = 'android-chrome' | 2526 target_depot = 'android-chrome' |
| 2373 | 2527 |
| 2374 cwd = os.getcwd() | 2528 cwd = os.getcwd() |
| 2375 self.ChangeToDepotWorkingDirectory(target_depot) | 2529 self.depot_manager.ChangeToDepotDir(target_depot) |
| 2376 | 2530 |
| 2377 # If they passed SVN revisions, we can try match them to git SHA1 hashes. | 2531 # If they passed SVN revisions, we can try match them to git SHA1 hashes. |
| 2378 bad_revision = self.source_control.ResolveToRevision( | 2532 bad_revision = self.source_control.ResolveToRevision( |
| 2379 bad_revision_in, target_depot, DEPOT_DEPS_NAME, 100) | 2533 bad_revision_in, target_depot, DEPOT_DEPS_NAME, 100) |
| 2380 good_revision = self.source_control.ResolveToRevision( | 2534 good_revision = self.source_control.ResolveToRevision( |
| 2381 good_revision_in, target_depot, DEPOT_DEPS_NAME, -100) | 2535 good_revision_in, target_depot, DEPOT_DEPS_NAME, -100) |
| 2382 | 2536 |
| 2383 os.chdir(cwd) | 2537 os.chdir(cwd) |
| 2384 if bad_revision is None: | 2538 if bad_revision is None: |
| 2385 results['error'] = 'Couldn\'t resolve [%s] to SHA1.' % bad_revision_in | 2539 results.error = 'Couldn\'t resolve [%s] to SHA1.' % bad_revision_in |
| 2386 return results | 2540 return results |
| 2387 | 2541 |
| 2388 if good_revision is None: | 2542 if good_revision is None: |
| 2389 results['error'] = 'Couldn\'t resolve [%s] to SHA1.' % good_revision_in | 2543 results.error = 'Couldn\'t resolve [%s] to SHA1.' % good_revision_in |
| 2390 return results | 2544 return results |
| 2391 | 2545 |
| 2392 # Check that they didn't accidentally swap good and bad revisions. | 2546 # Check that they didn't accidentally swap good and bad revisions. |
| 2393 if not self.CheckIfRevisionsInProperOrder( | 2547 if not self.CheckIfRevisionsInProperOrder( |
| 2394 target_depot, good_revision, bad_revision): | 2548 target_depot, good_revision, bad_revision): |
| 2395 results['error'] = ('bad_revision < good_revision, did you swap these ' | 2549 results.error = ('bad_revision < good_revision, did you swap these ' |
| 2396 'by mistake?') | 2550 'by mistake?') |
| 2397 return results | 2551 return results |
| 2398 bad_revision, good_revision = self.NudgeRevisionsIfDEPSChange( | 2552 bad_revision, good_revision = self.NudgeRevisionsIfDEPSChange( |
| 2399 bad_revision, good_revision, good_revision_in) | 2553 bad_revision, good_revision, good_revision_in) |
| 2400 if self.opts.output_buildbot_annotations: | 2554 if self.opts.output_buildbot_annotations: |
| 2401 bisect_utils.OutputAnnotationStepStart('Gathering Revisions') | 2555 bisect_utils.OutputAnnotationStepStart('Gathering Revisions') |
| 2402 | 2556 |
| 2403 cannot_bisect = self.CanPerformBisect(good_revision, bad_revision) | 2557 cannot_bisect = self.CanPerformBisect(good_revision, bad_revision) |
| 2404 if cannot_bisect: | 2558 if cannot_bisect: |
| 2405 results['error'] = cannot_bisect.get('error') | 2559 results.error = cannot_bisect.get('error') |
| 2406 return results | 2560 return results |
| 2407 | 2561 |
| 2408 print 'Gathering revision range for bisection.' | 2562 print 'Gathering revision range for bisection.' |
| 2409 # Retrieve a list of revisions to do bisection on. | 2563 # Retrieve a list of revisions to do bisection on. |
| 2410 src_revision_list = self.GetRevisionList( | 2564 src_revision_list = self.GetRevisionList( |
| 2411 target_depot, bad_revision, good_revision) | 2565 target_depot, bad_revision, good_revision) |
| 2412 | 2566 |
| 2413 if self.opts.output_buildbot_annotations: | 2567 if self.opts.output_buildbot_annotations: |
| 2414 bisect_utils.OutputAnnotationStepClosed() | 2568 bisect_utils.OutputAnnotationStepClosed() |
| 2415 | 2569 |
| 2416 if src_revision_list: | 2570 if src_revision_list: |
| 2417 # revision_data will store information about a revision such as the | 2571 # revision_data will store information about a revision such as the |
| 2418 # depot it came from, the webkit/V8 revision at that time, | 2572 # depot it came from, the webkit/V8 revision at that time, |
| 2419 # performance timing, build state, etc... | 2573 # performance timing, build state, etc... |
| 2420 revision_data = results['revision_data'] | 2574 revision_data = results.revision_data |
| 2421 | 2575 |
| 2422 # revision_list is the list we're binary searching through at the moment. | 2576 # revision_list is the list we're binary searching through at the moment. |
| 2423 revision_list = [] | 2577 revision_list = [] |
| 2424 | 2578 |
| 2425 sort_key_ids = 0 | 2579 sort_key_ids = 0 |
| 2426 | 2580 |
| 2427 for current_revision_id in src_revision_list: | 2581 for current_revision_id in src_revision_list: |
| 2428 sort_key_ids += 1 | 2582 sort_key_ids += 1 |
| 2429 | 2583 |
| 2430 revision_data[current_revision_id] = { | 2584 revision_data[current_revision_id] = { |
| (...skipping 22 matching lines...) Expand all Loading... | |
| 2453 bad_results, good_results = self.GatherReferenceValues(good_revision, | 2607 bad_results, good_results = self.GatherReferenceValues(good_revision, |
| 2454 bad_revision, | 2608 bad_revision, |
| 2455 command_to_run, | 2609 command_to_run, |
| 2456 metric, | 2610 metric, |
| 2457 target_depot) | 2611 target_depot) |
| 2458 | 2612 |
| 2459 if self.opts.output_buildbot_annotations: | 2613 if self.opts.output_buildbot_annotations: |
| 2460 bisect_utils.OutputAnnotationStepClosed() | 2614 bisect_utils.OutputAnnotationStepClosed() |
| 2461 | 2615 |
| 2462 if bad_results[1]: | 2616 if bad_results[1]: |
| 2463 results['error'] = ('An error occurred while building and running ' | 2617 results.error = ('An error occurred while building and running ' |
| 2464 'the \'bad\' reference value. The bisect cannot continue without ' | 2618 'the \'bad\' reference value. The bisect cannot continue without ' |
| 2465 'a working \'bad\' revision to start from.\n\nError: %s' % | 2619 'a working \'bad\' revision to start from.\n\nError: %s' % |
| 2466 bad_results[0]) | 2620 bad_results[0]) |
| 2467 return results | 2621 return results |
| 2468 | 2622 |
| 2469 if good_results[1]: | 2623 if good_results[1]: |
| 2470 results['error'] = ('An error occurred while building and running ' | 2624 results.error = ('An error occurred while building and running ' |
| 2471 'the \'good\' reference value. The bisect cannot continue without ' | 2625 'the \'good\' reference value. The bisect cannot continue without ' |
| 2472 'a working \'good\' revision to start from.\n\nError: %s' % | 2626 'a working \'good\' revision to start from.\n\nError: %s' % |
| 2473 good_results[0]) | 2627 good_results[0]) |
| 2474 return results | 2628 return results |
| 2475 | 2629 |
| 2476 | 2630 |
| 2477 # We need these reference values to determine if later runs should be | 2631 # We need these reference values to determine if later runs should be |
| 2478 # classified as pass or fail. | 2632 # classified as pass or fail. |
| 2479 known_bad_value = bad_results[0] | 2633 known_bad_value = bad_results[0] |
| 2480 known_good_value = good_results[0] | 2634 known_good_value = good_results[0] |
| 2481 | 2635 |
| 2482 # Can just mark the good and bad revisions explicitly here since we | 2636 # Can just mark the good and bad revisions explicitly here since we |
| 2483 # already know the results. | 2637 # already know the results. |
| (...skipping 44 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 2528 break | 2682 break |
| 2529 | 2683 |
| 2530 earliest_revision = max_revision_data['external'][external_depot] | 2684 earliest_revision = max_revision_data['external'][external_depot] |
| 2531 latest_revision = min_revision_data['external'][external_depot] | 2685 latest_revision = min_revision_data['external'][external_depot] |
| 2532 | 2686 |
| 2533 new_revision_list = self.PrepareToBisectOnDepot( | 2687 new_revision_list = self.PrepareToBisectOnDepot( |
| 2534 external_depot, latest_revision, earliest_revision, | 2688 external_depot, latest_revision, earliest_revision, |
| 2535 previous_revision) | 2689 previous_revision) |
| 2536 | 2690 |
| 2537 if not new_revision_list: | 2691 if not new_revision_list: |
| 2538 results['error'] = ('An error occurred attempting to retrieve ' | 2692 results.error = ('An error occurred attempting to retrieve ' |
| 2539 'revision range: [%s..%s]' % | 2693 'revision range: [%s..%s]' % |
| 2540 (earliest_revision, latest_revision)) | 2694 (earliest_revision, latest_revision)) |
| 2541 return results | 2695 return results |
| 2542 | 2696 |
| 2543 _AddRevisionsIntoRevisionData( | 2697 _AddRevisionsIntoRevisionData( |
| 2544 new_revision_list, external_depot, min_revision_data['sort'], | 2698 new_revision_list, external_depot, min_revision_data['sort'], |
| 2545 revision_data) | 2699 revision_data) |
| 2546 | 2700 |
| 2547 # Reset the bisection and perform it on the newly inserted | 2701 # Reset the bisection and perform it on the newly inserted |
| 2548 # changelists. | 2702 # changelists. |
| 2549 revision_list = new_revision_list | 2703 revision_list = new_revision_list |
| 2550 min_revision = 0 | 2704 min_revision = 0 |
| 2551 max_revision = len(revision_list) - 1 | 2705 max_revision = len(revision_list) - 1 |
| 2552 sort_key_ids += len(revision_list) | 2706 sort_key_ids += len(revision_list) |
| 2553 | 2707 |
| 2554 print ('Regression in metric %s appears to be the result of ' | 2708 print ('Regression in metric %s appears to be the result of ' |
| 2555 'changes in [%s].' % (metric, external_depot)) | 2709 'changes in [%s].' % (metric, external_depot)) |
| 2556 | 2710 |
| 2557 self.PrintRevisionsToBisectMessage(revision_list, external_depot) | 2711 self.PrintRevisionsToBisectMessage(revision_list, external_depot) |
| 2558 | 2712 |
| 2559 continue | 2713 continue |
| 2560 else: | 2714 else: |
| 2561 break | 2715 break |
| 2562 else: | 2716 else: |
| 2563 next_revision_index = (int((max_revision - min_revision) / 2) + | 2717 next_revision_index = (int((max_revision - min_revision) / 2) + |
| 2564 min_revision) | 2718 min_revision) |
| 2565 | 2719 |
| 2566 next_revision_id = revision_list[next_revision_index] | 2720 next_revision_id = revision_list[next_revision_index] |
| 2567 next_revision_data = revision_data[next_revision_id] | 2721 next_revision_data = revision_data[next_revision_id] |
| 2568 next_revision_depot = next_revision_data['depot'] | 2722 next_revision_depot = next_revision_data['depot'] |
| 2569 | 2723 |
| 2570 self.ChangeToDepotWorkingDirectory(next_revision_depot) | 2724 self.depot_manager.ChangeToDepotDir(next_revision_depot) |
| 2571 | 2725 |
| 2572 if self.opts.output_buildbot_annotations: | 2726 if self.opts.output_buildbot_annotations: |
| 2573 step_name = 'Working on [%s]' % next_revision_id | 2727 step_name = 'Working on [%s]' % next_revision_id |
| 2574 bisect_utils.OutputAnnotationStepStart(step_name) | 2728 bisect_utils.OutputAnnotationStepStart(step_name) |
| 2575 | 2729 |
| 2576 print 'Working on revision: [%s]' % next_revision_id | 2730 print 'Working on revision: [%s]' % next_revision_id |
| 2577 | 2731 |
| 2578 run_results = self.RunTest( | 2732 run_results = self.RunTest( |
| 2579 next_revision_id, next_revision_depot, command_to_run, metric, | 2733 next_revision_id, next_revision_depot, command_to_run, metric, |
| 2580 skippable=True) | 2734 skippable=True) |
| (...skipping 28 matching lines...) Expand all Loading... | |
| 2609 # If the build is broken, remove it and redo search. | 2763 # If the build is broken, remove it and redo search. |
| 2610 revision_list.pop(next_revision_index) | 2764 revision_list.pop(next_revision_index) |
| 2611 | 2765 |
| 2612 max_revision -= 1 | 2766 max_revision -= 1 |
| 2613 | 2767 |
| 2614 if self.opts.output_buildbot_annotations: | 2768 if self.opts.output_buildbot_annotations: |
| 2615 self._PrintPartialResults(results) | 2769 self._PrintPartialResults(results) |
| 2616 bisect_utils.OutputAnnotationStepClosed() | 2770 bisect_utils.OutputAnnotationStepClosed() |
| 2617 else: | 2771 else: |
| 2618 # Weren't able to sync and retrieve the revision range. | 2772 # Weren't able to sync and retrieve the revision range. |
| 2619 results['error'] = ('An error occurred attempting to retrieve revision ' | 2773 results.error = ('An error occurred attempting to retrieve revision ' |
| 2620 'range: [%s..%s]' % (good_revision, bad_revision)) | 2774 'range: [%s..%s]' % (good_revision, bad_revision)) |
| 2621 | 2775 |
| 2622 return results | 2776 return results |
| 2623 | 2777 |
| 2624 def _PrintPartialResults(self, results_dict): | 2778 def _PrintPartialResults(self, results): |
| 2625 revision_data = results_dict['revision_data'] | 2779 results_dict = results.GetResultsDict() |
| 2626 revision_data_sorted = sorted(revision_data.iteritems(), | 2780 self._PrintTestedCommitsTable(results_dict['revision_data_sorted'], |
| 2627 key = lambda x: x[1]['sort']) | |
| 2628 results_dict = self._GetResultsDict(revision_data, revision_data_sorted) | |
| 2629 | |
| 2630 self._PrintTestedCommitsTable(revision_data_sorted, | |
| 2631 results_dict['first_working_revision'], | 2781 results_dict['first_working_revision'], |
| 2632 results_dict['last_broken_revision'], | 2782 results_dict['last_broken_revision'], |
| 2633 100, final_step=False) | 2783 100, final_step=False) |
| 2634 | 2784 |
| 2635 def _ConfidenceLevelStatus(self, results_dict): | 2785 def _ConfidenceLevelStatus(self, results_dict): |
| 2636 if not results_dict['confidence']: | 2786 if not results_dict['confidence']: |
| 2637 return None | 2787 return None |
| 2638 confidence_status = 'Successful with %(level)s confidence%(warning)s.' | 2788 confidence_status = 'Successful with %(level)s confidence%(warning)s.' |
| 2639 if results_dict['confidence'] >= HIGH_CONFIDENCE: | 2789 if results_dict['confidence'] >= HIGH_CONFIDENCE: |
| 2640 level = 'high' | 2790 level = 'high' |
| 2641 else: | 2791 else: |
| 2642 level = 'low' | 2792 level = 'low' |
| 2643 warning = ' and warnings' | 2793 warning = ' and warnings' |
| 2644 if not self.warnings: | 2794 if not self.warnings: |
| 2645 warning = '' | 2795 warning = '' |
| 2646 return confidence_status % {'level': level, 'warning': warning} | 2796 return confidence_status % {'level': level, 'warning': warning} |
| 2647 | 2797 |
| 2648 def _GetViewVCLinkFromDepotAndHash(self, cl, depot): | 2798 def _GetViewVCLinkFromDepotAndHash(self, cl, depot): |
| 2649 info = self.source_control.QueryRevisionInfo(cl, | 2799 info = self.source_control.QueryRevisionInfo(cl, |
| 2650 self._GetDepotDirectory(depot)) | 2800 self.depot_manager.GetDepotDir(depot)) |
| 2651 if depot and DEPOT_DEPS_NAME[depot].has_key('viewvc'): | 2801 if depot and DEPOT_DEPS_NAME[depot].has_key('viewvc'): |
| 2652 try: | 2802 try: |
| 2653 # Format is "git-svn-id: svn://....@123456 <other data>" | 2803 # Format is "git-svn-id: svn://....@123456 <other data>" |
| 2654 svn_line = [i for i in info['body'].splitlines() if 'git-svn-id:' in i] | 2804 svn_line = [i for i in info['body'].splitlines() if 'git-svn-id:' in i] |
| 2655 svn_revision = svn_line[0].split('@') | 2805 svn_revision = svn_line[0].split('@') |
| 2656 svn_revision = svn_revision[1].split(' ')[0] | 2806 svn_revision = svn_revision[1].split(' ')[0] |
| 2657 return DEPOT_DEPS_NAME[depot]['viewvc'] + svn_revision | 2807 return DEPOT_DEPS_NAME[depot]['viewvc'] + svn_revision |
| 2658 except IndexError: | 2808 except IndexError: |
| 2659 return '' | 2809 return '' |
| 2660 return '' | 2810 return '' |
| (...skipping 132 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 2793 if not previous_link: | 2943 if not previous_link: |
| 2794 previous_link = previous_id | 2944 previous_link = previous_id |
| 2795 | 2945 |
| 2796 print ' %8s %70s %s' % ( | 2946 print ' %8s %70s %s' % ( |
| 2797 current_data['depot'], current_link, | 2947 current_data['depot'], current_link, |
| 2798 ('%d%%' % confidence).center(10, ' ')) | 2948 ('%d%%' % confidence).center(10, ' ')) |
| 2799 print ' %8s %70s' % ( | 2949 print ' %8s %70s' % ( |
| 2800 previous_data['depot'], previous_link) | 2950 previous_data['depot'], previous_link) |
| 2801 print | 2951 print |
| 2802 | 2952 |
| 2803 def _GetResultsDict(self, revision_data, revision_data_sorted): | |
| 2804 # Find range where it possibly broke. | |
| 2805 first_working_revision = None | |
| 2806 first_working_revision_index = -1 | |
| 2807 last_broken_revision = None | |
| 2808 last_broken_revision_index = -1 | |
| 2809 | |
| 2810 culprit_revisions = [] | |
| 2811 other_regressions = [] | |
| 2812 regression_size = 0.0 | |
| 2813 regression_std_err = 0.0 | |
| 2814 confidence = 0.0 | |
| 2815 | |
| 2816 for i in xrange(len(revision_data_sorted)): | |
| 2817 k, v = revision_data_sorted[i] | |
| 2818 if v['passed'] == 1: | |
| 2819 if not first_working_revision: | |
| 2820 first_working_revision = k | |
| 2821 first_working_revision_index = i | |
| 2822 | |
| 2823 if not v['passed']: | |
| 2824 last_broken_revision = k | |
| 2825 last_broken_revision_index = i | |
| 2826 | |
| 2827 if last_broken_revision != None and first_working_revision != None: | |
| 2828 broken_means = [] | |
| 2829 for i in xrange(0, last_broken_revision_index + 1): | |
| 2830 if revision_data_sorted[i][1]['value']: | |
| 2831 broken_means.append(revision_data_sorted[i][1]['value']['values']) | |
| 2832 | |
| 2833 working_means = [] | |
| 2834 for i in xrange(first_working_revision_index, len(revision_data_sorted)): | |
| 2835 if revision_data_sorted[i][1]['value']: | |
| 2836 working_means.append(revision_data_sorted[i][1]['value']['values']) | |
| 2837 | |
| 2838 # Flatten the lists to calculate mean of all values. | |
| 2839 working_mean = sum(working_means, []) | |
| 2840 broken_mean = sum(broken_means, []) | |
| 2841 | |
| 2842 # Calculate the approximate size of the regression | |
| 2843 mean_of_bad_runs = math_utils.Mean(broken_mean) | |
| 2844 mean_of_good_runs = math_utils.Mean(working_mean) | |
| 2845 | |
| 2846 regression_size = 100 * math_utils.RelativeChange(mean_of_good_runs, | |
| 2847 mean_of_bad_runs) | |
| 2848 if math.isnan(regression_size): | |
| 2849 regression_size = 'zero-to-nonzero' | |
| 2850 | |
| 2851 regression_std_err = math.fabs(math_utils.PooledStandardError( | |
| 2852 [working_mean, broken_mean]) / | |
| 2853 max(0.0001, min(mean_of_good_runs, mean_of_bad_runs))) * 100.0 | |
| 2854 | |
| 2855 # Give a "confidence" in the bisect. At the moment we use how distinct the | |
| 2856 # values are before and after the last broken revision, and how noisy the | |
| 2857 # overall graph is. | |
| 2858 confidence = ConfidenceScore(working_means, broken_means) | |
| 2859 | |
| 2860 culprit_revisions = [] | |
| 2861 | |
| 2862 cwd = os.getcwd() | |
| 2863 self.ChangeToDepotWorkingDirectory( | |
| 2864 revision_data[last_broken_revision]['depot']) | |
| 2865 | |
| 2866 if revision_data[last_broken_revision]['depot'] == 'cros': | |
| 2867 # Want to get a list of all the commits and what depots they belong | |
| 2868 # to so that we can grab info about each. | |
| 2869 cmd = ['repo', 'forall', '-c', | |
| 2870 'pwd ; git log --pretty=oneline --before=%d --after=%d' % ( | |
| 2871 last_broken_revision, first_working_revision + 1)] | |
| 2872 output, return_code = bisect_utils.RunProcessAndRetrieveOutput(cmd) | |
| 2873 | |
| 2874 changes = [] | |
| 2875 assert not return_code, ('An error occurred while running ' | |
| 2876 '"%s"' % ' '.join(cmd)) | |
| 2877 last_depot = None | |
| 2878 cwd = os.getcwd() | |
| 2879 for l in output.split('\n'): | |
| 2880 if l: | |
| 2881 # Output will be in form: | |
| 2882 # /path_to_depot | |
| 2883 # /path_to_other_depot | |
| 2884 # <SHA1> | |
| 2885 # /path_again | |
| 2886 # <SHA1> | |
| 2887 # etc. | |
| 2888 if l[0] == '/': | |
| 2889 last_depot = l | |
| 2890 else: | |
| 2891 contents = l.split(' ') | |
| 2892 if len(contents) > 1: | |
| 2893 changes.append([last_depot, contents[0]]) | |
| 2894 for c in changes: | |
| 2895 os.chdir(c[0]) | |
| 2896 info = self.source_control.QueryRevisionInfo(c[1]) | |
| 2897 culprit_revisions.append((c[1], info, None)) | |
| 2898 else: | |
| 2899 for i in xrange(last_broken_revision_index, len(revision_data_sorted)): | |
| 2900 k, v = revision_data_sorted[i] | |
| 2901 if k == first_working_revision: | |
| 2902 break | |
| 2903 self.ChangeToDepotWorkingDirectory(v['depot']) | |
| 2904 info = self.source_control.QueryRevisionInfo(k) | |
| 2905 culprit_revisions.append((k, info, v['depot'])) | |
| 2906 os.chdir(cwd) | |
| 2907 | |
| 2908 # Check for any other possible regression ranges. | |
| 2909 other_regressions = _FindOtherRegressions( | |
| 2910 revision_data_sorted, mean_of_bad_runs > mean_of_good_runs) | |
| 2911 | |
| 2912 return { | |
| 2913 'first_working_revision': first_working_revision, | |
| 2914 'last_broken_revision': last_broken_revision, | |
| 2915 'culprit_revisions': culprit_revisions, | |
| 2916 'other_regressions': other_regressions, | |
| 2917 'regression_size': regression_size, | |
| 2918 'regression_std_err': regression_std_err, | |
| 2919 'confidence': confidence, | |
| 2920 } | |
| 2921 | |
| 2922 def _CheckForWarnings(self, results_dict): | 2953 def _CheckForWarnings(self, results_dict): |
| 2923 if len(results_dict['culprit_revisions']) > 1: | 2954 if len(results_dict['culprit_revisions']) > 1: |
| 2924 self.warnings.append('Due to build errors, regression range could ' | 2955 self.warnings.append('Due to build errors, regression range could ' |
| 2925 'not be narrowed down to a single commit.') | 2956 'not be narrowed down to a single commit.') |
| 2926 if self.opts.repeat_test_count == 1: | 2957 if self.opts.repeat_test_count == 1: |
| 2927 self.warnings.append('Tests were only set to run once. This may ' | 2958 self.warnings.append('Tests were only set to run once. This may ' |
| 2928 'be insufficient to get meaningful results.') | 2959 'be insufficient to get meaningful results.') |
| 2929 if 0 < results_dict['confidence'] < HIGH_CONFIDENCE: | 2960 if 0 < results_dict['confidence'] < HIGH_CONFIDENCE: |
| 2930 self.warnings.append('Confidence is not high. Try bisecting again ' | 2961 self.warnings.append('Confidence is not high. Try bisecting again ' |
| 2931 'with increased repeat_count, larger range, or ' | 2962 'with increased repeat_count, larger range, or ' |
| 2932 'on another metric.') | 2963 'on another metric.') |
| 2933 if not results_dict['confidence']: | 2964 if not results_dict['confidence']: |
| 2934 self.warnings.append('Confidence score is 0%. Try bisecting again on ' | 2965 self.warnings.append('Confidence score is 0%. Try bisecting again on ' |
| 2935 'another platform or another metric.') | 2966 'another platform or another metric.') |
| 2936 | 2967 |
| 2937 def FormatAndPrintResults(self, bisect_results): | 2968 def FormatAndPrintResults(self, bisect_results): |
| 2938 """Prints the results from a bisection run in a readable format. | 2969 """Prints the results from a bisection run in a readable format. |
| 2939 | 2970 |
| 2940 Args: | 2971 Args: |
| 2941 bisect_results: The results from a bisection test run. | 2972 bisect_results: The results from a bisection test run. |
| 2942 """ | 2973 """ |
| 2943 revision_data = bisect_results['revision_data'] | 2974 results_dict = bisect_results.GetResultsDict() |
| 2944 revision_data_sorted = sorted(revision_data.iteritems(), | |
| 2945 key = lambda x: x[1]['sort']) | |
| 2946 results_dict = self._GetResultsDict(revision_data, revision_data_sorted) | |
| 2947 | 2975 |
| 2948 self._CheckForWarnings(results_dict) | 2976 self._CheckForWarnings(results_dict) |
| 2949 | 2977 |
| 2950 if self.opts.output_buildbot_annotations: | 2978 if self.opts.output_buildbot_annotations: |
| 2951 bisect_utils.OutputAnnotationStepStart('Build Status Per Revision') | 2979 bisect_utils.OutputAnnotationStepStart('Build Status Per Revision') |
| 2952 | 2980 |
| 2953 print | 2981 print |
| 2954 print 'Full results of bisection:' | 2982 print 'Full results of bisection:' |
| 2955 for current_id, current_data in revision_data_sorted: | 2983 for current_id, current_data in results_dict['revision_data_sorted']: |
| 2956 build_status = current_data['passed'] | 2984 build_status = current_data['passed'] |
| 2957 | 2985 |
| 2958 if type(build_status) is bool: | 2986 if type(build_status) is bool: |
| 2959 if build_status: | 2987 if build_status: |
| 2960 build_status = 'Good' | 2988 build_status = 'Good' |
| 2961 else: | 2989 else: |
| 2962 build_status = 'Bad' | 2990 build_status = 'Bad' |
| 2963 | 2991 |
| 2964 print ' %20s %40s %s' % (current_data['depot'], | 2992 print ' %20s %40s %s' % (current_data['depot'], |
| 2965 current_id, build_status) | 2993 current_id, build_status) |
| 2966 print | 2994 print |
| 2967 | 2995 |
| 2968 if self.opts.output_buildbot_annotations: | 2996 if self.opts.output_buildbot_annotations: |
| 2969 bisect_utils.OutputAnnotationStepClosed() | 2997 bisect_utils.OutputAnnotationStepClosed() |
| 2970 # The perf dashboard scrapes the "results" step in order to comment on | 2998 # The perf dashboard scrapes the "results" step in order to comment on |
| 2971 # bugs. If you change this, please update the perf dashboard as well. | 2999 # bugs. If you change this, please update the perf dashboard as well. |
| 2972 bisect_utils.OutputAnnotationStepStart('Results') | 3000 bisect_utils.OutputAnnotationStepStart('Results') |
| 2973 | 3001 |
| 2974 self._PrintBanner(results_dict) | 3002 self._PrintBanner(results_dict) |
| 2975 self._PrintWarnings() | 3003 self._PrintWarnings() |
| 2976 | 3004 |
| 2977 if results_dict['culprit_revisions'] and results_dict['confidence']: | 3005 if results_dict['culprit_revisions'] and results_dict['confidence']: |
| 2978 for culprit in results_dict['culprit_revisions']: | 3006 for culprit in results_dict['culprit_revisions']: |
| 2979 cl, info, depot = culprit | 3007 cl, info, depot = culprit |
| 2980 self._PrintRevisionInfo(cl, info, depot) | 3008 self._PrintRevisionInfo(cl, info, depot) |
| 2981 if results_dict['other_regressions']: | 3009 if results_dict['other_regressions']: |
| 2982 self._PrintOtherRegressions(results_dict['other_regressions'], | 3010 self._PrintOtherRegressions(results_dict['other_regressions'], |
| 2983 revision_data) | 3011 results_dict['revision_data']) |
| 2984 self._PrintTestedCommitsTable(revision_data_sorted, | 3012 self._PrintTestedCommitsTable(results_dict['revision_data_sorted'], |
| 2985 results_dict['first_working_revision'], | 3013 results_dict['first_working_revision'], |
| 2986 results_dict['last_broken_revision'], | 3014 results_dict['last_broken_revision'], |
| 2987 results_dict['confidence']) | 3015 results_dict['confidence']) |
| 2988 _PrintStepTime(revision_data_sorted) | 3016 _PrintStepTime(results_dict['revision_data_sorted']) |
| 2989 self._PrintReproSteps() | 3017 self._PrintReproSteps() |
| 2990 _PrintThankYou() | 3018 _PrintThankYou() |
| 2991 if self.opts.output_buildbot_annotations: | 3019 if self.opts.output_buildbot_annotations: |
| 2992 bisect_utils.OutputAnnotationStepClosed() | 3020 bisect_utils.OutputAnnotationStepClosed() |
| 2993 | 3021 |
| 2994 def _PrintBanner(self, results_dict): | 3022 def _PrintBanner(self, results_dict): |
| 2995 if self._IsBisectModeReturnCode(): | 3023 if self._IsBisectModeReturnCode(): |
| 2996 metrics = 'N/A' | 3024 metrics = 'N/A' |
| 2997 change = 'Yes' | 3025 change = 'Yes' |
| 2998 else: | 3026 else: |
| (...skipping 390 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 3389 if (not source_control.IsInProperBranch() and | 3417 if (not source_control.IsInProperBranch() and |
| 3390 not opts.debug_ignore_sync and | 3418 not opts.debug_ignore_sync and |
| 3391 not opts.working_directory): | 3419 not opts.working_directory): |
| 3392 raise RuntimeError('You must switch to master branch to run bisection.') | 3420 raise RuntimeError('You must switch to master branch to run bisection.') |
| 3393 bisect_test = BisectPerformanceMetrics(source_control, opts) | 3421 bisect_test = BisectPerformanceMetrics(source_control, opts) |
| 3394 try: | 3422 try: |
| 3395 bisect_results = bisect_test.Run(opts.command, | 3423 bisect_results = bisect_test.Run(opts.command, |
| 3396 opts.bad_revision, | 3424 opts.bad_revision, |
| 3397 opts.good_revision, | 3425 opts.good_revision, |
| 3398 opts.metric) | 3426 opts.metric) |
| 3399 if bisect_results['error']: | 3427 if bisect_results.error: |
| 3400 raise RuntimeError(bisect_results['error']) | 3428 raise RuntimeError(bisect_results.error) |
| 3401 bisect_test.FormatAndPrintResults(bisect_results) | 3429 bisect_test.FormatAndPrintResults(bisect_results) |
| 3402 return 0 | 3430 return 0 |
| 3403 finally: | 3431 finally: |
| 3404 bisect_test.PerformCleanup() | 3432 bisect_test.PerformCleanup() |
| 3405 except RuntimeError, e: | 3433 except RuntimeError, e: |
| 3406 if opts.output_buildbot_annotations: | 3434 if opts.output_buildbot_annotations: |
| 3407 # The perf dashboard scrapes the "results" step in order to comment on | 3435 # The perf dashboard scrapes the "results" step in order to comment on |
| 3408 # bugs. If you change this, please update the perf dashboard as well. | 3436 # bugs. If you change this, please update the perf dashboard as well. |
| 3409 bisect_utils.OutputAnnotationStepStart('Results') | 3437 bisect_utils.OutputAnnotationStepStart('Results') |
| 3410 print 'Error: %s' % e.message | 3438 print 'Error: %s' % e.message |
| 3411 if opts.output_buildbot_annotations: | 3439 if opts.output_buildbot_annotations: |
| 3412 bisect_utils.OutputAnnotationStepClosed() | 3440 bisect_utils.OutputAnnotationStepClosed() |
| 3413 return 1 | 3441 return 1 |
| 3414 | 3442 |
| 3415 | 3443 |
| 3416 if __name__ == '__main__': | 3444 if __name__ == '__main__': |
| 3417 sys.exit(main()) | 3445 sys.exit(main()) |
| OLD | NEW |