Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(437)

Side by Side Diff: tools/post_perf_builder_job.py

Issue 284493005: Add methods to get build status from tryserver perf bisect builders. (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src
Patch Set: Created 6 years, 7 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
« no previous file with comments | « no previous file | no next file » | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
1 # Copyright 2014 The Chromium Authors. All rights reserved. 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 2 # Use of this source code is governed by a BSD-style license that can be
3 # found in the LICENSE file. 3 # found in the LICENSE file.
4 4
5 """Post a try job to the Try server to produce build. It communicates 5 """Post a try job request via HTTP to the Tryserver to produce build."""
6 to server by directly connecting via HTTP.
7 """
8 6
9 import getpass 7 import getpass
8 import json
10 import optparse 9 import optparse
11 import os 10 import os
12 import sys 11 import sys
13 import urllib 12 import urllib
14 import urllib2 13 import urllib2
15 14
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')
18
19 # Link to display build steps
20 BUILDER_HTML_URL = ('%(server_url)s/builders/%(bot_name)s/builds/%(build_num)s')
21
22 # Tryserver buildbots status page
23 TRY_SERVER_URL = 'http://build.chromium.org/p/tryserver.chromium'
24
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 = '8328'
30
31
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)
35
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)
42
16 43
17 class ServerAccessError(Exception): 44 class ServerAccessError(Exception):
45
18 def __str__(self): 46 def __str__(self):
19 return '%s\nSorry, cannot connect to server.' % self.args[0] 47 return '%s\nSorry, cannot connect to server.' % self.args[0]
20 48
21 49
22 def PostTryJob(url_params): 50 def PostTryJob(url_params):
23 """Sends a build request to the server using the HTTP protocol. 51 """Sends a build request to the server using the HTTP protocol.
24 52
25 Args: 53 Args:
26 url_params: A dictionary of query parameters to be sent in the request. 54 url_params: A dictionary of query parameters to be sent in the request.
27 In order to post build request to try server, this dictionary 55 In order to post build request to try server, this dictionary
(...skipping 33 matching lines...) Expand 10 before | Expand all | Expand 10 after
61 raise ServerAccessError('%s is unaccessible. Reason: %s' % (url, e)) 89 raise ServerAccessError('%s is unaccessible. Reason: %s' % (url, e))
62 if not connection: 90 if not connection:
63 raise ServerAccessError('%s is unaccessible.' % url) 91 raise ServerAccessError('%s is unaccessible.' % url)
64 response = connection.read() 92 response = connection.read()
65 print 'Received %s from server' % response 93 print 'Received %s from server' % response
66 if response != 'OK': 94 if response != 'OK':
67 raise ServerAccessError('%s is unaccessible. Got:\n%s' % (url, response)) 95 raise ServerAccessError('%s is unaccessible. Got:\n%s' % (url, response))
68 return True 96 return True
69 97
70 98
99 def _IsBuildRunning(build_data):
100 """Checks whether the build is in progress on buildbot.
101
102 Presence of currentStep element in build JSON indicates build is in progress.
103
104 Args:
105 build_data: A dictionary with build data, loaded from buildbot JSON API.
106
107 Returns:
108 True if build is in progress, otherwise False.
109 """
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
115
116
117 def _IsBuildFailed(build_data):
118 """Checks whether the build failed on buildbot.
119
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.
125
126 Args:
127 build_data: A dictionary with build data, loaded from buildbot JSON API.
128
129 Returns:
130 True if revision is failed build, otherwise False.
131 """
132 if (build_data.get('results') in FAILED and
133 not _IsBuildSuccessful(build_data)):
134 return True
135 return False
136
137
138 def _IsBuildSuccessful(build_data):
139 """Checks whether the build succeeded on buildbot.
140
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.
144
145 Args:
146 build_data: A dictionary with build data, loaded from buildbot JSON API.
147
148 Returns:
149 True if revision is successfully build, otherwise False.
150 """
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
162
163
164 def _FetchBuilderData(builder_url):
165 """Fetches JSON data for the all the builds from the tryserver.
166
167 Args:
168 builder_url: A tryserver URL to fetch builds information.
169
170 Returns:
171 A dictionary with information of all build on the tryserver.
172 """
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
186
187
188 def _GetBuildData(buildbot_url):
189 """Gets build information for the given build id from the tryserver.
190
191 Args:
192 buildbot_url: A tryserver URL to fetch build information.
193
194 Returns:
195 A dictionary with build information if build exists, otherwise None.
196 """
197 builds_json = _FetchBuilderData(buildbot_url)
198 if builds_json:
199 return json.loads(builds_json)
200 return None
201
202
203 def _GetBuildBotUrl(builder_host, builder_port):
204 """Gets build bot URL based on the host and port of the builders.
205
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.
209
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.
213
214 Returns:
215 URL of the buildbot as a string.
216 """
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)
222
223
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.
226
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.
232
233 Returns:
234 A tuple consists of build status (SUCCESS, FAILED or PENDING) and a link
235 to build status page on the waterfall.
236 """
237 # Gets the buildbot url for the given host and port.
238 server_url = _GetBuildBotUrl(builder_host, builder_port)
239 buildbot_url = BUILDER_JSON_URL % {'server_url': server_url,
240 'bot_name': bot_name,
241 'build_num': build_num
242 }
243 build_data = _GetBuildData(buildbot_url)
244 results_url = None
245 if build_data:
246 # Link to build on the buildbot showing status of build steps.
247 results_url = BUILDER_HTML_URL % {'server_url': server_url,
248 'bot_name': bot_name,
249 'build_num': build_num
250 }
251 if _IsBuildFailed(build_data):
252 return (FAILED, results_url)
253
254 elif _IsBuildSuccessful(build_data):
255 return (OK, results_url)
256 return (PENDING, results_url)
257
258
259 def GetBuildNumFromBuilder(build_reason, bot_name, builder_host, builder_port):
260 """Gets build number on build status page for a given build reason.
261
262 It parses the JSON data from buildbot page and collect basic information
263 about the all the builds and then this uniquely identifies the build based
264 on the 'reason' attribute in builds's JSON data.
265 The 'reason' attribute set while a build request is posted, and same is used
266 to identify the build on status page.
267
268 Args:
269 build_reason: A unique build name set to build on tryserver.
270 bot_name: Name of the bot where the build information is scanned.
271 builder_host: Hostname of the server where the builder is hosted.
272 builder_port: Port number of ther server where the builder is hosted.
273
274 Returns:
275 A build number as a string if found, otherwise None.
276 """
277 # Gets the buildbot url for the given host and port.
278 server_url = _GetBuildBotUrl(builder_host, builder_port)
279 buildbot_url = BUILDER_JSON_URL % {'server_url': server_url,
280 'bot_name': bot_name,
281 'build_num': '_all'
282 }
283 builds_json = _FetchBuilderData(buildbot_url)
284 if builds_json:
285 builds_data = json.loads(builds_json)
286 for current_build in builds_data:
287 if builds_data[current_build].get('reason') == build_reason:
288 return builds_data[current_build].get('number')
289 return None
290
291
71 def _GetQueryParams(options): 292 def _GetQueryParams(options):
72 """Parses common query parameters which will be passed to PostTryJob. 293 """Parses common query parameters which will be passed to PostTryJob.
73 294
74 Args: 295 Args:
75 options: The options object parsed from the command line. 296 options: The options object parsed from the command line.
76 297
77 Returns: 298 Returns:
78 A dictionary consists of query parameters. 299 A dictionary consists of query parameters.
79 """ 300 """
80 values = {'host': options.host, 301 values = {'host': options.host,
(...skipping 13 matching lines...) Expand all
94 values['patch'] = options.patch 315 values['patch'] = options.patch
95 return values 316 return values
96 317
97 318
98 def _GenParser(): 319 def _GenParser():
99 """Parses the command line for posting build request.""" 320 """Parses the command line for posting build request."""
100 usage = ('%prog [options]\n' 321 usage = ('%prog [options]\n'
101 'Post a build request to the try server for the given revision.\n') 322 'Post a build request to the try server for the given revision.\n')
102 parser = optparse.OptionParser(usage=usage) 323 parser = optparse.OptionParser(usage=usage)
103 parser.add_option('-H', '--host', 324 parser.add_option('-H', '--host',
104 help='Host address of the try server.') 325 help='Host address of the try server.')
105 parser.add_option('-P', '--port', type='int', 326 parser.add_option('-P', '--port', type='int',
106 help='HTTP port of the try server.') 327 help='HTTP port of the try server.')
107 parser.add_option('-u', '--user', default=getpass.getuser(), 328 parser.add_option('-u', '--user', default=getpass.getuser(),
108 dest='user', 329 dest='user',
109 help='Owner user name [default: %default]') 330 help='Owner user name [default: %default]')
110 parser.add_option('-e', '--email', 331 parser.add_option('-e', '--email',
111 default=os.environ.get('TRYBOT_RESULTS_EMAIL_ADDRESS', 332 default=os.environ.get('TRYBOT_RESULTS_EMAIL_ADDRESS',
112 os.environ.get('EMAIL_ADDRESS')), 333 os.environ.get('EMAIL_ADDRESS')),
113 help='Email address where to send the results. Use either ' 334 help=('Email address where to send the results. Use either '
114 'the TRYBOT_RESULTS_EMAIL_ADDRESS environment ' 335 'the TRYBOT_RESULTS_EMAIL_ADDRESS environment '
115 'variable or EMAIL_ADDRESS to set the email address ' 336 'variable or EMAIL_ADDRESS to set the email address '
116 'the try bots report results to [default: %default]') 337 'the try bots report results to [default: %default]'))
117 parser.add_option('-n', '--name', 338 parser.add_option('-n', '--name',
118 default= 'try_job_http', 339 default='try_job_http',
119 help='Descriptive name of the try job') 340 help='Descriptive name of the try job')
120 parser.add_option('-b', '--bot', 341 parser.add_option('-b', '--bot',
121 help=('IMPORTANT: specify ONE builder per run is supported.' 342 help=('IMPORTANT: specify ONE builder per run is supported.'
122 'Run script for each builders separately.')) 343 'Run script for each builders separately.'))
123 parser.add_option('-r', '--revision', 344 parser.add_option('-r', '--revision',
124 help='Revision to use for the try job; default: the ' 345 help=('Revision to use for the try job; default: the '
125 'revision will be determined by the try server; see ' 346 'revision will be determined by the try server; see '
126 'its waterfall for more info') 347 'its waterfall for more info'))
127 parser.add_option('--root', 348 parser.add_option('--root',
128 help='Root to use for the patch; base subdirectory for ' 349 help=('Root to use for the patch; base subdirectory for '
129 'patch created in a subdirectory') 350 'patch created in a subdirectory'))
130 parser.add_option('--patch', 351 parser.add_option('--patch',
131 help='Patch information.') 352 help='Patch information.')
132 return parser 353 return parser
133 354
134 355
135 def Main(argv): 356 def Main(argv):
136 parser = _GenParser() 357 parser = _GenParser()
137 options, args = parser.parse_args() 358 options, _ = parser.parse_args()
138 if not options.host: 359 if not options.host:
139 raise ServerAccessError('Please use the --host option to specify the try ' 360 raise ServerAccessError('Please use the --host option to specify the try '
140 'server host to connect to.') 361 'server host to connect to.')
141 if not options.port: 362 if not options.port:
142 raise ServerAccessError('Please use the --port option to specify the try ' 363 raise ServerAccessError('Please use the --port option to specify the try '
143 'server port to connect to.') 364 'server port to connect to.')
144 params = _GetQueryParams(options) 365 params = _GetQueryParams(options)
145 PostTryJob(params) 366 PostTryJob(params)
146 367
147 368
148 if __name__ == '__main__': 369 if __name__ == '__main__':
149 sys.exit(Main(sys.argv)) 370 sys.exit(Main(sys.argv))
371
OLDNEW
« no previous file with comments | « no previous file | no next file » | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698