普通文本  |  787行  |  32.22 KB

#  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<html><body>"


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 "<a href='%s'> %s </a>" % (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 "<table border=1 cellpadding=3 cellspacing=0>"
    print "<tr> <td><b> Kernel </b></td>",
    for label in columns:
        if not label and column_argname == 'antag':
            label = 'no antag'
        print "<td><b>", label.replace('_', '<br>_'), "</b></td>"
    print "</tr>"
    for kernel in kernels:
        print "<tr> <td><b>", kernel, "</b>",
        if kernel in kernel_dates:
            print "<br><small>", kernel_dates[kernel], "</small>"
        print "</td>"
        for col in columns:
            print "<td",
            vals = plot_data[col].get(kernel, [])
            if not vals:
                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 "<a href='%s?%s&runs&attrs'>" % (myurl,
                                                       '&'.join(args))
                print "<b>%.4g</b>" % avg, "</a><br>",
                print "&nbsp; <small> %dr   </small>" % len(vals),
                print "&nbsp; <small> %.3g </small>" % std_dev,
            print "</td>"
        print "</tr>\n"
    print "</table>"
    print "<p> <b>Bold value:</b> Average of this metric, then <br>"
    print "number of good test runs, then standard deviation of those runs"
    print "<br> 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, "<br>"
    attrs = sorted(list(attrs))[:100]

    print "<table border=1 cellpadding=4 cellspacing=0>"
    print "<tr><td> Metric </td>"
    for kernel in kernels:
        kernel = kernel.replace("_", "_<br>")
        print "<td>", kernel, "</td>"
    print "</tr>"
    for attr in attrs:
        print "<tr>"
        print "<td>", attr, "</td>"
        baseline = None
        for kernel in kernels:
            print "<td>",
            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 "</td>"
        print "</tr>"
    print "</table>"


def table_variants_all_tests(plot_data, columns, colkeys, benchmarks,
                             myurl, filtered_passthru):
    # generate html table of graph's numbers
    #   for primary metric over all benchmarks (rows),
    #   on one platform and one kernel,
    #   over various combos of test run attribute constraints (cols).
    ref_thresholds = {}
    print "<table border=1 cellpadding=3 cellspacing=0>"
    print "<tr> <td><b> Benchmark </b></td>",
    for col in columns:
        print "<td><b>", colkeys[col].replace(',', ',<br>'), "</b></td>"
    print "</tr>"
    for benchmark in benchmarks:
        print "<tr> <td><b>", benchmark, "</b></td>"
        for col in columns:
            print "<td>",
            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 "<a href='%s?%s&runs&attrs'>" % (myurl,
                                                       '&'.join(args))
                print "<b>%.4g</b>" % avg, "</a><br>",
                print "&nbsp; <small> %dr   </small>" % len(vals),
                print "&nbsp; <small> %.3g </small>" % std_dev,
            print "</td>"
        print "</tr>\n"
    print "</table>"
    print "<p> <b>Bold value:</b> Average of this metric, then <br>"
    print "number of good test runs, then standard deviation of those runs"


def table_testrun_details(runs, metric, tko_server, show_attrs):
    print "<table border=1 cellpadding=4 cellspacing=0>"
    print "<tr><td> %s metric </td>" % metric
    print "<td> Job label </td> <td> Job tag </td> <td> Run results </td>"
    print "<td> Started_time </td>"
    if show_attrs:
        print "<td> Test attributes </td>"
    print "</tr>\n"

    for testrunx in runs:
        print "<tr> <td>",
        vals = perf.get_metric_at_point([testrunx], metric)
        for v in vals:
            print "%.4g&nbsp;" % v,
        print "</td>"
        row = get_testrun_context(testrunx)
        row[2] = ( "<a href='//%s/results/%s/%s/results/keyval'> %s </a>"
                   % (tko_server, row[1], row[2], row[2]) )
        for v in row:
            print "<td> %s </td>" % v
        if show_attrs:
            attrs = get_test_attributes(testrunx)
            print "<td>",
            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 &nbsp; &nbsp; " % (attr, val)
            print "</td>"
        print "</tr>\n"
    print "</table>"


def overview_thumb(test, metric, myurl, passthru):
    pass_ = passthru + ['test=%s' % test]
    if metric:
        pass_ += ['metric=%s' % metric]
    pass_ = '&'.join(pass_)
    print "<a    href='%s?%s&table'>"             % (myurl, pass_)
    print "  <img src='%s?%s&size=450,500'> </a>" % (myurl, pass_)
    # embedded graphs fit 3 across on 1400x1050 laptop


def graph_1_test(title, metric, plot_data, line_argname, lines,
                 kernel_legend, relative, size, dark=False):
    # generate graph image for one benchmark, showing avg and
    #  std dev of one metric, over various kernels (X columns),
    #  over various platforms or antagonists etc (graphed lines)
    xlegend = kernel_legend
    ylegend = metric.capitalize()
    if relative:
        ylegend += ', Relative'
        ymin = 0.8
    else:
        ymin = None
    if len(lines) > 1:
        keytitle = line_argname.capitalize() + ':'
    else:
        keytitle = ''
    graph = plotgraph.gnuplot(title, xlegend, ylegend, size=size,
                              xsort=perf.sort_kernels, keytitle=keytitle)
    for line in lines:
        label = line
        if not label and line_argname == 'antag':
            label = 'no antag'
        graph.add_dataset(label, plot_data[line])
    graph.plot(cgi_header=True, ymin=ymin, dark=dark)


def graph_variants_all_tests(title, plot_data, linekeys, size, dark):
        # generate graph image showing all benchmarks
        #   on one platform and one kernel,
        #   over various combos of test run attribute constraints (lines).
    xlegend = "Benchmark"
    ylegend = "Relative Perf"
    graph = plotgraph.gnuplot(title, xlegend, ylegend, size=size)
    for i in linekeys:
        graph.add_dataset(linekeys[i], plot_data[i])
    graph.plot(cgi_header=True, dark=dark, ymin=0.8)


class generate_views(object):


    def __init__(self, kernel_legend, benchmarks, test_group,
                     site_benchmark_metrics, tko_server,
                     jobs_selector, no_antag):
        self.kernel_legend = kernel_legend
        self.benchmarks = benchmarks
        self.test_group = test_group
        self.tko_server = tko_server
        self.jobs_selector = jobs_selector
        self.no_antag = no_antag

        cgitb.enable()
        test, antagonists = self.parse_most_cgi_args()

        perf.init(tko_server=tko_server)
        for b in site_benchmark_metrics:
            perf.add_benchmark_main_metric(b, site_benchmark_metrics[b])

        self.test_runs = {}     # kernel --> (platform --> list of test runs)
        self.job_table = {}     # kernel id --> list of job idxs
        self.kernel_dates = {}  # kernel id --> date of nightly test

        vary = self.cgiform.getlist('vary')
        if vary:
            platform = self.platforms_filter[0]
            self.analyze_variants_all_tests_1_platform(platform, vary)
        elif test:
            self.analyze_1_test(test, antagonists)
        else:
            self.overview_page_all_tests(self.benchmarks, antagonists)


    def collect_all_testruns(self, trimmed_kernels, test):
    # get test_runs run #s for 1 test on some kernels and platforms
        for kernel in trimmed_kernels:
            runs = collect_testruns(self.job_table[kernel], test,
                                    self.test_attributes, self.platforms_filter,
                                    'by_hosts' in self.toggles, self.no_antag)
            if runs:
                self.test_runs[kernel] = runs


    def table_for_graph_1_test(self, title, metric, plot_data,
                                 column_argname, columns, filtered_passthru):
        # generate detailed html page with 1 graph and corresp numbers
        #   for 1 benchmark metric over all kernels (rows),
        #   over various platforms or various antagonists etc (cols).
        html_top()
        print '<h3> %s </h3>' % title
        print ('%s, machine group %s on //%s server <br>' %
               (self.kernel_legend, self.test_group, self.tko_server))
        if self.test_tag:
            print '%s test script series <br>' % self.test_tag[1:]

        print "<img src='%s?%s'>" % (self.myurl, '&'.join(self.passthru))

        link = abs_rel_link(self.myurl, self.passthru+['table'])
        print "<p><p> <h4> Redraw this with %s performance? </h4>" % link

        heading = "%s, %s metric" % (title, metric)
        if self.relative:
            heading += ", relative"
        print "<p><p> <h3> %s: </h3>" % heading
        table_1_metric_all_kernels(plot_data, columns, column_argname,
                                   self.kernels, self.kernel_dates,
                                   self.myurl, filtered_passthru)
        print "</body></html>"


    def graph_1_test_all_platforms(self, test, metric, platforms, plot_data):
        # generate graph image for one benchmark
        title = test.capitalize()
        if 'regress' in self.toggles:
            title += ' Regressions'
        if 'table' in self.cgiform:
            self.table_for_graph_1_test(title, metric, plot_data,
                                        'platforms', platforms,
                                        filtered_passthru=self.passthru)
        else:
            graph_1_test(title, metric, plot_data, 'platforms', platforms,
                         self.kernel_legend, self.relative,
                         self.size, 'dark' in self.toggles)


    def testrun_details(self, title, runs, metric):
        html_top()
        print '<h3> %s </h3>' % title
        print ('%s, machine group %s on //%s server' %
               (self.kernel_legend, self.test_group, self.tko_server))
        if self.test_tag:
            print '<br> %s test script series' % self.test_tag[1:]
        print '<p>'
        table_testrun_details(runs, metric,
                              self.tko_server, 'attrs' in self.cgiform)
        print "</body></html>"


    def testrun_details_for_1_test_kernel_platform(self, test,
                                                   metric, platform):
        default_kernel = min(self.test_runs.keys())
        kernel = self.cgiform.getvalue('kernel', default_kernel)
        title = '%s on %s using %s' % (test.capitalize(), platform, kernel)
        runs = self.test_runs[kernel].get(platform, [])
        self.testrun_details(title, runs, metric)


    def analyze_1_metric_all_platforms(self, test, metric):
        if 'regress' in self.toggles:
            regressed_platforms = find_regressions(self.kernels, self.test_runs,
                                                   metric)
        else:
            regressed_platforms = None
        plot_data = collect_scaled_scores(metric, self.test_runs,
                                          regressed_platforms, self.relative)
        platforms = sorted(plot_data.keys())
        if not plot_data:
            html_top()
            print 'No runs'
        elif 'runs' in self.cgiform:
            self.testrun_details_for_1_test_kernel_platform(test, metric,
                                                            platforms[0])
        else:
            self.graph_1_test_all_platforms(test, metric, platforms, plot_data)


    def analyze_all_metrics_1_platform(self, test, platform):
        # TODO: show #runs in header
        html_top()
        heading = "%s %s:&nbsp %s" % (self.test_group, self.kernel_legend,
                                      test.capitalize())
        print "<h2> %s </h2>" % heading
        print "platform=%s <br>" % platform
        for attr in self.test_attributes:
            print "%s=%s &nbsp; " % (attr, self.test_attributes[attr])
        print "<p>"
        table_all_metrics_1_platform(self.test_runs, platform, self.relative)
        print "</body></html>"


    def table_for_variants_all_tests(self, title, plot_data, colkeys, columns,
                                       filtered_passthru, test_tag):
        # generate detailed html page with 1 graph and corresp numbers
        #   for primary metric over all benchmarks (rows),
        #   on one platform and one kernel,
        #   over various combos of test run attribute constraints (cols).
        html_top()
        print '<h3> %s </h3>' % title
        print ('%s, machine group %s on //%s server <br>' %
               (self.kernel_legend, self.test_group, self.tko_server))
        if test_tag:
            print '%s test script series <br>' % test_tag[1:]

        varies = ['vary='+colkeys[col] for col in columns]
        print "<img src='%s?%s'>" % (self.myurl, '&'.join(self.passthru+varies))

        print "<p><p> <h3> %s: </h3>" % title
        table_variants_all_tests(plot_data, columns, colkeys, self.benchmarks,
                                 self.myurl, filtered_passthru)
        print "</body></html>"


    def analyze_variants_all_tests_1_platform(self, platform, vary):
        # generate one graph image for results of all benchmarks
        # on one platform and one kernel, comparing effects of
        # two or more combos of kernel options (test run attributes)
        #   (numa_fake,stale_page,kswapd_merge,sched_idle, etc)
        kernel = self.cgiform.getvalue('kernel', 'some_kernel')
        self.passthru.append('kernel=%s' % kernel)

        # two or more vary_groups, one for each plotted line,
        # each group begins with vary= and ends with next  &
        # each group has comma-separated list of test attribute key=val pairs
        #    eg   vary=keyval1,keyval2&vary=keyval3,keyval4
        vary_groups = [dict(pair.split('=',1) for pair
                            in vary_group.split(','))
                       for vary_group in vary]

        test = self.benchmarks[0]  # pick any test in all jobs
        kernels, test_tag = self.jobs_selector(test, self.job_table,
                                               self.kernel_dates)

        linekeys = {}
        plot_data = {}
        baselines = {}
        for i, vary_group in enumerate(vary_groups):
            group_attributes = self.test_attributes.copy()
            group_attributes.update(vary_group)
            linekey = ','.join('%s=%s' % (attr, vary_group[attr])
                               for attr in vary_group)
            linekeys[i] = linekey
            data = {}
            for benchmark in self.benchmarks:
                metric = perf.benchmark_main_metric(benchmark)
                runs = collect_testruns(self.job_table[kernel],
                                        benchmark+test_tag,
                                        group_attributes,
                                        self.platforms_filter,
                                        'by_hosts' in self.toggles,
                                        self.no_antag)
                vals = []
                for testrunx in runs[platform]:
                    vals += perf.get_metric_at_point([testrunx], metric)
                if vals:
                    if benchmark not in baselines:
                        baselines[benchmark], stddev = plotgraph.avg_dev(vals)
                    vals = [val/baselines[benchmark] for val in vals]
                    data[benchmark] = vals
            plot_data[i] = data

        title  = "%s on %s" % (kernel, platform)
        for attr in self.test_attributes:
            title += ', %s=%s' % (attr, self.test_attributes[attr])
        if 'table' in self.cgiform:
            self.table_for_variants_all_tests(title, plot_data, linekeys,
                               range(len(linekeys)),
                               filtered_passthru=self.passthru,
                               test_tag=test_tag)
        else:
            graph_variants_all_tests(title, plot_data, linekeys,
                                     self.size, 'dark' in self.toggles)


    def graph_twoway_antagonists_1_test_1_platform(
                  self, test, metric, platform, antagonists, twoway_runs):
        # generate graph of one benchmark's performance paired with
        #    various antagonists, with one plotted line per antagonist,
        #    over most kernels (X axis), all on one machine type
        # performance is relative to the no-antag baseline case
        plot_data = collect_twoway_scores(metric, antagonists,
                                          twoway_runs, self.relative)
        title  = "%s vs. an Antagonist on %s:" % (test.capitalize(), platform)
        if 'table' in self.cgiform:
            filtered_passthru = [arg for arg in self.passthru
                                     if not arg.startswith('antag=')]
            self.table_for_graph_1_test(title, metric, plot_data,
                                   'antag', antagonists,
                                   filtered_passthru=filtered_passthru)
        else:
            graph_1_test(title, metric, plot_data, 'antag', antagonists,
                         self.kernel_legend, self.relative,
                         self.size, 'dark' in self.toggles)


    def testrun_details_for_twoway_test(self, test, metric, platform,
                                        antagonist, twoway_runs):
        default_kernel = min(twoway_runs.keys())
        kernel = self.cgiform.getvalue('kernel', default_kernel)
        title = '%s vs. Antagonist %s on %s using %s' % (
                test.capitalize(), antagonist.capitalize(), platform, kernel)
        runs = twoway_runs[kernel].get(antagonist, [])
        self.testrun_details(title, runs, metric)


    def analyze_twoway_antagonists_1_test_1_platform(
                  self, test, metric, platform, antagonists):
        twoway_runs, all_antagonists = divide_twoway_testruns(self.test_runs,
                                                              platform)
        if antagonists == ['*']:
            antagonists = all_antagonists
        if not twoway_runs:
            html_top()
            print 'No runs'
        elif 'runs' in self.cgiform:
            self.testrun_details_for_twoway_test(
                    test, metric, platform, antagonists[0], twoway_runs)
        else:
            self.graph_twoway_antagonists_1_test_1_platform(
                    test, metric, platform, antagonists, twoway_runs)


    def get_twoway_default_platform(self):
        if self.platforms_filter:
            return self.platforms_filter[0]
        test = 'unixbench'
        kernels, test_tag = self.jobs_selector(test, self.job_table,
                                               self.kernel_dates)
        self.collect_all_testruns(kernels, test+test_tag)
        return all_tested_platforms(self.test_runs)[0]


    def overview_page_all_tests(self, benchmarks, antagonists):
        # generate overview html page with small graphs for each benchmark
        #   linking to detailed html page for that benchmark
        #   recursively link to this same cgi to generate each image
        html_top()
        if antagonists is not None:
            heading = ('Twoway Container Isolation using %s on %s' %
                       (self.kernel_legend, self.get_twoway_default_platform()))
        else:
            heading = '%s, %s Benchmarks' % (self.kernel_legend,
                                             self.test_group)
        if 'regress' in self.toggles:
            heading += ", Regressions Only"
        print "<h3> %s </h3>" % heading
        for test in benchmarks:
            overview_thumb(test, '', self.myurl, self.passthru)
            if test == 'unixbench':
                overview_thumb('unixbench', 'Process_creation',
                               self.myurl, self.passthru)

        link = abs_rel_link(self.myurl, self.passthru)
        print "<p><p> <h4> Redraw this with %s performance? </h4>" % link
        print "</body></html>"


    def analyze_1_test(self, test, antagonists):
        self.passthru.append('test=%s' % test)
        metric = self.cgiform.getvalue('metric', '')
        if metric:
            self.passthru.append('metric=%s' % metric)
        else:
            metric = perf.benchmark_main_metric(test)
            assert metric, "no default metric for test %s" % test
        self.kernels, self.test_tag = self.jobs_selector(test, self.job_table,
                                                         self.kernel_dates)
        self.collect_all_testruns(self.kernels, test+self.test_tag)
        if not self.platforms_filter and (metric == '*' or
                                          antagonists is not None):
            # choose default platform
            self.platforms_filter = all_tested_platforms(self.test_runs)[0:1]
            self.passthru.append('platforms=%s' %
                                 ','.join(self.platforms_filter))
        if antagonists is not None:
            antagonists = antagonists.split(',')
            if len(antagonists) == 1 and antagonists != ['*']:
                self.relative = False
            self.analyze_twoway_antagonists_1_test_1_platform(
                    test, metric, self.platforms_filter[0], antagonists)
        elif metric == '*':
            platform = self.platforms_filter[0]
            self.analyze_all_metrics_1_platform(test, platform)
        else:
            self.analyze_1_metric_all_platforms(test, metric)


    def parse_most_cgi_args(self):
        self.myurl = os.path.basename(sys.argv[0])
        self.cgiform = cgi.FieldStorage(keep_blank_values=True)
        self.size = self.cgiform.getvalue('size', '1200,850')
        all_toggles = set(('absolute', 'regress', 'dark', 'by_hosts'))
        self.toggles = set(tog for tog in all_toggles if tog in self.cgiform)
        platforms = self.cgiform.getvalue('platforms', '')
        if '.' in platforms:
            self.toggles.add('by_hosts')
        self.passthru = list(self.toggles)
        self.relative = 'absolute' not in self.toggles
        if platforms:
            self.passthru.append('platforms=%s' % platforms)
            self.platforms_filter = platforms.split(',')
        else:
            self.platforms_filter = []
        self.test_attributes = perf.parse_test_attr_args(self.cgiform)
        perf.append_cgi_args(self.passthru, self.test_attributes)
        test = self.cgiform.getvalue('test', '')
        if 'antag' in self.cgiform:
            antagonists = ','.join(self.cgiform.getlist('antag'))
            #      antag=*
            #   or antag=test1,test2,test3,...
            #   or antag=test1&antag=test2&...
            #   testN is empty for solo case of no antagonist
            self.passthru.append('antag=%s' % antagonists)
        else:
            antagonists = None  # not same as ''
        return test, antagonists