1*d9f75844SAndroid Build Coastguard Worker#!/usr/bin/env vpython3 2*d9f75844SAndroid Build Coastguard Worker 3*d9f75844SAndroid Build Coastguard Worker# Copyright (c) 2020 The WebRTC project authors. All Rights Reserved. 4*d9f75844SAndroid Build Coastguard Worker# 5*d9f75844SAndroid Build Coastguard Worker# Use of this source code is governed by a BSD-style license 6*d9f75844SAndroid Build Coastguard Worker# that can be found in the LICENSE file in the root of the source 7*d9f75844SAndroid Build Coastguard Worker# tree. An additional intellectual property rights grant can be found 8*d9f75844SAndroid Build Coastguard Worker# in the file PATENTS. All contributing project authors may 9*d9f75844SAndroid Build Coastguard Worker# be found in the AUTHORS file in the root of the source tree. 10*d9f75844SAndroid Build Coastguard Worker 11*d9f75844SAndroid Build Coastguard Workerimport datetime 12*d9f75844SAndroid Build Coastguard Workerimport json 13*d9f75844SAndroid Build Coastguard Workerimport subprocess 14*d9f75844SAndroid Build Coastguard Workerimport time 15*d9f75844SAndroid Build Coastguard Workerimport zlib 16*d9f75844SAndroid Build Coastguard Worker 17*d9f75844SAndroid Build Coastguard Workerfrom typing import Optional 18*d9f75844SAndroid Build Coastguard Workerimport dataclasses 19*d9f75844SAndroid Build Coastguard Workerimport httplib2 20*d9f75844SAndroid Build Coastguard Worker 21*d9f75844SAndroid Build Coastguard Workerfrom tracing.value import histogram 22*d9f75844SAndroid Build Coastguard Workerfrom tracing.value import histogram_set 23*d9f75844SAndroid Build Coastguard Workerfrom tracing.value.diagnostics import generic_set 24*d9f75844SAndroid Build Coastguard Workerfrom tracing.value.diagnostics import reserved_infos 25*d9f75844SAndroid Build Coastguard Worker 26*d9f75844SAndroid Build Coastguard Worker 27*d9f75844SAndroid Build Coastguard Worker@dataclasses.dataclass 28*d9f75844SAndroid Build Coastguard Workerclass UploaderOptions(): 29*d9f75844SAndroid Build Coastguard Worker """Required information to upload perf metrics. 30*d9f75844SAndroid Build Coastguard Worker 31*d9f75844SAndroid Build Coastguard Worker Attributes: 32*d9f75844SAndroid Build Coastguard Worker perf_dashboard_machine_group: The "master" the bots are grouped under. 33*d9f75844SAndroid Build Coastguard Worker This string is the group in the the perf dashboard path 34*d9f75844SAndroid Build Coastguard Worker group/bot/perf_id/metric/subtest. 35*d9f75844SAndroid Build Coastguard Worker bot: The bot running the test (e.g. webrtc-win-large-tests). 36*d9f75844SAndroid Build Coastguard Worker test_suite: The key for the test in the dashboard (i.e. what you select 37*d9f75844SAndroid Build Coastguard Worker in the top-level test suite selector in the dashboard 38*d9f75844SAndroid Build Coastguard Worker webrtc_git_hash: webrtc.googlesource.com commit hash. 39*d9f75844SAndroid Build Coastguard Worker commit_position: Commit pos corresponding to the git hash. 40*d9f75844SAndroid Build Coastguard Worker build_page_url: URL to the build page for this build. 41*d9f75844SAndroid Build Coastguard Worker dashboard_url: Which dashboard to use. 42*d9f75844SAndroid Build Coastguard Worker input_results_file: A HistogramSet proto file coming from WebRTC tests. 43*d9f75844SAndroid Build Coastguard Worker output_json_file: Where to write the output (for debugging). 44*d9f75844SAndroid Build Coastguard Worker wait_timeout_sec: Maximum amount of time in seconds that the script will 45*d9f75844SAndroid Build Coastguard Worker wait for the confirmation. 46*d9f75844SAndroid Build Coastguard Worker wait_polling_period_sec: Status will be requested from the Dashboard 47*d9f75844SAndroid Build Coastguard Worker every wait_polling_period_sec seconds. 48*d9f75844SAndroid Build Coastguard Worker """ 49*d9f75844SAndroid Build Coastguard Worker perf_dashboard_machine_group: str 50*d9f75844SAndroid Build Coastguard Worker bot: str 51*d9f75844SAndroid Build Coastguard Worker test_suite: str 52*d9f75844SAndroid Build Coastguard Worker webrtc_git_hash: str 53*d9f75844SAndroid Build Coastguard Worker commit_position: int 54*d9f75844SAndroid Build Coastguard Worker build_page_url: str 55*d9f75844SAndroid Build Coastguard Worker dashboard_url: str 56*d9f75844SAndroid Build Coastguard Worker input_results_file: str 57*d9f75844SAndroid Build Coastguard Worker output_json_file: Optional[str] = None 58*d9f75844SAndroid Build Coastguard Worker wait_timeout_sec: datetime.timedelta = datetime.timedelta(seconds=1200) 59*d9f75844SAndroid Build Coastguard Worker wait_polling_period_sec: datetime.timedelta = datetime.timedelta(seconds=120) 60*d9f75844SAndroid Build Coastguard Worker 61*d9f75844SAndroid Build Coastguard Worker 62*d9f75844SAndroid Build Coastguard Workerdef _GenerateOauthToken(): 63*d9f75844SAndroid Build Coastguard Worker args = ['luci-auth', 'token'] 64*d9f75844SAndroid Build Coastguard Worker p = subprocess.Popen(args, 65*d9f75844SAndroid Build Coastguard Worker universal_newlines=True, 66*d9f75844SAndroid Build Coastguard Worker stdout=subprocess.PIPE, 67*d9f75844SAndroid Build Coastguard Worker stderr=subprocess.PIPE) 68*d9f75844SAndroid Build Coastguard Worker if p.wait() == 0: 69*d9f75844SAndroid Build Coastguard Worker output = p.stdout.read() 70*d9f75844SAndroid Build Coastguard Worker return output.strip() 71*d9f75844SAndroid Build Coastguard Worker raise RuntimeError( 72*d9f75844SAndroid Build Coastguard Worker 'Error generating authentication token.\nStdout: %s\nStderr:%s' % 73*d9f75844SAndroid Build Coastguard Worker (p.stdout.read(), p.stderr.read())) 74*d9f75844SAndroid Build Coastguard Worker 75*d9f75844SAndroid Build Coastguard Worker 76*d9f75844SAndroid Build Coastguard Workerdef _CreateHeaders(oauth_token): 77*d9f75844SAndroid Build Coastguard Worker return {'Authorization': 'Bearer %s' % oauth_token} 78*d9f75844SAndroid Build Coastguard Worker 79*d9f75844SAndroid Build Coastguard Worker 80*d9f75844SAndroid Build Coastguard Workerdef _SendHistogramSet(url, histograms): 81*d9f75844SAndroid Build Coastguard Worker """Make a HTTP POST with the given JSON to the Performance Dashboard. 82*d9f75844SAndroid Build Coastguard Worker 83*d9f75844SAndroid Build Coastguard Worker Args: 84*d9f75844SAndroid Build Coastguard Worker url: URL of Performance Dashboard instance, e.g. 85*d9f75844SAndroid Build Coastguard Worker "https://chromeperf.appspot.com". 86*d9f75844SAndroid Build Coastguard Worker histograms: a histogram set object that contains the data to be sent. 87*d9f75844SAndroid Build Coastguard Worker """ 88*d9f75844SAndroid Build Coastguard Worker headers = _CreateHeaders(_GenerateOauthToken()) 89*d9f75844SAndroid Build Coastguard Worker 90*d9f75844SAndroid Build Coastguard Worker serialized = json.dumps(_ApplyHacks(histograms.AsDicts()), indent=4) 91*d9f75844SAndroid Build Coastguard Worker 92*d9f75844SAndroid Build Coastguard Worker if url.startswith('http://localhost'): 93*d9f75844SAndroid Build Coastguard Worker # The catapult server turns off compression in developer mode. 94*d9f75844SAndroid Build Coastguard Worker data = serialized 95*d9f75844SAndroid Build Coastguard Worker else: 96*d9f75844SAndroid Build Coastguard Worker data = zlib.compress(serialized.encode('utf-8')) 97*d9f75844SAndroid Build Coastguard Worker 98*d9f75844SAndroid Build Coastguard Worker print('Sending %d bytes to %s.' % (len(data), url + '/add_histograms')) 99*d9f75844SAndroid Build Coastguard Worker 100*d9f75844SAndroid Build Coastguard Worker http = httplib2.Http() 101*d9f75844SAndroid Build Coastguard Worker response, content = http.request(url + '/add_histograms', 102*d9f75844SAndroid Build Coastguard Worker method='POST', 103*d9f75844SAndroid Build Coastguard Worker body=data, 104*d9f75844SAndroid Build Coastguard Worker headers=headers) 105*d9f75844SAndroid Build Coastguard Worker return response, content 106*d9f75844SAndroid Build Coastguard Worker 107*d9f75844SAndroid Build Coastguard Worker 108*d9f75844SAndroid Build Coastguard Workerdef _WaitForUploadConfirmation(url, upload_token, wait_timeout, 109*d9f75844SAndroid Build Coastguard Worker wait_polling_period): 110*d9f75844SAndroid Build Coastguard Worker """Make a HTTP GET requests to the Performance Dashboard untill upload 111*d9f75844SAndroid Build Coastguard Worker status is known or the time is out. 112*d9f75844SAndroid Build Coastguard Worker 113*d9f75844SAndroid Build Coastguard Worker Args: 114*d9f75844SAndroid Build Coastguard Worker url: URL of Performance Dashboard instance, e.g. 115*d9f75844SAndroid Build Coastguard Worker "https://chromeperf.appspot.com". 116*d9f75844SAndroid Build Coastguard Worker upload_token: String that identifies Performance Dashboard and can be used 117*d9f75844SAndroid Build Coastguard Worker for the status check. 118*d9f75844SAndroid Build Coastguard Worker wait_timeout: (datetime.timedelta) Maximum time to wait for the 119*d9f75844SAndroid Build Coastguard Worker confirmation. 120*d9f75844SAndroid Build Coastguard Worker wait_polling_period: (datetime.timedelta) Performance Dashboard will be 121*d9f75844SAndroid Build Coastguard Worker polled every wait_polling_period amount of time. 122*d9f75844SAndroid Build Coastguard Worker """ 123*d9f75844SAndroid Build Coastguard Worker assert wait_polling_period <= wait_timeout 124*d9f75844SAndroid Build Coastguard Worker 125*d9f75844SAndroid Build Coastguard Worker headers = _CreateHeaders(_GenerateOauthToken()) 126*d9f75844SAndroid Build Coastguard Worker http = httplib2.Http() 127*d9f75844SAndroid Build Coastguard Worker 128*d9f75844SAndroid Build Coastguard Worker oauth_refreshed = False 129*d9f75844SAndroid Build Coastguard Worker response = None 130*d9f75844SAndroid Build Coastguard Worker resp_json = None 131*d9f75844SAndroid Build Coastguard Worker current_time = datetime.datetime.now() 132*d9f75844SAndroid Build Coastguard Worker end_time = current_time + wait_timeout 133*d9f75844SAndroid Build Coastguard Worker next_poll_time = current_time + wait_polling_period 134*d9f75844SAndroid Build Coastguard Worker while datetime.datetime.now() < end_time: 135*d9f75844SAndroid Build Coastguard Worker current_time = datetime.datetime.now() 136*d9f75844SAndroid Build Coastguard Worker if next_poll_time > current_time: 137*d9f75844SAndroid Build Coastguard Worker time.sleep((next_poll_time - current_time).total_seconds()) 138*d9f75844SAndroid Build Coastguard Worker next_poll_time = datetime.datetime.now() + wait_polling_period 139*d9f75844SAndroid Build Coastguard Worker 140*d9f75844SAndroid Build Coastguard Worker response, content = http.request(url + '/uploads/' + upload_token, 141*d9f75844SAndroid Build Coastguard Worker method='GET', 142*d9f75844SAndroid Build Coastguard Worker headers=headers) 143*d9f75844SAndroid Build Coastguard Worker 144*d9f75844SAndroid Build Coastguard Worker print('Upload state polled. Response: %r.' % content) 145*d9f75844SAndroid Build Coastguard Worker 146*d9f75844SAndroid Build Coastguard Worker if not oauth_refreshed and response.status == 403: 147*d9f75844SAndroid Build Coastguard Worker print('Oauth token refreshed. Continue polling.') 148*d9f75844SAndroid Build Coastguard Worker headers = _CreateHeaders(_GenerateOauthToken()) 149*d9f75844SAndroid Build Coastguard Worker oauth_refreshed = True 150*d9f75844SAndroid Build Coastguard Worker continue 151*d9f75844SAndroid Build Coastguard Worker 152*d9f75844SAndroid Build Coastguard Worker if response.status != 200: 153*d9f75844SAndroid Build Coastguard Worker break 154*d9f75844SAndroid Build Coastguard Worker 155*d9f75844SAndroid Build Coastguard Worker resp_json = json.loads(content) 156*d9f75844SAndroid Build Coastguard Worker if resp_json['state'] == 'COMPLETED' or resp_json['state'] == 'FAILED': 157*d9f75844SAndroid Build Coastguard Worker break 158*d9f75844SAndroid Build Coastguard Worker 159*d9f75844SAndroid Build Coastguard Worker return response, resp_json 160*d9f75844SAndroid Build Coastguard Worker 161*d9f75844SAndroid Build Coastguard Worker 162*d9f75844SAndroid Build Coastguard Worker# Because of an issues on the Dashboard side few measurements over a large set 163*d9f75844SAndroid Build Coastguard Worker# can fail to upload. That would lead to the whole upload to be marked as 164*d9f75844SAndroid Build Coastguard Worker# failed. Check it, so it doesn't increase flakiness of our tests. 165*d9f75844SAndroid Build Coastguard Worker# TODO(crbug.com/1145904): Remove check after fixed. 166*d9f75844SAndroid Build Coastguard Workerdef _CheckFullUploadInfo(url, upload_token, 167*d9f75844SAndroid Build Coastguard Worker min_measurements_amount=50, 168*d9f75844SAndroid Build Coastguard Worker max_failed_measurements_percent=0.03): 169*d9f75844SAndroid Build Coastguard Worker """Make a HTTP GET requests to the Performance Dashboard to get full info 170*d9f75844SAndroid Build Coastguard Worker about upload (including measurements). Checks if upload is correct despite 171*d9f75844SAndroid Build Coastguard Worker not having status "COMPLETED". 172*d9f75844SAndroid Build Coastguard Worker 173*d9f75844SAndroid Build Coastguard Worker Args: 174*d9f75844SAndroid Build Coastguard Worker url: URL of Performance Dashboard instance, e.g. 175*d9f75844SAndroid Build Coastguard Worker "https://chromeperf.appspot.com". 176*d9f75844SAndroid Build Coastguard Worker upload_token: String that identifies Performance Dashboard and can be used 177*d9f75844SAndroid Build Coastguard Worker for the status check. 178*d9f75844SAndroid Build Coastguard Worker min_measurements_amount: minimal amount of measurements that the upload 179*d9f75844SAndroid Build Coastguard Worker should have to start tolerating failures in particular measurements. 180*d9f75844SAndroid Build Coastguard Worker max_failed_measurements_percent: maximal percent of failured measurements 181*d9f75844SAndroid Build Coastguard Worker to tolerate. 182*d9f75844SAndroid Build Coastguard Worker """ 183*d9f75844SAndroid Build Coastguard Worker headers = _CreateHeaders(_GenerateOauthToken()) 184*d9f75844SAndroid Build Coastguard Worker http = httplib2.Http() 185*d9f75844SAndroid Build Coastguard Worker 186*d9f75844SAndroid Build Coastguard Worker response, content = http.request(url + '/uploads/' + upload_token + 187*d9f75844SAndroid Build Coastguard Worker '?additional_info=measurements', 188*d9f75844SAndroid Build Coastguard Worker method='GET', 189*d9f75844SAndroid Build Coastguard Worker headers=headers) 190*d9f75844SAndroid Build Coastguard Worker 191*d9f75844SAndroid Build Coastguard Worker if response.status != 200: 192*d9f75844SAndroid Build Coastguard Worker print('Failed to reach the dashboard to get full upload info.') 193*d9f75844SAndroid Build Coastguard Worker return False 194*d9f75844SAndroid Build Coastguard Worker 195*d9f75844SAndroid Build Coastguard Worker resp_json = json.loads(content) 196*d9f75844SAndroid Build Coastguard Worker print('Full upload info: %s.' % json.dumps(resp_json, indent=4)) 197*d9f75844SAndroid Build Coastguard Worker 198*d9f75844SAndroid Build Coastguard Worker if 'measurements' in resp_json: 199*d9f75844SAndroid Build Coastguard Worker measurements_cnt = len(resp_json['measurements']) 200*d9f75844SAndroid Build Coastguard Worker not_completed_state_cnt = len( 201*d9f75844SAndroid Build Coastguard Worker [m for m in resp_json['measurements'] if m['state'] != 'COMPLETED']) 202*d9f75844SAndroid Build Coastguard Worker 203*d9f75844SAndroid Build Coastguard Worker if (measurements_cnt >= min_measurements_amount 204*d9f75844SAndroid Build Coastguard Worker and (not_completed_state_cnt / 205*d9f75844SAndroid Build Coastguard Worker (measurements_cnt * 1.0) <= max_failed_measurements_percent)): 206*d9f75844SAndroid Build Coastguard Worker print(('Not all measurements were confirmed to upload. ' 207*d9f75844SAndroid Build Coastguard Worker 'Measurements count: %d, failed to upload or timed out: %d' % 208*d9f75844SAndroid Build Coastguard Worker (measurements_cnt, not_completed_state_cnt))) 209*d9f75844SAndroid Build Coastguard Worker return True 210*d9f75844SAndroid Build Coastguard Worker 211*d9f75844SAndroid Build Coastguard Worker return False 212*d9f75844SAndroid Build Coastguard Worker 213*d9f75844SAndroid Build Coastguard Worker 214*d9f75844SAndroid Build Coastguard Worker# TODO(https://crbug.com/1029452): HACKHACK 215*d9f75844SAndroid Build Coastguard Worker# Remove once we have doubles in the proto and handle -infinity correctly. 216*d9f75844SAndroid Build Coastguard Workerdef _ApplyHacks(dicts): 217*d9f75844SAndroid Build Coastguard Worker def _NoInf(value): 218*d9f75844SAndroid Build Coastguard Worker if value == float('inf'): 219*d9f75844SAndroid Build Coastguard Worker return histogram.JS_MAX_VALUE 220*d9f75844SAndroid Build Coastguard Worker if value == float('-inf'): 221*d9f75844SAndroid Build Coastguard Worker return -histogram.JS_MAX_VALUE 222*d9f75844SAndroid Build Coastguard Worker return value 223*d9f75844SAndroid Build Coastguard Worker 224*d9f75844SAndroid Build Coastguard Worker for d in dicts: 225*d9f75844SAndroid Build Coastguard Worker if 'running' in d: 226*d9f75844SAndroid Build Coastguard Worker d['running'] = [_NoInf(value) for value in d['running']] 227*d9f75844SAndroid Build Coastguard Worker if 'sampleValues' in d: 228*d9f75844SAndroid Build Coastguard Worker d['sampleValues'] = [_NoInf(value) for value in d['sampleValues']] 229*d9f75844SAndroid Build Coastguard Worker 230*d9f75844SAndroid Build Coastguard Worker return dicts 231*d9f75844SAndroid Build Coastguard Worker 232*d9f75844SAndroid Build Coastguard Worker 233*d9f75844SAndroid Build Coastguard Workerdef _LoadHistogramSetFromProto(options): 234*d9f75844SAndroid Build Coastguard Worker hs = histogram_set.HistogramSet() 235*d9f75844SAndroid Build Coastguard Worker with open(options.input_results_file, 'rb') as f: 236*d9f75844SAndroid Build Coastguard Worker hs.ImportProto(f.read()) 237*d9f75844SAndroid Build Coastguard Worker 238*d9f75844SAndroid Build Coastguard Worker return hs 239*d9f75844SAndroid Build Coastguard Worker 240*d9f75844SAndroid Build Coastguard Worker 241*d9f75844SAndroid Build Coastguard Workerdef _AddBuildInfo(histograms, options): 242*d9f75844SAndroid Build Coastguard Worker common_diagnostics = { 243*d9f75844SAndroid Build Coastguard Worker reserved_infos.MASTERS: options.perf_dashboard_machine_group, 244*d9f75844SAndroid Build Coastguard Worker reserved_infos.BOTS: options.bot, 245*d9f75844SAndroid Build Coastguard Worker reserved_infos.POINT_ID: options.commit_position, 246*d9f75844SAndroid Build Coastguard Worker reserved_infos.BENCHMARKS: options.test_suite, 247*d9f75844SAndroid Build Coastguard Worker reserved_infos.WEBRTC_REVISIONS: str(options.webrtc_git_hash), 248*d9f75844SAndroid Build Coastguard Worker reserved_infos.BUILD_URLS: options.build_page_url, 249*d9f75844SAndroid Build Coastguard Worker } 250*d9f75844SAndroid Build Coastguard Worker 251*d9f75844SAndroid Build Coastguard Worker for k, v in list(common_diagnostics.items()): 252*d9f75844SAndroid Build Coastguard Worker histograms.AddSharedDiagnosticToAllHistograms(k.name, 253*d9f75844SAndroid Build Coastguard Worker generic_set.GenericSet([v])) 254*d9f75844SAndroid Build Coastguard Worker 255*d9f75844SAndroid Build Coastguard Worker 256*d9f75844SAndroid Build Coastguard Workerdef _DumpOutput(histograms, output_file): 257*d9f75844SAndroid Build Coastguard Worker with open(output_file, 'w') as f: 258*d9f75844SAndroid Build Coastguard Worker json.dump(_ApplyHacks(histograms.AsDicts()), f, indent=4) 259*d9f75844SAndroid Build Coastguard Worker 260*d9f75844SAndroid Build Coastguard Worker 261*d9f75844SAndroid Build Coastguard Workerdef UploadToDashboardImpl(options): 262*d9f75844SAndroid Build Coastguard Worker histograms = _LoadHistogramSetFromProto(options) 263*d9f75844SAndroid Build Coastguard Worker _AddBuildInfo(histograms, options) 264*d9f75844SAndroid Build Coastguard Worker 265*d9f75844SAndroid Build Coastguard Worker if options.output_json_file: 266*d9f75844SAndroid Build Coastguard Worker _DumpOutput(histograms, options.output_json_file) 267*d9f75844SAndroid Build Coastguard Worker 268*d9f75844SAndroid Build Coastguard Worker response, content = _SendHistogramSet(options.dashboard_url, histograms) 269*d9f75844SAndroid Build Coastguard Worker 270*d9f75844SAndroid Build Coastguard Worker if response.status != 200: 271*d9f75844SAndroid Build Coastguard Worker print(('Upload failed with %d: %s\n\n%s' % 272*d9f75844SAndroid Build Coastguard Worker (response.status, response.reason, content))) 273*d9f75844SAndroid Build Coastguard Worker return 1 274*d9f75844SAndroid Build Coastguard Worker 275*d9f75844SAndroid Build Coastguard Worker upload_token = json.loads(content).get('token') 276*d9f75844SAndroid Build Coastguard Worker if not upload_token: 277*d9f75844SAndroid Build Coastguard Worker print(('Received 200 from dashboard. ', 278*d9f75844SAndroid Build Coastguard Worker 'Not waiting for the upload status confirmation.')) 279*d9f75844SAndroid Build Coastguard Worker return 0 280*d9f75844SAndroid Build Coastguard Worker 281*d9f75844SAndroid Build Coastguard Worker response, resp_json = _WaitForUploadConfirmation( 282*d9f75844SAndroid Build Coastguard Worker options.dashboard_url, upload_token, options.wait_timeout_sec, 283*d9f75844SAndroid Build Coastguard Worker options.wait_polling_period_sec) 284*d9f75844SAndroid Build Coastguard Worker 285*d9f75844SAndroid Build Coastguard Worker if ((resp_json and resp_json['state'] == 'COMPLETED') 286*d9f75844SAndroid Build Coastguard Worker or _CheckFullUploadInfo(options.dashboard_url, upload_token)): 287*d9f75844SAndroid Build Coastguard Worker print('Upload completed.') 288*d9f75844SAndroid Build Coastguard Worker return 0 289*d9f75844SAndroid Build Coastguard Worker 290*d9f75844SAndroid Build Coastguard Worker if response.status != 200: 291*d9f75844SAndroid Build Coastguard Worker print(('Upload status poll failed with %d: %s' % 292*d9f75844SAndroid Build Coastguard Worker (response.status, response.reason))) 293*d9f75844SAndroid Build Coastguard Worker return 1 294*d9f75844SAndroid Build Coastguard Worker 295*d9f75844SAndroid Build Coastguard Worker if resp_json['state'] == 'FAILED': 296*d9f75844SAndroid Build Coastguard Worker print('Upload failed.') 297*d9f75844SAndroid Build Coastguard Worker return 1 298*d9f75844SAndroid Build Coastguard Worker 299*d9f75844SAndroid Build Coastguard Worker print(('Upload wasn\'t completed in a given time: %s seconds.' % 300*d9f75844SAndroid Build Coastguard Worker options.wait_timeout_sec)) 301*d9f75844SAndroid Build Coastguard Worker return 1 302*d9f75844SAndroid Build Coastguard Worker 303*d9f75844SAndroid Build Coastguard Worker 304*d9f75844SAndroid Build Coastguard Workerdef UploadToDashboard(options): 305*d9f75844SAndroid Build Coastguard Worker try: 306*d9f75844SAndroid Build Coastguard Worker exit_code = UploadToDashboardImpl(options) 307*d9f75844SAndroid Build Coastguard Worker except RuntimeError as e: 308*d9f75844SAndroid Build Coastguard Worker print(e) 309*d9f75844SAndroid Build Coastguard Worker return 1 310*d9f75844SAndroid Build Coastguard Worker return exit_code 311