# tko/nightly.py code shared by various tko/*.cgi graphing scripts import cgi, cgitb import os, sys import common from autotest_lib.tko import db, plotgraph, perf from autotest_lib.client.common_lib import kernel_versions def add_kernel_jobs(label_pattern): cmd = "select job_idx from tko_jobs where label like '%s'" % label_pattern nrows = perf.db_cur.execute(cmd) return [row[0] for row in perf.db_cur.fetchall()] def is_filtered_platform(platform, platforms_filter): if not platforms_filter: return True for p in platforms_filter: if platform.startswith(p): return True return False def get_test_attributes(testrunx): cmd = ( "select attribute, value from tko_test_attributes" " where test_idx = %d" % testrunx ) nrows = perf.db_cur.execute(cmd) return dict(perf.db_cur.fetchall()) def get_antag(testrunx): attrs = get_test_attributes(testrunx) return attrs.get('antag', None) def matching_test_attributes(attrs, required_test_attributes): if not required_test_attributes: return True matches = [attrs[key] == required_test_attributes[key] for key in attrs if key in required_test_attributes] return min(matches+[True]) # True if all jointly-existing keys matched def collect_testruns(jobs, test, test_attributes, platforms_filter, by_hosts, no_antag): # get test_runs run #s for 1 test on 1 kernel and some platforms # TODO: Is jobs list short enough to use directly in 1 sql cmd? # TODO: add filtering on test series? runs = {} # platform --> list of test runs for jobx in jobs: cmd = ( "select test_idx, machine_idx from tko_tests" " where job_idx = %s and test = %s" ) args = [jobx, test] nrows = perf.db_cur.execute(cmd, args) for testrunx, machx in perf.db_cur.fetchall(): platform, host = perf.machine_idx_to_platform_host(machx) if by_hosts: platform += '.'+host if ( is_filtered_platform(platform, platforms_filter) and matching_test_attributes(get_test_attributes(testrunx), test_attributes) and (not no_antag or get_antag(testrunx) == '') ): runs.setdefault(platform, []).append(testrunx) return runs def all_tested_platforms(test_runs): # extract list of all tested platforms from test_runs table platforms = set() for kernel in test_runs: platforms.update(set(test_runs[kernel].keys())) return sorted(platforms) def divide_twoway_testruns(test_runs, platform): # partition all twoway runs based on name of antagonist progs twoway_runs = {} antagonists = set() for kernel in test_runs: runs = {} for testrunx in test_runs[kernel].get(platform, []): antag = get_antag(testrunx) if antag is not None: runs.setdefault(antag, []).append(testrunx) antagonists.add(antag) twoway_runs[kernel] = runs return twoway_runs, sorted(antagonists) def collect_raw_scores(runs, metric): # get unscaled scores of test runs for 1 test on certain jobs # arrange them by platform type platform_scores = {} # platform --> list of perf scores for platform in runs: vals = perf.get_metric_at_point(runs[platform], metric) if vals: platform_scores[platform] = vals return platform_scores def collect_scaled_scores(metric, test_runs, regressed_platforms, relative): # get scores of test runs for 1 test on some kernels and platforms # optionally make relative to oldest (?) kernel on that platform # arrange by plotline (ie platform) for gnuplot plot_data = {} # platform --> (kernel --> list of perf scores) baseline = {} for kernel in sorted(test_runs.keys()): for platform in test_runs[kernel]: if not (regressed_platforms is None or platform in regressed_platforms): continue # delete results for uninteresting platforms vals = perf.get_metric_at_point(test_runs[kernel][platform], metric) if vals: if relative: if platform not in baseline: baseline[platform], std = plotgraph.avg_dev(vals) vals = [v/baseline[platform] for v in vals] pdp = plot_data.setdefault(platform, {}) pdp.setdefault(kernel, []).extend(vals) return plot_data def collect_twoway_scores(metric, antagonists, twoway_runs, relative): alone = '' plot_data = {} for kernel in twoway_runs: for test2 in antagonists: runs = twoway_runs[kernel].get(test2, []) vals = perf.get_metric_at_point(runs, metric) plot_data.setdefault(test2, {}) if vals: plot_data[test2][kernel] = vals if relative: vals = plot_data[alone].get(kernel, []) if vals: baseline = perf.average(vals) for test2 in antagonists: vals = plot_data[test2].get(kernel, []) vals = [val/baseline for val in vals] if vals: plot_data[test2][kernel] = vals else: for test2 in antagonists: if kernel in plot_data[test2]: del plot_data[test2][kernel] return plot_data def find_regressions(kernels, test_runs, metric): # A test is regressed on some platform if its latest results are # definitely lower than on the reference kernel. # Runs for the latest kernel may be underway and incomplete. # In that case, selectively use next-latest kernel. # TODO: the next-latest method hurts if latest run is not sorted last, # or if there are several dev threads ref = kernels[0] latest = kernels[-1] prev = kernels[-2:][0] scores = {} # kernel --> (platform --> list of perf scores) for k in [ref, prev, latest]: if k in test_runs: scores[k] = collect_raw_scores(test_runs[k], metric) regressed_platforms = [] for platform in scores[ref]: if latest in scores and platform in scores[latest]: k = latest elif prev in scores and platform in scores[prev]: k = prev else: # perhaps due to decay of test machines k = ref # no regression info avail ref_avg, ref_std = plotgraph.avg_dev(scores[ref][platform]) avg, std = plotgraph.avg_dev(scores[ k ][platform]) if avg+std < ref_avg-ref_std: regressed_platforms.append(platform) return sorted(regressed_platforms) def get_testrun_context(testrun): cmd = ( 'select tko_jobs.label, tko_jobs.tag, tko_tests.subdir,' ' tko_tests.started_time' ' from tko_jobs, tko_tests' ' where tko_jobs.job_idx = tko_tests.job_idx' ' and tko_tests.test_idx = %d' % testrun ) nrows = perf.db_cur.execute(cmd) assert nrows == 1 row = perf.db_cur.fetchone() row = [row[0], row[1], row[2], row[3].strftime('%m/%d/%y %H:%M')] return row def html_top(): print "Content-Type: text/html\n\n
" def abs_rel_link(myurl, passthru): # link redraws current page with opposite absolute/relative choice mod_passthru = passthru[:] if 'absolute' in passthru: mod_passthru.remove('absolute') opposite = 'relative' else: mod_passthru.append('absolute') opposite = 'absolute' url = '%s?%s' % (myurl, '&'.join(mod_passthru)) return " %s " % (url, opposite) def table_1_metric_all_kernels(plot_data, columns, column_argname, kernels, kernel_dates, myurl, filtered_passthru): # generate html table of graph's numbers # for 1 benchmark metric over all kernels (rows), # over various platforms or various antagonists etc (cols). ref_thresholds = {} print "Kernel | ", for label in columns: if not label and column_argname == 'antag': label = 'no antag' print "", label.replace('_', ' _'), " | "
print "
", kernel, "",
if kernel in kernel_dates:
print " ", kernel_dates[kernel], "" print " | "
for col in columns:
print " ?",
else:
(avg, std_dev) = plotgraph.avg_dev(vals)
if col not in ref_thresholds:
ref_thresholds[col] = avg - std_dev
if avg+std_dev < ref_thresholds[col]:
print "bgcolor=pink",
print "> ",
args = filtered_passthru[:]
perf.append_cgi_args(args,
{column_argname:col, 'kernel':kernel})
print "" % (myurl,
'&'.join(args))
print "%.4g" % avg, " ", print " %dr " % len(vals), print " %.3g " % std_dev, print " | "
print "
Bold value: Average of this metric, then
"
print "number of good test runs, then standard deviation of those runs"
print "
Pink if regressed from reference kernel"
def table_all_metrics_1_platform(test_runs, platform, relative):
# TODO: show std dev in cells
# can't mark regressions, since some metrics improve downwards
kernels = perf.sort_kernels(test_runs.keys())
scores = {}
attrs = set()
for kernel in kernels:
testruns = test_runs[kernel].get(platform, [])
if testruns:
d = perf.collect_all_metrics_scores(testruns)
scores[kernel] = d
attrs.update(set(d.keys()))
else:
print "No runs completed on", kernel, "
"
attrs = sorted(list(attrs))[:100]
print "
Metric | " for kernel in kernels: kernel = kernel.replace("_", "_", kernel, " | " print "
", attr, " | " baseline = None for kernel in kernels: print "", if kernel in scores and attr in scores[kernel]: (avg, dev) = plotgraph.avg_dev(scores[kernel][attr]) if baseline and relative: percent = (avg/baseline - 1)*100 print "%+.1f%%" % percent, else: baseline = avg print "%.4g" % avg, else: print "?" print " | " print "
Benchmark | ", for col in columns: print "", colkeys[col].replace(',', ', '), " | "
print "
", benchmark, " | " for col in columns: print "",
vals = plot_data[col].get(benchmark, [])
if not vals:
print "?",
else:
(avg, std_dev) = plotgraph.avg_dev(vals)
args = filtered_passthru[:]
perf.append_cgi_args(args, {'test':benchmark})
for keyval in colkeys[col].split(','):
key, val = keyval.split('=', 1)
perf.append_cgi_args(args, {key:val})
print "" % (myurl,
'&'.join(args))
print "%.4g" % avg, " ", print " %dr " % len(vals), print " %.3g " % std_dev, print " | "
print "
Bold value: Average of this metric, then
"
print "number of good test runs, then standard deviation of those runs"
def table_testrun_details(runs, metric, tko_server, show_attrs):
print "
%s metric | " % metric print "Job label | Job tag | Run results | " print "Started_time | " if show_attrs: print "Test attributes | " print "
", vals = perf.get_metric_at_point([testrunx], metric) for v in vals: print "%.4g " % v, print " | " row = get_testrun_context(testrunx) row[2] = ( " %s " % (tko_server, row[1], row[2], row[2]) ) for v in row: print "%s | " % v if show_attrs: attrs = get_test_attributes(testrunx) print "", for attr in sorted(attrs.keys()): if attr == "sysinfo-cmdline": continue if attr[:4] == "svs-": continue val = attrs[attr] if len(val) > 40: val = val[:40-3] + "..." print "%s=%s " % (attr, val) print " | " print "
' table_testrun_details(runs, metric, self.tko_server, 'attrs' in self.cgiform) print "