Add zea@ to sync and gcm watchlists
[chromium-blink-merge.git] / tools / post_perf_builder_job.py
blob97c6eccb02abfb07bc306af5c74c4f407a62e42c
1 # Copyright 2014 The Chromium Authors. All rights reserved.
2 # Use of this source code is governed by a BSD-style license that can be
3 # found in the LICENSE file.
5 """Post a try job request via HTTP to the Tryserver to produce build."""
7 import getpass
8 import json
9 import optparse
10 import os
11 import sys
12 import urllib
13 import urllib2
15 # Link to get JSON data of builds
16 BUILDER_JSON_URL = ('%(server_url)s/json/builders/%(bot_name)s/builds/'
17 '%(build_num)s?as_text=1&filter=0')
19 # Link to display build steps
20 BUILDER_HTML_URL = ('%(server_url)s/builders/%(bot_name)s/builds/%(build_num)s')
22 # Tryserver buildbots status page
23 TRY_SERVER_URL = 'http://build.chromium.org/p/tryserver.chromium.perf'
25 # Hostname of the tryserver where perf bisect builders are hosted. This is used
26 # for posting build request to tryserver.
27 BISECT_BUILDER_HOST = 'master4.golo.chromium.org'
28 # 'try_job_port' on tryserver to post build request.
29 BISECT_BUILDER_PORT = 8341
32 # From buildbot.status.builder.
33 # See: http://docs.buildbot.net/current/developer/results.html
34 SUCCESS, WARNINGS, FAILURE, SKIPPED, EXCEPTION, RETRY, TRYPENDING = range(7)
36 # Status codes that can be returned by the GetBuildStatus method.
37 OK = (SUCCESS, WARNINGS)
38 # Indicates build failure.
39 FAILED = (FAILURE, EXCEPTION, SKIPPED)
40 # Inidcates build in progress or in pending queue.
41 PENDING = (RETRY, TRYPENDING)
44 class ServerAccessError(Exception):
46 def __str__(self):
47 return '%s\nSorry, cannot connect to server.' % self.args[0]
50 def PostTryJob(url_params):
51 """Sends a build request to the server using the HTTP protocol.
53 Args:
54 url_params: A dictionary of query parameters to be sent in the request.
55 In order to post build request to try server, this dictionary
56 should contain information for following keys:
57 'host': Hostname of the try server.
58 'port': Port of the try server.
59 'revision': SVN Revision to build.
60 'bot': Name of builder bot which would be used.
61 Returns:
62 True if the request is posted successfully. Otherwise throws an exception.
63 """
64 # Parse url parameters to be sent to Try server.
65 if not url_params.get('host'):
66 raise ValueError('Hostname of server to connect is missing.')
67 if not url_params.get('port'):
68 raise ValueError('Port of server to connect is missing.')
69 if not url_params.get('revision'):
70 raise ValueError('Missing revision details. Please specify revision'
71 ' information.')
72 if not url_params.get('bot'):
73 raise ValueError('Missing bot details. Please specify bot information.')
75 # Pop 'host' and 'port' to avoid passing them as query params.
76 url = 'http://%s:%s/send_try_patch' % (url_params.pop('host'),
77 url_params.pop('port'))
79 print 'Sending by HTTP'
80 query_params = '&'.join('%s=%s' % (k, v) for k, v in url_params.iteritems())
81 print 'url: %s?%s' % (url, query_params)
83 connection = None
84 try:
85 print 'Opening connection...'
86 connection = urllib2.urlopen(url, urllib.urlencode(url_params))
87 print 'Done, request sent to server to produce build.'
88 except IOError, e:
89 raise ServerAccessError('%s is unaccessible. Reason: %s' % (url, e))
90 if not connection:
91 raise ServerAccessError('%s is unaccessible.' % url)
92 response = connection.read()
93 print 'Received %s from server' % response
94 if response != 'OK':
95 raise ServerAccessError('%s is unaccessible. Got:\n%s' % (url, response))
96 return True
99 def _IsBuildRunning(build_data):
100 """Checks whether the build is in progress on buildbot.
102 Presence of currentStep element in build JSON indicates build is in progress.
104 Args:
105 build_data: A dictionary with build data, loaded from buildbot JSON API.
107 Returns:
108 True if build is in progress, otherwise False.
110 current_step = build_data.get('currentStep')
111 if (current_step and current_step.get('isStarted') and
112 current_step.get('results') is None):
113 return True
114 return False
117 def _IsBuildFailed(build_data):
118 """Checks whether the build failed on buildbot.
120 Sometime build status is marked as failed even though compile and packaging
121 steps are successful. This may happen due to some intermediate steps of less
122 importance such as gclient revert, generate_telemetry_profile are failed.
123 Therefore we do an addition check to confirm if build was successful by
124 calling _IsBuildSuccessful.
126 Args:
127 build_data: A dictionary with build data, loaded from buildbot JSON API.
129 Returns:
130 True if revision is failed build, otherwise False.
132 if (build_data.get('results') in FAILED and
133 not _IsBuildSuccessful(build_data)):
134 return True
135 return False
138 def _IsBuildSuccessful(build_data):
139 """Checks whether the build succeeded on buildbot.
141 We treat build as successful if the package_build step is completed without
142 any error i.e., when results attribute of the this step has value 0 or 1
143 in its first element.
145 Args:
146 build_data: A dictionary with build data, loaded from buildbot JSON API.
148 Returns:
149 True if revision is successfully build, otherwise False.
151 if build_data.get('steps'):
152 for item in build_data.get('steps'):
153 # The 'results' attribute of each step consists of two elements,
154 # results[0]: This represents the status of build step.
155 # See: http://docs.buildbot.net/current/developer/results.html
156 # results[1]: List of items, contains text if step fails, otherwise empty.
157 if (item.get('name') == 'package_build' and
158 item.get('isFinished') and
159 item.get('results')[0] in OK):
160 return True
161 return False
164 def _FetchBuilderData(builder_url):
165 """Fetches JSON data for the all the builds from the tryserver.
167 Args:
168 builder_url: A tryserver URL to fetch builds information.
170 Returns:
171 A dictionary with information of all build on the tryserver.
173 data = None
174 try:
175 url = urllib2.urlopen(builder_url)
176 except urllib2.URLError, e:
177 print ('urllib2.urlopen error %s, waterfall status page down.[%s]' % (
178 builder_url, str(e)))
179 return None
180 if url is not None:
181 try:
182 data = url.read()
183 except IOError, e:
184 print 'urllib2 file object read error %s, [%s].' % (builder_url, str(e))
185 return data
188 def _GetBuildData(buildbot_url):
189 """Gets build information for the given build id from the tryserver.
191 Args:
192 buildbot_url: A tryserver URL to fetch build information.
194 Returns:
195 A dictionary with build information if build exists, otherwise None.
197 builds_json = _FetchBuilderData(buildbot_url)
198 if builds_json:
199 return json.loads(builds_json)
200 return None
203 def _GetBuildBotUrl(builder_host, builder_port):
204 """Gets build bot URL based on the host and port of the builders.
206 Note: All bisect builder bots are hosted on tryserver.chromium i.e.,
207 on master4:8328, since we cannot access tryserver using host and port
208 number directly, we use tryserver URL.
210 Args:
211 builder_host: Hostname of the server where the builder is hosted.
212 builder_port: Port number of ther server where the builder is hosted.
214 Returns:
215 URL of the buildbot as a string.
217 if (builder_host == BISECT_BUILDER_HOST and
218 builder_port == BISECT_BUILDER_PORT):
219 return TRY_SERVER_URL
220 else:
221 return 'http://%s:%s' % (builder_host, builder_port)
224 def GetBuildStatus(build_num, bot_name, builder_host, builder_port):
225 """Gets build status from the buildbot status page for a given build number.
227 Args:
228 build_num: A build number on tryserver to determine its status.
229 bot_name: Name of the bot where the build information is scanned.
230 builder_host: Hostname of the server where the builder is hosted.
231 builder_port: Port number of ther server where the builder is hosted.
233 Returns:
234 A tuple consists of build status (SUCCESS, FAILED or PENDING) and a link
235 to build status page on the waterfall.
237 results_url = None
238 if build_num:
239 # Gets the buildbot url for the given host and port.
240 server_url = _GetBuildBotUrl(builder_host, builder_port)
241 buildbot_url = BUILDER_JSON_URL % {'server_url': server_url,
242 'bot_name': bot_name,
243 'build_num': build_num
245 build_data = _GetBuildData(buildbot_url)
246 if build_data:
247 # Link to build on the buildbot showing status of build steps.
248 results_url = BUILDER_HTML_URL % {'server_url': server_url,
249 'bot_name': bot_name,
250 'build_num': build_num
252 if _IsBuildFailed(build_data):
253 return (FAILED, results_url)
255 elif _IsBuildSuccessful(build_data):
256 return (OK, results_url)
257 return (PENDING, results_url)
260 def GetBuildNumFromBuilder(build_reason, bot_name, builder_host, builder_port):
261 """Gets build number on build status page for a given build reason.
263 It parses the JSON data from buildbot page and collect basic information
264 about the all the builds and then this uniquely identifies the build based
265 on the 'reason' attribute in builds's JSON data.
266 The 'reason' attribute set while a build request is posted, and same is used
267 to identify the build on status page.
269 Args:
270 build_reason: A unique build name set to build on tryserver.
271 bot_name: Name of the bot where the build information is scanned.
272 builder_host: Hostname of the server where the builder is hosted.
273 builder_port: Port number of ther server where the builder is hosted.
275 Returns:
276 A build number as a string if found, otherwise None.
278 # Gets the buildbot url for the given host and port.
279 server_url = _GetBuildBotUrl(builder_host, builder_port)
280 buildbot_url = BUILDER_JSON_URL % {'server_url': server_url,
281 'bot_name': bot_name,
282 'build_num': '_all'
284 builds_json = _FetchBuilderData(buildbot_url)
285 if builds_json:
286 builds_data = json.loads(builds_json)
287 for current_build in builds_data:
288 if builds_data[current_build].get('reason') == build_reason:
289 return builds_data[current_build].get('number')
290 return None
293 def _GetQueryParams(options):
294 """Parses common query parameters which will be passed to PostTryJob.
296 Args:
297 options: The options object parsed from the command line.
299 Returns:
300 A dictionary consists of query parameters.
302 values = {'host': options.host,
303 'port': options.port,
304 'user': options.user,
305 'name': options.name
307 if options.email:
308 values['email'] = options.email
309 if options.revision:
310 values['revision'] = options.revision
311 if options.root:
312 values['root'] = options.root
313 if options.bot:
314 values['bot'] = options.bot
315 if options.patch:
316 values['patch'] = options.patch
317 return values
320 def _GenParser():
321 """Parses the command line for posting build request."""
322 usage = ('%prog [options]\n'
323 'Post a build request to the try server for the given revision.\n')
324 parser = optparse.OptionParser(usage=usage)
325 parser.add_option('-H', '--host',
326 help='Host address of the try server.')
327 parser.add_option('-P', '--port', type='int',
328 help='HTTP port of the try server.')
329 parser.add_option('-u', '--user', default=getpass.getuser(),
330 dest='user',
331 help='Owner user name [default: %default]')
332 parser.add_option('-e', '--email',
333 default=os.environ.get('TRYBOT_RESULTS_EMAIL_ADDRESS',
334 os.environ.get('EMAIL_ADDRESS')),
335 help=('Email address where to send the results. Use either '
336 'the TRYBOT_RESULTS_EMAIL_ADDRESS environment '
337 'variable or EMAIL_ADDRESS to set the email address '
338 'the try bots report results to [default: %default]'))
339 parser.add_option('-n', '--name',
340 default='try_job_http',
341 help='Descriptive name of the try job')
342 parser.add_option('-b', '--bot',
343 help=('IMPORTANT: specify ONE builder per run is supported.'
344 'Run script for each builders separately.'))
345 parser.add_option('-r', '--revision',
346 help=('Revision to use for the try job; default: the '
347 'revision will be determined by the try server; see '
348 'its waterfall for more info'))
349 parser.add_option('--root',
350 help=('Root to use for the patch; base subdirectory for '
351 'patch created in a subdirectory'))
352 parser.add_option('--patch',
353 help='Patch information.')
354 return parser
357 def Main(argv):
358 parser = _GenParser()
359 options, _ = parser.parse_args()
360 if not options.host:
361 raise ServerAccessError('Please use the --host option to specify the try '
362 'server host to connect to.')
363 if not options.port:
364 raise ServerAccessError('Please use the --port option to specify the try '
365 'server port to connect to.')
366 params = _GetQueryParams(options)
367 PostTryJob(params)
370 if __name__ == '__main__':
371 sys.exit(Main(sys.argv))