xref: /aosp_15_r20/external/webrtc/tools_webrtc/perf/catapult_uploader.py (revision d9f758449e529ab9291ac668be2861e7a55c2422)
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