summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorprasadv@chromium.org <prasadv@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2014-03-13 17:23:34 +0000
committerprasadv@chromium.org <prasadv@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2014-03-13 17:23:34 +0000
commitd12bf78f1d0ed66a9c20405ce4040ab3adeba76a (patch)
treefca25b50a5a2635471c09294613af4ffa9a4d5da
parent0ee530b3d8612a98f9063dae2afe203bafea919e (diff)
downloadchromium_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-xtools/bisect-perf-regression.py149
-rw-r--r--tools/post_perf_builder_job.py147
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))