| OLD | NEW |
| 1 # Copyright 2015 The Chromium Authors. All rights reserved. | 1 # Copyright 2015 The Chromium Authors. All rights reserved. |
| 2 # Use of this source code is governed by a BSD-style license that can be | 2 # Use of this source code is governed by a BSD-style license that can be |
| 3 # found in the LICENSE file. | 3 # found in the LICENSE file. |
| 4 | 4 |
| 5 # Disable the line-too-long warning. | 5 # Disable the line-too-long warning. |
| 6 # pylint: disable=C0301 | 6 # pylint: disable=C0301 |
| 7 """This module implements the Chromium Performance Dashboard JSON v1.0 data | 7 """This module implements the Chromium Performance Dashboard JSON v1.0 data |
| 8 format. | 8 format. |
| 9 | 9 |
| 10 See http://www.chromium.org/developers/speed-infra/performance-dashboard/sending
-data-to-the-performance-dashboard. | 10 See http://www.chromium.org/developers/speed-infra/performance-dashboard/sending
-data-to-the-performance-dashboard. |
| 11 """ | 11 """ |
| 12 | 12 |
| 13 from collections import defaultdict |
| 14 import httplib |
| 13 import json | 15 import json |
| 14 from collections import defaultdict | 16 import pprint |
| 17 import urllib |
| 18 import urllib2 |
| 19 |
| 20 |
| 21 _LOCAL_SERVER = "http://127.0.0.1:8080" |
| 15 | 22 |
| 16 | 23 |
| 17 class ChartDataRecorder(object): | 24 class ChartDataRecorder(object): |
| 18 """Allows one to record measurement values one by one and then generate the | 25 """Allows one to record measurement values one by one and then generate the |
| 19 JSON string that represents them in the 'chart_data' format expected by the | 26 JSON string that represents them in the 'chart_data' format expected by the |
| 20 performance dashboard. | 27 performance dashboard. |
| 21 """ | 28 """ |
| 22 | 29 |
| 23 def __init__(self, benchmark_name): | 30 def __init__(self, benchmark_name): |
| 24 self.charts = defaultdict(list) | 31 self.charts = defaultdict(list) |
| 25 self.benchmark_name = benchmark_name | 32 self.benchmark_name = benchmark_name |
| 26 | 33 |
| 27 def record_scalar(self, chart_name, value_name, units, value): | 34 def record_scalar(self, chart_name, value_name, units, value): |
| 28 """Records a single measurement value of a scalar type.""" | 35 """Records a single measurement value of a scalar type.""" |
| 29 self.charts[chart_name].append({ | 36 self.charts[chart_name].append({ |
| 30 'type': 'scalar', | 37 'type': 'scalar', |
| 31 'name': value_name, | 38 'name': value_name, |
| 32 'units': units, | 39 'units': units, |
| 33 'value': value}) | 40 'value': value}) |
| 34 | 41 |
| 35 def get_json(self): | 42 def get_json(self): |
| 36 """Returns the JSON string representing the recorded chart data, wrapping | 43 """Returns the JSON string representing the recorded chart data, wrapping |
| 37 it with the required meta data.""" | 44 it with the required meta data.""" |
| 38 chart_data = { | 45 chart_data = { |
| 39 'format_version': '1.0', | 46 'format_version': '1.0', |
| 40 'benchmark_name': self.benchmark_name, | 47 'benchmark_name': self.benchmark_name, |
| 41 'charts': self.charts | 48 'charts': self.charts |
| 42 } | 49 } |
| 43 return json.dumps(chart_data) | 50 return json.dumps(chart_data) |
| 51 |
| 52 |
| 53 def add_argparse_server_arguments(parser): |
| 54 """Adds argparse arguments needed to upload the chart data to a performance |
| 55 dashboard to the given parser. |
| 56 """ |
| 57 dashboard_group = parser.add_argument_group('Performance dashboard server', |
| 58 'These arguments allow to specify the performance dashboard server ' |
| 59 'to upload the results to.') |
| 60 |
| 61 dashboard_group.add_argument( |
| 62 '--server-url', |
| 63 help='Url of the server instance to upload the results to. By default a ' |
| 64 'local instance is assumed to be running on port 8080.') |
| 65 dashboard_group.add_argument( |
| 66 '--master-name', |
| 67 help='Buildbot master name, used to construct link to buildbot log by ' |
| 68 'the dashboard, and also as the top-level category for the data.') |
| 69 dashboard_group.add_argument( |
| 70 '--perf-id', |
| 71 help='Used as the second-level category for the data, usually the ' |
| 72 'platform type.') |
| 73 dashboard_group.add_argument( |
| 74 '--test-name', |
| 75 help='Name of the test that the perf data was generated from.') |
| 76 dashboard_group.add_argument( |
| 77 '--builder-name', |
| 78 help='Buildbot builder name, used to construct link to buildbot log by ' |
| 79 'the dashboard.') |
| 80 dashboard_group.add_argument( |
| 81 '--build-number', type=int, |
| 82 help='Build number, used to construct link to buildbot log by the ' |
| 83 'dashboard.') |
| 84 dashboard_group.add_argument( |
| 85 '--dry-run', action='store_true', default=False, |
| 86 help='Display the server URL and the data to upload, but do not actually ' |
| 87 'upload the data.') |
| 88 |
| 89 |
| 90 def upload_chart_data(master_name, perf_id, test_name, builder_name, |
| 91 build_number, revision, chart_data, point_id, |
| 92 server_url=None, dry_run=False): |
| 93 """Uploads the provided chart data to an instance of performance dashboard. |
| 94 See the argparse help above for description of the arguments. |
| 95 |
| 96 |
| 97 Returns: |
| 98 A boolean value indicating whether the operation succeeded or not. |
| 99 """ |
| 100 class _UploadException(Exception): |
| 101 pass |
| 102 |
| 103 def _upload(server_url, json_data): |
| 104 """Make an HTTP POST with the given data to the performance dashboard. |
| 105 |
| 106 Args: |
| 107 server_url: URL of the performance dashboard instance. |
| 108 json_data: JSON string that contains the data to be sent. |
| 109 |
| 110 Raises: |
| 111 _UploadException: An error occurred during uploading. |
| 112 """ |
| 113 # When data is provided to urllib2.Request, a POST is sent instead of GET. |
| 114 # The data must be in the application/x-www-form-urlencoded format. |
| 115 data = urllib.urlencode({"data": json_data}) |
| 116 req = urllib2.Request("%s/add_point" % server_url, data) |
| 117 try: |
| 118 urllib2.urlopen(req) |
| 119 except urllib2.HTTPError as e: |
| 120 raise _UploadException("HTTPError: %d. Response: %s\n" |
| 121 "JSON: %s\n" % (e.code, e.read(), json_data)) |
| 122 except urllib2.URLError as e: |
| 123 raise _UploadException("URLError: %s for JSON %s\n" % |
| 124 (str(e.reason), json_data)) |
| 125 except httplib.HTTPException as e: |
| 126 raise _UploadException("HTTPException for JSON %s\n" % json_data) |
| 127 |
| 128 # Wrap the |chart_data| with meta data as required by the spec. |
| 129 formatted_data = { |
| 130 "master": master_name, |
| 131 "bot": perf_id, |
| 132 "masterid": master_name, |
| 133 "buildername": builder_name, |
| 134 "buildnumber": build_number, |
| 135 "versions": { |
| 136 "mojo": revision |
| 137 }, |
| 138 "point_id": point_id, |
| 139 "supplemental": {}, |
| 140 "chart_data": chart_data |
| 141 } |
| 142 |
| 143 upload_url = server_url if server_url else _LOCAL_SERVER |
| 144 |
| 145 if dry_run: |
| 146 print "Won't upload because --dry-run is specified." |
| 147 print "Server: %s" % upload_url |
| 148 print "Data:" |
| 149 pprint.pprint(formatted_data) |
| 150 else: |
| 151 print "Uploading data to %s ..." % upload_url |
| 152 try: |
| 153 _upload(upload_url, json.dumps(formatted_data)) |
| 154 except _UploadException as e: |
| 155 print e |
| 156 return False |
| 157 |
| 158 print "Done." |
| 159 |
| 160 dashboard_params = urllib.urlencode({ |
| 161 "masters": master_name, |
| 162 "bots": perf_id, |
| 163 "tests": test_name, |
| 164 "rev": point_id |
| 165 }) |
| 166 print "Results Dashboard: %s/report?%s" % (upload_url, dashboard_params) |
| 167 |
| 168 return True |
| OLD | NEW |