Upstream version 9.37.197.0
[platform/framework/web/crosswalk.git] / src / tools / post_perf_builder_job.py
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.
4
5 """Post a try job request via HTTP to the Tryserver to produce build."""
6
7 import getpass
8 import json
9 import optparse
10 import os
11 import sys
12 import urllib
13 import urllib2
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.perf'
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 = 8341
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
43
44 class ServerAccessError(Exception):
45
46   def __str__(self):
47     return '%s\nSorry, cannot connect to server.' % self.args[0]
48
49
50 def PostTryJob(url_params):
51   """Sends a build request to the server using the HTTP protocol.
52
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.')
74
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'))
78
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)
82
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
97
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   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
244                                       }
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
251                                        }
252       if _IsBuildFailed(build_data):
253         return (FAILED, results_url)
254
255       elif _IsBuildSuccessful(build_data):
256         return (OK, results_url)
257   return (PENDING, results_url)
258
259
260 def GetBuildNumFromBuilder(build_reason, bot_name, builder_host, builder_port):
261   """Gets build number on build status page for a given build reason.
262
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.
268
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.
274
275   Returns:
276     A build number as a string if found, otherwise None.
277   """
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'
283                                     }
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
291
292
293 def _GetQueryParams(options):
294   """Parses common query parameters which will be passed to PostTryJob.
295
296   Args:
297     options: The options object parsed from the command line.
298
299   Returns:
300     A dictionary consists of query parameters.
301   """
302   values = {'host': options.host,
303             'port': options.port,
304             'user': options.user,
305             'name': options.name
306            }
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
318
319
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
355
356
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)
368
369
370 if __name__ == '__main__':
371   sys.exit(Main(sys.argv))
372