diff --git a/tools_webrtc/perf/catapult_uploader.py b/tools_webrtc/perf/catapult_uploader.py index d9bc0a8895..f3333987d6 100644 --- a/tools_webrtc/perf/catapult_uploader.py +++ b/tools_webrtc/perf/catapult_uploader.py @@ -7,9 +7,11 @@ # in the file PATENTS. All contributing project authors may # be found in the AUTHORS file in the root of the source tree. +import datetime import httplib2 import json import subprocess +import time import zlib from tracing.value import histogram @@ -33,12 +35,12 @@ def _GenerateOauthToken(): def _SendHistogramSet(url, histograms, oauth_token): """Make a HTTP POST with the given JSON to the Performance Dashboard. - Args: - url: URL of Performance Dashboard instance, e.g. - "https://chromeperf.appspot.com". - histograms: a histogram set object that contains the data to be sent. - oauth_token: An oauth token to use for authorization. - """ + Args: + url: URL of Performance Dashboard instance, e.g. + "https://chromeperf.appspot.com". + histograms: a histogram set object that contains the data to be sent. + oauth_token: An oauth token to use for authorization. + """ headers = {'Authorization': 'Bearer %s' % oauth_token} serialized = json.dumps(_ApplyHacks(histograms.AsDicts()), indent=4) @@ -59,6 +61,52 @@ def _SendHistogramSet(url, histograms, oauth_token): return response, content +def _WaitForUploadConfirmation(url, oauth_token, upload_token, wait_timeout, + wait_polling_period): + """Make a HTTP GET requests to the Performance Dashboard untill upload + status is known or the time is out. + + Args: + url: URL of Performance Dashboard instance, e.g. + "https://chromeperf.appspot.com". + oauth_token: An oauth token to use for authorization. + upload_token: String that identifies Performance Dashboard and can be used + for the status check. + wait_timeout: (datetime.timedelta) Maximum time to wait for the + confirmation. + wait_polling_period: (datetime.timedelta) Performance Dashboard will be + polled every wait_polling_period amount of time. + """ + assert wait_polling_period <= wait_timeout + + headers = {'Authorization': 'Bearer %s' % oauth_token} + http = httplib2.Http() + + response = None + resp_json = None + current_time = datetime.datetime.now() + end_time = current_time + wait_timeout + next_poll_time = current_time + wait_polling_period + while datetime.datetime.now() < end_time: + current_time = datetime.datetime.now() + if next_poll_time > current_time: + time.sleep((next_poll_time - current_time).total_seconds()) + next_poll_time = datetime.datetime.now() + wait_polling_period + + response, content = http.request(url + '/uploads' + upload_token, + method='GET', headers=headers) + resp_json = json.loads(content) + + print 'Upload state polled. Response: %s.' % content + + if (response.status != 200 or + resp_json['state'] == 'COMPLETED' or + resp_json['state'] == 'FAILED'): + break + + return response, resp_json + + # TODO(https://crbug.com/1029452): HACKHACK # Remove once we have doubles in the proto and handle -infinity correctly. def _ApplyHacks(dicts): @@ -113,13 +161,36 @@ def UploadToDashboard(options): _DumpOutput(histograms, options.output_json_file) oauth_token = _GenerateOauthToken() - response, content = _SendHistogramSet(options.dashboard_url, histograms, - oauth_token) + response, content = _SendHistogramSet( + options.dashboard_url, histograms, oauth_token) - if response.status == 200: - print 'Received 200 from dashboard.' - return 0 - else: - print('Upload failed with %d: %s\n\n%s' % - (response.status, response.reason, content)) + upload_token = json.loads(content).get('token') + if not options.wait_for_upload or not upload_token: + print 'Not waiting for upload status confirmation.' + if response.status == 200: + print 'Received 200 from dashboard.' + return 0 + else: + print('Upload failed with %d: %s\n\n%s' % (response.status, + response.reason, content)) + return 1 + + response, resp_json = _WaitForUploadConfirmation( + options.dashboard_url, + oauth_token, + upload_token, + datetime.timedelta(seconds=options.wait_timeout_sec), + datetime.timedelta(seconds=options.wait_polling_period_sec)) + + if response.status != 200 or resp_json['state'] == 'FAILED': + print('Upload failed with %d: %s\n\n%s' % (response.status, + response.reason, + str(resp_json))) return 1 + + if resp_json['state'] == 'COMPLETED': + print 'Upload completed.' + return 0 + + print('Upload wasn\'t completed in a given time: %d.', options.wait_timeout) + return 1 diff --git a/tools_webrtc/perf/webrtc_dashboard_upload.py b/tools_webrtc/perf/webrtc_dashboard_upload.py index 25de2264ef..0d42e9ccf7 100644 --- a/tools_webrtc/perf/webrtc_dashboard_upload.py +++ b/tools_webrtc/perf/webrtc_dashboard_upload.py @@ -24,54 +24,54 @@ import sys def _CreateParser(): parser = argparse.ArgumentParser() - parser.add_argument('--perf-dashboard-machine-group', - required=True, + parser.add_argument('--perf-dashboard-machine-group', required=True, help='The "master" the bots are grouped under. This ' 'string is the group in the the perf dashboard path ' 'group/bot/perf_id/metric/subtest.') - parser.add_argument('--bot', - required=True, + parser.add_argument('--bot', required=True, help='The bot running the test (e.g. ' - 'webrtc-win-large-tests).') - parser.add_argument( - '--test-suite', - required=True, - help='The key for the test in the dashboard (i.e. what ' - 'you select in the top-level test suite selector in the ' - 'dashboard') - parser.add_argument('--webrtc-git-hash', - required=True, + 'webrtc-win-large-tests).') + parser.add_argument('--test-suite', required=True, + help='The key for the test in the dashboard (i.e. what ' + 'you select in the top-level test suite selector in ' + 'the dashboard') + parser.add_argument('--webrtc-git-hash', required=True, help='webrtc.googlesource.com commit hash.') - parser.add_argument('--commit-position', - type=int, - required=True, + parser.add_argument('--commit-position', type=int, required=True, help='Commit pos corresponding to the git hash.') - parser.add_argument('--build-page-url', - required=True, + parser.add_argument('--build-page-url', required=True, help='URL to the build page for this build.') - parser.add_argument('--dashboard-url', - required=True, + parser.add_argument('--dashboard-url', required=True, help='Which dashboard to use.') - parser.add_argument('--input-results-file', - type=argparse.FileType(), + parser.add_argument('--input-results-file', type=argparse.FileType(), required=True, help='A JSON file with output from WebRTC tests.') - parser.add_argument('--output-json-file', - type=argparse.FileType('w'), + parser.add_argument('--output-json-file', type=argparse.FileType('w'), help='Where to write the output (for debugging).') - parser.add_argument( - '--outdir', - required=True, - help='Path to the local out/ dir (usually out/Default)') + parser.add_argument('--outdir', required=True, + help='Path to the local out/ dir (usually out/Default)') + parser.add_argument('--wait-for-upload', action='store_true', + help='If specified, script will wait untill Chrome ' + 'perf dashboard confirms that the data was succesfully ' + 'proccessed and uploaded') + parser.add_argument('--wait-timeout-sec', type=int, default=1200, + help='Used only if wait-for-upload is True. Maximum ' + 'amount of time in seconds that the script will wait ' + 'for the confirmation.') + parser.add_argument('--wait-polling-period-sec', type=int, default=120, + help='Used only if wait-for-upload is True. Status ' + 'will be requested from the Dashboard every ' + 'wait-polling-period-sec seconds.') return parser def _ConfigurePythonPath(options): # We just yank the python scripts we require into the PYTHONPATH. You could - # also imagine a solution where we use for instance protobuf:py_proto_runtime - # to copy catapult and protobuf code to out/. This is the convention in - # Chromium and WebRTC python scripts. We do need to build histogram_pb2 - # however, so that's why we add out/ to sys.path below. + # also imagine a solution where we use for instance + # protobuf:py_proto_runtime to copy catapult and protobuf code to out/. + # This is the convention in Chromium and WebRTC python scripts. We do need + # to build histogram_pb2 however, so that's why we add out/ to sys.path + # below. # # It would be better if there was an equivalent to py_binary in GN, but # there's not. @@ -84,8 +84,9 @@ def _ConfigurePythonPath(options): sys.path.insert( 0, os.path.join(checkout_root, 'third_party', 'protobuf', 'python')) - # The webrtc_dashboard_upload gn rule will build the protobuf stub for python, - # so put it in the path for this script before we attempt to import it. + # The webrtc_dashboard_upload gn rule will build the protobuf stub for + # python, so put it in the path for this script before we attempt to import + # it. histogram_proto_path = os.path.join(options.outdir, 'pyproto', 'tracing', 'tracing', 'proto') sys.path.insert(0, histogram_proto_path)