diff options
author | prasadv@chromium.org <prasadv@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2014-03-13 17:23:34 +0000 |
---|---|---|
committer | prasadv@chromium.org <prasadv@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2014-03-13 17:23:34 +0000 |
commit | d12bf78f1d0ed66a9c20405ce4040ab3adeba76a (patch) | |
tree | fca25b50a5a2635471c09294613af4ffa9a4d5da | |
parent | 0ee530b3d8612a98f9063dae2afe203bafea919e (diff) | |
download | chromium_src-d12bf78f1d0ed66a9c20405ce4040ab3adeba76a.zip chromium_src-d12bf78f1d0ed66a9c20405ce4040ab3adeba76a.tar.gz chromium_src-d12bf78f1d0ed66a9c20405ce4040ab3adeba76a.tar.bz2 |
Make bisect script to post build request job to try server.
During the course of bisect, if the script don't find build archive for a given revision on cloud storage, a request is made to try server to produce one and waits till build archive is created on storage.
Note: In this CL, only chromium revision are taken care. I'll create a separate CL for third-party repositories such as v8, webkit.
Review URL: https://codereview.chromium.org/184293011
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@256862 0039d316-1c4b-4281-b951-d872f2087c98
-rwxr-xr-x | tools/bisect-perf-regression.py | 149 | ||||
-rw-r--r-- | tools/post_perf_builder_job.py | 147 |
2 files changed, 255 insertions, 41 deletions
diff --git a/tools/bisect-perf-regression.py b/tools/bisect-perf-regression.py index cbc56cf..e15cabe 100755 --- a/tools/bisect-perf-regression.py +++ b/tools/bisect-perf-regression.py @@ -52,6 +52,8 @@ import time import zipfile import bisect_utils +import post_perf_builder_job + try: from telemetry.page import cloud_storage @@ -1331,23 +1333,16 @@ class BisectPerformanceMetrics(object): return destination_dir return None - def DownloadCurrentBuild(self, sha_revision, build_type='Release'): + def DownloadCurrentBuild(self, revision, build_type='Release'): """Download the build archive for the given revision. Args: - sha_revision: The git SHA1 for the revision. + revision: The SVN revision to build. build_type: Target build type ('Release', 'Debug', 'Release_x64' etc.) Returns: True if download succeeds, otherwise False. """ - # Get SVN revision for the given SHA, since builds are archived using SVN - # revision. - revision = self.source_control.SVNFindRev(sha_revision) - if not revision: - raise RuntimeError( - 'Failed to determine SVN revision for %s' % sha_revision) - abs_build_dir = os.path.abspath( self.builder.GetBuildOutputDirectory(self.opts, self.src_cwd)) target_build_output_dir = os.path.join(abs_build_dir, build_type) @@ -1356,33 +1351,85 @@ class BisectPerformanceMetrics(object): # File path of the downloaded archive file. archive_file_dest = os.path.join(abs_build_dir, GetZipFileName(revision, build_arch)) - if FetchFromCloudStorage(self.opts.gs_bucket, - GetRemoteBuildPath(revision, build_arch), - abs_build_dir): - # Generic name for the archive, created when archive file is extracted. - output_dir = os.path.join(abs_build_dir, - GetZipFileName(target_arch=build_arch)) - # Unzip build archive directory. - try: + remote_build = GetRemoteBuildPath(revision, build_arch) + fetch_build_func = lambda: FetchFromCloudStorage(self.opts.gs_bucket, + remote_build, + abs_build_dir) + if not fetch_build_func(): + if not self.PostBuildRequestAndWait(revision, condition=fetch_build_func): + raise RuntimeError('Somewthing went wrong while processing build' + 'request for: %s' % revision) + + # Generic name for the archive, created when archive file is extracted. + output_dir = os.path.join(abs_build_dir, + GetZipFileName(target_arch=build_arch)) + # Unzip build archive directory. + try: + RmTreeAndMkDir(output_dir, skip_makedir=True) + ExtractZip(archive_file_dest, abs_build_dir) + if os.path.exists(output_dir): + self.BackupOrRestoreOutputdirectory(restore=False) + print 'Moving build from %s to %s' % ( + output_dir, target_build_output_dir) + shutil.move(output_dir, target_build_output_dir) + return True + raise IOError('Missing extracted folder %s ' % output_dir) + except e: + print 'Somewthing went wrong while extracting archive file: %s' % e + self.BackupOrRestoreOutputdirectory(restore=True) + # Cleanup any leftovers from unzipping. + if os.path.exists(output_dir): RmTreeAndMkDir(output_dir, skip_makedir=True) - ExtractZip(archive_file_dest, abs_build_dir) - if os.path.exists(output_dir): - self.BackupOrRestoreOutputdirectory(restore=False) - print 'Moving build from %s to %s' % ( - output_dir, target_build_output_dir) - shutil.move(output_dir, target_build_output_dir) - return True - raise IOError('Missing extracted folder %s ' % output_dir) - except e: - print 'Somewthing went wrong while extracting archive file: %s' % e - self.BackupOrRestoreOutputdirectory(restore=True) - # Cleanup any leftovers from unzipping. - if os.path.exists(output_dir): - RmTreeAndMkDir(output_dir, skip_makedir=True) - finally: - # Delete downloaded archive - if os.path.exists(archive_file_dest): - os.remove(archive_file_dest) + finally: + # Delete downloaded archive + if os.path.exists(archive_file_dest): + os.remove(archive_file_dest) + return False + + def PostBuildRequestAndWait(self, revision, condition, patch=None): + """POSTs the build request job to the tryserver instance.""" + + def GetBuilderNameAndBuildTime(target_arch='ia32'): + """Gets builder name and buildtime in seconds based on platform.""" + if IsWindows(): + if Is64BitWindows() and target_arch == 'x64': + return ('Win x64 Bisect Builder', 3600) + return ('Win Bisect Builder', 3600) + if IsLinux(): + return ('Linux Bisect Builder', 1800) + if IsMac(): + return ('Mac Bisect Builder', 2700) + raise NotImplementedError('Unsupported Platform "%s".' % sys.platform) + if not condition: + return False + + bot_name, build_timeout = GetBuilderNameAndBuildTime(self.opts.target_arch) + + # Creates a try job description. + job_args = {'host': self.opts.builder_host, + 'port': self.opts.builder_port, + 'revision': revision, + 'bot': bot_name, + 'name': 'Bisect Job-%s' % revision + } + # Update patch information if supplied. + if patch: + job_args['patch'] = patch + # Posts job to build the revision on the server. + if post_perf_builder_job.PostTryJob(job_args): + poll_interval = 60 + start_time = time.time() + while True: + res = condition() + if res: + return res + elapsed_time = time.time() - start_time + if elapsed_time > build_timeout: + raise RuntimeError('Timed out while waiting %ds for %s build.' % + (build_timeout, revision)) + print ('Time elapsed: %ss, still waiting for %s build' % + (elapsed_time, revision)) + time.sleep(poll_interval) return False def BuildCurrentRevision(self, depot, revision=None): @@ -1398,6 +1445,12 @@ class BisectPerformanceMetrics(object): # Fetch build archive for the given revision from the cloud storage when # the storage bucket is passed. if depot == 'chromium' and self.opts.gs_bucket and revision: + # Get SVN revision for the given SHA, since builds are archived using SVN + # revision. + revision = self.source_control.SVNFindRev(revision) + if not revision: + raise RuntimeError( + 'Failed to determine SVN revision for %s' % sha_revision) if self.DownloadCurrentBuild(revision): os.chdir(cwd) return True @@ -1406,6 +1459,7 @@ class BisectPerformanceMetrics(object): 'further. Please try running script without ' '--gs_bucket flag to produce local builds.' % revision) + build_success = self.builder.Build(depot, self.opts) os.chdir(cwd) return build_success @@ -3040,6 +3094,8 @@ class BisectOptions(object): self.debug_ignore_perf_test = None self.gs_bucket = None self.target_arch = 'ia32' + self.builder_host = None + self.builder_port = None def _CreateCommandLineParser(self): """Creates a parser with bisect options. @@ -3153,7 +3209,16 @@ class BisectOptions(object): dest='target_arch', help=('The target build architecture. Choices are "ia32" ' '(default), "x64" or "arm".')) - + group.add_option('--builder_host', + dest='builder_host', + type='str', + help=('Host address of server to produce build by posting' + ' try job request.')) + group.add_option('--builder_port', + dest='builder_port', + type='int', + help=('HTTP port of the server to produce build by posting' + ' try job request.')) parser.add_option_group(group) group = optparse.OptionGroup(parser, 'Debug options') @@ -3167,8 +3232,6 @@ class BisectOptions(object): action="store_true", help='DEBUG: Don\'t perform performance tests.') parser.add_option_group(group) - - return parser def ParseCommandLine(self): @@ -3191,8 +3254,13 @@ class BisectOptions(object): if opts.gs_bucket: if not cloud_storage.List(opts.gs_bucket): - raise RuntimeError('Invalid Google Storage URL: [%s]', e) - + raise RuntimeError('Invalid Google Storage: gs://%s' % opts.gs_bucket) + if not opts.builder_host: + raise RuntimeError('Must specify try server hostname, when ' + 'gs_bucket is used: --builder_host') + if not opts.builder_port: + raise RuntimeError('Must specify try server port number, when ' + 'gs_bucket is used: --builder_port') if opts.target_platform == 'cros': # Run sudo up front to make sure credentials are cached for later. print 'Sudo is required to build cros:' @@ -3297,7 +3365,6 @@ def main(): not opts.debug_ignore_sync and not opts.working_directory): raise RuntimeError("You must switch to master branch to run bisection.") - bisect_test = BisectPerformanceMetrics(source_control, opts) try: bisect_results = bisect_test.Run(opts.command, diff --git a/tools/post_perf_builder_job.py b/tools/post_perf_builder_job.py new file mode 100644 index 0000000..70d0f53 --- /dev/null +++ b/tools/post_perf_builder_job.py @@ -0,0 +1,147 @@ +# Copyright 2014 The Chromium Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +"""Post a try job to the Try server to produce build. It communicates +to server by directly connecting via HTTP. +""" + +import getpass +import optparse +import os +import sys +import urllib +import urllib2 + + +class ServerAccessError(Exception): + def __str__(self): + return '%s\nSorry, cannot connect to server.' % self.args[0] + + +def PostTryJob(url_params): + """Sends a build request to the server using the HTTP protocol. + + Args: + url_params: A dictionary of query parameters to be sent in the request. + In order to post build request to try server, this dictionary + should contain information for following keys: + 'host': Hostname of the try server. + 'port': Port of the try server. + 'revision': SVN Revision to build. + 'bot': Name of builder bot which would be used. + Returns: + True if the request is posted successfully. Otherwise throws an exception. + """ + # Parse url parameters to be sent to Try server. + if not url_params.get('host'): + raise ValueError('Hostname of server to connect is missing.') + if not url_params.get('port'): + raise ValueError('Port of server to connect is missing.') + if not url_params.get('revision'): + raise ValueError('Missing revision details. Please specify revision' + ' information.') + if not url_params.get('bot'): + raise ValueError('Missing bot details. Please specify bot information.') + + url = 'http://%s:%s/send_try_patch' % (url_params['host'], url_params['port']) + + print 'Sending by HTTP' + query_params = '&'.join('%s=%s' % (k, v) for k, v in url_params.iteritems()) + print 'url: %s?%s' % (url, query_params) + + connection = None + try: + print 'Opening connection...' + connection = urllib2.urlopen(url, urllib.urlencode(url_params)) + print 'Done, request sent to server to produce build.' + except IOError, e: + raise ServerAccessError('%s is unaccessible. Reason: %s' % (url, e)) + if not connection: + raise ServerAccessError('%s is unaccessible.' % url) + response = connection.read() + print 'Received %s from server' % response + if response != 'OK': + raise ServerAccessError('%s is unaccessible. Got:\n%s' % (url, response)) + return True + + +def _GetQueryParams(options): + """Parses common query parameters which will be passed to PostTryJob. + + Args: + options: The options object parsed from the command line. + + Returns: + A dictionary consists of query parameters. + """ + values = {'host': options.host, + 'port': options.port, + 'user': options.user, + 'name': options.name + } + if options.email: + values['email'] = options.email + if options.revision: + values['revision'] = options.revision + if options.root: + values['root'] = options.root + if options.bot: + values['bot'] = options.bot + if options.patch: + values['patch'] = options.patch + return values + + +def _GenParser(): + """Parses the command line for posting build request.""" + usage = ('%prog [options]\n' + 'Post a build request to the try server for the given revision.\n') + parser = optparse.OptionParser(usage=usage) + parser.add_option('-H', '--host', + help='Host address of the try server.') + parser.add_option('-P', '--port', type='int', + help='HTTP port of the try server.') + parser.add_option('-u', '--user', default=getpass.getuser(), + dest='user', + help='Owner user name [default: %default]') + parser.add_option('-e', '--email', + default=os.environ.get('TRYBOT_RESULTS_EMAIL_ADDRESS', + os.environ.get('EMAIL_ADDRESS')), + help='Email address where to send the results. Use either ' + 'the TRYBOT_RESULTS_EMAIL_ADDRESS environment ' + 'variable or EMAIL_ADDRESS to set the email address ' + 'the try bots report results to [default: %default]') + parser.add_option('-n', '--name', + default= 'try_job_http', + help='Descriptive name of the try job') + parser.add_option('-b', '--bot', + help=('IMPORTANT: specify ONE builder per run is supported.' + 'Run script for each builders separately.')) + parser.add_option('-r', '--revision', + help='Revision to use for the try job; default: the ' + 'revision will be determined by the try server; see ' + 'its waterfall for more info') + parser.add_option('--root', + help='Root to use for the patch; base subdirectory for ' + 'patch created in a subdirectory') + parser.add_option('--patch', + help='Patch information.') + return parser + + +def Main(argv): + parser = _GenParser() + options, args = parser.parse_args() + if not options.host: + raise ServerAccessError('Please use the --host option to specify the try ' + 'server host to connect to.') + if not options.port: + raise ServerAccessError('Please use the --port option to specify the try ' + 'server port to connect to.') + params = _GetQueryParams(options) + PostTryJob(params) + + +if __name__ == '__main__': + sys.exit(Main(sys.argv)) |