Index: ct/py/trigger_wait_ct_task.py |
diff --git a/ct/py/trigger_wait_ct_task.py b/ct/py/trigger_wait_ct_task.py |
new file mode 100644 |
index 0000000000000000000000000000000000000000..351a84ba05b25df2a6bfaf83608488b934dc96d7 |
--- /dev/null |
+++ b/ct/py/trigger_wait_ct_task.py |
@@ -0,0 +1,168 @@ |
+#!/usr/bin/env python |
+# Copyright (c) 2015 The Chromium Authors. All rights reserved. |
+# Use of this source code is governed by a BSD-style license that can be |
+# found in the LICENSE file. |
+ |
+"""Python utility that triggers and waits for tasks to complete on CTFE.""" |
+ |
+import base64 |
+import hashlib |
+import json |
+import optparse |
+import requests |
+import sys |
+import time |
+ |
+ |
+CTFE_HOST = "http://ct.skia.org" |
+CTFE_QUEUE = CTFE_HOST + '/queue/' |
+CHROMIUM_PERF_TASK_POST_URI = CTFE_HOST + "/_/webhook_add_chromium_perf_task" |
+GET_CHROMIUM_PERF_RUN_STATUS_URI = CTFE_HOST + "/get_chromium_perf_run_status" |
+CHROMIUM_PERF_RUNS_HISTORY = CTFE_HOST + "/chromium_perf_runs/" |
+GCE_WEBHOOK_SALT_METADATA_URI = ( |
+ "http://metadata/computeMetadata/v1/project/attributes/" |
+ "webhook_request_salt") |
+ |
+ |
+POLLING_FREQUENCY_SECS = 60 # 1 minute. |
+TRYBOT_DEADLINE_SECS = 24 * 60 * 60 # 24 hours. |
+ |
+ |
+class CtTrybotException(Exception): |
+ pass |
+ |
+ |
+def _CreateTaskJSON(options): |
+ """Creates a JSON representation of the requested task.""" |
+ task_params = {} |
+ task_params["username"] = options.requester |
+ task_params["benchmark"] = options.benchmark |
+ task_params["platform"] = "Linux" |
+ task_params["page_sets"] = "10k" |
borenet
2015/09/24 18:42:46
Can we plumb the platform and page_sets parameters
rmistry
2015/09/24 18:51:18
Yes we could plumb different combinations of the p
|
+ task_params["repeat_runs"] = "3" |
+ task_params["benchmark_args"] = "--output-format=csv-pivot-table" |
+ task_params["browser_args_nopatch"] = ( |
+ "--disable-setuid-sandbox --enable-threaded-compositing " |
+ "--enable-impl-side-painting") |
+ task_params["browser_args_withpatch"] = ( |
+ "--disable-setuid-sandbox --enable-threaded-compositing " |
+ "--enable-impl-side-painting") |
+ |
+ trybot_params = {} |
+ trybot_params["issue"] = options.issue |
+ trybot_params["task"] = task_params |
+ return json.dumps(trybot_params) |
+ |
+ |
+def _GetWebhookSaltFromMetadata(): |
+ """Gets webhook_request_salt from GCE's metadata server.""" |
+ headers = {"Metadata-Flavor": "Google"} |
+ resp = requests.get(GCE_WEBHOOK_SALT_METADATA_URI, headers=headers) |
+ if resp.status_code != 200: |
+ raise CtTrybotException( |
+ 'Return code from %s was %s' % (GCE_WEBHOOK_SALT_METADATA_URI, |
+ resp.status_code)) |
+ return resp.text |
+ |
+ |
+def _TriggerTask(options): |
+ """Triggers the requested task on CTFE and returns the new task's ID.""" |
+ task = _CreateTaskJSON(options) |
+ m = hashlib.sha512() |
+ m.update(task) |
+ m.update('notverysecret' if options.local else _GetWebhookSaltFromMetadata()) |
+ encoded = base64.standard_b64encode(m.digest()) |
+ |
+ headers = { |
+ "Content-type": "application/x-www-form-urlencoded", |
+ "Accept": "application/json", |
+ "X-Webhook-Auth-Hash": encoded} |
+ resp = requests.post(CHROMIUM_PERF_TASK_POST_URI, task, headers=headers) |
+ |
+ if resp.status_code != 200: |
+ raise CtTrybotException( |
+ 'Return code from %s was %s' % (CHROMIUM_PERF_TASK_POST_URI, |
+ resp.status_code)) |
+ try: |
+ ret = json.loads(resp.text) |
+ except ValueError, e: |
+ raise CtTrybotException( |
+ 'Did not get a JSON response from %s: %s' % ( |
+ CHROMIUM_PERF_TASK_POST_URI, e)) |
+ return ret["taskID"] |
+ |
+ |
+def TriggerAndWait(options): |
+ task_id = _TriggerTask(options) |
+ |
+ print 'Task %s has been successfull scheduled on CTFE (%s).' % ( |
+ task_id, CHROMIUM_PERF_RUNS_HISTORY) |
+ print 'You will get an email once the task has been picked up by the server.' |
+ |
+ # Now poll CTFE till the task completes or till deadline is hit. |
+ time_started_polling = time.time() |
+ while True: |
+ if (time.time() - time_started_polling) > TRYBOT_DEADLINE_SECS: |
+ raise CtTrybotException( |
+ 'Task did not complete in the deadline of %s seconds.' % ( |
+ TRYBOT_DEADLINE_SECS)) |
+ |
+ # Get the status of the task the trybot added. |
+ get_url = '%s?task_id=%s' % (GET_CHROMIUM_PERF_RUN_STATUS_URI, task_id) |
+ resp = requests.get(get_url) |
+ if resp.status_code != 200: |
+ raise CtTrybotException( |
+ 'Return code from %s was %s' % (GET_CHROMIUM_PERF_RUN_STATUS_URI, |
+ resp.status_code)) |
+ try: |
+ ret = json.loads(resp.text) |
+ except ValueError, e: |
+ raise CtTrybotException( |
+ 'Did not get a JSON response from %s: %s' % (get_url, e)) |
+ # Assert that the status is for the task we asked for. |
+ assert int(ret["taskID"]) == int(task_id) |
+ |
+ status = ret["status"] |
+ if status == "Completed": |
+ print ('Your run was successfully completed. Please check your email for ' |
+ 'results of the run.') |
+ return 0 |
+ elif status == "Completed with failures": |
+ raise CtTrybotException( |
+ 'Your run was completed with failures. Please check your email for ' |
+ 'links to logs of the run.') |
+ |
+ print ('The current status of the task %s is "%s". You can view the size ' |
+ 'of the queue here: %s' % (task_id, status, CTFE_QUEUE)) |
+ print 'Checking again after %s seconds' % POLLING_FREQUENCY_SECS |
+ time.sleep(POLLING_FREQUENCY_SECS) |
+ |
+ |
+if '__main__' == __name__: |
+ option_parser = optparse.OptionParser() |
+ option_parser.add_option( |
+ '', '--issue', |
+ help='The Rietveld CL number to get the patch from.') |
borenet
2015/09/24 18:42:46
Looks like we're missing the patchset option?
rmistry
2015/09/24 18:51:18
I initially thought it would always use only the l
|
+ option_parser.add_option( |
+ '', '--requester', |
+ help='Email address of the user who requested this run.') |
+ option_parser.add_option( |
+ '', '--benchmark', |
+ help='The CT benchmark to run on the patch.') |
+ option_parser.add_option( |
+ '', '--local', default=False, action='store_true', |
+ help='Uses a dummy metadata salt if this flag is true else it tries to ' |
+ 'get the salt from GCE metadata.') |
+ options, unused_args = option_parser.parse_args() |
+ if not options.issue or not options.requester or not options.benchmark: |
+ option_parser.error('Must specify issue, requester and benchmark') |
+ |
+ sys.exit(TriggerAndWait(options)) |
+ |