| 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 994 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 1005 source_dir = os.path.join(build_dir, build_type) | 1005 source_dir = os.path.join(build_dir, build_type) |
| 1006 destination_dir = os.path.join(build_dir, '%s.bak' % build_type) | 1006 destination_dir = os.path.join(build_dir, '%s.bak' % build_type) |
| 1007 if restore: | 1007 if restore: |
| 1008 source_dir, destination_dir = destination_dir, source_dir | 1008 source_dir, destination_dir = destination_dir, source_dir |
| 1009 if os.path.exists(source_dir): | 1009 if os.path.exists(source_dir): |
| 1010 RemoveDirectoryTree(destination_dir) | 1010 RemoveDirectoryTree(destination_dir) |
| 1011 shutil.move(source_dir, destination_dir) | 1011 shutil.move(source_dir, destination_dir) |
| 1012 return destination_dir | 1012 return destination_dir |
| 1013 return None | 1013 return None |
| 1014 | 1014 |
| 1015 def GetBuildArchiveForRevision(self, revision, gs_bucket, target_arch, | 1015 def _GetBuildArchiveForRevision(self, revision, gs_bucket, target_arch, |
| 1016 patch_sha, out_dir): | 1016 patch_sha, out_dir): |
| 1017 """Checks and downloads build archive for a given revision. | 1017 """Checks and downloads build archive for a given revision. |
| 1018 | 1018 |
| 1019 Checks for build archive with Git hash or SVN revision. If either of the | 1019 Checks for build archive with Git hash or SVN revision. If either of the |
| 1020 file exists, then downloads the archive file. | 1020 file exists, then downloads the archive file. |
| 1021 | 1021 |
| 1022 Args: | 1022 Args: |
| 1023 revision: A Git hash revision. | 1023 revision: A git commit hash. |
| 1024 gs_bucket: Cloud storage bucket name | 1024 gs_bucket: Cloud storage bucket name. |
| 1025 target_arch: 32 or 64 bit build target | 1025 target_arch: Architecture name string, e.g. "ia32" or "x64". |
| 1026 patch: A DEPS patch (used while bisecting 3rd party repositories). | 1026 patch_sha: A SHA1 hex digest of a DEPS file patch, used while |
| 1027 bisecting 3rd party repositories. |
| 1027 out_dir: Build output directory where downloaded file is stored. | 1028 out_dir: Build output directory where downloaded file is stored. |
| 1028 | 1029 |
| 1029 Returns: | 1030 Returns: |
| 1030 Downloaded archive file path if exists, otherwise None. | 1031 Downloaded archive file path if exists, otherwise None. |
| 1031 """ | 1032 """ |
| 1032 # Source archive file path on cloud storage using Git revision. | 1033 # Source archive file path on cloud storage using Git revision. |
| 1033 source_file = GetRemoteBuildPath( | 1034 source_file = GetRemoteBuildPath( |
| 1034 revision, self.opts.target_platform, target_arch, patch_sha) | 1035 revision, self.opts.target_platform, target_arch, patch_sha) |
| 1035 downloaded_archive = FetchFromCloudStorage(gs_bucket, source_file, out_dir) | 1036 downloaded_archive = FetchFromCloudStorage(gs_bucket, source_file, out_dir) |
| 1036 if not downloaded_archive: | 1037 if not downloaded_archive: |
| 1037 # Get commit position for the given SHA. | 1038 # Get commit position for the given SHA. |
| 1038 commit_position = source_control.GetCommitPosition(revision) | 1039 commit_position = source_control.GetCommitPosition(revision) |
| 1039 if commit_position: | 1040 if commit_position: |
| 1040 # Source archive file path on cloud storage using SVN revision. | 1041 # Source archive file path on cloud storage using SVN revision. |
| 1041 source_file = GetRemoteBuildPath( | 1042 source_file = GetRemoteBuildPath( |
| 1042 commit_position, self.opts.target_platform, target_arch, patch_sha) | 1043 commit_position, self.opts.target_platform, target_arch, patch_sha) |
| 1043 return FetchFromCloudStorage(gs_bucket, source_file, out_dir) | 1044 return FetchFromCloudStorage(gs_bucket, source_file, out_dir) |
| 1044 return downloaded_archive | 1045 return downloaded_archive |
| 1045 | 1046 |
| 1046 def DownloadCurrentBuild(self, revision, depot, build_type='Release'): | 1047 def _DownloadAndUnzipBuild(self, revision, depot, build_type='Release'): |
| 1047 """Downloads the build archive for the given revision. | 1048 """Downloads the build archive for the given revision. |
| 1048 | 1049 |
| 1049 Args: | 1050 Args: |
| 1050 revision: The git revision to download or build. | 1051 revision: The git revision to download. |
| 1051 depot: The name of a dependency repository. Should be in DEPOT_NAMES. | 1052 depot: The name of a dependency repository. Should be in DEPOT_NAMES. |
| 1052 build_type: Target build type, e.g. Release', 'Debug', 'Release_x64' etc. | 1053 build_type: Target build type, e.g. Release', 'Debug', 'Release_x64' etc. |
| 1053 | 1054 |
| 1054 Returns: | 1055 Returns: |
| 1055 True if download succeeds, otherwise False. | 1056 True if download succeeds, otherwise False. |
| 1056 """ | 1057 """ |
| 1057 patch = None | 1058 patch = None |
| 1058 patch_sha = None | 1059 patch_sha = None |
| 1059 if depot != 'chromium': | 1060 if depot != 'chromium': |
| 1060 # Create a DEPS patch with new revision for dependency repository. | 1061 # Create a DEPS patch with new revision for dependency repository. |
| 1061 revision, patch = self.CreateDEPSPatch(depot, revision) | 1062 revision, patch = self.CreateDEPSPatch(depot, revision) |
| 1062 | 1063 |
| 1063 if patch: | 1064 if patch: |
| 1064 # Get the SHA of the DEPS changes patch. | 1065 # Get the SHA of the DEPS changes patch. |
| 1065 patch_sha = GetSHA1HexDigest(patch) | 1066 patch_sha = GetSHA1HexDigest(patch) |
| 1066 | 1067 |
| 1067 # Update the DEPS changes patch with a patch to create a new file named | 1068 # Update the DEPS changes patch with a patch to create a new file named |
| 1068 # 'DEPS.sha' and add patch_sha evaluated above to it. | 1069 # 'DEPS.sha' and add patch_sha evaluated above to it. |
| 1069 patch = '%s\n%s' % (patch, DEPS_SHA_PATCH % {'deps_sha': patch_sha}) | 1070 patch = '%s\n%s' % (patch, DEPS_SHA_PATCH % {'deps_sha': patch_sha}) |
| 1070 | 1071 |
| 1071 # Get build output directory. | |
| 1072 build_dir = builder.GetBuildOutputDirectory(self.opts, self.src_cwd) | 1072 build_dir = builder.GetBuildOutputDirectory(self.opts, self.src_cwd) |
| 1073 downloaded_file = self._WaitForBuildDownload( |
| 1074 revision, build_dir, deps_patch=patch, deps_patch_sha=patch_sha) |
| 1075 if not downloaded_file: |
| 1076 return False |
| 1077 return self._UnzipAndMoveBuildProducts(downloaded_file, build_dir, |
| 1078 build_type=build_type) |
| 1079 |
| 1080 def _WaitForBuildDownload(self, revision, build_dir, deps_patch=None, |
| 1081 deps_patch_sha=None): |
| 1082 """Tries to download a zip archive for a build. |
| 1083 |
| 1084 This involves seeing whether the archive is already available, and if not, |
| 1085 then requesting a build and waiting before downloading. |
| 1086 |
| 1087 Args: |
| 1088 revision: A git commit hash. |
| 1089 build_dir: The directory to download the build into. |
| 1090 deps_patch: A patch which changes a dependency repository revision in |
| 1091 the DEPS, if applicable. |
| 1092 deps_patch_sha: The SHA1 hex digest of the above patch. |
| 1093 |
| 1094 Returns: |
| 1095 File path of the downloaded file if successful, otherwise None. |
| 1096 """ |
| 1073 abs_build_dir = os.path.abspath(build_dir) | 1097 abs_build_dir = os.path.abspath(build_dir) |
| 1074 | 1098 fetch_build_func = lambda: self._GetBuildArchiveForRevision( |
| 1075 fetch_build_func = lambda: self.GetBuildArchiveForRevision( | 1099 revision, self.opts.gs_bucket, self.opts.target_arch, |
| 1076 revision, self.opts.gs_bucket, self.opts.target_arch, | 1100 deps_patch_sha, abs_build_dir) |
| 1077 patch_sha, abs_build_dir) | |
| 1078 | 1101 |
| 1079 # Downloaded archive file path, downloads build archive for given revision. | 1102 # Downloaded archive file path, downloads build archive for given revision. |
| 1103 # This will be False if the build isn't yet available. |
| 1080 downloaded_file = fetch_build_func() | 1104 downloaded_file = fetch_build_func() |
| 1081 | 1105 |
| 1082 # When build archive doesn't exists, post a build request to tryserver | 1106 # When build archive doesn't exist, post a build request to try server |
| 1083 # and wait for the build to be produced. | 1107 # and wait for the build to be produced. |
| 1084 if not downloaded_file: | 1108 if not downloaded_file: |
| 1085 downloaded_file = self._RequestBuildAndWait( | 1109 downloaded_file = self._RequestBuildAndWait( |
| 1086 revision, fetch_build=fetch_build_func, patch=patch) | 1110 revision, fetch_build=fetch_build_func, patch=deps_patch) |
| 1087 if not downloaded_file: | 1111 if not downloaded_file: |
| 1088 return False | 1112 return None |
| 1089 | 1113 |
| 1090 # Generic name for the archive, created when archive file is extracted. | 1114 return downloaded_file |
| 1091 output_dir = os.path.join( | |
| 1092 abs_build_dir, GetZipFileName(target_arch=self.opts.target_arch)) | |
| 1093 | |
| 1094 # Unzip build archive directory. | |
| 1095 try: | |
| 1096 RemoveDirectoryTree(output_dir) | |
| 1097 self.BackupOrRestoreOutputDirectory(restore=False) | |
| 1098 # Build output directory based on target(e.g. out/Release, out/Debug). | |
| 1099 target_build_output_dir = os.path.join(abs_build_dir, build_type) | |
| 1100 ExtractZip(downloaded_file, abs_build_dir) | |
| 1101 if not os.path.exists(output_dir): | |
| 1102 # Due to recipe changes, the builds extract folder contains | |
| 1103 # out/Release instead of full-build-<platform>/Release. | |
| 1104 if os.path.exists(os.path.join(abs_build_dir, 'out', build_type)): | |
| 1105 output_dir = os.path.join(abs_build_dir, 'out', build_type) | |
| 1106 else: | |
| 1107 raise IOError('Missing extracted folder %s ' % output_dir) | |
| 1108 | |
| 1109 print 'Moving build from %s to %s' % ( | |
| 1110 output_dir, target_build_output_dir) | |
| 1111 shutil.move(output_dir, target_build_output_dir) | |
| 1112 return True | |
| 1113 except Exception as e: | |
| 1114 print 'Something went wrong while extracting archive file: %s' % e | |
| 1115 self.BackupOrRestoreOutputDirectory(restore=True) | |
| 1116 # Cleanup any leftovers from unzipping. | |
| 1117 if os.path.exists(output_dir): | |
| 1118 RemoveDirectoryTree(output_dir) | |
| 1119 finally: | |
| 1120 # Delete downloaded archive | |
| 1121 if os.path.exists(downloaded_file): | |
| 1122 os.remove(downloaded_file) | |
| 1123 return False | |
| 1124 | 1115 |
| 1125 def _RequestBuildAndWait(self, git_revision, fetch_build, patch=None): | 1116 def _RequestBuildAndWait(self, git_revision, fetch_build, patch=None): |
| 1126 """Triggers a try job for a build job. | 1117 """Triggers a try job for a build job. |
| 1127 | 1118 |
| 1128 This function prepares and starts a try job on the tryserver.chromium.perf | 1119 This function prepares and starts a try job on the tryserver.chromium.perf |
| 1129 master, and waits for the binaries to be produced and archived in cloud | 1120 master, and waits for the binaries to be produced and archived in cloud |
| 1130 storage. Once the build is ready it's downloaded. | 1121 storage. Once the build is ready it's downloaded. |
| 1131 | 1122 |
| 1132 Args: | 1123 Args: |
| 1133 git_revision: A Git hash revision. | 1124 git_revision: A Git hash revision. |
| (...skipping 50 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 1184 def _GetBuilderBuildTime(): | 1175 def _GetBuilderBuildTime(): |
| 1185 """Returns the time to wait for a build after requesting one.""" | 1176 """Returns the time to wait for a build after requesting one.""" |
| 1186 if bisect_utils.IsWindowsHost(): | 1177 if bisect_utils.IsWindowsHost(): |
| 1187 return MAX_WIN_BUILD_TIME | 1178 return MAX_WIN_BUILD_TIME |
| 1188 if bisect_utils.IsLinuxHost(): | 1179 if bisect_utils.IsLinuxHost(): |
| 1189 return MAX_LINUX_BUILD_TIME | 1180 return MAX_LINUX_BUILD_TIME |
| 1190 if bisect_utils.IsMacHost(): | 1181 if bisect_utils.IsMacHost(): |
| 1191 return MAX_MAC_BUILD_TIME | 1182 return MAX_MAC_BUILD_TIME |
| 1192 raise NotImplementedError('Unsupported Platform "%s".' % sys.platform) | 1183 raise NotImplementedError('Unsupported Platform "%s".' % sys.platform) |
| 1193 | 1184 |
| 1185 def _UnzipAndMoveBuildProducts(self, downloaded_file, build_dir, |
| 1186 build_type='Release'): |
| 1187 """Unzips the build archive and moves it to the build output directory. |
| 1188 |
| 1189 The build output directory is whereever the binaries are expected to |
| 1190 be in order to start Chrome and run tests. |
| 1191 |
| 1192 Args: |
| 1193 downloaded_file: File path of the downloaded zip file. |
| 1194 build_dir: Directory where the the zip file was downloaded to. |
| 1195 build_type: "Release" or "Debug". |
| 1196 |
| 1197 Returns: |
| 1198 True if successful, False otherwise. |
| 1199 """ |
| 1200 abs_build_dir = os.path.abspath(build_dir) |
| 1201 output_dir = os.path.join( |
| 1202 abs_build_dir, GetZipFileName(target_arch=self.opts.target_arch)) |
| 1203 |
| 1204 try: |
| 1205 RemoveDirectoryTree(output_dir) |
| 1206 self.BackupOrRestoreOutputDirectory(restore=False) |
| 1207 # Build output directory based on target(e.g. out/Release, out/Debug). |
| 1208 target_build_output_dir = os.path.join(abs_build_dir, build_type) |
| 1209 ExtractZip(downloaded_file, abs_build_dir) |
| 1210 if not os.path.exists(output_dir): |
| 1211 # Due to recipe changes, the builds extract folder contains |
| 1212 # out/Release instead of full-build-<platform>/Release. |
| 1213 if os.path.exists(os.path.join(abs_build_dir, 'out', build_type)): |
| 1214 output_dir = os.path.join(abs_build_dir, 'out', build_type) |
| 1215 else: |
| 1216 raise IOError('Missing extracted folder %s ' % output_dir) |
| 1217 |
| 1218 print 'Moving build from %s to %s' % ( |
| 1219 output_dir, target_build_output_dir) |
| 1220 shutil.move(output_dir, target_build_output_dir) |
| 1221 return True |
| 1222 except Exception as e: |
| 1223 print 'Something went wrong while extracting archive file: %s' % e |
| 1224 self.BackupOrRestoreOutputDirectory(restore=True) |
| 1225 # Cleanup any leftovers from unzipping. |
| 1226 if os.path.exists(output_dir): |
| 1227 RemoveDirectoryTree(output_dir) |
| 1228 finally: |
| 1229 # Delete downloaded archive |
| 1230 if os.path.exists(downloaded_file): |
| 1231 os.remove(downloaded_file) |
| 1232 return False |
| 1233 |
| 1194 def IsDownloadable(self, depot): | 1234 def IsDownloadable(self, depot): |
| 1195 """Checks if build can be downloaded based on target platform and depot.""" | 1235 """Checks if build can be downloaded based on target platform and depot.""" |
| 1196 if (self.opts.target_platform in ['chromium', 'android'] and | 1236 if (self.opts.target_platform in ['chromium', 'android'] and |
| 1197 self.opts.gs_bucket): | 1237 self.opts.gs_bucket): |
| 1198 return (depot == 'chromium' or | 1238 return (depot == 'chromium' or |
| 1199 'chromium' in bisect_utils.DEPOT_DEPS_NAME[depot]['from'] or | 1239 'chromium' in bisect_utils.DEPOT_DEPS_NAME[depot]['from'] or |
| 1200 'v8' in bisect_utils.DEPOT_DEPS_NAME[depot]['from']) | 1240 'v8' in bisect_utils.DEPOT_DEPS_NAME[depot]['from']) |
| 1201 return False | 1241 return False |
| 1202 | 1242 |
| 1203 def UpdateDepsContents(self, deps_contents, depot, git_revision, deps_key): | 1243 def UpdateDepsContents(self, deps_contents, depot, git_revision, deps_key): |
| (...skipping 117 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 1321 diff_text = bisect_utils.CheckRunGit(diff_command, cwd=self.src_cwd) | 1361 diff_text = bisect_utils.CheckRunGit(diff_command, cwd=self.src_cwd) |
| 1322 return (chromium_sha, ChangeBackslashToSlashInPatch(diff_text)) | 1362 return (chromium_sha, ChangeBackslashToSlashInPatch(diff_text)) |
| 1323 else: | 1363 else: |
| 1324 raise RuntimeError( | 1364 raise RuntimeError( |
| 1325 'Failed to update DEPS file for chromium: [%s]' % chromium_sha) | 1365 'Failed to update DEPS file for chromium: [%s]' % chromium_sha) |
| 1326 else: | 1366 else: |
| 1327 raise RuntimeError( | 1367 raise RuntimeError( |
| 1328 'DEPS checkout Failed for chromium revision : [%s]' % chromium_sha) | 1368 'DEPS checkout Failed for chromium revision : [%s]' % chromium_sha) |
| 1329 return (None, None) | 1369 return (None, None) |
| 1330 | 1370 |
| 1331 def BuildCurrentRevision(self, depot, revision=None): | 1371 def _ObtainBuild(self, depot, revision=None): |
| 1332 """Builds chrome and performance_ui_tests on the current revision. | 1372 """Obtains a build by either downloading or building directly. |
| 1373 |
| 1374 Args: |
| 1375 depot: Dependency repository name. |
| 1376 revision: A git commit hash. If None is given, the currently checked-out |
| 1377 revision is built. |
| 1333 | 1378 |
| 1334 Returns: | 1379 Returns: |
| 1335 True if the build was successful. | 1380 True for success. |
| 1336 """ | 1381 """ |
| 1337 if self.opts.debug_ignore_build: | 1382 if self.opts.debug_ignore_build: |
| 1338 return True | 1383 return True |
| 1339 | 1384 |
| 1340 build_success = False | 1385 build_success = False |
| 1341 cwd = os.getcwd() | 1386 cwd = os.getcwd() |
| 1342 os.chdir(self.src_cwd) | 1387 os.chdir(self.src_cwd) |
| 1343 # Fetch build archive for the given revision from the cloud storage when | 1388 # Fetch build archive for the given revision from the cloud storage when |
| 1344 # the storage bucket is passed. | 1389 # the storage bucket is passed. |
| 1345 if self.IsDownloadable(depot) and revision: | 1390 if self.IsDownloadable(depot) and revision: |
| 1346 build_success = self.DownloadCurrentBuild(revision, depot) | 1391 build_success = self._DownloadAndUnzipBuild(revision, depot) |
| 1347 else: | 1392 else: |
| 1348 # These codes are executed when bisect bots builds binaries locally. | 1393 # These codes are executed when bisect bots builds binaries locally. |
| 1349 build_success = self.builder.Build(depot, self.opts) | 1394 build_success = self.builder.Build(depot, self.opts) |
| 1350 os.chdir(cwd) | 1395 os.chdir(cwd) |
| 1351 return build_success | 1396 return build_success |
| 1352 | 1397 |
| 1353 def RunGClientHooks(self): | 1398 def RunGClientHooks(self): |
| 1354 """Runs gclient with runhooks command. | 1399 """Runs gclient with runhooks command. |
| 1355 | 1400 |
| 1356 Returns: | 1401 Returns: |
| (...skipping 376 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 1733 return ('Failed to run [gclient runhooks].', BUILD_RESULT_FAIL) | 1778 return ('Failed to run [gclient runhooks].', BUILD_RESULT_FAIL) |
| 1734 | 1779 |
| 1735 # Skip this revision if it can be skipped. | 1780 # Skip this revision if it can be skipped. |
| 1736 if skippable and self.ShouldSkipRevision(depot, revision): | 1781 if skippable and self.ShouldSkipRevision(depot, revision): |
| 1737 return ('Skipped revision: [%s]' % str(revision), | 1782 return ('Skipped revision: [%s]' % str(revision), |
| 1738 BUILD_RESULT_SKIPPED) | 1783 BUILD_RESULT_SKIPPED) |
| 1739 | 1784 |
| 1740 # Obtain a build for this revision. This may be done by requesting a build | 1785 # Obtain a build for this revision. This may be done by requesting a build |
| 1741 # from another builder, waiting for it and downloading it. | 1786 # from another builder, waiting for it and downloading it. |
| 1742 start_build_time = time.time() | 1787 start_build_time = time.time() |
| 1743 build_success = self.BuildCurrentRevision(depot, revision) | 1788 build_success = self._ObtainBuild(depot, revision) |
| 1744 if not build_success: | 1789 if not build_success: |
| 1745 return ('Failed to build revision: [%s]' % str(revision), | 1790 return ('Failed to build revision: [%s]' % str(revision), |
| 1746 BUILD_RESULT_FAIL) | 1791 BUILD_RESULT_FAIL) |
| 1747 after_build_time = time.time() | 1792 after_build_time = time.time() |
| 1748 | 1793 |
| 1749 # Possibly alter the command. | 1794 # Possibly alter the command. |
| 1750 command = self.GetCompatibleCommand(command, revision, depot) | 1795 command = self.GetCompatibleCommand(command, revision, depot) |
| 1751 | 1796 |
| 1752 # Run the command and get the results. | 1797 # Run the command and get the results. |
| 1753 results = self.RunPerformanceTestAndParseResults(command, metric) | 1798 results = self.RunPerformanceTestAndParseResults(command, metric) |
| (...skipping 993 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 2747 # bugs. If you change this, please update the perf dashboard as well. | 2792 # bugs. If you change this, please update the perf dashboard as well. |
| 2748 bisect_utils.OutputAnnotationStepStart('Results') | 2793 bisect_utils.OutputAnnotationStepStart('Results') |
| 2749 print 'Error: %s' % e.message | 2794 print 'Error: %s' % e.message |
| 2750 if opts.output_buildbot_annotations: | 2795 if opts.output_buildbot_annotations: |
| 2751 bisect_utils.OutputAnnotationStepClosed() | 2796 bisect_utils.OutputAnnotationStepClosed() |
| 2752 return 1 | 2797 return 1 |
| 2753 | 2798 |
| 2754 | 2799 |
| 2755 if __name__ == '__main__': | 2800 if __name__ == '__main__': |
| 2756 sys.exit(main()) | 2801 sys.exit(main()) |
| OLD | NEW |