OLD | NEW |
| (Empty) |
1 #!/usr/bin/env python | |
2 # Copyright 2014 The Chromium Authors. All rights reserved. | |
3 # Use of this source code is governed by a BSD-style license that can be | |
4 # found in the LICENSE file. | |
5 | |
6 """A tool that uploads data to the performance dashboard.""" | |
7 | |
8 import argparse | |
9 import httplib | |
10 import json | |
11 import pprint | |
12 import re | |
13 import sys | |
14 import urllib | |
15 import urllib2 | |
16 | |
17 # TODO(yzshen): The following are missing currently: | |
18 # (1) CL range on the dashboard; | |
19 # (2) improvement direction on the dashboard; | |
20 # (3) a link from the build step pointing to the dashboard page. | |
21 | |
22 | |
23 _PERF_LINE_FORMAT = r"""^\s*([^\s/]+) # chart name | |
24 (/([^\s/]+))? # trace name (optional, separated with | |
25 # the chart name by a '/') | |
26 \s+(\S+) # value | |
27 \s+(\S+) # units | |
28 \s*$""" | |
29 | |
30 _PRODUCTION_SERVER = "https://chromeperf.appspot.com" | |
31 _TESTING_SERVER = "https://chrome-perf.googleplex.com" | |
32 | |
33 | |
34 def UploadPerfData(master_name, perf_id, test_name, builder_name, build_number, | |
35 revision, perf_data, point_id, dry_run=False, | |
36 testing_dashboard=True): | |
37 """Uploads perf data. | |
38 | |
39 Args: | |
40 Please see the help for command-line args. | |
41 | |
42 Returns: | |
43 A boolean value indicating whether the operation succeeded or not. | |
44 """ | |
45 | |
46 def _ConvertToUploadFormat(): | |
47 """Converts perf data to the format that the server understands. | |
48 | |
49 Returns: | |
50 A dictionary that (after being converted to JSON) conforms to the server | |
51 format. | |
52 """ | |
53 charts = {} | |
54 line_format = re.compile(_PERF_LINE_FORMAT, re.VERBOSE) | |
55 for line in perf_data: | |
56 match = re.match(line_format, line) | |
57 assert match, "Unable to parse the following input: %s" % line | |
58 | |
59 chart_name = match.group(1) | |
60 trace_name = match.group(3) if match.group(3) else "summary" | |
61 | |
62 if chart_name not in charts: | |
63 charts[chart_name] = {} | |
64 charts[chart_name][trace_name] = { | |
65 "type": "scalar", | |
66 "value": float(match.group(4)), | |
67 "units": match.group(5) | |
68 } | |
69 | |
70 return { | |
71 "master": master_name, | |
72 "bot": perf_id, | |
73 "masterid": master_name, | |
74 "buildername": builder_name, | |
75 "buildnumber": build_number, | |
76 "versions": { | |
77 "mojo": revision | |
78 }, | |
79 "point_id": point_id, | |
80 "supplemental": {}, | |
81 "chart_data": { | |
82 "format_version": "1.0", | |
83 "benchmark_name": test_name, | |
84 "charts": charts | |
85 } | |
86 } | |
87 | |
88 class _UploadException(Exception): | |
89 pass | |
90 | |
91 def _Upload(server_url, json_data): | |
92 """Make an HTTP POST with the given data to the performance dashboard. | |
93 | |
94 Args: | |
95 server_url: URL of the performance dashboard instance. | |
96 json_data: JSON string that contains the data to be sent. | |
97 | |
98 Raises: | |
99 _UploadException: An error occurred during uploading. | |
100 """ | |
101 # When data is provided to urllib2.Request, a POST is sent instead of GET. | |
102 # The data must be in the application/x-www-form-urlencoded format. | |
103 data = urllib.urlencode({"data": json_data}) | |
104 req = urllib2.Request("%s/add_point" % server_url, data) | |
105 try: | |
106 urllib2.urlopen(req) | |
107 except urllib2.HTTPError as e: | |
108 raise _UploadException("HTTPError: %d. Response: %s\n" | |
109 "JSON: %s\n" % (e.code, e.read(), json_data)) | |
110 except urllib2.URLError as e: | |
111 raise _UploadException("URLError: %s for JSON %s\n" % | |
112 (str(e.reason), json_data)) | |
113 except httplib.HTTPException as e: | |
114 raise _UploadException("HTTPException for JSON %s\n" % json_data) | |
115 | |
116 formatted_data = _ConvertToUploadFormat() | |
117 | |
118 server_url = _TESTING_SERVER if testing_dashboard else _PRODUCTION_SERVER | |
119 | |
120 if dry_run: | |
121 print "Won't upload because --dry-run is specified." | |
122 print "Server: %s" % server_url | |
123 print "Data:" | |
124 pprint.pprint(formatted_data) | |
125 else: | |
126 print "Uploading data to %s ..." % server_url | |
127 try: | |
128 _Upload(server_url, json.dumps(formatted_data)) | |
129 except _UploadException as e: | |
130 print e | |
131 return False | |
132 print "Done successfully." | |
133 | |
134 return True | |
135 | |
136 | |
137 def main(): | |
138 parser = argparse.ArgumentParser( | |
139 description="A tool that uploads data to the performance dashboard.") | |
140 | |
141 parser.add_argument( | |
142 "--master-name", required=True, | |
143 help="Buildbot master name, used to construct link to buildbot log by " | |
144 "the dashboard, and also as the top-level category for the data.") | |
145 parser.add_argument( | |
146 "--perf-id", required=True, | |
147 help="Used as the second-level category for the data, usually the " | |
148 "platform type.") | |
149 parser.add_argument( | |
150 "--test-name", required=True, | |
151 help="Name of the test that the perf data was generated from.") | |
152 parser.add_argument( | |
153 "--builder-name", required=True, | |
154 help="Buildbot builder name, used to construct link to buildbot log by " | |
155 "the dashboard.") | |
156 parser.add_argument( | |
157 "--build-number", required=True, type=int, | |
158 help="Build number, used to construct link to buildbot log by the " | |
159 "dashboard.") | |
160 parser.add_argument( | |
161 "--revision", required=True, help="The mojo git commit hash.") | |
162 parser.add_argument( | |
163 "--perf-data", required=True, metavar="foo_perf.log", | |
164 type=argparse.FileType("r"), | |
165 help="A text file containing the perf data. Each line is a data point in " | |
166 "the following format: chart_name[/trace_name] value units") | |
167 parser.add_argument( | |
168 "--point-id", required=True, type=int, | |
169 help="The x coordinate for the data points.") | |
170 parser.add_argument( | |
171 "--dry-run", action="store_true", | |
172 help="Display the server URL and the data to upload, but not actually " | |
173 "upload the data.") | |
174 server_group = parser.add_mutually_exclusive_group() | |
175 server_group.add_argument( | |
176 "--testing-dashboard", action="store_true", default=True, | |
177 help="Upload the data to the testing dashboard (default).") | |
178 server_group.add_argument( | |
179 "--production-dashboard", dest="testing_dashboard", action="store_false", | |
180 default=False, help="Upload the data to the production dashboard.") | |
181 args = parser.parse_args() | |
182 | |
183 result = UploadPerfData(args.master_name, args.perf_id, args.test_name, | |
184 args.builder_name, args.build_number, args.revision, | |
185 args.perf_data, args.point_id, args.dry_run, | |
186 args.testing_dashboard) | |
187 return 0 if result else 1 | |
188 | |
189 | |
190 if __name__ == '__main__': | |
191 sys.exit(main()) | |
OLD | NEW |