| #!/usr/bin/env python |
| # Copyright 2014 The Chromium Authors. All rights reserved. |
| # Use of this source code is governed by a BSD-style license that can be |
| # found in the LICENSE file. |
| |
| """A tool that uploads data to the performance dashboard.""" |
| |
| import argparse |
| import httplib |
| import json |
| import pprint |
| import re |
| import sys |
| import urllib |
| import urllib2 |
| |
| # TODO(yzshen): The following are missing currently: |
| # (1) CL range on the dashboard; |
| # (2) improvement direction on the dashboard; |
| # (3) a link from the build step pointing to the dashboard page. |
| |
| |
| _PERF_LINE_FORMAT = r"""^\s*([^\s/]+) # chart name |
| (/([^\s/]+))? # trace name (optional, separated with |
| # the chart name by a '/') |
| \s+(\S+) # value |
| \s+(\S+) # units |
| \s*$""" |
| |
| _PRODUCTION_SERVER = "https://chromeperf.appspot.com" |
| _TESTING_SERVER = "https://chrome-perf.googleplex.com" |
| |
| |
| def UploadPerfData(master_name, perf_id, test_name, builder_name, build_number, |
| revision, perf_data, point_id, dry_run=False, |
| testing_dashboard=True): |
| """Uploads perf data. |
| |
| Args: |
| Please see the help for command-line args. |
| |
| Returns: |
| A boolean value indicating whether the operation succeeded or not. |
| """ |
| |
| def _ConvertToUploadFormat(): |
| """Converts perf data to the format that the server understands. |
| |
| Returns: |
| A dictionary that (after being converted to JSON) conforms to the server |
| format. |
| """ |
| charts = {} |
| line_format = re.compile(_PERF_LINE_FORMAT, re.VERBOSE) |
| for line in perf_data: |
| match = re.match(line_format, line) |
| assert match, "Unable to parse the following input: %s" % line |
| |
| chart_name = match.group(1) |
| trace_name = match.group(3) if match.group(3) else "summary" |
| |
| if chart_name not in charts: |
| charts[chart_name] = {} |
| charts[chart_name][trace_name] = { |
| "type": "scalar", |
| "value": float(match.group(4)), |
| "units": match.group(5) |
| } |
| |
| return { |
| "master": master_name, |
| "bot": perf_id, |
| "masterid": master_name, |
| "buildername": builder_name, |
| "buildnumber": build_number, |
| "versions": { |
| "mojo": revision |
| }, |
| "point_id": point_id, |
| "supplemental": {}, |
| "chart_data": { |
| "format_version": "1.0", |
| "benchmark_name": test_name, |
| "charts": charts |
| } |
| } |
| |
| class _UploadException(Exception): |
| pass |
| |
| def _Upload(server_url, json_data): |
| """Make an HTTP POST with the given data to the performance dashboard. |
| |
| Args: |
| server_url: URL of the performance dashboard instance. |
| json_data: JSON string that contains the data to be sent. |
| |
| Raises: |
| _UploadException: An error occurred during uploading. |
| """ |
| # When data is provided to urllib2.Request, a POST is sent instead of GET. |
| # The data must be in the application/x-www-form-urlencoded format. |
| data = urllib.urlencode({"data": json_data}) |
| req = urllib2.Request("%s/add_point" % server_url, data) |
| try: |
| urllib2.urlopen(req) |
| except urllib2.HTTPError as e: |
| raise _UploadException("HTTPError: %d. Response: %s\n" |
| "JSON: %s\n" % (e.code, e.read(), json_data)) |
| except urllib2.URLError as e: |
| raise _UploadException("URLError: %s for JSON %s\n" % |
| (str(e.reason), json_data)) |
| except httplib.HTTPException as e: |
| raise _UploadException("HTTPException for JSON %s\n" % json_data) |
| |
| formatted_data = _ConvertToUploadFormat() |
| |
| server_url = _TESTING_SERVER if testing_dashboard else _PRODUCTION_SERVER |
| |
| if dry_run: |
| print "Won't upload because --dry-run is specified." |
| print "Server: %s" % server_url |
| print "Data:" |
| pprint.pprint(formatted_data) |
| else: |
| print "Uploading data to %s ..." % server_url |
| try: |
| _Upload(server_url, json.dumps(formatted_data)) |
| except _UploadException as e: |
| print e |
| return False |
| |
| print "Done." |
| |
| dashboard_params = urllib.urlencode({ |
| "masters": master_name, |
| "bots": perf_id, |
| "tests": test_name, |
| "rev": point_id |
| }) |
| print "Results Dashboard: %s/report?%s" % (server_url, dashboard_params) |
| |
| return True |
| |
| |
| def main(): |
| parser = argparse.ArgumentParser( |
| description="A tool that uploads data to the performance dashboard.") |
| |
| parser.add_argument( |
| "--master-name", required=True, |
| help="Buildbot master name, used to construct link to buildbot log by " |
| "the dashboard, and also as the top-level category for the data.") |
| parser.add_argument( |
| "--perf-id", required=True, |
| help="Used as the second-level category for the data, usually the " |
| "platform type.") |
| parser.add_argument( |
| "--test-name", required=True, |
| help="Name of the test that the perf data was generated from.") |
| parser.add_argument( |
| "--builder-name", required=True, |
| help="Buildbot builder name, used to construct link to buildbot log by " |
| "the dashboard.") |
| parser.add_argument( |
| "--build-number", required=True, type=int, |
| help="Build number, used to construct link to buildbot log by the " |
| "dashboard.") |
| parser.add_argument( |
| "--revision", required=True, help="The mojo git commit hash.") |
| parser.add_argument( |
| "--perf-data", required=True, metavar="foo_perf.log", |
| type=argparse.FileType("r"), |
| help="A text file containing the perf data. Each line is a data point in " |
| "the following format: chart_name[/trace_name] value units") |
| parser.add_argument( |
| "--point-id", required=True, type=int, |
| help="The x coordinate for the data points.") |
| parser.add_argument( |
| "--dry-run", action="store_true", |
| help="Display the server URL and the data to upload, but not actually " |
| "upload the data.") |
| server_group = parser.add_mutually_exclusive_group() |
| server_group.add_argument( |
| "--testing-dashboard", action="store_true", default=True, |
| help="Upload the data to the testing dashboard (default).") |
| server_group.add_argument( |
| "--production-dashboard", dest="testing_dashboard", action="store_false", |
| default=False, help="Upload the data to the production dashboard.") |
| args = parser.parse_args() |
| |
| result = UploadPerfData(args.master_name, args.perf_id, args.test_name, |
| args.builder_name, args.build_number, args.revision, |
| args.perf_data, args.point_id, args.dry_run, |
| args.testing_dashboard) |
| return 0 if result else 1 |
| |
| |
| if __name__ == '__main__': |
| sys.exit(main()) |