blob: 0c6140037396dd888f229c59d2b86149c93fe342 [file] [log] [blame]
Patrik Höglundcb0b8742019-11-18 13:46:38 +01001#!/usr/bin/env python
2# Copyright (c) 2019 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
Patrik Höglundabea2682020-01-17 13:36:29 +010010"""Adds build info to perf results and uploads them.
Patrik Höglundcb0b8742019-11-18 13:46:38 +010011
Patrik Höglundabea2682020-01-17 13:36:29 +010012The tests don't know which bot executed the tests or at what revision, so we
Patrik Höglund83245bd2020-01-30 09:33:57 +010013need to take their output and enrich it with this information. We load the proto
Patrik Höglundabea2682020-01-17 13:36:29 +010014from the tests, add the build information as shared diagnostics and then
15upload it to the dashboard.
Patrik Höglundcb0b8742019-11-18 13:46:38 +010016
17This script can't be in recipes, because we can't access the catapult APIs from
18there. It needs to be here source-side.
Patrik Höglundcb0b8742019-11-18 13:46:38 +010019"""
20
21import argparse
22import httplib2
23import json
Patrik Höglundabea2682020-01-17 13:36:29 +010024import os
Patrik Höglundcb0b8742019-11-18 13:46:38 +010025import sys
26import subprocess
27import zlib
28
Patrik Höglund83245bd2020-01-30 09:33:57 +010029# We just yank the python scripts we require into the PYTHONPATH. You could also
30# imagine a solution where we use for instance protobuf:py_proto_runtime to copy
31# catapult and protobuf code to out/, but this approach is allowed by
32# convention. Fortunately neither catapult nor protobuf require any build rules
33# to be executed. We can't do this for the histogram proto stub though because
34# it's generated; see _LoadHistogramSetFromProto.
35#
36# It would be better if there was an equivalent to py_binary in GN, but there's
37# not.
Patrik Höglundabea2682020-01-17 13:36:29 +010038SCRIPT_DIR = os.path.dirname(os.path.realpath(__file__))
39CHECKOUT_ROOT = os.path.abspath(os.path.join(SCRIPT_DIR, os.pardir, os.pardir))
40sys.path.insert(0, os.path.join(CHECKOUT_ROOT, 'third_party', 'catapult',
41 'tracing'))
Patrik Höglund83245bd2020-01-30 09:33:57 +010042sys.path.insert(0, os.path.join(CHECKOUT_ROOT, 'third_party', 'protobuf',
43 'python'))
Patrik Höglundabea2682020-01-17 13:36:29 +010044
45from tracing.value import histogram_set
46from tracing.value.diagnostics import generic_set
47from tracing.value.diagnostics import reserved_infos
Patrik Höglundcb0b8742019-11-18 13:46:38 +010048
Patrik Höglund8f47b272020-03-11 14:20:14 +010049from google.protobuf import json_format
50
Patrik Höglundcb0b8742019-11-18 13:46:38 +010051
52def _GenerateOauthToken():
53 args = ['luci-auth', 'token']
54 p = subprocess.Popen(args, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
55 if p.wait() == 0:
56 output = p.stdout.read()
57 return output.strip()
58 else:
59 raise RuntimeError(
60 'Error generating authentication token.\nStdout: %s\nStderr:%s' %
61 (p.stdout.read(), p.stderr.read()))
62
63
Patrik Höglundabea2682020-01-17 13:36:29 +010064def _SendHistogramSet(url, histograms, oauth_token):
Patrik Höglundcb0b8742019-11-18 13:46:38 +010065 """Make a HTTP POST with the given JSON to the Performance Dashboard.
66
67 Args:
68 url: URL of Performance Dashboard instance, e.g.
69 "https://chromeperf.appspot.com".
Patrik Höglundabea2682020-01-17 13:36:29 +010070 histograms: a histogram set object that contains the data to be sent.
Patrik Höglundcb0b8742019-11-18 13:46:38 +010071 oauth_token: An oauth token to use for authorization.
72 """
73 headers = {'Authorization': 'Bearer %s' % oauth_token}
Patrik Höglund85037bc2020-03-13 09:56:58 +010074
75 # TODO(https://crbug.com/1029452): HACKHACK
76 # Remove once we set bin bounds correctly in the proto writer.
77 dicts = histograms.AsDicts()
78 for d in dicts:
79 if 'name' in d:
80 d['allBins'] = [[1]]
81
82 serialized = json.dumps(dicts, indent=4)
Patrik Höglundabea2682020-01-17 13:36:29 +010083
84 if url.startswith('http://localhost'):
85 # The catapult server turns off compression in developer mode.
86 data = serialized
87 else:
88 data = zlib.compress(serialized)
Patrik Höglundcb0b8742019-11-18 13:46:38 +010089
Patrik Höglund72524572020-02-14 14:14:56 +010090 print 'Sending %d bytes to %s.' % (len(data), url + '/add_histograms')
91
Patrik Höglundcb0b8742019-11-18 13:46:38 +010092 http = httplib2.Http()
93 response, content = http.request(url + '/add_histograms', method='POST',
94 body=data, headers=headers)
95 return response, content
96
97
Patrik Höglund83245bd2020-01-30 09:33:57 +010098def _LoadHistogramSetFromProto(options):
99 # The webrtc_dashboard_upload gn rule will build the protobuf stub for python,
100 # so put it in the path for this script before we attempt to import it.
101 histogram_proto_path = os.path.join(options.outdir, 'pyproto', 'tracing',
102 'tracing', 'proto')
103 sys.path.insert(0, histogram_proto_path)
Patrik Höglundcb0b8742019-11-18 13:46:38 +0100104
Patrik Höglund8f47b272020-03-11 14:20:14 +0100105 # TODO(https://crbug.com/1029452): Get rid of this import hack once we can
106 # just hand the contents of input_results_file straight to the histogram set.
107 try:
108 import histogram_pb2
109 except ImportError:
110 raise ImportError('Could not find histogram_pb2. You need to build the '
111 'webrtc_dashboard_upload target before invoking this '
112 'script. Expected to find '
113 'histogram_pb2 in %s.' % histogram_proto_path)
Patrik Höglund7427fc62020-03-10 10:42:40 +0100114
Patrik Höglund8f47b272020-03-11 14:20:14 +0100115 with options.input_results_file as f:
116 histograms = histogram_pb2.HistogramSet()
117 histograms.ParseFromString(f.read())
118
119 # TODO(https://crbug.com/1029452): Don't convert to JSON as a middle step once
120 # there is a proto de-serializer ready in catapult.
121 json_data = json.loads(json_format.MessageToJson(histograms))
122 hs = histogram_set.HistogramSet()
123 hs.ImportDicts(json_data)
Patrik Höglund83245bd2020-01-30 09:33:57 +0100124 return hs
Patrik Höglundcb0b8742019-11-18 13:46:38 +0100125
Patrik Höglundabea2682020-01-17 13:36:29 +0100126
127def _AddBuildInfo(histograms, options):
128 common_diagnostics = {
129 reserved_infos.MASTERS: options.perf_dashboard_machine_group,
130 reserved_infos.BOTS: options.bot,
131 reserved_infos.POINT_ID: options.commit_position,
132 reserved_infos.BENCHMARKS: options.test_suite,
133 reserved_infos.WEBRTC_REVISIONS: str(options.webrtc_git_hash),
134 reserved_infos.BUILD_URLS: options.build_page_url,
135 }
136
137 for k, v in common_diagnostics.items():
138 histograms.AddSharedDiagnosticToAllHistograms(
139 k.name, generic_set.GenericSet([v]))
140
141
Patrik Höglund8f47b272020-03-11 14:20:14 +0100142# TODO(https://crbug.com/1029452): Remove this once
143# https://chromium-review.googlesource.com/c/catapult/+/2094312 lands.
144def _HackSummaryOptions(histograms):
145 for histogram in histograms:
146 histogram.CustomizeSummaryOptions({
147 'avg': False,
148 'std': False,
149 'count': False,
150 'sum': False,
151 'min': False,
152 'max': False,
153 'nans': False,
154 })
155
156
Patrik Höglundabea2682020-01-17 13:36:29 +0100157def _DumpOutput(histograms, output_file):
158 with output_file:
159 json.dump(histograms.AsDicts(), output_file, indent=4)
Patrik Höglundcb0b8742019-11-18 13:46:38 +0100160
161
162def _CreateParser():
163 parser = argparse.ArgumentParser()
164 parser.add_argument('--perf-dashboard-machine-group', required=True,
165 help='The "master" the bots are grouped under. This '
166 'string is the group in the the perf dashboard path '
167 'group/bot/perf_id/metric/subtest.')
168 parser.add_argument('--bot', required=True,
169 help='The bot running the test (e.g. '
170 'webrtc-win-large-tests).')
171 parser.add_argument('--test-suite', required=True,
172 help='The key for the test in the dashboard (i.e. what '
173 'you select in the top-level test suite selector in the '
174 'dashboard')
175 parser.add_argument('--webrtc-git-hash', required=True,
176 help='webrtc.googlesource.com commit hash.')
177 parser.add_argument('--commit-position', type=int, required=True,
178 help='Commit pos corresponding to the git hash.')
179 parser.add_argument('--build-page-url', required=True,
180 help='URL to the build page for this build.')
181 parser.add_argument('--dashboard-url', required=True,
182 help='Which dashboard to use.')
Patrik Höglund8f47b272020-03-11 14:20:14 +0100183 parser.add_argument('--input-results-file', type=argparse.FileType(),
Patrik Höglundcb0b8742019-11-18 13:46:38 +0100184 required=True,
185 help='A JSON file with output from WebRTC tests.')
186 parser.add_argument('--output-json-file', type=argparse.FileType('w'),
187 help='Where to write the output (for debugging).')
Patrik Höglund83245bd2020-01-30 09:33:57 +0100188 parser.add_argument('--outdir', required=True,
189 help='Path to the local out/ dir (usually out/Default)')
Patrik Höglundcb0b8742019-11-18 13:46:38 +0100190 return parser
191
192
193def main(args):
194 parser = _CreateParser()
195 options = parser.parse_args(args)
196
Patrik Höglund83245bd2020-01-30 09:33:57 +0100197 histograms = _LoadHistogramSetFromProto(options)
Patrik Höglundabea2682020-01-17 13:36:29 +0100198 _AddBuildInfo(histograms, options)
Patrik Höglund7427fc62020-03-10 10:42:40 +0100199 _HackSummaryOptions(histograms)
Patrik Höglundcb0b8742019-11-18 13:46:38 +0100200
201 if options.output_json_file:
Patrik Höglundabea2682020-01-17 13:36:29 +0100202 _DumpOutput(histograms, options.output_json_file)
Patrik Höglundcb0b8742019-11-18 13:46:38 +0100203
204 oauth_token = _GenerateOauthToken()
Patrik Höglundabea2682020-01-17 13:36:29 +0100205 response, content = _SendHistogramSet(
206 options.dashboard_url, histograms, oauth_token)
Patrik Höglundcb0b8742019-11-18 13:46:38 +0100207
208 if response.status == 200:
Patrik Höglund72524572020-02-14 14:14:56 +0100209 print 'Received 200 from dashboard.'
Patrik Höglundcb0b8742019-11-18 13:46:38 +0100210 return 0
211 else:
Patrik Höglund72524572020-02-14 14:14:56 +0100212 print('Upload failed with %d: %s\n\n%s' % (response.status, response.reason,
Patrik Höglundcb0b8742019-11-18 13:46:38 +0100213 content))
214 return 1
215
216
217if __name__ == '__main__':
218 sys.exit(main(sys.argv[1:]))