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 |