| 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 36 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 47 import shutil | 47 import shutil |
| 48 import StringIO | 48 import StringIO |
| 49 import subprocess | 49 import subprocess |
| 50 import sys | 50 import sys |
| 51 import time | 51 import time |
| 52 import zipfile | 52 import zipfile |
| 53 | 53 |
| 54 sys.path.append(os.path.join(os.path.dirname(__file__), 'telemetry')) | 54 sys.path.append(os.path.join(os.path.dirname(__file__), 'telemetry')) |
| 55 | 55 |
| 56 import bisect_utils | 56 import bisect_utils |
| 57 import post_perf_builder_job | 57 import post_perf_builder_job as bisect_builder |
| 58 from telemetry.page import cloud_storage | 58 from telemetry.page import cloud_storage |
| 59 | 59 |
| 60 # The additional repositories that might need to be bisected. | 60 # The additional repositories that might need to be bisected. |
| 61 # If the repository has any dependant repositories (such as skia/src needs | 61 # If the repository has any dependant repositories (such as skia/src needs |
| 62 # skia/include and skia/gyp to be updated), specify them in the 'depends' | 62 # skia/include and skia/gyp to be updated), specify them in the 'depends' |
| 63 # so that they're synced appropriately. | 63 # so that they're synced appropriately. |
| 64 # Format is: | 64 # Format is: |
| 65 # src: path to the working directory. | 65 # src: path to the working directory. |
| 66 # recurse: True if this repositry will get bisected. | 66 # recurse: True if this repositry will get bisected. |
| 67 # depends: A list of other repositories that are actually part of the same | 67 # depends: A list of other repositories that are actually part of the same |
| (...skipping 1509 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 1577 downloaded_file = os.path.join( | 1577 downloaded_file = os.path.join( |
| 1578 abs_build_dir, | 1578 abs_build_dir, |
| 1579 GetZipFileName(revision, self.opts.target_arch, patch_sha)) | 1579 GetZipFileName(revision, self.opts.target_arch, patch_sha)) |
| 1580 | 1580 |
| 1581 fetch_build_func = lambda: FetchFromCloudStorage(self.opts.gs_bucket, | 1581 fetch_build_func = lambda: FetchFromCloudStorage(self.opts.gs_bucket, |
| 1582 source_file, | 1582 source_file, |
| 1583 abs_build_dir) | 1583 abs_build_dir) |
| 1584 | 1584 |
| 1585 if not fetch_build_func(): | 1585 if not fetch_build_func(): |
| 1586 if not self.PostBuildRequestAndWait(revision, | 1586 if not self.PostBuildRequestAndWait(revision, |
| 1587 condition=fetch_build_func, | 1587 fetch_build=fetch_build_func, |
| 1588 patch=patch): | 1588 patch=patch): |
| 1589 raise RuntimeError('Somewthing went wrong while processing build' | 1589 raise RuntimeError('Somewthing went wrong while processing build' |
| 1590 'request for: %s' % revision) | 1590 'request for: %s' % revision) |
| 1591 # Generic name for the archive, created when archive file is extracted. | 1591 # Generic name for the archive, created when archive file is extracted. |
| 1592 output_dir = os.path.join( | 1592 output_dir = os.path.join( |
| 1593 abs_build_dir, GetZipFileName(target_arch=self.opts.target_arch)) | 1593 abs_build_dir, GetZipFileName(target_arch=self.opts.target_arch)) |
| 1594 # Unzip build archive directory. | 1594 # Unzip build archive directory. |
| 1595 try: | 1595 try: |
| 1596 RmTreeAndMkDir(output_dir, skip_makedir=True) | 1596 RmTreeAndMkDir(output_dir, skip_makedir=True) |
| 1597 ExtractZip(downloaded_file, abs_build_dir) | 1597 ExtractZip(downloaded_file, abs_build_dir) |
| (...skipping 11 matching lines...) Expand all Loading... |
| 1609 self.BackupOrRestoreOutputdirectory(restore=True) | 1609 self.BackupOrRestoreOutputdirectory(restore=True) |
| 1610 # Cleanup any leftovers from unzipping. | 1610 # Cleanup any leftovers from unzipping. |
| 1611 if os.path.exists(output_dir): | 1611 if os.path.exists(output_dir): |
| 1612 RmTreeAndMkDir(output_dir, skip_makedir=True) | 1612 RmTreeAndMkDir(output_dir, skip_makedir=True) |
| 1613 finally: | 1613 finally: |
| 1614 # Delete downloaded archive | 1614 # Delete downloaded archive |
| 1615 if os.path.exists(downloaded_file): | 1615 if os.path.exists(downloaded_file): |
| 1616 os.remove(downloaded_file) | 1616 os.remove(downloaded_file) |
| 1617 return False | 1617 return False |
| 1618 | 1618 |
| 1619 def PostBuildRequestAndWait(self, revision, condition, patch=None): | 1619 def WaitUntilBuildIsReady(self, fetch_build, bot_name, builder_host, |
| 1620 builder_port, build_request_id, max_timeout): |
| 1621 """Waits until build is produced by bisect builder on tryserver. |
| 1622 |
| 1623 Args: |
| 1624 fetch_build: Function to check and download build from cloud storage. |
| 1625 bot_name: Builder bot name on tryserver. |
| 1626 builder_host Tryserver hostname. |
| 1627 builder_port: Tryserver port. |
| 1628 build_request_id: A unique ID of the build request posted to tryserver. |
| 1629 max_timeout: Maximum time to wait for the build. |
| 1630 |
| 1631 Returns: |
| 1632 True if build exists and download is successful, otherwise throws |
| 1633 RuntimeError exception when time elapse. |
| 1634 """ |
| 1635 # Build number on the tryserver. |
| 1636 build_num = None |
| 1637 # Interval to check build on cloud storage. |
| 1638 poll_interval = 60 |
| 1639 # Interval to check build status on tryserver. |
| 1640 status_check_interval = 600 |
| 1641 last_status_check = time.time() |
| 1642 start_time = time.time() |
| 1643 while True: |
| 1644 # Checks for build on gs://chrome-perf and download if exists. |
| 1645 res = fetch_build() |
| 1646 if res: |
| 1647 return (res, 'Build successfully found') |
| 1648 elapsed_status_check = time.time() - last_status_check |
| 1649 # To avoid overloading tryserver with status check requests, we check |
| 1650 # build status for every 10 mins. |
| 1651 if elapsed_status_check > status_check_interval: |
| 1652 last_status_check = time.time() |
| 1653 if not build_num: |
| 1654 # Get the build number on tryserver for the current build. |
| 1655 build_num = bisect_builder.GetBuildNumFromBuilder( |
| 1656 build_request_id, bot_name, builder_host, builder_port) |
| 1657 # Check the status of build using the build number. |
| 1658 # Note: Build is treated as PENDING if build number is not found |
| 1659 # on the the tryserver. |
| 1660 build_status, status_link = bisect_builder.GetBuildStatus( |
| 1661 build_num, bot_name, builder_host, builder_port) |
| 1662 if build_status == bisect_builder.FAILED: |
| 1663 return (False, 'Failed to produce build, log: %s' % status_link) |
| 1664 elapsed_time = time.time() - start_time |
| 1665 if elapsed_time > max_timeout: |
| 1666 return (False, 'Timed out: %ss without build' % max_timeout) |
| 1667 |
| 1668 print 'Time elapsed: %ss without build.' % elapsed_time |
| 1669 time.sleep(poll_interval) |
| 1670 |
| 1671 def PostBuildRequestAndWait(self, revision, fetch_build, patch=None): |
| 1620 """POSTs the build request job to the tryserver instance.""" | 1672 """POSTs the build request job to the tryserver instance.""" |
| 1621 | 1673 |
| 1622 def GetBuilderNameAndBuildTime(target_arch='ia32'): | 1674 def GetBuilderNameAndBuildTime(target_arch='ia32'): |
| 1623 """Gets builder bot name and buildtime in seconds based on platform.""" | 1675 """Gets builder bot name and buildtime in seconds based on platform.""" |
| 1624 # Bot names should match the one listed in tryserver.chromium's | 1676 # Bot names should match the one listed in tryserver.chromium's |
| 1625 # master.cfg which produces builds for bisect. | 1677 # master.cfg which produces builds for bisect. |
| 1626 if IsWindows(): | 1678 if IsWindows(): |
| 1627 if Is64BitWindows() and target_arch == 'x64': | 1679 if Is64BitWindows() and target_arch == 'x64': |
| 1628 return ('win_perf_bisect_builder', MAX_WIN_BUILD_TIME) | 1680 return ('win_perf_bisect_builder', MAX_WIN_BUILD_TIME) |
| 1629 return ('win_perf_bisect_builder', MAX_WIN_BUILD_TIME) | 1681 return ('win_perf_bisect_builder', MAX_WIN_BUILD_TIME) |
| 1630 if IsLinux(): | 1682 if IsLinux(): |
| 1631 return ('linux_perf_bisect_builder', MAX_LINUX_BUILD_TIME) | 1683 return ('linux_perf_bisect_builder', MAX_LINUX_BUILD_TIME) |
| 1632 if IsMac(): | 1684 if IsMac(): |
| 1633 return ('mac_perf_bisect_builder', MAX_MAC_BUILD_TIME) | 1685 return ('mac_perf_bisect_builder', MAX_MAC_BUILD_TIME) |
| 1634 raise NotImplementedError('Unsupported Platform "%s".' % sys.platform) | 1686 raise NotImplementedError('Unsupported Platform "%s".' % sys.platform) |
| 1635 if not condition: | 1687 if not fetch_build: |
| 1636 return False | 1688 return False |
| 1637 | 1689 |
| 1638 bot_name, build_timeout = GetBuilderNameAndBuildTime(self.opts.target_arch) | 1690 bot_name, build_timeout = GetBuilderNameAndBuildTime(self.opts.target_arch) |
| 1639 | 1691 builder_host = self.opts.builder_host |
| 1640 # Create a unique ID for each build request posted to try server builders. | 1692 builder_port = self.opts.builder_port |
| 1693 # Create a unique ID for each build request posted to tryserver builders. |
| 1641 # This ID is added to "Reason" property in build's json. | 1694 # This ID is added to "Reason" property in build's json. |
| 1642 # TODO: Use this id to track the build status. | 1695 build_request_id = GetSHA1HexDigest( |
| 1643 build_request_id = GetSHA1HexDigest('%s-%s' % (revision, patch)) | 1696 '%s-%s-%s' % (revision, patch, time.time())) |
| 1644 | 1697 |
| 1645 # Creates a try job description. | 1698 # Creates a try job description. |
| 1646 job_args = {'host': self.opts.builder_host, | 1699 job_args = {'host': builder_host, |
| 1647 'port': self.opts.builder_port, | 1700 'port': builder_port, |
| 1648 'revision': 'src@%s' % revision, | 1701 'revision': 'src@%s' % revision, |
| 1649 'bot': bot_name, | 1702 'bot': bot_name, |
| 1650 'name': build_request_id | 1703 'name': build_request_id |
| 1651 } | 1704 } |
| 1652 # Update patch information if supplied. | 1705 # Update patch information if supplied. |
| 1653 if patch: | 1706 if patch: |
| 1654 job_args['patch'] = patch | 1707 job_args['patch'] = patch |
| 1655 # Posts job to build the revision on the server. | 1708 # Posts job to build the revision on the server. |
| 1656 if post_perf_builder_job.PostTryJob(job_args): | 1709 if bisect_builder.PostTryJob(job_args): |
| 1657 poll_interval = 60 | 1710 status, error_msg = self.WaitUntilBuildIsReady(fetch_build, |
| 1658 start_time = time.time() | 1711 bot_name, |
| 1659 while True: | 1712 builder_host, |
| 1660 res = condition() | 1713 builder_port, |
| 1661 if res: | 1714 build_request_id, |
| 1662 return res | 1715 build_timeout) |
| 1663 elapsed_time = time.time() - start_time | 1716 if not status: |
| 1664 if elapsed_time > build_timeout: | 1717 raise RuntimeError('%s [revision: %s]' % (error_msg, revision)) |
| 1665 raise RuntimeError('Timed out while waiting %ds for %s build.' % | 1718 return True |
| 1666 (build_timeout, revision)) | |
| 1667 print ('Time elapsed: %ss, still waiting for %s build' % | |
| 1668 (elapsed_time, revision)) | |
| 1669 time.sleep(poll_interval) | |
| 1670 return False | 1719 return False |
| 1671 | 1720 |
| 1672 def IsDownloadable(self, depot): | 1721 def IsDownloadable(self, depot): |
| 1673 """Checks if build is downloadable based on target platform and depot.""" | 1722 """Checks if build is downloadable based on target platform and depot.""" |
| 1674 if self.opts.target_platform in ['chromium'] and self.opts.gs_bucket: | 1723 if self.opts.target_platform in ['chromium'] and self.opts.gs_bucket: |
| 1675 return (depot == 'chromium' or | 1724 return (depot == 'chromium' or |
| 1676 'chromium' in DEPOT_DEPS_NAME[depot]['from'] or | 1725 'chromium' in DEPOT_DEPS_NAME[depot]['from'] or |
| 1677 'v8' in DEPOT_DEPS_NAME[depot]['from']) | 1726 'v8' in DEPOT_DEPS_NAME[depot]['from']) |
| 1678 return False | 1727 return False |
| 1679 | 1728 |
| (...skipping 2234 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 3914 # The perf dashboard scrapes the "results" step in order to comment on | 3963 # The perf dashboard scrapes the "results" step in order to comment on |
| 3915 # bugs. If you change this, please update the perf dashboard as well. | 3964 # bugs. If you change this, please update the perf dashboard as well. |
| 3916 bisect_utils.OutputAnnotationStepStart('Results') | 3965 bisect_utils.OutputAnnotationStepStart('Results') |
| 3917 print 'Error: %s' % e.message | 3966 print 'Error: %s' % e.message |
| 3918 if opts.output_buildbot_annotations: | 3967 if opts.output_buildbot_annotations: |
| 3919 bisect_utils.OutputAnnotationStepClosed() | 3968 bisect_utils.OutputAnnotationStepClosed() |
| 3920 return 1 | 3969 return 1 |
| 3921 | 3970 |
| 3922 if __name__ == '__main__': | 3971 if __name__ == '__main__': |
| 3923 sys.exit(main()) | 3972 sys.exit(main()) |
| OLD | NEW |