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 1499 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
1567 downloaded_file = os.path.join( | 1567 downloaded_file = os.path.join( |
1568 abs_build_dir, | 1568 abs_build_dir, |
1569 GetZipFileName(revision, self.opts.target_arch, patch_sha)) | 1569 GetZipFileName(revision, self.opts.target_arch, patch_sha)) |
1570 | 1570 |
1571 fetch_build_func = lambda: FetchFromCloudStorage(self.opts.gs_bucket, | 1571 fetch_build_func = lambda: FetchFromCloudStorage(self.opts.gs_bucket, |
1572 source_file, | 1572 source_file, |
1573 abs_build_dir) | 1573 abs_build_dir) |
1574 | 1574 |
1575 if not fetch_build_func(): | 1575 if not fetch_build_func(): |
1576 if not self.PostBuildRequestAndWait(revision, | 1576 if not self.PostBuildRequestAndWait(revision, |
1577 condition=fetch_build_func, | 1577 condition=fetch_build_func, |
ghost stip (do not use)
2014/05/30 22:36:31
you should rename condition= to fetch_build=, righ
| |
1578 patch=patch): | 1578 patch=patch): |
1579 raise RuntimeError('Somewthing went wrong while processing build' | 1579 raise RuntimeError('Somewthing went wrong while processing build' |
1580 'request for: %s' % revision) | 1580 'request for: %s' % revision) |
1581 # Generic name for the archive, created when archive file is extracted. | 1581 # Generic name for the archive, created when archive file is extracted. |
1582 output_dir = os.path.join( | 1582 output_dir = os.path.join( |
1583 abs_build_dir, GetZipFileName(target_arch=self.opts.target_arch)) | 1583 abs_build_dir, GetZipFileName(target_arch=self.opts.target_arch)) |
1584 # Unzip build archive directory. | 1584 # Unzip build archive directory. |
1585 try: | 1585 try: |
1586 RmTreeAndMkDir(output_dir, skip_makedir=True) | 1586 RmTreeAndMkDir(output_dir, skip_makedir=True) |
1587 ExtractZip(downloaded_file, abs_build_dir) | 1587 ExtractZip(downloaded_file, abs_build_dir) |
(...skipping 11 matching lines...) Expand all Loading... | |
1599 self.BackupOrRestoreOutputdirectory(restore=True) | 1599 self.BackupOrRestoreOutputdirectory(restore=True) |
1600 # Cleanup any leftovers from unzipping. | 1600 # Cleanup any leftovers from unzipping. |
1601 if os.path.exists(output_dir): | 1601 if os.path.exists(output_dir): |
1602 RmTreeAndMkDir(output_dir, skip_makedir=True) | 1602 RmTreeAndMkDir(output_dir, skip_makedir=True) |
1603 finally: | 1603 finally: |
1604 # Delete downloaded archive | 1604 # Delete downloaded archive |
1605 if os.path.exists(downloaded_file): | 1605 if os.path.exists(downloaded_file): |
1606 os.remove(downloaded_file) | 1606 os.remove(downloaded_file) |
1607 return False | 1607 return False |
1608 | 1608 |
1609 def PostBuildRequestAndWait(self, revision, condition, patch=None): | 1609 def WaitUntilBuildIsReady(self, fetch_build, bot_name, builder_host, |
1610 builder_port, max_timeout): | |
1611 """Waits until build is produced by bisect builder on tryserver. | |
1612 | |
1613 Args: | |
1614 fetch_build: Function to check and download build from cloud storage. | |
1615 bot_name: Builder bot name on tryserver. | |
1616 builder_host Tryserver hostname. | |
1617 builder_port: Tryserver port. | |
1618 max_timeout: Maximum time to wait for the build. | |
1619 | |
1620 Returns: | |
1621 True if build exists and download is successful, otherwise throws | |
1622 RuntimeError exception when time elapse. | |
1623 """ | |
1624 # Build number on the tryserver. | |
1625 build_num = None | |
1626 # Interval to check build on cloud storage. | |
1627 poll_interval = 60 | |
1628 # Interval to check build status on tryserver. | |
1629 status_check_interval = 600 | |
1630 last_status_check = time.time() | |
1631 start_time = time.time() | |
1632 while True: | |
1633 # Checks for build on gs://chrome-perf and download if exists. | |
1634 res = fetch_build() | |
1635 if res: | |
1636 return (res, None) | |
ghost stip (do not use)
2014/05/30 22:36:31
nit: it's slightly clearer to the reader that res
| |
1637 elapsed_status_check = time.time() - last_status_check | |
1638 # To avoid overloading tryserver with status check requests, we check | |
1639 # build status for every 10 mins. | |
1640 if elapsed_status_check > status_check_interval: | |
1641 last_status_check = time.time() | |
1642 if not build_num: | |
1643 # Get the build number on tryserver for the current build. | |
1644 build_num = bisect_builder.GetBuildNumFromBuilder( | |
1645 build_request_id, bot_name, builder_host, builder_port) | |
1646 # Check the status of build using the build number. | |
1647 # Note: Build is treated as PENDING if build number is not found | |
1648 # on the the tryserver. | |
1649 build_status, status_link = bisect_builder.GetBuildStatus( | |
1650 build_num, bot_name, builder_host, builder_port) | |
1651 if build_status == bisect_builder.FAILED: | |
1652 return (False, 'Failed to produce build, log: %s,' % status_link) | |
1653 elapsed_time = time.time() - start_time | |
1654 if elapsed_time > max_timeout: | |
1655 return (False, 'Timed out: %ss without build' % max_timeout) | |
1656 | |
1657 print 'Time elapsed: %ss without build.' % elapsed_time | |
1658 time.sleep(poll_interval) | |
1659 | |
1660 def PostBuildRequestAndWait(self, revision, fetch_build, patch=None): | |
1610 """POSTs the build request job to the tryserver instance.""" | 1661 """POSTs the build request job to the tryserver instance.""" |
1611 | 1662 |
1612 def GetBuilderNameAndBuildTime(target_arch='ia32'): | 1663 def GetBuilderNameAndBuildTime(target_arch='ia32'): |
1613 """Gets builder bot name and buildtime in seconds based on platform.""" | 1664 """Gets builder bot name and buildtime in seconds based on platform.""" |
1614 # Bot names should match the one listed in tryserver.chromium's | 1665 # Bot names should match the one listed in tryserver.chromium's |
1615 # master.cfg which produces builds for bisect. | 1666 # master.cfg which produces builds for bisect. |
1616 if IsWindows(): | 1667 if IsWindows(): |
1617 if Is64BitWindows() and target_arch == 'x64': | 1668 if Is64BitWindows() and target_arch == 'x64': |
1618 return ('win_perf_bisect_builder', MAX_WIN_BUILD_TIME) | 1669 return ('win_perf_bisect_builder', MAX_WIN_BUILD_TIME) |
1619 return ('win_perf_bisect_builder', MAX_WIN_BUILD_TIME) | 1670 return ('win_perf_bisect_builder', MAX_WIN_BUILD_TIME) |
1620 if IsLinux(): | 1671 if IsLinux(): |
1621 return ('linux_perf_bisect_builder', MAX_LINUX_BUILD_TIME) | 1672 return ('linux_perf_bisect_builder', MAX_LINUX_BUILD_TIME) |
1622 if IsMac(): | 1673 if IsMac(): |
1623 return ('mac_perf_bisect_builder', MAX_MAC_BUILD_TIME) | 1674 return ('mac_perf_bisect_builder', MAX_MAC_BUILD_TIME) |
1624 raise NotImplementedError('Unsupported Platform "%s".' % sys.platform) | 1675 raise NotImplementedError('Unsupported Platform "%s".' % sys.platform) |
1625 if not condition: | 1676 if not condition: |
1626 return False | 1677 return False |
1627 | 1678 |
1628 bot_name, build_timeout = GetBuilderNameAndBuildTime(self.opts.target_arch) | 1679 bot_name, build_timeout = GetBuilderNameAndBuildTime(self.opts.target_arch) |
1629 | 1680 builder_host = self.opts.builder_host |
1681 builder_port = self.opts.builder_port | |
1630 # Create a unique ID for each build request posted to try server builders. | 1682 # Create a unique ID for each build request posted to try server builders. |
1631 # This ID is added to "Reason" property in build's json. | 1683 # This ID is added to "Reason" property in build's json. |
1632 # TODO: Use this id to track the build status. | 1684 build_request_id = GetSHA1HexDigest( |
1633 build_request_id = GetSHA1HexDigest('%s-%s' % (revision, patch)) | 1685 '%s-%s-%s' % (revision, patch, time.time())) |
1634 | 1686 |
1635 # Creates a try job description. | 1687 # Creates a try job description. |
1636 job_args = {'host': self.opts.builder_host, | 1688 job_args = {'host': builder_host, |
1637 'port': self.opts.builder_port, | 1689 'port': builder_port, |
1638 'revision': 'src@%s' % revision, | 1690 'revision': 'src@%s' % revision, |
1639 'bot': bot_name, | 1691 'bot': bot_name, |
1640 'name': build_request_id | 1692 'name': build_request_id |
1641 } | 1693 } |
1642 # Update patch information if supplied. | 1694 # Update patch information if supplied. |
1643 if patch: | 1695 if patch: |
1644 job_args['patch'] = patch | 1696 job_args['patch'] = patch |
1645 # Posts job to build the revision on the server. | 1697 # Posts job to build the revision on the server. |
1646 if post_perf_builder_job.PostTryJob(job_args): | 1698 if bisect_builder.PostTryJob(job_args): |
1647 poll_interval = 60 | 1699 status, error_msg = self.WaitUntilBuildIsReady( |
1648 start_time = time.time() | 1700 fetch_build, bot_name, builder_host, builder_port, build_timeout) |
1649 while True: | 1701 if not status: |
1650 res = condition() | 1702 raise RuntimeError('Error: %s, revision: [%s]' % (error_msg, revision)) |
1651 if res: | 1703 return True |
1652 return res | |
1653 elapsed_time = time.time() - start_time | |
1654 if elapsed_time > build_timeout: | |
1655 raise RuntimeError('Timed out while waiting %ds for %s build.' % | |
1656 (build_timeout, revision)) | |
1657 print ('Time elapsed: %ss, still waiting for %s build' % | |
1658 (elapsed_time, revision)) | |
1659 time.sleep(poll_interval) | |
1660 return False | 1704 return False |
1661 | 1705 |
1662 def IsDownloadable(self, depot): | 1706 def IsDownloadable(self, depot): |
1663 """Checks if build is downloadable based on target platform and depot.""" | 1707 """Checks if build is downloadable based on target platform and depot.""" |
1664 if self.opts.target_platform in ['chromium'] and self.opts.gs_bucket: | 1708 if self.opts.target_platform in ['chromium'] and self.opts.gs_bucket: |
1665 return (depot == 'chromium' or | 1709 return (depot == 'chromium' or |
1666 'chromium' in DEPOT_DEPS_NAME[depot]['from'] or | 1710 'chromium' in DEPOT_DEPS_NAME[depot]['from'] or |
1667 'v8' in DEPOT_DEPS_NAME[depot]['from']) | 1711 'v8' in DEPOT_DEPS_NAME[depot]['from']) |
1668 return False | 1712 return False |
1669 | 1713 |
(...skipping 2234 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
3904 # The perf dashboard scrapes the "results" step in order to comment on | 3948 # The perf dashboard scrapes the "results" step in order to comment on |
3905 # bugs. If you change this, please update the perf dashboard as well. | 3949 # bugs. If you change this, please update the perf dashboard as well. |
3906 bisect_utils.OutputAnnotationStepStart('Results') | 3950 bisect_utils.OutputAnnotationStepStart('Results') |
3907 print 'Error: %s' % e.message | 3951 print 'Error: %s' % e.message |
3908 if opts.output_buildbot_annotations: | 3952 if opts.output_buildbot_annotations: |
3909 bisect_utils.OutputAnnotationStepClosed() | 3953 bisect_utils.OutputAnnotationStepClosed() |
3910 return 1 | 3954 return 1 |
3911 | 3955 |
3912 if __name__ == '__main__': | 3956 if __name__ == '__main__': |
3913 sys.exit(main()) | 3957 sys.exit(main()) |
OLD | NEW |