blob: 81e6583c45a3287bf7511eb38dec4c5e91406d0f [file] [log] [blame]
Patrik Höglund0569a122020-03-13 12:26:42 +01001#!/usr/bin/env python
2# Copyright (c) 2020 The WebRTC project authors. All Rights Reserved.
3#
4# Use of this source code is governed by a BSD-style license
5# that can be found in the LICENSE file in the root of the source
6# tree. An additional intellectual property rights grant can be found
7# in the file PATENTS. All contributing project authors may
8# be found in the AUTHORS file in the root of the source tree.
9
Andrey Logvin728b5d02020-11-11 17:16:26 +000010import datetime
Patrik Höglund0569a122020-03-13 12:26:42 +010011import httplib2
12import json
13import subprocess
Andrey Logvin728b5d02020-11-11 17:16:26 +000014import time
Patrik Höglund0569a122020-03-13 12:26:42 +010015import zlib
16
Patrik Höglund620bed12020-03-17 09:59:10 +010017from tracing.value import histogram
Patrik Höglund0569a122020-03-13 12:26:42 +010018from tracing.value import histogram_set
19from tracing.value.diagnostics import generic_set
20from tracing.value.diagnostics import reserved_infos
21
22
23def _GenerateOauthToken():
Mirko Bonadei8cc66952020-10-30 10:13:45 +010024 args = ['luci-auth', 'token']
25 p = subprocess.Popen(args, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
26 if p.wait() == 0:
27 output = p.stdout.read()
28 return output.strip()
29 else:
30 raise RuntimeError(
31 'Error generating authentication token.\nStdout: %s\nStderr:%s' %
32 (p.stdout.read(), p.stderr.read()))
Patrik Höglund0569a122020-03-13 12:26:42 +010033
34
35def _SendHistogramSet(url, histograms, oauth_token):
Mirko Bonadei8cc66952020-10-30 10:13:45 +010036 """Make a HTTP POST with the given JSON to the Performance Dashboard.
Patrik Höglund0569a122020-03-13 12:26:42 +010037
Andrey Logvin728b5d02020-11-11 17:16:26 +000038 Args:
39 url: URL of Performance Dashboard instance, e.g.
40 "https://chromeperf.appspot.com".
41 histograms: a histogram set object that contains the data to be sent.
42 oauth_token: An oauth token to use for authorization.
43 """
Mirko Bonadei8cc66952020-10-30 10:13:45 +010044 headers = {'Authorization': 'Bearer %s' % oauth_token}
Patrik Höglund0569a122020-03-13 12:26:42 +010045
Mirko Bonadei8cc66952020-10-30 10:13:45 +010046 serialized = json.dumps(_ApplyHacks(histograms.AsDicts()), indent=4)
Patrik Höglund0569a122020-03-13 12:26:42 +010047
Mirko Bonadei8cc66952020-10-30 10:13:45 +010048 if url.startswith('http://localhost'):
49 # The catapult server turns off compression in developer mode.
50 data = serialized
51 else:
52 data = zlib.compress(serialized)
Patrik Höglund0569a122020-03-13 12:26:42 +010053
Mirko Bonadei8cc66952020-10-30 10:13:45 +010054 print 'Sending %d bytes to %s.' % (len(data), url + '/add_histograms')
Patrik Höglund0569a122020-03-13 12:26:42 +010055
Mirko Bonadei8cc66952020-10-30 10:13:45 +010056 http = httplib2.Http()
57 response, content = http.request(url + '/add_histograms',
58 method='POST',
59 body=data,
60 headers=headers)
61 return response, content
Patrik Höglund0569a122020-03-13 12:26:42 +010062
63
Andrey Logvin728b5d02020-11-11 17:16:26 +000064def _WaitForUploadConfirmation(url, oauth_token, upload_token, wait_timeout,
65 wait_polling_period):
66 """Make a HTTP GET requests to the Performance Dashboard untill upload
67 status is known or the time is out.
68
69 Args:
70 url: URL of Performance Dashboard instance, e.g.
71 "https://chromeperf.appspot.com".
72 oauth_token: An oauth token to use for authorization.
73 upload_token: String that identifies Performance Dashboard and can be used
74 for the status check.
75 wait_timeout: (datetime.timedelta) Maximum time to wait for the
76 confirmation.
77 wait_polling_period: (datetime.timedelta) Performance Dashboard will be
78 polled every wait_polling_period amount of time.
79 """
80 assert wait_polling_period <= wait_timeout
81
82 headers = {'Authorization': 'Bearer %s' % oauth_token}
83 http = httplib2.Http()
84
85 response = None
86 resp_json = None
87 current_time = datetime.datetime.now()
88 end_time = current_time + wait_timeout
89 next_poll_time = current_time + wait_polling_period
90 while datetime.datetime.now() < end_time:
91 current_time = datetime.datetime.now()
92 if next_poll_time > current_time:
93 time.sleep((next_poll_time - current_time).total_seconds())
94 next_poll_time = datetime.datetime.now() + wait_polling_period
95
96 response, content = http.request(url + '/uploads' + upload_token,
97 method='GET', headers=headers)
Andrey Logvine850af22020-11-18 15:23:53 +000098
99 print 'Upload state polled. Response: %r.' % content
100
Andrey Logvin728b5d02020-11-11 17:16:26 +0000101 resp_json = json.loads(content)
Andrey Logvin728b5d02020-11-11 17:16:26 +0000102 if (response.status != 200 or
103 resp_json['state'] == 'COMPLETED' or
104 resp_json['state'] == 'FAILED'):
105 break
106
107 return response, resp_json
108
109
Patrik Höglund457c8cf2020-03-13 14:43:21 +0100110# TODO(https://crbug.com/1029452): HACKHACK
Patrik Höglund620bed12020-03-17 09:59:10 +0100111# Remove once we have doubles in the proto and handle -infinity correctly.
Patrik Höglunda89ad612020-03-13 16:08:08 +0100112def _ApplyHacks(dicts):
Mirko Bonadei8cc66952020-10-30 10:13:45 +0100113 for d in dicts:
114 if 'running' in d:
Patrik Höglund457c8cf2020-03-13 14:43:21 +0100115
Mirko Bonadei8cc66952020-10-30 10:13:45 +0100116 def _NoInf(value):
117 if value == float('inf'):
118 return histogram.JS_MAX_VALUE
119 if value == float('-inf'):
120 return -histogram.JS_MAX_VALUE
121 return value
122
123 d['running'] = [_NoInf(value) for value in d['running']]
124
125 return dicts
Patrik Höglund457c8cf2020-03-13 14:43:21 +0100126
127
Patrik Höglund0569a122020-03-13 12:26:42 +0100128def _LoadHistogramSetFromProto(options):
Mirko Bonadei8cc66952020-10-30 10:13:45 +0100129 hs = histogram_set.HistogramSet()
130 with options.input_results_file as f:
131 hs.ImportProto(f.read())
Patrik Höglund0569a122020-03-13 12:26:42 +0100132
Mirko Bonadei8cc66952020-10-30 10:13:45 +0100133 return hs
Patrik Höglund0569a122020-03-13 12:26:42 +0100134
135
136def _AddBuildInfo(histograms, options):
Mirko Bonadei8cc66952020-10-30 10:13:45 +0100137 common_diagnostics = {
138 reserved_infos.MASTERS: options.perf_dashboard_machine_group,
139 reserved_infos.BOTS: options.bot,
140 reserved_infos.POINT_ID: options.commit_position,
141 reserved_infos.BENCHMARKS: options.test_suite,
142 reserved_infos.WEBRTC_REVISIONS: str(options.webrtc_git_hash),
143 reserved_infos.BUILD_URLS: options.build_page_url,
144 }
Patrik Höglund0569a122020-03-13 12:26:42 +0100145
Mirko Bonadei8cc66952020-10-30 10:13:45 +0100146 for k, v in common_diagnostics.items():
147 histograms.AddSharedDiagnosticToAllHistograms(
148 k.name, generic_set.GenericSet([v]))
Patrik Höglund0569a122020-03-13 12:26:42 +0100149
150
151def _DumpOutput(histograms, output_file):
Mirko Bonadei8cc66952020-10-30 10:13:45 +0100152 with output_file:
153 json.dump(_ApplyHacks(histograms.AsDicts()), output_file, indent=4)
Patrik Höglund0569a122020-03-13 12:26:42 +0100154
155
Patrik Höglund0569a122020-03-13 12:26:42 +0100156def UploadToDashboard(options):
Mirko Bonadei8cc66952020-10-30 10:13:45 +0100157 histograms = _LoadHistogramSetFromProto(options)
158 _AddBuildInfo(histograms, options)
Patrik Höglund0569a122020-03-13 12:26:42 +0100159
Mirko Bonadei8cc66952020-10-30 10:13:45 +0100160 if options.output_json_file:
161 _DumpOutput(histograms, options.output_json_file)
Patrik Höglund0569a122020-03-13 12:26:42 +0100162
Mirko Bonadei8cc66952020-10-30 10:13:45 +0100163 oauth_token = _GenerateOauthToken()
Andrey Logvin728b5d02020-11-11 17:16:26 +0000164 response, content = _SendHistogramSet(
165 options.dashboard_url, histograms, oauth_token)
Patrik Höglund0569a122020-03-13 12:26:42 +0100166
Andrey Logvin728b5d02020-11-11 17:16:26 +0000167 upload_token = json.loads(content).get('token')
168 if not options.wait_for_upload or not upload_token:
169 print 'Not waiting for upload status confirmation.'
170 if response.status == 200:
171 print 'Received 200 from dashboard.'
172 return 0
173 else:
174 print('Upload failed with %d: %s\n\n%s' % (response.status,
175 response.reason, content))
176 return 1
177
178 response, resp_json = _WaitForUploadConfirmation(
179 options.dashboard_url,
180 oauth_token,
181 upload_token,
182 datetime.timedelta(seconds=options.wait_timeout_sec),
183 datetime.timedelta(seconds=options.wait_polling_period_sec))
184
185 if response.status != 200 or resp_json['state'] == 'FAILED':
186 print('Upload failed with %d: %s\n\n%s' % (response.status,
187 response.reason,
188 str(resp_json)))
Mirko Bonadei8cc66952020-10-30 10:13:45 +0100189 return 1
Andrey Logvin728b5d02020-11-11 17:16:26 +0000190
191 if resp_json['state'] == 'COMPLETED':
192 print 'Upload completed.'
193 return 0
194
195 print('Upload wasn\'t completed in a given time: %d.', options.wait_timeout)
196 return 1