blob: 4c66aae26b99b9bd87676a02a97ff2ec47eba527 [file] [log] [blame]
mbligh4bfe3612009-02-26 00:38:13 +00001#!/usr/bin/python
2# tko/nightly.py code shared by various *_nightly.cgi scripts
3
4import cgi, cgitb
mbligh5b380be2009-06-22 19:02:09 +00005import os, sys
6import common
mbligh4bfe3612009-02-26 00:38:13 +00007from autotest_lib.tko import db, plotgraph, perf
mbligh5b380be2009-06-22 19:02:09 +00008from autotest_lib.client.common_lib import kernel_versions
mbligh4bfe3612009-02-26 00:38:13 +00009
10
mbligh5b380be2009-06-22 19:02:09 +000011def nightly_views(suite_notes, kernel_legend, benchmarks,
12 released_kernel_series,
13 nightly_kernel_series,
14 smp = 'smp',
15 test_group='Kernel_Qual_Containers',
16 tko_mysql_server='autotest', results_server='autotest',
17 max_rel_kernels=8, max_dev_kernels=5):
mbligh4bfe3612009-02-26 00:38:13 +000018
19 test_runs = {} # kernel --> (platform --> list of test runs)
mbligh5b380be2009-06-22 19:02:09 +000020 job_table = {} # kernel id --> list of job idxs
21 kernel_dates = {} # Kernel id --> date of nightly test
mbligh4bfe3612009-02-26 00:38:13 +000022
23
24 def add_kernel_jobs(label_pattern):
25 cmd = "select job_idx from jobs where label like '%s'" % label_pattern
26 nrows = perf.db_cur.execute(cmd)
27 return [row[0] for row in perf.db_cur.fetchall()]
28
29
30 def is_filtered_platform(platform):
31 for p in platforms_filter.split(','):
32 if platform.startswith(p):
33 return True
34 return False
35
36
37 def collect_testruns(jobs, regressed_platforms, test):
38 # get test_runs run #s for 1 test on 1 kernel and some platforms
39 # TODO: Is jobs list short enough to use directly in 1 sql cmd?
40 runs = {} # platform --> list of test runs
41 for jobx in jobs:
42 cmd = ( "select test_idx, machine_idx from tests"
43 " where job_idx = %s and test = %s" )
44 args = [jobx, test]
45 nrows = perf.db_cur.execute(cmd, args)
46 for testrunx, machx in perf.db_cur.fetchall():
47 platform, host = perf.machine_idx_to_platform_host(machx)
48 if by_hosts:
49 platform += '.'+host
50 if ( is_filtered_platform(platform) and
51 (regressed_platforms is None
52 or platform in regressed_platforms) ):
53 runs.setdefault(platform, []).append(testrunx)
54 return runs
55
56
57 def collect_all_testruns(trimmed_kernels, regressed_platforms, test):
58 # get test_runs run #s for 1 test on some kernels and platforms
59 for kernel in trimmed_kernels:
60 runs = collect_testruns(job_table[kernel], regressed_platforms, test)
61 if runs:
62 test_runs[kernel] = runs
63
64
65 def collect_raw_scores(runs, metric):
66 # get unscaled scores of test runs for 1 test on certain jobs
67 # arrange them by platform type
68 platform_scores = {} # platform --> list of perf scores
69 for platform in runs:
70 vals = perf.get_metric_at_point(runs[platform], metric)
71 if vals:
72 platform_scores[platform] = vals
73 return platform_scores
74
75
76 def collect_scaled_scores(metric):
77 # get scores of test runs for 1 test on some kernels and platforms
78 # optionally make relative to first kernel on that platform
79 # arrange by plotline (ie platform) for gnuplot
80 plot_data = {} # platform --> (kernel --> list of perf scores)
81 baseline = {}
82 for kernel in sorted(test_runs.keys()):
83 for platform in test_runs[kernel]:
84 vals = perf.get_metric_at_point(test_runs[kernel][platform], metric)
85 if vals:
86 if relative:
87 if platform not in baseline:
88 baseline[platform], std = plotgraph.avg_dev(vals)
89 vals = [v/baseline[platform] for v in vals]
90 pdp = plot_data.setdefault(platform, {})
91 pdp.setdefault(kernel, []).extend(vals)
92 return plot_data
93
94
95 def find_regressions(kernels, test, metric):
96 # A test is regressed on some platform if its latest results are
97 # definitely lower than on the reference kernel.
98 # Runs for the latest kernel may be underway and incomplete.
99 # In that case, selectively use next-latest kernel.
100 if not regress:
101 return None
102 ref = kernels[0]
103 latest = kernels[-1]
104 prev = kernels[-2:][0]
105 scores = {} # kernel --> (platform --> list of perf scores)
106 for k in [ref, prev, latest]:
107 runs = collect_testruns(job_table[k], None, test)
108 scores[k] = collect_raw_scores(runs, metric)
109 regressed_platforms = []
110 for platform in scores[ref]:
111 k = latest
112 if platform not in scores[k]:
113 k = prev
114 if platform not in scores[k]:
115 continue # perhaps due to decay of test machines
116 ref_avg, ref_std = plotgraph.avg_dev(scores[ref][platform])
117 avg, std = plotgraph.avg_dev(scores[ k ][platform])
118 if avg+std < ref_avg-ref_std:
119 regressed_platforms.append(platform)
120 return sorted(regressed_platforms)
121
122
123 def select_dev_kernels():
mbligh5b380be2009-06-22 19:02:09 +0000124 # collect table of all tested kernels' test runs
125 kernels = []
126 for series in released_kernel_series:
127 kernels += survey_all_kernels_tested(perf.db_cur, series+'.',
128 '', smp, test_group,
129 max_rel_kernels,
130 job_table, kernel_dates)
131 for series in nightly_kernel_series:
132 kernels += survey_all_kernels_tested(perf.db_cur,
133 '2.6.26-%s-' % series,
134 series, smp, test_group,
135 max_dev_kernels,
136 job_table, kernel_dates)
137 kernels = sort_kernels(kernels)
138 return kernels # sorted subset of kernels in job_table
mbligh4bfe3612009-02-26 00:38:13 +0000139
140
141 def graph_1_test(test, metric, size):
142 # generate graph image for one benchmark, showing avg and
143 # std dev of one metric, over various kernels (X columns)
144 # and various platform types (graphed lines)
145 title = test.capitalize() + suite_notes
146 if regress:
147 title += ', Regressions Only'
148 if relative:
149 ylegend = 'Relative '
150 ymin = 0.9
151 else:
152 ylegend = ''
153 ymin = None
154 ylegend += metric.capitalize()
mbligh5b380be2009-06-22 19:02:09 +0000155 graph = plotgraph.gnuplot(title, kernel_legend, ylegend, size=size,
156 xsort=sort_kernels)
mbligh4bfe3612009-02-26 00:38:13 +0000157 for platform in platforms:
158 graph.add_dataset(platform, plot_data[platform])
159 graph.plot(cgi_header=True, ymin=ymin, dark=dark)
160
161
162 def table_for_1_test(test, metric):
163 # generate detailed html page with graph plus numeric table for 1 benchmark
164 print "Content-Type: text/html\n\n<html><body>"
165 heading = "%s %s:&nbsp %s%s" % (test_group, kernel_legend,
166 test.capitalize(), suite_notes)
167 if regress:
168 heading += ", Regressions Only"
169 print "<h2> %s </h2>" % heading
170 print "<img src='%s?%s'>" % (myself, '&'.join(passthru))
171
172 heading = "%s %s metric" % (test.capitalize(), metric)
173 if relative: heading += ", relative"
174 print "<p><p> <h3> %s: </h3>" % heading
175
176 ref_thresholds = {}
177 print "<table border=1, cellpadding=3>"
178 print "<tr> <td><b> Kernel </b></td>",
179 for platform in platforms:
180 p = platform.replace("_", "_<br>").replace(".", "<br>")
181 print "<td><b>", p, "</b></td>"
182 print "</tr>"
183 for kernel in kernels:
mbligh5b380be2009-06-22 19:02:09 +0000184 print "<tr> <td><b>", kernel, "</b><br><small>",
185 print kernel_dates[kernel], "</small></td>"
mbligh4bfe3612009-02-26 00:38:13 +0000186 for platform in platforms:
187 print "<td",
188 vals = plot_data[platform].get(kernel, [])
189 if vals:
190 (avg, std_dev) = plotgraph.avg_dev(vals)
191 if platform not in ref_thresholds:
192 ref_thresholds[platform] = avg - std_dev
193 if avg+std_dev < ref_thresholds[platform]:
194 print "bgcolor=pink",
195 print ( "> <a href='%s?test=%s&metric=%s"
196 "&platforms=%s&runs&kernel=%s'>"
197 % (myself, test, metric, platform, kernel) )
198 print "<b>%.4g</b>" % avg, "</a><br>",
mbligh5b380be2009-06-22 19:02:09 +0000199 print "&nbsp; <small> %dr </small>" % len(vals),
mbligh4bfe3612009-02-26 00:38:13 +0000200 print "&nbsp; <small> %.3g </small>" % std_dev,
201 else:
202 print "> ?",
203 print "</td>"
204 print "</tr>\n"
205 print "</table>"
206 print "<p> <b>Bold value:</b> Average of this metric, then <br>"
207 print "number of good test runs, then standard deviation of those runs"
208 print "<br> Pink if regressed from reference kernel"
209 print "</body></html>"
210
211
212 def get_testrun_context(testrun):
213 cmd = ( 'select jobs.tag, tests.subdir,'
214 ' jobs.label, tests.started_time'
215 ' from jobs, tests'
216 ' where jobs.job_idx = tests.job_idx'
217 ' and tests.test_idx = %d' % testrun )
218 nrows = perf.db_cur.execute(cmd)
219 assert nrows == 1
220 row = perf.db_cur.fetchone()
221 row = (row[0], row[1], row[2], row[3].strftime('%m/%d/%y %H:%M'))
222 return row
223
224
225 def testrun_details_for_1_test_kernel_platform(test, metric, platform):
226 kernel = form.getvalue('kernel')
227 show_attrs = 'attrs' in form
228 print "Content-Type: text/html\n\n<html><body>"
229 print "<h3> %s %s:&nbsp; %s%s, Kernel %s on %s </h3>" % (
230 test_group, kernel_legend, test.capitalize(), suite_notes, kernel, platform)
231 print "<table border=1 cellpadding=4>"
232 print "<tr><td> Test_idx </td> <td> %s metric </td>" % metric
233 print "<td> Job tag </td> <td> Subdir </td> <td> Job label </td>"
234 print "<td> Started_time </td>"
235 if show_attrs:
236 print "<td> Test attributes </td>"
237 print "</tr>\n"
238
239 for testrunx in test_runs[kernel][platform]:
240 print "<tr> <td>", testrunx, "</td>"
241 print "<td>",
242 vals = perf.get_metric_at_point([testrunx], metric)
243 for v in vals:
244 print "%.4g" % v,
245 print "</td>"
246 row = get_testrun_context(testrunx)
247 print ( "<td> <a href='//%s/results/%s/%s/results'> %s </a></td>"
248 % (results_server, row[0], row[1], row[0]) )
249 for v in row[1:]:
250 print "<td> %s </td>" % v
251 if show_attrs:
252 attrs = perf.get_test_attributes(testrunx)
253 print "<td>",
254 for attr in attrs:
255 # if attr == "sysinfo-cmdline": continue
256 # if attr[:4] == "svs-": continue
257 val = attrs[attr]
258 if len(val) > 40:
259 val = val[:40-3] + "..."
260 print "%s=%s" % (attr, val)
261 print "</td>"
262 print "</tr>\n"
263 print "</table>"
264 print "</body></html>"
265
266
267 def overview_thumb(test, metric=[]):
268 pass_ = passthru + ['test=%s' % test]
269 if metric:
270 pass_ += ['metric=%s' % metric]
271 pass_ = '&'.join(pass_)
272 print "<a href='%s?%s&table'>" % (myself, pass_)
273 print " <img src='%s?%s&size=650,600'> </a>" % (myself, pass_)
274 # embedded graphs fit 2 across on 1400x1050 laptop
275
276
277 def overview_page(benchmarks):
278 # generate overview html page with small graphs for each benchmark
279 # linking to detailed html page for that benchmark
280 # recursively link to this same cgi to generate each image
281 print "Content-Type: text/html\n\n<html><body>"
282 heading = "%s %s" % (test_group, kernel_legend)
283 if regress:
284 heading += ", Regressions Only"
285 print "<h2> %s </h2>" % heading
286 for test in benchmarks:
287 overview_thumb(test)
288 overview_thumb('unixbench', 'Process_creation')
289 print "</body></html>"
290
291
292 # body of nightly_views():
293 cgitb.enable()
294 form = cgi.FieldStorage(keep_blank_values=True)
295 test = form.getvalue('test', '')
296 relative = 'relative' in form
297 regress = 'regress' in form
298 dark = 'dark' in form
299 platforms_filter = form.getvalue('platforms', '')
300 by_hosts = 'by_hosts' in form or '.' in platforms_filter
mbligh4bfe3612009-02-26 00:38:13 +0000301 passthru = []
302 if relative:
303 passthru += ['relative']
304 if regress:
305 passthru += ['regress']
306 if dark:
307 passthru += ['dark']
308 if by_hosts:
309 passthru += ['by_hosts']
310 if platforms_filter:
311 passthru += ['platforms=%s' % platforms_filter]
mbligh4bfe3612009-02-26 00:38:13 +0000312 myself = os.path.basename(sys.argv[0])
313 if test:
314 passthru += ['test=%s' % test]
315 metric = form.getvalue('metric', '')
316 if metric:
317 passthru += ['metric=%s' % metric]
318 else:
319 metric = perf.benchmark_main_metric(test)
320 assert metric, "no default metric for test %s" % test
321 # perf.init()
322 perf.db_cur = db.db(host=tko_mysql_server,
323 user='nobody', password='').cur
324 kernels = select_dev_kernels()
325 regressed_platforms = find_regressions(kernels, test, metric)
326 collect_all_testruns(kernels, regressed_platforms, test)
327 plot_data = collect_scaled_scores(metric)
328 platforms = sorted(plot_data.keys())
329 if 'runs' in form:
330 testrun_details_for_1_test_kernel_platform(test, metric,
331 platforms[0])
332 elif 'table' in form:
333 table_for_1_test(test, metric)
334 else:
335 size = form.getvalue('size', '1200,850' )
336 graph_1_test(test, metric, size)
337 else:
338 overview_page(benchmarks)
mbligh5b380be2009-06-22 19:02:09 +0000339
340
341def sort_kernels(kernels):
342 return sorted(kernels, key=kernel_versions.version_encode)
343
344
345def survey_all_kernels_tested(db_cur, kernel_series, kname_prefix, smp,
346 test_group, maxkernels,
347 kernel_jobs, kernel_dates):
348 kernels = set()
349 # script run's job label has form
350 # 'Kernel_Qual_Containers : 2.6.26-300.8-jilee : smp : 2009-05-15'
351 # or 'Kernel_Qual_Containers : 2.6.26-DEV-4099999 : smp : 2009-05-15'
352 job_label = ('%s : %s%% : %s : %%'
353 % (test_group, kernel_series, smp))
354 # find names and job#s of all matching perf runs
355 cmd = ( "select job_idx, label, tag from jobs"
356 " where label like '%s' order by label desc" % job_label )
357 nrows = db_cur.execute(cmd)
358 for jobx, joblabel, tag in db_cur.fetchall():
359 cols = joblabel.split(' : ')
360 kernvers = cols[1].split('-') # 2.6.26 300.8 jilee
361 # or 2.6.26 DEV 4099999
362 if kname_prefix: # nightly build, eg 'DEV' or '300'
363 changelist = kernvers[2] # build's CL number
364 testdate = cols[3]
365 kernel = '%s_%s' % (kname_prefix, changelist)
366 else: # release candidates
367 if len(kernvers) > 2: # reject jobs with -qual suffix
368 continue
369 kernel = kernvers[1] # 300.8
370 testdate = ''
371 kernel_jobs.setdefault(kernel, [])
372 kernel_jobs[kernel].append(jobx)
373 kernel_dates[kernel] = testdate
374 kernels.add(kernel)
375 kernels = sort_kernels(kernels)[-maxkernels:]
376 return kernels
377