| 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 1049 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 1060 depot: Name of depot being bisected. | 1060 depot: Name of depot being bisected. |
| 1061 | 1061 |
| 1062 Returns: | 1062 Returns: |
| 1063 A dict in the format {depot:revision} if successful, otherwise None. | 1063 A dict in the format {depot:revision} if successful, otherwise None. |
| 1064 """ | 1064 """ |
| 1065 try: | 1065 try: |
| 1066 deps_data = { | 1066 deps_data = { |
| 1067 'Var': lambda _: deps_data["vars"][_], | 1067 'Var': lambda _: deps_data["vars"][_], |
| 1068 'From': lambda *args: None, | 1068 'From': lambda *args: None, |
| 1069 } | 1069 } |
| 1070 execfile(bisect_utils.FILE_DEPS_GIT, {}, deps_data) | 1070 |
| 1071 deps_file = bisect_utils.FILE_DEPS_GIT |
| 1072 if not os.path.exists(deps_file): |
| 1073 deps_file = bisect_utils.FILE_DEPS |
| 1074 execfile(deps_file, {}, deps_data) |
| 1071 deps_data = deps_data['deps'] | 1075 deps_data = deps_data['deps'] |
| 1072 | 1076 |
| 1073 rxp = re.compile(".git@(?P<revision>[a-fA-F0-9]+)") | 1077 rxp = re.compile(".git@(?P<revision>[a-fA-F0-9]+)") |
| 1074 results = {} | 1078 results = {} |
| 1075 for depot_name, depot_data in DEPOT_DEPS_NAME.iteritems(): | 1079 for depot_name, depot_data in DEPOT_DEPS_NAME.iteritems(): |
| 1076 if (depot_data.get('platform') and | 1080 if (depot_data.get('platform') and |
| 1077 depot_data.get('platform') != os.name): | 1081 depot_data.get('platform') != os.name): |
| 1078 continue | 1082 continue |
| 1079 | 1083 |
| 1080 if (depot_data.get('recurse') and depot in depot_data.get('from')): | 1084 if (depot_data.get('recurse') and depot in depot_data.get('from')): |
| 1081 depot_data_src = depot_data.get('src') or depot_data.get('src_old') | 1085 depot_data_src = depot_data.get('src') or depot_data.get('src_old') |
| 1082 src_dir = deps_data.get(depot_data_src) | 1086 src_dir = deps_data.get(depot_data_src) |
| 1083 if src_dir: | 1087 if src_dir: |
| 1084 self.depot_cwd[depot_name] = os.path.join(self.src_cwd, | 1088 self.depot_cwd[depot_name] = os.path.join(self.src_cwd, |
| 1085 depot_data_src[4:]) | 1089 depot_data_src[4:]) |
| 1086 re_results = rxp.search(src_dir) | 1090 re_results = rxp.search(src_dir) |
| 1087 if re_results: | 1091 if re_results: |
| 1088 results[depot_name] = re_results.group('revision') | 1092 results[depot_name] = re_results.group('revision') |
| 1089 else: | 1093 else: |
| 1090 warning_text = ('Could not parse revision for %s while bisecting ' | 1094 warning_text = ('Could not parse revision for %s while bisecting ' |
| 1091 '%s' % (depot_name, depot)) | 1095 '%s' % (depot_name, depot)) |
| 1092 if not warning_text in self.warnings: | 1096 if not warning_text in self.warnings: |
| 1093 self.warnings.append(warning_text) | 1097 self.warnings.append(warning_text) |
| 1094 else: | 1098 else: |
| 1095 results[depot_name] = None | 1099 results[depot_name] = None |
| 1096 return results | 1100 return results |
| 1097 except ImportError: | 1101 except ImportError: |
| 1098 deps_file_contents = ReadStringFromFile(bisect_utils.FILE_DEPS_GIT) | 1102 deps_file_contents = ReadStringFromFile(deps_file) |
| 1099 parse_results = _ParseRevisionsFromDEPSFileManually(deps_file_contents) | 1103 parse_results = _ParseRevisionsFromDEPSFileManually(deps_file_contents) |
| 1100 results = {} | 1104 results = {} |
| 1101 for depot_name, depot_revision in parse_results.iteritems(): | 1105 for depot_name, depot_revision in parse_results.iteritems(): |
| 1102 depot_revision = depot_revision.strip('@') | 1106 depot_revision = depot_revision.strip('@') |
| 1103 print depot_name, depot_revision | 1107 print depot_name, depot_revision |
| 1104 for current_name, current_data in DEPOT_DEPS_NAME.iteritems(): | 1108 for current_name, current_data in DEPOT_DEPS_NAME.iteritems(): |
| 1105 if (current_data.has_key('deps_var') and | 1109 if (current_data.has_key('deps_var') and |
| 1106 current_data['deps_var'] == depot_name): | 1110 current_data['deps_var'] == depot_name): |
| 1107 src_name = current_name | 1111 src_name = current_name |
| 1108 results[src_name] = depot_revision | 1112 results[src_name] = depot_revision |
| (...skipping 661 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 1770 def PerformWebkitDirectoryCleanup(self, revision): | 1774 def PerformWebkitDirectoryCleanup(self, revision): |
| 1771 """Cleans up the Webkit directory before syncing another revision. | 1775 """Cleans up the Webkit directory before syncing another revision. |
| 1772 | 1776 |
| 1773 If the script is switching between Blink and WebKit during bisect, | 1777 If the script is switching between Blink and WebKit during bisect, |
| 1774 its faster to just delete the directory rather than leave it up to git | 1778 its faster to just delete the directory rather than leave it up to git |
| 1775 to sync. | 1779 to sync. |
| 1776 | 1780 |
| 1777 Returns: | 1781 Returns: |
| 1778 True if successful. | 1782 True if successful. |
| 1779 """ | 1783 """ |
| 1780 if not self.source_control.CheckoutFileAtRevision( | |
| 1781 bisect_utils.FILE_DEPS_GIT, revision, cwd=self.src_cwd): | |
| 1782 return False | |
| 1783 | |
| 1784 cwd = os.getcwd() | 1784 cwd = os.getcwd() |
| 1785 os.chdir(self.src_cwd) | 1785 os.chdir(self.src_cwd) |
| 1786 | 1786 |
| 1787 is_blink = bisect_utils.IsDepsFileBlink() | 1787 is_blink = bisect_utils.IsDepsFileBlink(revision) |
| 1788 | 1788 |
| 1789 os.chdir(cwd) | 1789 os.chdir(cwd) |
| 1790 | 1790 |
| 1791 if not self.source_control.RevertFileToHead( | |
| 1792 bisect_utils.FILE_DEPS_GIT): | |
| 1793 return False | |
| 1794 | |
| 1795 if self.was_blink != is_blink: | 1791 if self.was_blink != is_blink: |
| 1796 self.was_blink = is_blink | 1792 self.was_blink = is_blink |
| 1797 # Removes third_party/Webkit directory. | 1793 # Removes third_party/Webkit directory. |
| 1798 return bisect_utils.RemoveThirdPartyDirectory('Webkit') | 1794 return bisect_utils.RemoveThirdPartyDirectory('Webkit') |
| 1799 return True | 1795 return True |
| 1800 | 1796 |
| 1801 def PerformCrosChrootCleanup(self): | 1797 def PerformCrosChrootCleanup(self): |
| 1802 """Deletes the chroot. | 1798 """Deletes the chroot. |
| 1803 | 1799 |
| 1804 Returns: | 1800 Returns: |
| (...skipping 363 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 2168 | 2164 |
| 2169 print | 2165 print |
| 2170 print 'Revisions to bisect on [%s]:' % depot | 2166 print 'Revisions to bisect on [%s]:' % depot |
| 2171 for revision_id in revision_list: | 2167 for revision_id in revision_list: |
| 2172 print ' -> %s' % (revision_id, ) | 2168 print ' -> %s' % (revision_id, ) |
| 2173 print | 2169 print |
| 2174 | 2170 |
| 2175 if self.opts.output_buildbot_annotations: | 2171 if self.opts.output_buildbot_annotations: |
| 2176 bisect_utils.OutputAnnotationStepClosed() | 2172 bisect_utils.OutputAnnotationStepClosed() |
| 2177 | 2173 |
| 2178 def NudgeRevisionsIfDEPSChange(self, bad_revision, good_revision): | 2174 def NudgeRevisionsIfDEPSChange(self, bad_revision, good_revision, |
| 2175 good_svn_revision=None): |
| 2179 """Checks to see if changes to DEPS file occurred, and that the revision | 2176 """Checks to see if changes to DEPS file occurred, and that the revision |
| 2180 range also includes the change to .DEPS.git. If it doesn't, attempts to | 2177 range also includes the change to .DEPS.git. If it doesn't, attempts to |
| 2181 expand the revision range to include it. | 2178 expand the revision range to include it. |
| 2182 | 2179 |
| 2183 Args: | 2180 Args: |
| 2184 bad_rev: First known bad revision. | 2181 bad_revision: First known bad git revision. |
| 2185 good_revision: Last known good revision. | 2182 good_revision: Last known good git revision. |
| 2183 good_svn_revision: Last known good svn revision. |
| 2186 | 2184 |
| 2187 Returns: | 2185 Returns: |
| 2188 A tuple with the new bad and good revisions. | 2186 A tuple with the new bad and good revisions. |
| 2189 """ | 2187 """ |
| 2188 # DONOT perform nudge because at revision 291563 .DEPS.git was removed |
| 2189 # and source contain only DEPS file for dependency changes. |
| 2190 if good_svn_revision >= 291563: |
| 2191 return (bad_revision, good_revision) |
| 2192 |
| 2190 if self.source_control.IsGit() and self.opts.target_platform == 'chromium': | 2193 if self.source_control.IsGit() and self.opts.target_platform == 'chromium': |
| 2191 changes_to_deps = self.source_control.QueryFileRevisionHistory( | 2194 changes_to_deps = self.source_control.QueryFileRevisionHistory( |
| 2192 'DEPS', good_revision, bad_revision) | 2195 'DEPS', good_revision, bad_revision) |
| 2193 | 2196 |
| 2194 if changes_to_deps: | 2197 if changes_to_deps: |
| 2195 # DEPS file was changed, search from the oldest change to DEPS file to | 2198 # DEPS file was changed, search from the oldest change to DEPS file to |
| 2196 # bad_revision to see if there are matching .DEPS.git changes. | 2199 # bad_revision to see if there are matching .DEPS.git changes. |
| 2197 oldest_deps_change = changes_to_deps[-1] | 2200 oldest_deps_change = changes_to_deps[-1] |
| 2198 changes_to_gitdeps = self.source_control.QueryFileRevisionHistory( | 2201 changes_to_gitdeps = self.source_control.QueryFileRevisionHistory( |
| 2199 bisect_utils.FILE_DEPS_GIT, oldest_deps_change, bad_revision) | 2202 bisect_utils.FILE_DEPS_GIT, oldest_deps_change, bad_revision) |
| 2200 | 2203 |
| 2201 if len(changes_to_deps) != len(changes_to_gitdeps): | 2204 if len(changes_to_deps) != len(changes_to_gitdeps): |
| 2202 # Grab the timestamp of the last DEPS change | 2205 # Grab the timestamp of the last DEPS change |
| 2203 cmd = ['log', '--format=%ct', '-1', changes_to_deps[0]] | 2206 cmd = ['log', '--format=%ct', '-1', changes_to_deps[0]] |
| 2204 output = bisect_utils.CheckRunGit(cmd) | 2207 output = bisect_utils.CheckRunGit(cmd) |
| 2205 commit_time = int(output) | 2208 commit_time = int(output) |
| 2206 | 2209 |
| 2207 # Try looking for a commit that touches the .DEPS.git file in the | 2210 # Try looking for a commit that touches the .DEPS.git file in the |
| 2208 # next 15 minutes after the DEPS file change. | 2211 # next 15 minutes after the DEPS file change. |
| 2209 cmd = ['log', '--format=%H', '-1', | 2212 cmd = ['log', '--format=%H', '-1', |
| 2210 '--before=%d' % (commit_time + 900), '--after=%d' % commit_time, | 2213 '--before=%d' % (commit_time + 900), '--after=%d' % commit_time, |
| 2211 'origin/master', bisect_utils.FILE_DEPS_GIT] | 2214 'origin/master', '--', bisect_utils.FILE_DEPS_GIT] |
| 2212 output = bisect_utils.CheckRunGit(cmd) | 2215 output = bisect_utils.CheckRunGit(cmd) |
| 2213 output = output.strip() | 2216 output = output.strip() |
| 2214 if output: | 2217 if output: |
| 2215 self.warnings.append('Detected change to DEPS and modified ' | 2218 self.warnings.append('Detected change to DEPS and modified ' |
| 2216 'revision range to include change to .DEPS.git') | 2219 'revision range to include change to .DEPS.git') |
| 2217 return (output, good_revision) | 2220 return (output, good_revision) |
| 2218 else: | 2221 else: |
| 2219 self.warnings.append('Detected change to DEPS but couldn\'t find ' | 2222 self.warnings.append('Detected change to DEPS but couldn\'t find ' |
| 2220 'matching change to .DEPS.git') | 2223 'matching change to .DEPS.git') |
| 2221 return (bad_revision, good_revision) | 2224 return (bad_revision, good_revision) |
| (...skipping 109 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 2331 cwd = os.getcwd() | 2334 cwd = os.getcwd() |
| 2332 self.ChangeToDepotWorkingDirectory(target_depot) | 2335 self.ChangeToDepotWorkingDirectory(target_depot) |
| 2333 | 2336 |
| 2334 # If they passed SVN revisions, we can try match them to git SHA1 hashes. | 2337 # If they passed SVN revisions, we can try match them to git SHA1 hashes. |
| 2335 bad_revision = self.source_control.ResolveToRevision( | 2338 bad_revision = self.source_control.ResolveToRevision( |
| 2336 bad_revision_in, target_depot, DEPOT_DEPS_NAME, 100) | 2339 bad_revision_in, target_depot, DEPOT_DEPS_NAME, 100) |
| 2337 good_revision = self.source_control.ResolveToRevision( | 2340 good_revision = self.source_control.ResolveToRevision( |
| 2338 good_revision_in, target_depot, DEPOT_DEPS_NAME, -100) | 2341 good_revision_in, target_depot, DEPOT_DEPS_NAME, -100) |
| 2339 | 2342 |
| 2340 os.chdir(cwd) | 2343 os.chdir(cwd) |
| 2341 | |
| 2342 if bad_revision is None: | 2344 if bad_revision is None: |
| 2343 results['error'] = 'Couldn\'t resolve [%s] to SHA1.' % bad_revision_in | 2345 results['error'] = 'Couldn\'t resolve [%s] to SHA1.' % bad_revision_in |
| 2344 return results | 2346 return results |
| 2345 | 2347 |
| 2346 if good_revision is None: | 2348 if good_revision is None: |
| 2347 results['error'] = 'Couldn\'t resolve [%s] to SHA1.' % good_revision_in | 2349 results['error'] = 'Couldn\'t resolve [%s] to SHA1.' % good_revision_in |
| 2348 return results | 2350 return results |
| 2349 | 2351 |
| 2350 # Check that they didn't accidentally swap good and bad revisions. | 2352 # Check that they didn't accidentally swap good and bad revisions. |
| 2351 if not self.CheckIfRevisionsInProperOrder( | 2353 if not self.CheckIfRevisionsInProperOrder( |
| 2352 target_depot, good_revision, bad_revision): | 2354 target_depot, good_revision, bad_revision): |
| 2353 results['error'] = ('bad_revision < good_revision, did you swap these ' | 2355 results['error'] = ('bad_revision < good_revision, did you swap these ' |
| 2354 'by mistake?') | 2356 'by mistake?') |
| 2355 return results | 2357 return results |
| 2356 | |
| 2357 bad_revision, good_revision = self.NudgeRevisionsIfDEPSChange( | 2358 bad_revision, good_revision = self.NudgeRevisionsIfDEPSChange( |
| 2358 bad_revision, good_revision) | 2359 bad_revision, good_revision, good_revision_in) |
| 2359 | |
| 2360 if self.opts.output_buildbot_annotations: | 2360 if self.opts.output_buildbot_annotations: |
| 2361 bisect_utils.OutputAnnotationStepStart('Gathering Revisions') | 2361 bisect_utils.OutputAnnotationStepStart('Gathering Revisions') |
| 2362 | 2362 |
| 2363 cannot_bisect = self.CanPerformBisect(good_revision) | 2363 cannot_bisect = self.CanPerformBisect(good_revision) |
| 2364 if cannot_bisect: | 2364 if cannot_bisect: |
| 2365 results['error'] = cannot_bisect.get('error') | 2365 results['error'] = cannot_bisect.get('error') |
| 2366 return results | 2366 return results |
| 2367 | 2367 |
| 2368 print 'Gathering revision range for bisection.' | 2368 print 'Gathering revision range for bisection.' |
| 2369 # Retrieve a list of revisions to do bisection on. | 2369 # Retrieve a list of revisions to do bisection on. |
| (...skipping 992 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 3362 # bugs. If you change this, please update the perf dashboard as well. | 3362 # bugs. If you change this, please update the perf dashboard as well. |
| 3363 bisect_utils.OutputAnnotationStepStart('Results') | 3363 bisect_utils.OutputAnnotationStepStart('Results') |
| 3364 print 'Error: %s' % e.message | 3364 print 'Error: %s' % e.message |
| 3365 if opts.output_buildbot_annotations: | 3365 if opts.output_buildbot_annotations: |
| 3366 bisect_utils.OutputAnnotationStepClosed() | 3366 bisect_utils.OutputAnnotationStepClosed() |
| 3367 return 1 | 3367 return 1 |
| 3368 | 3368 |
| 3369 | 3369 |
| 3370 if __name__ == '__main__': | 3370 if __name__ == '__main__': |
| 3371 sys.exit(main()) | 3371 sys.exit(main()) |
| OLD | NEW |