OLD | NEW |
---|---|
(Empty) | |
1 #!/usr/bin/python | |
2 # Copyright (c) 2010 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 | |
7 import math | |
8 import optparse | |
9 import re | |
10 import simplejson | |
11 import subprocess | |
12 import sys | |
13 import time | |
14 import urllib2 | |
15 | |
16 | |
17 __version__ = '1.0' | |
18 DEFAULT_EXPECTATIONS_FILE = 'perf_expectations.json' | |
19 DEFAULT_VARIANCE = 0.05 | |
20 USAGE = "" | |
21 | |
22 | |
23 def ReadFile(filename): | |
24 try: | |
25 file = open(filename, 'r') | |
26 except IOError, e: | |
27 print >> sys.stderr, ("I/O Error reading file %s(%s): %s" % | |
28 (filename, e.errno, e.strerror)) | |
nsylvain
2010/12/07 02:36:33
i thin it might need more spacing here
| |
29 raise e | |
30 if not file: | |
nsylvain
2010/12/07 02:36:33
can this happen?
| |
31 return None | |
32 contents = file.read() | |
33 file.close() | |
34 return contents | |
35 | |
36 | |
37 def ConvertJsonIntoDict(string): | |
38 """Read a JSON string and convert its contents into a Python datatype.""" | |
39 if len(string) == 0: | |
40 print >> sys.stderr, ("Error could not parse empty string") | |
41 raise Exception("JSON data missing") | |
42 | |
43 try: | |
44 json = simplejson.loads(string) | |
45 except ValueError, e: | |
46 print >> sys.stderr, ("Error parsing string: '%s'" % string) | |
nsylvain
2010/12/07 02:36:33
quotes
| |
47 raise e | |
48 return json | |
49 | |
50 | |
51 # Floating point representation of last time we fetched a URL. | |
52 last_fetched_at = None | |
53 def FetchUrlContents(url): | |
54 global last_fetched_at | |
55 if last_fetched_at and ((time.time() - last_fetched_at) <= 0.5): | |
56 # Sleep for half a second to avoid overloading the server. | |
57 time.sleep(0.5) | |
58 try: | |
59 last_fetched_at = time.time() | |
60 connection = urllib2.urlopen(url) | |
61 except urllib2.HTTPError, e: | |
62 if e.code == 404: | |
63 return None | |
64 raise e | |
65 text = connection.read().strip() | |
66 connection.close() | |
67 return text | |
68 | |
69 | |
70 def WriteJson(filename, data, keys): | |
71 """Write a list of |keys| in |data| to the file specified in |filename|.""" | |
72 try: | |
73 file = open(filename, 'w') | |
74 except IOError, e: | |
75 print >> sys.stderr, ("I/O Error writing file %s(%s): %s" % | |
76 (filename, e.errno, e.strerror)) | |
77 return False | |
78 jsondata = [] | |
79 for key in keys: | |
80 rowdata = [] | |
81 for subkey in ['reva', 'revb', 'improve', 'regress']: | |
82 if subkey in data[key]: | |
83 rowdata.append('"%s": %s' % (subkey, data[key][subkey])) | |
84 jsondata.append('"%s": {%s}' % (key, ', '.join(rowdata))) | |
85 jsondata.append('"load": true') | |
86 json = '{%s\n}' % ',\n '.join(jsondata) | |
87 file.write(json + "\n") | |
88 file.close() | |
89 return True | |
90 | |
91 | |
92 def Main(args): | |
93 parser = optparse.OptionParser(usage=USAGE, version=__version__) | |
94 options, args = parser.parse_args(args) | |
95 | |
96 # Get the list of summaries for a test. | |
97 base_url = 'http://build.chromium.org/f/chromium/perf' | |
98 perf = ConvertJsonIntoDict(ReadFile(DEFAULT_EXPECTATIONS_FILE)) | |
99 | |
100 # Fetch graphs.dat for this combination. | |
101 perfkeys = perf.keys() | |
102 # In perf_expectations.json, ignore the 'load' key. | |
103 perfkeys.remove("load") | |
104 perfkeys.sort() | |
105 | |
106 write_new_expectations = False | |
107 for key in perfkeys: | |
108 value = perf[key] | |
109 variance = DEFAULT_VARIANCE | |
110 | |
111 # Skip expectations that are missing a reva or revb. We can't generate | |
112 # expectations for those. | |
113 if not(value.has_key('reva') and value.has_key('revb')): | |
114 print '%s (skipping, missing revision range)' % key | |
115 continue | |
116 revb = int(value['revb']) | |
117 reva = int(value['reva']) | |
118 | |
119 # Ensure that reva is less than revb. | |
120 if reva > revb: | |
121 temp = reva | |
122 reva = revb | |
123 revb = temp | |
124 | |
125 # Get the system/test/graph/tracename and reftracename for the current key. | |
126 matchData = re.match(r'^([^/]+)\/([^/]+)\/([^/]+)\/([^/]+)$', key) | |
127 if not matchData: | |
128 print '%s (skipping, cannot parse key)' % key | |
129 continue | |
130 system = matchData.group(1) | |
131 test = matchData.group(2) | |
132 graph = matchData.group(3) | |
133 tracename = matchData.group(4) | |
134 reftracename = tracename + '_ref' | |
135 | |
136 # Create the summary_url and get the json data for that URL. | |
137 # FetchUrlContents() may sleep to avoid overloading the server with | |
138 # requests. | |
139 summary_url = '%s/%s/%s/%s-summary.dat' % (base_url, system, test, graph) | |
140 summaryjson = FetchUrlContents(summary_url) | |
141 if not summaryjson: | |
142 print '%s (skipping, missing json data)' % key | |
143 continue | |
144 | |
145 summarylist = summaryjson.split('\n') | |
146 trace_values = {} | |
147 for trace in [tracename, reftracename]: | |
148 trace_values.setdefault(trace, {}) | |
149 | |
150 # Find the high and low values for each of the traces. | |
151 scanning = False | |
152 printed_error = False | |
153 for line in summarylist: | |
154 json = ConvertJsonIntoDict(line) | |
155 if int(json['rev']) <= revb: | |
156 scanning = True | |
157 if int(json['rev']) < reva: | |
158 break | |
159 | |
160 # We found the upper revision in the range. Scan for trace data until we | |
161 # find the lower revision in the range. | |
162 if scanning: | |
163 for trace in [tracename, reftracename]: | |
164 if trace not in json['traces']: | |
165 if not printed_error: | |
166 print '%s (error)' % key | |
167 printed_error = True | |
168 print ' trace %s missing' % trace | |
169 continue | |
170 if type(json['traces'][trace]) != type([]): | |
171 if not printed_error: | |
172 print '%s (error)' % key | |
173 printed_error = True | |
174 print ' trace %s format not recognized' % trace | |
175 continue | |
176 try: | |
177 tracevalue = float(json['traces'][trace][0]) | |
178 except ValueError: | |
179 if not printed_error: | |
180 print '%s (error)' % key | |
181 printed_error = True | |
182 print ' trace %s value error: %s' % ( | |
183 trace, str(json['traces'][trace][0])) | |
184 continue | |
185 | |
186 for bound in ['high', 'low']: | |
187 trace_values[trace].setdefault(bound, tracevalue) | |
188 | |
189 trace_values[trace]['high'] = max(trace_values[trace]['high'], | |
190 tracevalue) | |
191 trace_values[trace]['low'] = min(trace_values[trace]['low'], | |
192 tracevalue) | |
193 | |
194 if 'high' not in trace_values[tracename]: | |
195 print '%s (skipping, no suitable traces matched)' % key | |
196 continue | |
197 | |
198 # Calculate assuming high deltas are regressions and low deltas are | |
199 # improvements. | |
200 regress = (float(trace_values[tracename]['high']) - | |
201 float(trace_values[reftracename]['low'])) | |
202 improve = (float(trace_values[tracename]['low']) - | |
203 float(trace_values[reftracename]['high'])) | |
204 # If the existing values assume regressions are low deltas relative to | |
205 # improvements, swap our regress and improve. This value must be a | |
206 # scores-like result. | |
207 if perf[key]['regress'] < perf[key]['improve']: | |
208 temp = regress | |
209 regress = improve | |
210 improve = temp | |
211 if regress < improve: | |
212 regress = int(math.floor(regress - abs(regress*variance))) | |
213 improve = int(math.ceil(improve + abs(improve*variance))) | |
214 else: | |
215 improve = int(math.floor(improve - abs(improve*variance))) | |
216 regress = int(math.ceil(regress + abs(regress*variance))) | |
217 | |
218 if (perf[key]['regress'] == regress and perf[key]['improve'] == improve): | |
219 print '%s (no change)' % key | |
220 continue | |
221 | |
222 write_new_expectations = True | |
223 print key | |
224 print ' before = %s' % perf[key] | |
225 print ' traces = %s' % trace_values | |
226 perf[key]['regress'] = regress | |
227 perf[key]['improve'] = improve | |
228 print ' after = %s' % perf[key] | |
229 | |
230 if write_new_expectations: | |
231 print 'writing expectations... ', | |
232 WriteJson(DEFAULT_EXPECTATIONS_FILE, perf, perfkeys) | |
233 print 'done' | |
234 else: | |
235 print 'no updates made' | |
236 | |
237 | |
238 if __name__ == '__main__': | |
239 sys.exit(Main(sys.argv)) | |
OLD | NEW |