mbligh | 4bfe361 | 2009-02-26 00:38:13 +0000 | [diff] [blame] | 1 | #!/usr/bin/python |
| 2 | # tko/nightly.py code shared by various *_nightly.cgi scripts |
| 3 | |
| 4 | import cgi, cgitb |
mbligh | 5b380be | 2009-06-22 19:02:09 +0000 | [diff] [blame^] | 5 | import os, sys |
| 6 | import common |
mbligh | 4bfe361 | 2009-02-26 00:38:13 +0000 | [diff] [blame] | 7 | from autotest_lib.tko import db, plotgraph, perf |
mbligh | 5b380be | 2009-06-22 19:02:09 +0000 | [diff] [blame^] | 8 | from autotest_lib.client.common_lib import kernel_versions |
mbligh | 4bfe361 | 2009-02-26 00:38:13 +0000 | [diff] [blame] | 9 | |
| 10 | |
mbligh | 5b380be | 2009-06-22 19:02:09 +0000 | [diff] [blame^] | 11 | def 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): |
mbligh | 4bfe361 | 2009-02-26 00:38:13 +0000 | [diff] [blame] | 18 | |
| 19 | test_runs = {} # kernel --> (platform --> list of test runs) |
mbligh | 5b380be | 2009-06-22 19:02:09 +0000 | [diff] [blame^] | 20 | job_table = {} # kernel id --> list of job idxs |
| 21 | kernel_dates = {} # Kernel id --> date of nightly test |
mbligh | 4bfe361 | 2009-02-26 00:38:13 +0000 | [diff] [blame] | 22 | |
| 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(): |
mbligh | 5b380be | 2009-06-22 19:02:09 +0000 | [diff] [blame^] | 124 | # 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 |
mbligh | 4bfe361 | 2009-02-26 00:38:13 +0000 | [diff] [blame] | 139 | |
| 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() |
mbligh | 5b380be | 2009-06-22 19:02:09 +0000 | [diff] [blame^] | 155 | graph = plotgraph.gnuplot(title, kernel_legend, ylegend, size=size, |
| 156 | xsort=sort_kernels) |
mbligh | 4bfe361 | 2009-02-26 00:38:13 +0000 | [diff] [blame] | 157 | 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:  %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: |
mbligh | 5b380be | 2009-06-22 19:02:09 +0000 | [diff] [blame^] | 184 | print "<tr> <td><b>", kernel, "</b><br><small>", |
| 185 | print kernel_dates[kernel], "</small></td>" |
mbligh | 4bfe361 | 2009-02-26 00:38:13 +0000 | [diff] [blame] | 186 | 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>", |
mbligh | 5b380be | 2009-06-22 19:02:09 +0000 | [diff] [blame^] | 199 | print " <small> %dr </small>" % len(vals), |
mbligh | 4bfe361 | 2009-02-26 00:38:13 +0000 | [diff] [blame] | 200 | print " <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: %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 |
mbligh | 4bfe361 | 2009-02-26 00:38:13 +0000 | [diff] [blame] | 301 | 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] |
mbligh | 4bfe361 | 2009-02-26 00:38:13 +0000 | [diff] [blame] | 312 | 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) |
mbligh | 5b380be | 2009-06-22 19:02:09 +0000 | [diff] [blame^] | 339 | |
| 340 | |
| 341 | def sort_kernels(kernels): |
| 342 | return sorted(kernels, key=kernel_versions.version_encode) |
| 343 | |
| 344 | |
| 345 | def 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 | |