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 |