Index: tools/post_perf_builder_job.py |
diff --git a/tools/post_perf_builder_job.py b/tools/post_perf_builder_job.py |
index 0f926b3735d15ba560edb5209042fb0d44e4843c..d6e625fb71129fa0c5e49682f0a814b67e500e8e 100644 |
--- a/tools/post_perf_builder_job.py |
+++ b/tools/post_perf_builder_job.py |
@@ -2,11 +2,10 @@ |
# 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. |
-""" |
+"""Post a try job request via HTTP to the Tryserver to produce build.""" |
import getpass |
+import json |
import optparse |
import os |
import sys |
@@ -14,7 +13,37 @@ import urllib |
import urllib2 |
qyearsley
2014/05/15 19:40:06
Technically, I think there's no need for two blank
prasadv
2014/05/15 20:09:42
Done.
|
+# Link to get JSON data of builds |
+BUILDER_JSON_URL = ('%(server_url)s/json/builders/%(bot_name)s/builds/' |
+ '%(build_num)s?as_text=1&filter=0') |
+ |
+# Link to display build steps |
+BUILDER_HTML_URL = ('%(server_url)s/builders/%(bot_name)s/builds/%(build_num)s') |
+ |
+# Tryserver buildbots status page |
+TRY_SERVER_URL = 'http://build.chromium.org/p/tryserver.chromium' |
+ |
+# Hostname of the tryserver where perf bisect builders are hosted. This is used |
+# for posting build request to tryserver. |
+BISECT_BUILDER_HOST = 'master4.golo.chromium.org' |
+# 'try_job_port' on tryserver to post build request. |
+BISECT_BUILDER_PORT = '8328' |
+ |
+ |
+# From buildbot.status.builder. |
+# See: http://docs.buildbot.net/current/developer/results.html |
+SUCCESS, WARNINGS, FAILURE, SKIPPED, EXCEPTION, RETRY, TRYPENDING = range(7) |
+ |
+# Status codes that can be returned by the GetBuildStatus method. |
+OK = (SUCCESS, WARNINGS, SKIPPED) |
+# Indicates build failure. |
+FAILED = (FAILURE, EXCEPTION) |
+# Inidcates build in progress or in pending queue. |
+PENDING = (RETRY, TRYPENDING) |
+ |
+ |
class ServerAccessError(Exception): |
+ |
def __str__(self): |
return '%s\nSorry, cannot connect to server.' % self.args[0] |
@@ -68,6 +97,199 @@ def PostTryJob(url_params): |
return True |
+def _IsBuildRunning(build_data): |
+ """Checks whether the build is in progress on buildbot. |
+ |
+ Presence of currentStep element in build JSON indicates build is in progress. |
+ |
+ Args: |
+ build_data: JSON object with build data, loaded from buildbot JSON API. |
qyearsley
2014/05/15 19:40:06
It could be slightly confusing to say that the exp
prasadv
2014/05/15 20:09:42
Done.
|
+ |
+ Returns: |
+ Returns True if build is in progress, otherwise False. |
qyearsley
2014/05/15 19:40:06
The word "Returns" is redundant here. Same applies
prasadv
2014/05/15 20:09:42
Done.
|
+ """ |
+ current_step = build_data.get('currentStep') |
+ if (current_step and current_step.get('isStarted') and |
+ current_step.get('results') is None): |
+ return True |
+ return False |
+ |
+ |
+def _IsBuildFailed(build_data): |
+ """Checks whether the build failed on buildbot. |
+ |
+ Sometime build status is marked as failed even though compile and packaging |
+ steps are successful. This may happen due to some intermediate steps of less |
+ importance such as gclient revert, generate_telemetry_profile are failed. |
+ Therefore we do an addition check to confirm if build was successful by |
+ calling _IsBuildSucceed. |
+ |
+ Args: |
+ build_data: JSON object with build data, loaded from buildbot JSON API. |
+ |
+ Returns: |
+ Returns True if revision is failed build, otherwise False. |
+ """ |
+ if (build_data.get('results') in FAILED and |
+ not _IsBuildSucceed(build_data)): |
+ return True |
+ return False |
+ |
+ |
+def _IsBuildSucceed(build_data): |
qyearsley
2014/05/15 19:40:06
Should rename to _IsBuildSuccessful. (Also update
prasadv
2014/05/15 20:09:42
Done.
|
+ """Checks whether the build succeeded on buildbot. |
+ |
+ We treat build as successful if the package_build step is completed without |
+ any error i.e., when results attribute of the this step has value 0, 1 or 3 |
+ in its first element. |
+ |
+ Args: |
+ build_data: JSON object with build data, loaded from buildbot JSON API. |
+ |
+ Returns: |
+ Returns True if revision is successfully build, otherwise False. |
+ """ |
+ if build_data.get('steps'): |
+ for item in build_data.get('steps'): |
+ # The 'results' attribute of each step consists of two elements, |
+ # results[0]: This represents the status of build step. |
+ # See: http://docs.buildbot.net/current/developer/results.html |
+ # results[1]: List of items, contains text if step fails, otherwise empty. |
+ if (item.get('name') == 'package_build' and |
+ item.get('isFinished') and |
+ item.get('results')[0] in OK): |
ghost stip (do not use)
2014/05/15 19:27:55
OK includes SKIPPED, did you want to include that
prasadv
2014/05/15 20:09:42
I'm really not sure why a build could be skipped,
|
+ return True |
+ return False |
+ |
+ |
+def _FetchBuilderData(builder_url): |
+ """Fetches JSON data for the all the builds from the tryserver. |
+ |
+ Args: |
+ builder_url: A tryserver URL to fetch builds information. |
+ |
+ Returns: |
+ A dictionary with information of all build on the tryserver. |
+ """ |
+ data = None |
+ try: |
+ url = urllib2.urlopen(builder_url) |
+ except urllib2.URLError, e: |
+ print ('urllib2.urlopen error %s, waterfall status page down.[%s]' % ( |
+ builder_url, str(e))) |
+ return None |
+ if url is not None: |
+ try: |
+ data = url.read() |
+ except IOError, e: |
+ print 'urllib2 file object read error %s, [%s].' % (builder_url, str(e)) |
+ return data |
+ |
+ |
+def _GetBuildData(buildbot_url): |
+ """Gets build information for the given build id from the tryserver. |
+ |
+ Args: |
+ buildbot_url: A tryserver URL to fetch build information. |
+ |
+ Returns: |
+ A JSON object with build information if build exists, otherwise None. |
+ """ |
+ builds_json = _FetchBuilderData(buildbot_url) |
+ if builds_json: |
+ return json.loads(builds_json) |
+ return None |
+ |
+ |
+def _GetBuildBotUrl(builder_host, builder_port): |
+ """Gets build bot URL based on the host and port of the builders. |
+ |
+ Note: All bisect builder bots are hosted on tryserver.chromium i.e., |
+ on master4:8328, since we cannot access tryserver using host and port |
+ number directly, we use tryserver URL. |
+ |
+ Args: |
+ builder_host: Hostname of the server where the builder is hosted. |
+ builder_port: Port number of ther server where the builder is hosted. |
+ |
+ Returns: |
+ URL of the buildbot as a string. |
+ """ |
+ if (builder_host == BISECT_BUILDER_HOST and |
+ builder_port == BISECT_BUILDER_PORT): |
+ return TRY_SERVER_URL |
+ else: |
+ return 'http://%s:%s' % (builder_host, builder_port) |
+ |
+ |
+def GetBuildStatus(build_num, bot_name, builder_host, builder_port): |
+ """Gets build status from the buildbot status page for a given build number. |
+ |
+ Args: |
+ build_num: A build number on tryserver to determine its status. |
+ bot_name: Name of the bot where the build information is scanned. |
+ builder_host: Hostname of the server where the builder is hosted. |
+ builder_port: Port number of ther server where the builder is hosted. |
+ |
+ Returns: |
+ A tuple consists of build status (SUCCESS, FAILED or PENDING ) and a link |
ghost stip (do not use)
2014/05/15 19:27:55
nit: remove space
prasadv
2014/05/15 20:09:42
Done.
|
+ to build status page on the waterfall. |
+ """ |
+ # Gets the buildbot url for the given host and port. |
+ server_url = _GetBuildBotUrl(builder_host, builder_port) |
+ buildbot_url = BUILDER_JSON_URL % {'server_url': server_url, |
+ 'bot_name': bot_name, |
+ 'build_num': build_num |
+ } |
+ build_data = _GetBuildData(buildbot_url) |
+ results_url = None |
+ if build_data: |
+ # Link to build on the buildbot showing status of build steps. |
+ results_url = BUILDER_HTML_URL % {'server_url': server_url, |
+ 'bot_name': bot_name, |
+ 'build_num': build_num |
+ } |
+ if _IsBuildFailed(build_data): |
+ return (FAILED, results_url) |
+ |
+ elif _IsBuildSucceed(build_data): |
+ return (OK, results_url) |
+ return (PENDING, results_url) |
qyearsley
2014/05/15 19:40:06
The function _IsBuildRunning isn't used anywhere c
prasadv
2014/05/15 20:09:42
Added this to check if build is in pending queue o
|
+ |
+ |
+def GetBuildNumFromBuilder(build_reason, bot_name, builder_host, builder_port): |
ghost stip (do not use)
2014/05/15 19:27:55
just checking, these methods aren't used yet right
prasadv
2014/05/15 20:09:42
Yes, these aren't used anywhere yet.
On 2014/05/15
|
+ """Gets build number on build status page for a given build reason. |
+ |
+ It parses the JSON data from buildbot page and collect basic information |
+ about the all the builds and then this uniquely identifies the build based |
+ on the 'reason' attribute in builds's JSON data. |
+ The 'reason' attribute set while a build request is posted, and same is used |
+ to identify the build on status page. |
+ |
+ Args: |
+ build_reason: A unique build name set to build on tryserver. |
+ bot_name: Name of the bot where the build information is scanned. |
+ builder_host: Hostname of the server where the builder is hosted. |
+ builder_port: Port number of ther server where the builder is hosted. |
+ |
+ Returns: |
+ A build number as a string if found, otherwise None |
qyearsley
2014/05/15 19:40:06
Could add a period on this line.
prasadv
2014/05/15 20:09:42
Done.
|
+ """ |
+ # Gets the buildbot url for the given host and port. |
+ server_url = _GetBuildBotUrl(builder_host, builder_port) |
+ buildbot_url = BUILDER_JSON_URL % {'server_url': server_url, |
+ 'bot_name': bot_name, |
+ 'build_num': '_all' |
+ } |
+ builds_json = _FetchBuilderData(buildbot_url) |
+ if builds_json: |
+ builds_data = json.loads(builds_json) |
+ for current_build in builds_data: |
+ if builds_data[current_build].get('reason') == build_reason: |
+ return builds_data[current_build].get('number') |
+ return None |
+ |
+ |
def _GetQueryParams(options): |
"""Parses common query parameters which will be passed to PostTryJob. |
@@ -101,49 +323,50 @@ def _GenParser(): |
'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.') |
+ help='Host address of the try server.') |
parser.add_option('-P', '--port', type='int', |
- help='HTTP port of the try server.') |
+ 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]') |
+ 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', |
+ 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') |
+ 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') |
+ help=('Root to use for the patch; base subdirectory for ' |
+ 'patch created in a subdirectory')) |
parser.add_option('--patch', |
- help='Patch information.') |
+ help='Patch information.') |
return parser |
def Main(argv): |
parser = _GenParser() |
- options, args = parser.parse_args() |
+ options, _ = parser.parse_args() |
if not options.host: |
raise ServerAccessError('Please use the --host option to specify the try ' |
- 'server host to connect to.') |
+ '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.') |
+ 'server port to connect to.') |
params = _GetQueryParams(options) |
PostTryJob(params) |
if __name__ == '__main__': |
sys.exit(Main(sys.argv)) |
+ |