| #!/usr/bin/env python |
| # Copyright (c) 2019 The WebRTC project authors. All Rights Reserved. |
| # |
| # Use of this source code is governed by a BSD-style license |
| # that can be found in the LICENSE file in the root of the source |
| # tree. An additional intellectual property rights grant can be found |
| # in the file PATENTS. All contributing project authors may |
| # be found in the AUTHORS file in the root of the source tree. |
| |
| """Adds build info to perf results and uploads them. |
| |
| The tests don't know which bot executed the tests or at what revision, so we |
| need to take their output and enrich it with this information. We load the proto |
| from the tests, add the build information as shared diagnostics and then |
| upload it to the dashboard. |
| |
| This script can't be in recipes, because we can't access the catapult APIs from |
| there. It needs to be here source-side. |
| """ |
| |
| import argparse |
| import httplib2 |
| import json |
| import os |
| import sys |
| import subprocess |
| import zlib |
| |
| # 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/, but this approach is allowed by |
| # convention. Fortunately neither catapult nor protobuf require any build rules |
| # to be executed. We can't do this for the histogram proto stub though because |
| # it's generated; see _LoadHistogramSetFromProto. |
| # |
| # It would be better if there was an equivalent to py_binary in GN, but there's |
| # not. |
| SCRIPT_DIR = os.path.dirname(os.path.realpath(__file__)) |
| CHECKOUT_ROOT = os.path.abspath(os.path.join(SCRIPT_DIR, os.pardir, os.pardir)) |
| sys.path.insert(0, os.path.join(CHECKOUT_ROOT, 'third_party', 'catapult', |
| 'tracing')) |
| sys.path.insert(0, os.path.join(CHECKOUT_ROOT, 'third_party', 'protobuf', |
| 'python')) |
| |
| from tracing.value import histogram_set |
| from tracing.value.diagnostics import generic_set |
| from tracing.value.diagnostics import reserved_infos |
| |
| from google.protobuf import json_format |
| |
| |
| def _GenerateOauthToken(): |
| args = ['luci-auth', 'token'] |
| p = subprocess.Popen(args, stdout=subprocess.PIPE, stderr=subprocess.PIPE) |
| if p.wait() == 0: |
| output = p.stdout.read() |
| return output.strip() |
| else: |
| raise RuntimeError( |
| 'Error generating authentication token.\nStdout: %s\nStderr:%s' % |
| (p.stdout.read(), p.stderr.read())) |
| |
| |
| 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. |
| """ |
| headers = {'Authorization': 'Bearer %s' % oauth_token} |
| serialized = json.dumps(histograms.AsDicts(), indent=4) |
| |
| if url.startswith('http://localhost'): |
| # The catapult server turns off compression in developer mode. |
| data = serialized |
| else: |
| data = zlib.compress(serialized) |
| |
| http = httplib2.Http() |
| response, content = http.request(url + '/add_histograms', method='POST', |
| body=data, headers=headers) |
| return response, content |
| |
| |
| def _LoadHistogramSetFromProto(options): |
| # 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) |
| |
| # TODO(https://crbug.com/1029452): Get rid of this import hack once we can |
| # just hand the contents of input_results_file straight to the histogram set. |
| try: |
| import histogram_pb2 |
| except ImportError: |
| raise ImportError('Could not find histogram_pb2. You need to build the ' |
| 'webrtc_dashboard_upload target before invoking this ' |
| 'script. Expected to find ' |
| 'histogram_pb2 in %s.' % histogram_proto_path) |
| |
| with options.input_results_file as f: |
| histograms = histogram_pb2.HistogramSet() |
| histograms.ParseFromString(f.read()) |
| |
| # TODO(https://crbug.com/1029452): Don't convert to JSON as a middle step once |
| # there is a proto de-serializer ready in catapult. |
| json_data = json.loads(json_format.MessageToJson(histograms)) |
| hs = histogram_set.HistogramSet() |
| hs.ImportDicts(json_data) |
| return hs |
| |
| |
| def _AddBuildInfo(histograms, options): |
| common_diagnostics = { |
| reserved_infos.MASTERS: options.perf_dashboard_machine_group, |
| reserved_infos.BOTS: options.bot, |
| reserved_infos.POINT_ID: options.commit_position, |
| reserved_infos.BENCHMARKS: options.test_suite, |
| reserved_infos.WEBRTC_REVISIONS: str(options.webrtc_git_hash), |
| reserved_infos.BUILD_URLS: options.build_page_url, |
| } |
| |
| for k, v in common_diagnostics.items(): |
| histograms.AddSharedDiagnosticToAllHistograms( |
| k.name, generic_set.GenericSet([v])) |
| |
| |
| def _DumpOutput(histograms, output_file): |
| with output_file: |
| json.dump(histograms.AsDicts(), output_file, indent=4) |
| |
| |
| def _CreateParser(): |
| parser = argparse.ArgumentParser() |
| 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, |
| 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, |
| help='webrtc.googlesource.com commit hash.') |
| 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, |
| help='URL to the build page for this build.') |
| parser.add_argument('--dashboard-url', required=True, |
| help='Which dashboard to use.') |
| 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'), |
| help='Where to write the output (for debugging).') |
| parser.add_argument('--outdir', required=True, |
| help='Path to the local out/ dir (usually out/Default)') |
| return parser |
| |
| |
| def main(args): |
| parser = _CreateParser() |
| options = parser.parse_args(args) |
| |
| histograms = _LoadHistogramSetFromProto(options) |
| _AddBuildInfo(histograms, options) |
| |
| if options.output_json_file: |
| _DumpOutput(histograms, options.output_json_file) |
| |
| oauth_token = _GenerateOauthToken() |
| response, content = _SendHistogramSet( |
| options.dashboard_url, histograms, oauth_token) |
| |
| if response.status == 200: |
| return 0 |
| else: |
| print("Upload failed with %d: %s\n\n%s" % (response.status, response.reason, |
| content)) |
| return 1 |
| |
| |
| if __name__ == '__main__': |
| sys.exit(main(sys.argv[1:])) |