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 |