OLD | NEW |
(Empty) | |
| 1 #!/usr/bin/env python |
| 2 # Copyright (c) 2015 The Chromium Authors. All rights reserved. |
| 3 # Use of this source code is governed by a BSD-style license that can be |
| 4 # found in the LICENSE file. |
| 5 |
| 6 """Python utility that triggers and waits for tasks to complete on CTFE.""" |
| 7 |
| 8 import base64 |
| 9 import hashlib |
| 10 import json |
| 11 import optparse |
| 12 import requests |
| 13 import sys |
| 14 import time |
| 15 |
| 16 |
| 17 CTFE_HOST = "http://ct.skia.org" |
| 18 CTFE_QUEUE = CTFE_HOST + '/queue/' |
| 19 CHROMIUM_PERF_TASK_POST_URI = CTFE_HOST + "/_/webhook_add_chromium_perf_task" |
| 20 GET_CHROMIUM_PERF_RUN_STATUS_URI = CTFE_HOST + "/get_chromium_perf_run_status" |
| 21 CHROMIUM_PERF_RUNS_HISTORY = CTFE_HOST + "/chromium_perf_runs/" |
| 22 GCE_WEBHOOK_SALT_METADATA_URI = ( |
| 23 "http://metadata/computeMetadata/v1/project/attributes/" |
| 24 "webhook_request_salt") |
| 25 |
| 26 |
| 27 POLLING_FREQUENCY_SECS = 60 # 1 minute. |
| 28 TRYBOT_DEADLINE_SECS = 24 * 60 * 60 # 24 hours. |
| 29 |
| 30 |
| 31 class CtTrybotException(Exception): |
| 32 pass |
| 33 |
| 34 |
| 35 def _CreateTaskJSON(options): |
| 36 """Creates a JSON representation of the requested task.""" |
| 37 task_params = {} |
| 38 task_params["username"] = options.requester |
| 39 task_params["benchmark"] = options.benchmark |
| 40 task_params["platform"] = "Linux" |
| 41 task_params["page_sets"] = "10k" |
| 42 task_params["repeat_runs"] = "3" |
| 43 task_params["benchmark_args"] = "--output-format=csv-pivot-table" |
| 44 task_params["browser_args_nopatch"] = ( |
| 45 "--disable-setuid-sandbox --enable-threaded-compositing " |
| 46 "--enable-impl-side-painting") |
| 47 task_params["browser_args_withpatch"] = ( |
| 48 "--disable-setuid-sandbox --enable-threaded-compositing " |
| 49 "--enable-impl-side-painting") |
| 50 |
| 51 trybot_params = {} |
| 52 trybot_params["issue"] = options.issue |
| 53 trybot_params["patchset"] = options.patchset |
| 54 trybot_params["task"] = task_params |
| 55 return json.dumps(trybot_params) |
| 56 |
| 57 |
| 58 def _GetWebhookSaltFromMetadata(): |
| 59 """Gets webhook_request_salt from GCE's metadata server.""" |
| 60 headers = {"Metadata-Flavor": "Google"} |
| 61 resp = requests.get(GCE_WEBHOOK_SALT_METADATA_URI, headers=headers) |
| 62 if resp.status_code != 200: |
| 63 raise CtTrybotException( |
| 64 'Return code from %s was %s' % (GCE_WEBHOOK_SALT_METADATA_URI, |
| 65 resp.status_code)) |
| 66 return resp.text |
| 67 |
| 68 |
| 69 def _TriggerTask(options): |
| 70 """Triggers the requested task on CTFE and returns the new task's ID.""" |
| 71 task = _CreateTaskJSON(options) |
| 72 m = hashlib.sha512() |
| 73 m.update(task) |
| 74 m.update('notverysecret' if options.local else _GetWebhookSaltFromMetadata()) |
| 75 encoded = base64.standard_b64encode(m.digest()) |
| 76 |
| 77 headers = { |
| 78 "Content-type": "application/x-www-form-urlencoded", |
| 79 "Accept": "application/json", |
| 80 "X-Webhook-Auth-Hash": encoded} |
| 81 resp = requests.post(CHROMIUM_PERF_TASK_POST_URI, task, headers=headers) |
| 82 |
| 83 if resp.status_code != 200: |
| 84 raise CtTrybotException( |
| 85 'Return code from %s was %s' % (CHROMIUM_PERF_TASK_POST_URI, |
| 86 resp.status_code)) |
| 87 try: |
| 88 ret = json.loads(resp.text) |
| 89 except ValueError, e: |
| 90 raise CtTrybotException( |
| 91 'Did not get a JSON response from %s: %s' % ( |
| 92 CHROMIUM_PERF_TASK_POST_URI, e)) |
| 93 return ret["taskID"] |
| 94 |
| 95 |
| 96 def TriggerAndWait(options): |
| 97 task_id = _TriggerTask(options) |
| 98 |
| 99 print |
| 100 print 'Task %s has been successfull scheduled on CTFE (%s).' % ( |
| 101 task_id, CHROMIUM_PERF_RUNS_HISTORY) |
| 102 print 'You will get an email once the task has been picked up by the server.' |
| 103 print |
| 104 print |
| 105 |
| 106 # Now poll CTFE till the task completes or till deadline is hit. |
| 107 time_started_polling = time.time() |
| 108 while True: |
| 109 if (time.time() - time_started_polling) > TRYBOT_DEADLINE_SECS: |
| 110 raise CtTrybotException( |
| 111 'Task did not complete in the deadline of %s seconds.' % ( |
| 112 TRYBOT_DEADLINE_SECS)) |
| 113 |
| 114 # Get the status of the task the trybot added. |
| 115 get_url = '%s?task_id=%s' % (GET_CHROMIUM_PERF_RUN_STATUS_URI, task_id) |
| 116 resp = requests.get(get_url) |
| 117 if resp.status_code != 200: |
| 118 raise CtTrybotException( |
| 119 'Return code from %s was %s' % (GET_CHROMIUM_PERF_RUN_STATUS_URI, |
| 120 resp.status_code)) |
| 121 try: |
| 122 ret = json.loads(resp.text) |
| 123 except ValueError, e: |
| 124 raise CtTrybotException( |
| 125 'Did not get a JSON response from %s: %s' % (get_url, e)) |
| 126 # Assert that the status is for the task we asked for. |
| 127 assert int(ret["taskID"]) == int(task_id) |
| 128 |
| 129 status = ret["status"] |
| 130 if status == "Completed": |
| 131 print |
| 132 print ('Your run was successfully completed. Please check your email for ' |
| 133 'results of the run.') |
| 134 print |
| 135 return 0 |
| 136 elif status == "Completed with failures": |
| 137 print |
| 138 raise CtTrybotException( |
| 139 'Your run was completed with failures. Please check your email for ' |
| 140 'links to logs of the run.') |
| 141 |
| 142 print ('The current status of the task %s is "%s". You can view the size ' |
| 143 'of the queue here: %s' % (task_id, status, CTFE_QUEUE)) |
| 144 print 'Checking again after %s seconds' % POLLING_FREQUENCY_SECS |
| 145 print |
| 146 time.sleep(POLLING_FREQUENCY_SECS) |
| 147 |
| 148 |
| 149 if '__main__' == __name__: |
| 150 option_parser = optparse.OptionParser() |
| 151 option_parser.add_option( |
| 152 '', '--issue', |
| 153 help='The Rietveld CL number to get the patch from.') |
| 154 option_parser.add_option( |
| 155 '', '--patchset', |
| 156 help='The Rietveld CL patchset to use.') |
| 157 option_parser.add_option( |
| 158 '', '--requester', |
| 159 help='Email address of the user who requested this run.') |
| 160 option_parser.add_option( |
| 161 '', '--benchmark', |
| 162 help='The CT benchmark to run on the patch.') |
| 163 option_parser.add_option( |
| 164 '', '--local', default=False, action='store_true', |
| 165 help='Uses a dummy metadata salt if this flag is true else it tries to ' |
| 166 'get the salt from GCE metadata.') |
| 167 options, unused_args = option_parser.parse_args() |
| 168 if (not options.issue or not options.patchset or not options.requester |
| 169 or not options.benchmark): |
| 170 option_parser.error('Must specify issue, patchset, requester and benchmark') |
| 171 |
| 172 sys.exit(TriggerAndWait(options)) |
| 173 |
OLD | NEW |