コード例 #1
0
ファイル: views.py プロジェクト: BillSeurer/lnt
def v4_graph():
    from lnt.server.ui import util
    from lnt.testing import PASS
    from lnt.util import stats
    from lnt.external.stats import stats as ext_stats

    ts = request.get_testsuite()

    # Parse the view options.
    options = {}
    options['hide_lineplot'] = bool(request.args.get('hide_lineplot'))
    show_lineplot = not options['hide_lineplot']
    options['show_mad'] = show_mad = bool(request.args.get('show_mad'))
    options['show_stddev'] = show_stddev = bool(request.args.get('show_stddev'))
    options['hide_all_points'] = hide_all_points = bool(
        request.args.get('hide_all_points'))
    options['show_linear_regression'] = show_linear_regression = bool(
        request.args.get('show_linear_regression'))
    options['show_failures'] = show_failures = bool(
        request.args.get('show_failures'))
    options['normalize_by_median'] = normalize_by_median = bool(
        request.args.get('normalize_by_median'))
    options['show_moving_average'] = moving_average = bool(
        request.args.get('show_moving_average'))
    options['show_moving_median'] = moving_median = bool(
        request.args.get('show_moving_median'))
    options['moving_window_size'] = moving_window_size = int(
        request.args.get('moving_window_size', 10))
    options['hide_highlight'] = bool(
        request.args.get('hide_highlight'))
    show_highlight = not options['hide_highlight']

    def convert_revision(dotted):
        """Turn a version number like 489.2.10 into something
        that is ordered and sortable.
        For now 489.2.10 will be returned as a tuple of ints.
        """
        dotted = integral_rex.findall(dotted)
        return tuple([int(d) for d in dotted])

    # Load the graph parameters.
    graph_parameters = []
    for name,value in request.args.items():
        # Plots to graph are passed as::
        #
        #  plot.<unused>=<machine id>.<test id>.<field index>
        if not name.startswith(str('plot.')):
            continue

        # Ignore the extra part of the key, it is unused.
        machine_id_str,test_id_str,field_index_str = value.split('.')
        try:
            machine_id = int(machine_id_str)
            test_id = int(test_id_str)
            field_index = int(field_index_str)
        except:
            return abort(400)

        if not (0 <= field_index < len(ts.sample_fields)):
            return abort(404)

        try:
            machine = \
                ts.query(ts.Machine).filter(ts.Machine.id == machine_id).one()
            test = ts.query(ts.Test).filter(ts.Test.id == test_id).one()
            field = ts.sample_fields[field_index]
        except NoResultFound:
            return abort(404)
        graph_parameters.append((machine, test, field))

    # Order the plots by machine name, test name and then field.
    graph_parameters.sort(key = lambda (m,t,f): (m.name, t.name, f.name))

    # Extract requested mean trend.
    mean_parameter = None
    for name,value in request.args.items():
        # Mean to graph is passed as:
        #
        #  mean=<machine id>.<field index>
        if name != 'mean':
            continue

        machine_id_str,field_index_str  = value.split('.')
        try:
            machine_id = int(machine_id_str)
            field_index = int(field_index_str)
        except ValueError:
            return abort(400)

        if not (0 <= field_index < len(ts.sample_fields)):
            return abort(404)

        try:
            machine = \
                ts.query(ts.Machine).filter(ts.Machine.id == machine_id).one()
        except NoResultFound:
            return abort(404)
        field = ts.sample_fields[field_index]

        mean_parameter = (machine, field)

    # Sanity check the arguments.
    if not graph_parameters and not mean_parameter:
        return render_template("error.html", message="Nothing to graph.")

    # Extract requested baselines, and their titles.
    baseline_parameters = []
    for name,value in request.args.items():
        # Baselines to graph are passed as:
        #
        #  baseline.title=<run id>
        if not name.startswith(str('baseline.')):
            continue

        baseline_title = name[len('baseline.'):]

        run_id_str = value
        try:
            run_id = int(run_id_str)
        except:
            return abort(400)

        try:
            run = ts.query(ts.Run).join(ts.Machine).filter(ts.Run.id == run_id).one()
        except:
            err_msg = "The run {} was not found in the database.".format(run_id)
            return render_template("error.html",
                                   message=err_msg)

        baseline_parameters.append((run, baseline_title))

    # Create region of interest for run data region if we are performing a
    # comparison.
    revision_range = None
    highlight_run_id = request.args.get('highlight_run')
    if show_highlight and highlight_run_id and highlight_run_id.isdigit():
        highlight_run = ts.query(ts.Run).filter_by(
            id=int(highlight_run_id)).first()
        if highlight_run is None:
            abort(404)

        # Find the neighboring runs, by order.
        prev_runs = list(ts.get_previous_runs_on_machine(highlight_run, N = 1))
        if prev_runs:
            start_rev = prev_runs[0].order.llvm_project_revision
            end_rev = highlight_run.order.llvm_project_revision
            revision_range = {
                "start": convert_revision(start_rev),
                "end": convert_revision(end_rev) }

    # Build the graph data.
    legend = []
    graph_plots = []
    graph_datum = []
    overview_plots = []
    baseline_plots = []
    num_plots = len(graph_parameters)
    for i,(machine,test,field) in enumerate(graph_parameters):
        # Determine the base plot color.
        col = list(util.makeDarkColor(float(i) / num_plots))
        legend.append((machine, test.name, field.name, tuple(col)))

        # Load all the field values for this test on the same machine.
        #
        # FIXME: Don't join to Order here, aggregate this across all the tests
        # we want to load. Actually, we should just make this a single query.
        #
        # FIXME: Don't hard code field name.
        q = ts.query(field.column, ts.Order.llvm_project_revision, ts.Run.start_time).\
            join(ts.Run).join(ts.Order).\
            filter(ts.Run.machine_id == machine.id).\
            filter(ts.Sample.test == test).\
            filter(field.column != None)

        # Unless all samples requested, filter out failing tests.
        if not show_failures:
            if field.status_field:
                q = q.filter((field.status_field.column == PASS) |
                             (field.status_field.column == None))

        # Aggregate by revision.
        data = util.multidict((rev, (val, date)) for val,rev,date in q).items()
        data.sort(key=lambda sample: convert_revision(sample[0]))

        graph_datum.append((test.name, data, col, field))

        # Get baselines for this line
        num_baselines = len(baseline_parameters)
        for baseline_id, (baseline, baseline_title) in enumerate(baseline_parameters):
            q_baseline = ts.query(field.column, ts.Order.llvm_project_revision, ts.Run.start_time, ts.Machine.name).\
                         join(ts.Run).join(ts.Order).join(ts.Machine).\
                         filter(ts.Run.id == baseline.id).\
                         filter(ts.Sample.test == test).\
                         filter(field.column != None)
            # In the event of many samples, use the mean of the samples as the baseline.
            samples = []
            for sample in q_baseline:
                samples.append(sample[0])
            # Skip this baseline if there is no data.
            if not samples:
                continue
            mean = sum(samples)/len(samples)
            # Darken the baseline color distinguish from non-baselines.
            # Make a color closer to the sample than its neighbour.
            color_offset = float(baseline_id) / num_baselines / 2
            my_color = (i + color_offset) / num_plots
            dark_col = list(util.makeDarkerColor(my_color))
            str_dark_col =  util.toColorString(dark_col)
            baseline_plots.append({'color': str_dark_col,
                                   'lineWidth': 2,
                                   'yaxis': {'from': mean, 'to': mean},
                                   'name': q_baseline[0].llvm_project_revision})
            baseline_name = "Baseline {} on {}".format(baseline_title,  q_baseline[0].name)
            legend.append((BaselineLegendItem(baseline_name, baseline.id), test.name, field.name, dark_col))

    # Draw mean trend if requested.
    if mean_parameter:
        machine, field = mean_parameter
        test_name = 'Geometric Mean'

        col = (0,0,0)
        legend.append((machine, test_name, field.name, col))

        q = ts.query(sqlalchemy.sql.func.min(field.column),
                ts.Order.llvm_project_revision,
                sqlalchemy.sql.func.min(ts.Run.start_time)).\
            join(ts.Run).join(ts.Order).join(ts.Test).\
            filter(ts.Run.machine_id == machine.id).\
            filter(field.column != None).\
            group_by(ts.Order.llvm_project_revision, ts.Test)

        # Calculate geomean of each revision.
        data = util.multidict(((rev, date), val) for val,rev,date in q).items()
        data = [(rev, [(lnt.server.reporting.analysis.calc_geomean(vals), date)])
                for ((rev, date), vals) in data]

        # Sort data points according to revision number.
        data.sort(key=lambda sample: convert_revision(sample[0]))

        graph_datum.append((test_name, data, col, field))

    for name, data, col, field in graph_datum:
        # Compute the graph points.
        errorbar_data = []
        points_data = []
        pts = []
        moving_median_data = []
        moving_average_data = []

        if normalize_by_median:
            normalize_by = 1.0/stats.median([min([d[0] for d in values])
                                           for _,values in data])
        else:
            normalize_by = 1.0

        for pos, (point_label, datapoints) in enumerate(data):
            # Get the samples.
            data = [data_date[0] for data_date in datapoints]
            # And the date on which they were taken.
            dates = [data_date[1] for data_date in datapoints]

            # When we can, map x-axis to revisions, but when that is too hard
            # use the position of the sample instead.
            rev_x = convert_revision(point_label)
            x = rev_x[0] if len(rev_x)==1 else pos

            values = [v*normalize_by for v in data]
            aggregation_fn = min
            if field.bigger_is_better:
                aggregation_fn = max
            agg_value, agg_index = \
                aggregation_fn((value, index)
                               for (index, value) in enumerate(values))

            # Generate metadata.
            metadata = {"label":point_label}
            metadata["date"] = str(dates[agg_index])
            if len(graph_datum) > 1:
                # If there are more than one plot in the graph, also label the
                # test name.
                metadata["test_name"] = name

            pts.append((x, agg_value, metadata))

            # Add the individual points, if requested.
            # For each point add a text label for the mouse over.
            if not hide_all_points:
                for i,v in enumerate(values):
                    point_metadata = dict(metadata)
                    point_metadata["date"] = str(dates[i])
                    points_data.append((x, v, point_metadata))
            
            # Add the standard deviation error bar, if requested.
            if show_stddev:
                mean = stats.mean(values)
                sigma = stats.standard_deviation(values)
                errorbar_data.append((x, mean, sigma))

            # Add the MAD error bar, if requested.
            if show_mad:
                med = stats.median(values)
                mad = stats.median_absolute_deviation(values, med)
                errorbar_data.append((x, med, mad))

        # Compute the moving average and or moving median of our data if requested.
        if moving_average or moving_median:
            fun = None

            def compute_moving_average(x, window, average_list, median_list):
                average_list.append((x, lnt.util.stats.mean(window)))
            def compute_moving_median(x, window, average_list, median_list):
                median_list.append((x, lnt.util.stats.median(window)))
            def compute_moving_average_and_median(x, window, average_list, median_list):
                average_list.append((x, lnt.util.stats.mean(window)))
                median_list.append((x, lnt.util.stats.median(window)))

            if moving_average and moving_median:
                fun = compute_moving_average_and_median
            elif moving_average:
                fun = compute_moving_average
            else:
                fun = compute_moving_median

            len_pts = len(pts)
            for i in range(len_pts):
                start_index = max(0, i - moving_window_size)
                end_index = min(len_pts, i + moving_window_size)

                window_pts = [x[1] for x in pts[start_index:end_index]]
                fun(pts[i][0], window_pts, moving_average_data, moving_median_data)

        # On the overview, we always show the line plot.
        overview_plots.append({
                "data" : pts,
                "color" : util.toColorString(col) })

        # Add the minimum line plot, if requested.
        if show_lineplot:
            graph_plots.append({
                    "data" : pts,
                    "color" : util.toColorString(col) })

        # Add regression line, if requested.
        if show_linear_regression:
            xs = [t for t,v,_ in pts]
            ys = [v for t,v,_ in pts]

            # We compute the regression line in terms of a normalized X scale.
            x_min, x_max = min(xs), max(xs)
            try:
                norm_xs = [(x - x_min) / (x_max - x_min)
                           for x in xs]
            except ZeroDivisionError:
                norm_xs = xs

            try:
                info = ext_stats.linregress(norm_xs, ys)
            except ZeroDivisionError:
                info = None
            except ValueError:
                info = None

            if info is not None:
                slope, intercept,_,_,_ = info

                reglin_col = [c * .7 for c in col]
                reglin_pts = [(x_min, 0.0 * slope + intercept),
                              (x_max, 1.0 * slope + intercept)]
                graph_plots.insert(0, {
                        "data" : reglin_pts,
                        "color" : util.toColorString(reglin_col),
                        "lines" : {
                            "lineWidth" : 2 },
                        "shadowSize" : 4 })

        # Add the points plot, if used.
        if points_data:
            pts_col = (0,0,0)
            graph_plots.append({
                    "data" : points_data,
                    "color" : util.toColorString(pts_col),
                    "lines" : {
                        "show" : False },
                    "points" : {
                        "show" : True,
                        "radius" : .25,
                        "fill" : True } })

        # Add the error bar plot, if used.
        if errorbar_data:
            bar_col = [c*.7 for c in col]
            graph_plots.append({
                    "data" : errorbar_data,
                    "lines" : { "show" : False },
                    "color" : util.toColorString(bar_col),
                    "points" : {
                        "errorbars" : "y",
                        "yerr" : { "show" : True,
                                   "lowerCap" : "-",
                                   "upperCap" : "-",
                                   "lineWidth" : 1 } } })

        # Add the moving average plot, if used.
        if moving_average_data:
            col = [0.32, 0.6, 0.0]
            graph_plots.append({
                    "data" : moving_average_data,
                    "color" : util.toColorString(col) })


        # Add the moving median plot, if used.
        if moving_median_data:
            col = [0.75, 0.0, 1.0]
            graph_plots.append({
                    "data" : moving_median_data,
                    "color" : util.toColorString(col) })

    if bool(request.args.get('json')):
        json_obj = dict()
        json_obj['data'] = graph_plots
        # Flatten ORM machine objects to their string names.
        simple_type_legend = []
        for machine, test, unit, color in legend:
            # Flatten name, make color a dict.
            new_entry = {'name': machine.name,
                         'test': test,
                         'unit': unit,
                         'color': util.toColorString(color),}
            simple_type_legend.append(new_entry)
        json_obj['legend'] = simple_type_legend
        json_obj['revision_range'] = revision_range
        json_obj['current_options'] = options
        json_obj['test_suite_name'] = ts.name
        json_obj['baselines'] = baseline_plots
        return flask.jsonify(**json_obj)

    return render_template("v4_graph.html", ts=ts, options=options,
                           revision_range=revision_range,
                           graph_plots=graph_plots,
                           overview_plots=overview_plots, legend=legend,
                           baseline_plots=baseline_plots)
コード例 #2
0
ファイル: compile.py プロジェクト: AlmondLee/llvm_build
    def run_test(self, opts):

        # Resolve the cc_under_test path.
        opts.cc = resolve_command_path(opts.cc)

        if not lnt.testing.util.compilers.is_valid(opts.cc):
            self._fatal('--cc does not point to a valid executable.')

        # Attempt to infer the cxx compiler if not given.
        if opts.cc and opts.cxx is None:
            opts.cxx = lnt.testing.util.compilers.infer_cxx_compiler(opts.cc)
            if opts.cxx is not None:
                logger.info("inferred C++ compiler under test as: %r" %
                            (opts.cxx, ))

        if opts.cxx is None:
            self._fatal('--cxx is required (and could not be inferred)')

        # Force the CC and CXX variables to be absolute paths.
        cc_abs = os.path.abspath(commands.which(opts.cc))
        cxx_abs = os.path.abspath(commands.which(opts.cxx))

        if not os.path.exists(cc_abs):
            self._fatal("unable to determine absolute path for --cc: %r" %
                        (opts.cc, ))
        if not os.path.exists(cxx_abs):
            self._fatal("unable to determine absolute path for --cc: %r" %
                        (opts.cc, ))
        opts.cc = cc_abs
        opts.cxx = cxx_abs

        # If no ld was set, set ld to opts.cc
        if opts.ld is None:
            opts.ld = opts.cc
        # If no ldxx was set, set ldxx to opts.cxx
        if opts.ldxx is None:
            opts.ldxx = opts.cxx

        # Set up the sandbox.
        global g_output_dir
        if not os.path.exists(opts.sandbox_path):
            print >> sys.stderr, "%s: creating sandbox: %r" % (
                timestamp(), opts.sandbox_path)
            os.mkdir(opts.sandbox_path)
        if opts.timestamp_build:
            fmt_timestamp = timestamp().replace(' ', '_').replace(':', '-')
            report_name = "test-%s" % (fmt_timestamp)
        else:
            report_name = "build"
        g_output_dir = os.path.join(os.path.abspath(opts.sandbox_path),
                                    report_name)

        try:
            os.mkdir(g_output_dir)
        except OSError as e:
            if e.errno == errno.EEXIST:
                self._fatal("sandbox output directory %r already exists!" %
                            (g_output_dir, ))
            else:
                raise

        # Setup log file
        global g_log

        def setup_log(output_dir):
            def stderr_log_handler():
                h = logging.StreamHandler()
                f = logging.Formatter(
                    "%(asctime)-7s: %(levelname)s: %(message)s",
                    "%Y-%m-%d %H:%M:%S")
                h.setFormatter(f)
                return h

            def file_log_handler(path):
                h = logging.FileHandler(path, mode='w')
                f = logging.Formatter(
                    "%(asctime)-7s: %(levelname)s: %(message)s",
                    "%Y-%m-%d %H:%M:%S")
                h.setFormatter(f)
                return h

            log = logging.Logger('compile_test')
            log.setLevel(logging.INFO)
            log.addHandler(
                file_log_handler(os.path.join(output_dir, 'test.log')))
            log.addHandler(stderr_log_handler())
            return log

        g_log = setup_log(g_output_dir)

        # Collect machine and run information.
        machine_info, run_info = machineinfo.get_machine_information(
            opts.use_machdep_info)

        # FIXME: Include information on test source versions.
        #
        # FIXME: Get more machine information? Cocoa.h hash, for example.

        for name, cmd in (('sys_cc_version', ('/usr/bin/gcc', '-v')),
                          ('sys_as_version', ('/usr/bin/as', '-v',
                                              '/dev/null')),
                          ('sys_ld_version', ('/usr/bin/ld', '-v')),
                          ('sys_xcodebuild', ('xcodebuild', '-version'))):
            run_info[name] = commands.capture(cmd, include_stderr=True).strip()

        # Set command line machine and run information.
        for info, params in ((machine_info, opts.machine_parameters),
                             (run_info, opts.run_parameters)):
            for entry in params:
                if '=' not in entry:
                    name, value = entry, ''
                else:
                    name, value = entry.split('=', 1)
                info[name] = value

        # Set user variables.
        variables = {}
        variables['cc'] = opts.cc
        variables['run_count'] = opts.run_count

        # Get compiler info.
        cc_info = lnt.testing.util.compilers.get_cc_info(variables['cc'])
        variables.update(cc_info)

        # Set the run order from the user, if given.
        if opts.run_order is not None:
            variables['run_order'] = opts.run_order
        else:
            # Otherwise, use the inferred run order.
            variables['run_order'] = cc_info['inferred_run_order']
            logger.info("inferred run order to be: %r" %
                        (variables['run_order'], ))

        if opts.verbose:
            format = pprint.pformat(variables)
            msg = '\n\t'.join(['using variables:'] + format.splitlines())
            logger.info(msg)

            format = pprint.pformat(machine_info)
            msg = '\n\t'.join(['using machine info:'] + format.splitlines())
            logger.info(msg)

            format = pprint.pformat(run_info)
            msg = '\n\t'.join(['using run info:'] + format.splitlines())
            logger.info(msg)

        # Compute the set of flags to test.
        if not opts.flags_to_test:
            flags_to_test = DEFAULT_FLAGS_TO_TEST
        else:
            flags_to_test = [
                string.split(' ') for string in opts.flags_to_test
            ]

        # Compute the set of job counts to use in full build tests.
        if not opts.jobs_to_test:
            jobs_to_test = [1, 2, 4, 8]
        else:
            jobs_to_test = opts.jobs_to_test

        # Compute the build configurations to test.
        if not opts.configs_to_test:
            configs_to_test = ['Debug', 'Release']
        else:
            configs_to_test = opts.configs_to_test

        # Compute the list of all tests.
        all_tests = list(
            get_tests(opts.test_suite_externals, opts.test_subdir,
                      flags_to_test, jobs_to_test, configs_to_test))

        # Show the tests, if requested.
        if opts.show_tests:
            print >> sys.stderr, 'Available Tests'
            for name in sorted(set(name for name, _ in all_tests)):
                print >> sys.stderr, '  %s' % (name, )
            print
            raise SystemExit

        # Find the tests to run.
        if not opts.tests and not opts.test_filters:
            tests_to_run = list(all_tests)
        else:
            all_test_names = set(test[0] for test in all_tests)

            # Validate the test names.
            requested_tests = set(opts.tests)
            missing_tests = requested_tests - all_test_names
            if missing_tests:
                self._fatal(("invalid test names %s, use --show-tests to "
                             "see available tests") %
                            (", ".join(map(repr, missing_tests)), ))

            # Validate the test filters.
            test_filters = [
                re.compile(pattern) for pattern in opts.test_filters
            ]

            # Form the list of tests.
            tests_to_run = [
                test for test in all_tests if
                (test[0] in requested_tests or
                 [True for filter in test_filters if filter.search(test[0])])
            ]
        if not tests_to_run:
            self._fatal("no tests requested "
                        "(invalid --test or --test-filter options)!")

        # Ensure output directory is available.
        if not os.path.exists(g_output_dir):
            os.mkdir(g_output_dir)

        # Execute the run.
        run_info.update(variables)
        run_info['tag'] = tag = 'compile'

        testsamples = []
        start_time = datetime.utcnow()
        g_log.info('run started')
        g_log.info('using CC: %r' % opts.cc)
        g_log.info('using CXX: %r' % opts.cxx)
        no_errors = True
        for basename, test_fn in tests_to_run:
            for success, name, samples in test_fn(basename, run_info,
                                                  variables):
                g_log.info('collected samples: %r' % name)
                num_samples = len(samples)
                if num_samples:
                    samples_median = '%.4f' % (stats.median(samples), )
                    samples_mad = '%.4f' % (
                        stats.median_absolute_deviation(samples), )
                else:
                    samples_median = samples_mad = 'N/A'
                g_log.info('N=%d, median=%s, MAD=%s' %
                           (num_samples, samples_median, samples_mad))
                test_name = '%s.%s' % (tag, name)
                if not success:
                    testsamples.append(
                        lnt.testing.TestSamples(test_name + '.status',
                                                [lnt.testing.FAIL]))
                    no_errors = False
                if samples:
                    testsamples.append(
                        lnt.testing.TestSamples(test_name, samples))
        run_info['no_errors'] = no_errors
        end_time = datetime.utcnow()

        g_log.info('run complete')

        # Package up the report.
        machine = lnt.testing.Machine(opts.machine_name, machine_info)
        run = lnt.testing.Run(start_time, end_time, info=run_info)

        # Write out the report.
        lnt_report_path = os.path.join(g_output_dir, 'report.json')
        report = lnt.testing.Report(machine, run, testsamples)

        # Save report to disk for submission.
        self.print_report(report, lnt_report_path)

        # Then, also print to screen if requested.
        if opts.output is not None:
            self.print_report(report, opts.output)

        server_report = self.submit(lnt_report_path, opts, ts_name='compile')

        return server_report
コード例 #3
0
class CompileTest(builtintest.BuiltinTest):
    def describe(self):
        return 'Single file compile-time performance testing'

    def run_test(self, name, args):
        global opts
        parser = OptionParser(
            ("%(name)s [options] [<output file>]\n" + usage_info) % locals())
        parser.add_option("-s",
                          "--sandbox",
                          dest="sandbox_path",
                          help="Parent directory to build and run tests in",
                          type=str,
                          default=None,
                          metavar="PATH")

        group = OptionGroup(parser, "Test Options")
        group.add_option("",
                         "--no-timestamp",
                         dest="timestamp_build",
                         help="Don't timestamp build directory (for testing)",
                         action="store_false",
                         default=True)
        group.add_option("",
                         "--cc",
                         dest="cc",
                         type='str',
                         help="Path to the compiler under test",
                         action="store",
                         default=None)
        group.add_option("",
                         "--cxx",
                         dest="cxx",
                         help="Path to the C++ compiler to test",
                         type=str,
                         default=None)
        group.add_option(
            "",
            "--ld",
            dest="ld",
            help="Path to the c linker to use. (Xcode Distinction)",
            type=str,
            default=None)
        group.add_option(
            "",
            "--ldxx",
            dest="ldxx",
            help="Path to the cxx linker to use. (Xcode Distinction)",
            type=str,
            default=None)
        group.add_option("",
                         "--test-externals",
                         dest="test_suite_externals",
                         help="Path to the LLVM test-suite externals",
                         type=str,
                         default=None,
                         metavar="PATH")
        group.add_option("",
                         "--machine-param",
                         dest="machine_parameters",
                         metavar="NAME=VAL",
                         help="Add 'NAME' = 'VAL' to the machine parameters",
                         type=str,
                         action="append",
                         default=[])
        group.add_option("",
                         "--run-param",
                         dest="run_parameters",
                         metavar="NAME=VAL",
                         help="Add 'NAME' = 'VAL' to the run parameters",
                         type=str,
                         action="append",
                         default=[])
        group.add_option("",
                         "--run-order",
                         dest="run_order",
                         metavar="STR",
                         help="String to use to identify and order this run",
                         action="store",
                         type=str,
                         default=None)
        group.add_option(
            "",
            "--test-subdir",
            dest="test_subdir",
            help="Subdirectory of test external dir to look for tests in.",
            type=str,
            default="lnt-compile-suite-src")
        parser.add_option_group(group)

        group = OptionGroup(parser, "Test Selection")
        group.add_option("",
                         "--no-memory-profiling",
                         dest="memory_profiling",
                         help="Disable memory profiling",
                         action="store_false",
                         default=True)
        group.add_option("",
                         "--multisample",
                         dest="run_count",
                         metavar="N",
                         help="Accumulate test data from multiple runs",
                         action="store",
                         type=int,
                         default=3)
        group.add_option("",
                         "--min-sample-time",
                         dest="min_sample_time",
                         help="Ensure all tests run for at least N seconds",
                         metavar="N",
                         action="store",
                         type=float,
                         default=.5)
        group.add_option("",
                         "--save-temps",
                         dest="save_temps",
                         help="Save temporary build output files",
                         action="store_true",
                         default=False)
        group.add_option(
            "",
            "--show-tests",
            dest="show_tests",
            help="Only list the availables tests that will be run",
            action="store_true",
            default=False)
        group.add_option("",
                         "--test",
                         dest="tests",
                         metavar="NAME",
                         help="Individual test to run",
                         action="append",
                         default=[])
        group.add_option("",
                         "--test-filter",
                         dest="test_filters",
                         help="Run tests matching the given pattern",
                         metavar="REGEXP",
                         action="append",
                         default=[])
        group.add_option("",
                         "--flags-to-test",
                         dest="flags_to_test",
                         help="Add a set of flags to test (space separated)",
                         metavar="FLAGLIST",
                         action="append",
                         default=[])
        group.add_option("",
                         "--jobs-to-test",
                         dest="jobs_to_test",
                         help="Add a job count to test (full builds)",
                         metavar="NUM",
                         action="append",
                         default=[],
                         type=int)
        group.add_option("",
                         "--config-to-test",
                         dest="configs_to_test",
                         help="Add build configuration to test (full builds)",
                         metavar="NAME",
                         action="append",
                         default=[],
                         choices=('Debug', 'Release'))
        parser.add_option_group(group)

        group = OptionGroup(parser, "Output Options")
        group.add_option("",
                         "--no-machdep-info",
                         dest="use_machdep_info",
                         help=("Don't put machine (instance) dependent "
                               "variables in machine info"),
                         action="store_false",
                         default=True)
        group.add_option("",
                         "--machine-name",
                         dest="machine_name",
                         type='str',
                         help="Machine name to use in submission [%default]",
                         action="store",
                         default=platform.uname()[1])
        group.add_option(
            "",
            "--submit",
            dest="submit_url",
            metavar="URLORPATH",
            help=("autosubmit the test result to the given server "
                  "(or local instance) [%default]"),
            type=str,
            default=None)
        group.add_option(
            "",
            "--commit",
            dest="commit",
            help=("whether the autosubmit result should be committed "
                  "[%default]"),
            type=int,
            default=True)
        group.add_option(
            "",
            "--output",
            dest="output",
            metavar="PATH",
            help="write raw report data to PATH (or stdout if '-')",
            action="store",
            default=None)
        group.add_option("-v",
                         "--verbose",
                         dest="verbose",
                         help="show verbose test results",
                         action="store_true",
                         default=False)

        parser.add_option_group(group)

        opts, args = parser.parse_args(args)

        if len(args) != 0:
            parser.error("invalid number of arguments")

        if opts.cc is None:
            parser.error("You must specify a --cc argument.")

        # Resolve the cc_under_test path.
        opts.cc = resolve_command_path(opts.cc)

        if not lnt.testing.util.compilers.is_valid(opts.cc):
            parser.error('--cc does not point to a valid executable.')

        # Attempt to infer the cxx compiler if not given.
        if opts.cc and opts.cxx is None:
            opts.cxx = lnt.testing.util.compilers.infer_cxx_compiler(opts.cc)
            if opts.cxx is not None:
                note("inferred C++ compiler under test as: %r" % (opts.cxx, ))

        # Validate options.
        if opts.cc is None:
            parser.error('--cc is required')
        if opts.cxx is None:
            parser.error('--cxx is required (and could not be inferred)')
        if opts.sandbox_path is None:
            parser.error('--sandbox is required')
        if opts.test_suite_externals is None:
            parser.error("--test-externals option is required")

        # Force the CC and CXX variables to be absolute paths.
        cc_abs = os.path.abspath(commands.which(opts.cc))
        cxx_abs = os.path.abspath(commands.which(opts.cxx))

        if not os.path.exists(cc_abs):
            parser.error("unable to determine absolute path for --cc: %r" %
                         (opts.cc, ))
        if not os.path.exists(cxx_abs):
            parser.error("unable to determine absolute path for --cc: %r" %
                         (opts.cc, ))
        opts.cc = cc_abs
        opts.cxx = cxx_abs

        # If no ld was set, set ld to opts.cc
        if opts.ld is None:
            opts.ld = opts.cc
        # If no ldxx was set, set ldxx to opts.cxx
        if opts.ldxx is None:
            opts.ldxx = opts.cxx

        # Set up the sandbox.
        global g_output_dir
        if not os.path.exists(opts.sandbox_path):
            print >> sys.stderr, "%s: creating sandbox: %r" % (
                timestamp(), opts.sandbox_path)
            os.mkdir(opts.sandbox_path)
        if opts.timestamp_build:
            report_name = "test-%s" % (timestamp().replace(' ', '_').replace(
                ':', '-'))
        else:
            report_name = "build"
        g_output_dir = os.path.join(os.path.abspath(opts.sandbox_path),
                                    report_name)

        try:
            os.mkdir(g_output_dir)
        except OSError, e:
            if e.errno == errno.EEXIST:
                parser.error("sandbox output directory %r already exists!" %
                             (g_output_dir, ))
            else:
                raise

        # Setup log file
        global g_log

        def setup_log(output_dir):
            def stderr_log_handler():
                h = logging.StreamHandler()
                f = logging.Formatter(
                    "%(asctime)-7s: %(levelname)s: %(message)s",
                    "%Y-%m-%d %H:%M:%S")
                h.setFormatter(f)
                return h

            def file_log_handler(path):
                h = logging.FileHandler(path, mode='w')
                f = logging.Formatter(
                    "%(asctime)-7s: %(levelname)s: %(message)s",
                    "%Y-%m-%d %H:%M:%S")
                h.setFormatter(f)
                return h

            l = logging.Logger('compile_test')
            l.setLevel(logging.INFO)
            l.addHandler(file_log_handler(os.path.join(output_dir,
                                                       'test.log')))
            l.addHandler(stderr_log_handler())
            return l

        g_log = setup_log(g_output_dir)

        # Collect machine and run information.
        machine_info, run_info = machineinfo.get_machine_information(
            opts.use_machdep_info)

        # FIXME: Include information on test source versions.
        #
        # FIXME: Get more machine information? Cocoa.h hash, for example.

        for name, cmd in (('sys_cc_version', ('/usr/bin/gcc', '-v')),
                          ('sys_as_version', ('/usr/bin/as', '-v',
                                              '/dev/null')),
                          ('sys_ld_version', ('/usr/bin/ld', '-v')),
                          ('sys_xcodebuild', ('xcodebuild', '-version'))):
            run_info[name] = commands.capture(cmd, include_stderr=True).strip()

        # Set command line machine and run information.
        for info, params in ((machine_info, opts.machine_parameters),
                             (run_info, opts.run_parameters)):
            for entry in params:
                if '=' not in entry:
                    name, value = entry, ''
                else:
                    name, value = entry.split('=', 1)
                info[name] = value

        # Set user variables.
        variables = {}
        variables['cc'] = opts.cc
        variables['run_count'] = opts.run_count

        # Get compiler info.
        cc_info = lnt.testing.util.compilers.get_cc_info(variables['cc'])
        variables.update(cc_info)

        # Set the run order from the user, if given.
        if opts.run_order is not None:
            variables['run_order'] = opts.run_order
        else:
            # Otherwise, use the inferred run order.
            variables['run_order'] = cc_info['inferred_run_order']
            note("inferred run order to be: %r" % (variables['run_order'], ))

        if opts.verbose:
            format = pprint.pformat(variables)
            msg = '\n\t'.join(['using variables:'] + format.splitlines())
            note(msg)

            format = pprint.pformat(machine_info)
            msg = '\n\t'.join(['using machine info:'] + format.splitlines())
            note(msg)

            format = pprint.pformat(run_info)
            msg = '\n\t'.join(['using run info:'] + format.splitlines())
            note(msg)

        # Compute the set of flags to test.
        if not opts.flags_to_test:
            flags_to_test = [('-O0', ), (
                '-O0',
                '-g',
            ), ('-Os', '-g'), ('-O3', )]
        else:
            flags_to_test = [
                string.split(' ') for string in opts.flags_to_test
            ]

        # Compute the set of job counts to use in full build tests.
        if not opts.jobs_to_test:
            jobs_to_test = [1, 2, 4, 8]
        else:
            jobs_to_test = opts.jobs_to_test

        # Compute the build configurations to test.
        if not opts.configs_to_test:
            configs_to_test = ['Debug', 'Release']
        else:
            configs_to_test = opts.configs_to_test

        # Compute the list of all tests.
        all_tests = list(
            get_tests(opts.test_suite_externals, opts.test_subdir,
                      flags_to_test, jobs_to_test, configs_to_test))

        # Show the tests, if requested.
        if opts.show_tests:
            print >> sys.stderr, 'Available Tests'
            for name in sorted(set(name for name, _ in all_tests)):
                print >> sys.stderr, '  %s' % (name, )
            print
            raise SystemExit

        # Find the tests to run.
        if not opts.tests and not opts.test_filters:
            tests_to_run = list(all_tests)
        else:
            all_test_names = set(test[0] for test in all_tests)

            # Validate the test names.
            requested_tests = set(opts.tests)
            missing_tests = requested_tests - all_test_names
            if missing_tests:
                parser.error(("invalid test names %s, use --show-tests to "
                              "see available tests") %
                             (", ".join(map(repr, missing_tests)), ))

            # Validate the test filters.
            test_filters = [
                re.compile(pattern) for pattern in opts.test_filters
            ]

            # Form the list of tests.
            tests_to_run = [
                test for test in all_tests if
                (test[0] in requested_tests or
                 [True for filter in test_filters if filter.search(test[0])])
            ]
        if not tests_to_run:
            parser.error(
                "no tests requested (invalid --test or --test-filter options)!"
            )

        # Ensure output directory is available.
        if not os.path.exists(g_output_dir):
            os.mkdir(g_output_dir)

        # Execute the run.
        run_info.update(variables)
        run_info['tag'] = tag = 'compile'

        testsamples = []
        start_time = datetime.utcnow()
        g_log.info('run started')
        g_log.info('using CC: %r' % opts.cc)
        g_log.info('using CXX: %r' % opts.cxx)
        for basename, test_fn in tests_to_run:
            for success, name, samples in test_fn(basename, run_info,
                                                  variables):
                g_log.info('collected samples: %r' % name)
                num_samples = len(samples)
                if num_samples:
                    samples_median = '%.4f' % (stats.median(samples), )
                    samples_mad = '%.4f' % (
                        stats.median_absolute_deviation(samples), )
                else:
                    samples_median = samples_mad = 'N/A'
                g_log.info('N=%d, median=%s, MAD=%s' %
                           (num_samples, samples_median, samples_mad))
                test_name = '%s.%s' % (tag, name)
                if not success:
                    testsamples.append(
                        lnt.testing.TestSamples(test_name + '.status',
                                                [lnt.testing.FAIL]))
                if samples:
                    testsamples.append(
                        lnt.testing.TestSamples(test_name, samples))
        end_time = datetime.utcnow()

        g_log.info('run complete')

        # Package up the report.
        machine = lnt.testing.Machine(opts.machine_name, machine_info)
        run = lnt.testing.Run(start_time, end_time, info=run_info)

        # Write out the report.
        lnt_report_path = os.path.join(g_output_dir, 'report.json')
        report = lnt.testing.Report(machine, run, testsamples)

        # Save report to disk for submission.
        self.print_report(report, lnt_report_path)

        # Then, also print to screen if requested.
        if opts.output is not None:
            self.print_report(report, opts.output)

        server_report = self.submit(lnt_report_path, opts)

        return server_report
コード例 #4
0
    def __init__(self,
                 aggregation_fn,
                 cur_failed,
                 prev_failed,
                 samples,
                 prev_samples,
                 cur_hash,
                 prev_hash,
                 cur_profile=None,
                 prev_profile=None,
                 confidence_lv=0.05,
                 bigger_is_better=False,
                 stable_test=True):
        self.aggregation_fn = aggregation_fn

        # Special case: if we're using the minimum to aggregate, swap it for max
        # if bigger_is_better.
        if aggregation_fn == stats.safe_min and bigger_is_better:
            aggregation_fn = stats.safe_max

        self.cur_hash = cur_hash
        self.prev_hash = prev_hash
        self.cur_profile = cur_profile
        self.prev_profile = prev_profile

        if samples:
            self.current = aggregation_fn(samples)
        else:
            self.current = None

        self.previous = None
        # Compute the comparison status for the test value.
        self.delta = 0
        self.pct_delta = 0.0
        if self.current and prev_samples:
            prev_median = stats.median(prev_samples)
            self.delta, value = absmin_diff(self.current, [prev_median])
            if value != 0:
                self.pct_delta = self.delta / value
            self.previous = value

        # If we have multiple values for this run, use that to estimate the
        # distribution.
        #
        # We can get integer sample types here - for example if the field is
        # .exec.status. Make sure we don't assert by avoiding the stats
        # functions in this case.
        if samples and len(samples) > 1 and isinstance(samples[0], float):
            self.stddev = stats.standard_deviation(samples)
            self.MAD = stats.median_absolute_deviation(samples)
            self.variance = stats.variance(samples)
        else:
            self.stddev = None
            self.MAD = None
            self.variance = None

        if prev_samples and len(prev_samples) > 1 and isinstance(
                prev_samples[0], float):
            self.prev_stddev = stats.standard_deviation(prev_samples)
            self.prev_MAD = stats.median_absolute_deviation(prev_samples)
            self.prev_variance = stats.variance(prev_samples)
        else:
            self.prev_stddev = None
            self.prev_MAD = None
            self.prev_variance = None

        self.stddev_mean = None  # Only calculate this if needed.
        self.failed = cur_failed
        self.prev_failed = prev_failed
        self.samples = samples
        self.prev_samples = prev_samples

        self.confidence_lv = confidence_lv
        self.bigger_is_better = bigger_is_better
        self.stable_test = stable_test
コード例 #5
0
ファイル: compile.py プロジェクト: llvm-mirror/lnt
    def run_test(self, opts):

        # Resolve the cc_under_test path.
        opts.cc = resolve_command_path(opts.cc)

        if not lnt.testing.util.compilers.is_valid(opts.cc):
            self._fatal('--cc does not point to a valid executable.')

        # Attempt to infer the cxx compiler if not given.
        if opts.cc and opts.cxx is None:
            opts.cxx = lnt.testing.util.compilers.infer_cxx_compiler(opts.cc)
            if opts.cxx is not None:
                logger.info("inferred C++ compiler under test as: %r" %
                            (opts.cxx,))

        if opts.cxx is None:
            self._fatal('--cxx is required (and could not be inferred)')

        # Force the CC and CXX variables to be absolute paths.
        cc_abs = os.path.abspath(commands.which(opts.cc))
        cxx_abs = os.path.abspath(commands.which(opts.cxx))

        if not os.path.exists(cc_abs):
            self._fatal("unable to determine absolute path for --cc: %r" % (
                opts.cc,))
        if not os.path.exists(cxx_abs):
            self._fatal("unable to determine absolute path for --cc: %r" % (
                opts.cc,))
        opts.cc = cc_abs
        opts.cxx = cxx_abs

        # If no ld was set, set ld to opts.cc
        if opts.ld is None:
            opts.ld = opts.cc
        # If no ldxx was set, set ldxx to opts.cxx
        if opts.ldxx is None:
            opts.ldxx = opts.cxx

        # Set up the sandbox.
        global g_output_dir
        if not os.path.exists(opts.sandbox_path):
            print >>sys.stderr, "%s: creating sandbox: %r" % (
                timestamp(), opts.sandbox_path)
            os.mkdir(opts.sandbox_path)
        if opts.timestamp_build:
            fmt_timestamp = timestamp().replace(' ', '_').replace(':', '-')
            report_name = "test-%s" % (fmt_timestamp)
        else:
            report_name = "build"
        g_output_dir = os.path.join(os.path.abspath(opts.sandbox_path),
                                    report_name)

        try:
            os.mkdir(g_output_dir)
        except OSError as e:
            if e.errno == errno.EEXIST:
                self._fatal("sandbox output directory %r already exists!" % (
                    g_output_dir,))
            else:
                raise

        # Setup log file
        global g_log

        def setup_log(output_dir):
            def stderr_log_handler():
                h = logging.StreamHandler()
                f = logging.Formatter(
                    "%(asctime)-7s: %(levelname)s: %(message)s",
                    "%Y-%m-%d %H:%M:%S")
                h.setFormatter(f)
                return h

            def file_log_handler(path):
                h = logging.FileHandler(path, mode='w')
                f = logging.Formatter(
                    "%(asctime)-7s: %(levelname)s: %(message)s",
                    "%Y-%m-%d %H:%M:%S")
                h.setFormatter(f)
                return h
            log = logging.Logger('compile_test')
            log.setLevel(logging.INFO)
            log.addHandler(file_log_handler(os.path.join(output_dir,
                                                         'test.log')))
            log.addHandler(stderr_log_handler())
            return log
        g_log = setup_log(g_output_dir)

        # Collect machine and run information.
        machine_info, run_info = machineinfo.get_machine_information(
            opts.use_machdep_info)

        # FIXME: Include information on test source versions.
        #
        # FIXME: Get more machine information? Cocoa.h hash, for example.

        for name, cmd in (('sys_cc_version', ('/usr/bin/gcc', '-v')),
                          ('sys_as_version',
                           ('/usr/bin/as', '-v', '/dev/null')),
                          ('sys_ld_version', ('/usr/bin/ld', '-v')),
                          ('sys_xcodebuild', ('xcodebuild', '-version'))):
            run_info[name] = commands.capture(cmd, include_stderr=True).strip()

        # Set command line machine and run information.
        for info, params in ((machine_info, opts.machine_parameters),
                             (run_info, opts.run_parameters)):
            for entry in params:
                if '=' not in entry:
                    name, value = entry, ''
                else:
                    name, value = entry.split('=', 1)
                info[name] = value

        # Set user variables.
        variables = {}
        variables['cc'] = opts.cc
        variables['run_count'] = opts.run_count

        # Get compiler info.
        cc_info = lnt.testing.util.compilers.get_cc_info(variables['cc'])
        variables.update(cc_info)

        # Set the run order from the user, if given.
        if opts.run_order is not None:
            variables['run_order'] = opts.run_order
        else:
            # Otherwise, use the inferred run order.
            variables['run_order'] = cc_info['inferred_run_order']
            logger.info("inferred run order to be: %r" %
                        (variables['run_order'],))

        if opts.verbose:
            format = pprint.pformat(variables)
            msg = '\n\t'.join(['using variables:'] + format.splitlines())
            logger.info(msg)

            format = pprint.pformat(machine_info)
            msg = '\n\t'.join(['using machine info:'] + format.splitlines())
            logger.info(msg)

            format = pprint.pformat(run_info)
            msg = '\n\t'.join(['using run info:'] + format.splitlines())
            logger.info(msg)

        # Compute the set of flags to test.
        if not opts.flags_to_test:
            flags_to_test = DEFAULT_FLAGS_TO_TEST
        else:
            flags_to_test = [string.split(' ')
                             for string in opts.flags_to_test]

        # Compute the set of job counts to use in full build tests.
        if not opts.jobs_to_test:
            jobs_to_test = [1, 2, 4, 8]
        else:
            jobs_to_test = opts.jobs_to_test

        # Compute the build configurations to test.
        if not opts.configs_to_test:
            configs_to_test = ['Debug', 'Release']
        else:
            configs_to_test = opts.configs_to_test

        # Compute the list of all tests.
        all_tests = list(get_tests(opts.test_suite_externals, opts.test_subdir,
                                   flags_to_test, jobs_to_test,
                                   configs_to_test))

        # Show the tests, if requested.
        if opts.show_tests:
            print >>sys.stderr, 'Available Tests'
            for name in sorted(set(name for name, _ in all_tests)):
                print >>sys.stderr, '  %s' % (name,)
            print
            raise SystemExit

        # Find the tests to run.
        if not opts.tests and not opts.test_filters:
            tests_to_run = list(all_tests)
        else:
            all_test_names = set(test[0] for test in all_tests)

            # Validate the test names.
            requested_tests = set(opts.tests)
            missing_tests = requested_tests - all_test_names
            if missing_tests:
                self._fatal(("invalid test names %s, use --show-tests to "
                             "see available tests") %
                            (", ".join(map(repr, missing_tests)), ))

            # Validate the test filters.
            test_filters = [re.compile(pattern)
                            for pattern in opts.test_filters]

            # Form the list of tests.
            tests_to_run = [test
                            for test in all_tests
                            if (test[0] in requested_tests or
                                [True
                                 for filter in test_filters
                                 if filter.search(test[0])])]
        if not tests_to_run:
            self._fatal("no tests requested "
                        "(invalid --test or --test-filter options)!")

        # Ensure output directory is available.
        if not os.path.exists(g_output_dir):
            os.mkdir(g_output_dir)

        # Execute the run.
        run_info.update(variables)
        run_info['tag'] = tag = 'compile'

        testsamples = []
        start_time = datetime.utcnow()
        g_log.info('run started')
        g_log.info('using CC: %r' % opts.cc)
        g_log.info('using CXX: %r' % opts.cxx)
        no_errors = True
        for basename, test_fn in tests_to_run:
            for success, name, samples in test_fn(basename, run_info,
                                                  variables):
                g_log.info('collected samples: %r' % name)
                num_samples = len(samples)
                if num_samples:
                    samples_median = '%.4f' % (stats.median(samples),)
                    samples_mad = '%.4f' % (
                        stats.median_absolute_deviation(samples),)
                else:
                    samples_median = samples_mad = 'N/A'
                g_log.info('N=%d, median=%s, MAD=%s' % (
                    num_samples, samples_median, samples_mad))
                test_name = '%s.%s' % (tag, name)
                if not success:
                    testsamples.append(lnt.testing.TestSamples(
                        test_name + '.status', [lnt.testing.FAIL]))
                    no_errors = False
                if samples:
                    testsamples.append(lnt.testing.TestSamples(
                        test_name, samples))
        run_info['no_errors'] = no_errors
        end_time = datetime.utcnow()

        g_log.info('run complete')

        # Package up the report.
        machine = lnt.testing.Machine(opts.machine_name, machine_info)
        run = lnt.testing.Run(start_time, end_time, info=run_info)

        # Write out the report.
        lnt_report_path = os.path.join(g_output_dir, 'report.json')
        report = lnt.testing.Report(machine, run, testsamples)

        # Save report to disk for submission.
        self.print_report(report, lnt_report_path)

        # Then, also print to screen if requested.
        if opts.output is not None:
            self.print_report(report, opts.output)

        server_report = self.submit(lnt_report_path, opts, ts_name='compile')

        return server_report
コード例 #6
0
ファイル: compile.py プロジェクト: couchbaselabs/cbnt
    def run_test(self, name, args):
        global opts
        parser = OptionParser(
            ("%(name)s [options] [<output file>]\n" +
             usage_info) % locals())
        parser.add_option("-s", "--sandbox", dest="sandbox_path",
                          help="Parent directory to build and run tests in",
                          type=str, default=None, metavar="PATH")

        group = OptionGroup(parser, "Test Options")
        group.add_option("", "--no-timestamp", dest="timestamp_build",
                         help="Don't timestamp build directory (for testing)",
                         action="store_false", default=True)
        group.add_option("", "--cc", dest="cc", type='str',
                         help="Path to the compiler under test",
                         action="store", default=None)
        group.add_option("", "--cxx", dest="cxx",
                         help="Path to the C++ compiler to test",
                         type=str, default=None)
        group.add_option("", "--ld", dest="ld",
                         help="Path to the c linker to use. (Xcode Distinction)",
                         type=str, default=None)
        group.add_option("", "--ldxx", dest="ldxx",
                         help="Path to the cxx linker to use. (Xcode Distinction)",
                         type=str, default=None)
        group.add_option("", "--test-externals", dest="test_suite_externals",
                         help="Path to the LLVM test-suite externals",
                         type=str, default=None, metavar="PATH")
        group.add_option("", "--machine-param", dest="machine_parameters",
                         metavar="NAME=VAL",
                         help="Add 'NAME' = 'VAL' to the machine parameters",
                         type=str, action="append", default=[])
        group.add_option("", "--run-param", dest="run_parameters",
                         metavar="NAME=VAL",
                         help="Add 'NAME' = 'VAL' to the run parameters",
                         type=str, action="append", default=[])
        group.add_option("", "--run-order", dest="run_order", metavar="STR",
                         help="String to use to identify and order this run",
                         action="store", type=str, default=None)
        group.add_option("", "--test-subdir", dest="test_subdir",
                         help="Subdirectory of test external dir to look for tests in.",
                         type=str, default="lnt-compile-suite-src")
        parser.add_option_group(group)

        group = OptionGroup(parser, "Test Selection")
        group.add_option("", "--no-memory-profiling", dest="memory_profiling",
                         help="Disable memory profiling",
                         action="store_false", default=True)
        group.add_option("", "--multisample", dest="run_count", metavar="N",
                         help="Accumulate test data from multiple runs",
                         action="store", type=int, default=3)
        group.add_option("", "--min-sample-time", dest="min_sample_time",
                         help="Ensure all tests run for at least N seconds",
                         metavar="N", action="store", type=float, default=.5)
        group.add_option("", "--save-temps", dest="save_temps",
                         help="Save temporary build output files",
                         action="store_true", default=False)
        group.add_option("", "--show-tests", dest="show_tests",
                         help="Only list the availables tests that will be run",
                         action="store_true", default=False)
        group.add_option("", "--test", dest="tests", metavar="NAME",
                         help="Individual test to run",
                         action="append", default=[])
        group.add_option("", "--test-filter", dest="test_filters",
                         help="Run tests matching the given pattern",
                         metavar="REGEXP", action="append", default=[])
        group.add_option("", "--flags-to-test", dest="flags_to_test",
                         help="Add a set of flags to test (space separated)",
                         metavar="FLAGLIST", action="append", default=[])
        group.add_option("", "--jobs-to-test", dest="jobs_to_test",
                         help="Add a job count to test (full builds)",
                         metavar="NUM", action="append", default=[], type=int)
        group.add_option("", "--config-to-test", dest="configs_to_test",
                         help="Add build configuration to test (full builds)",
                         metavar="NAME", action="append", default=[],
                         choices=('Debug', 'Release'))
        parser.add_option_group(group)

        group = OptionGroup(parser, "Output Options")
        group.add_option("", "--no-machdep-info", dest="use_machdep_info",
                         help=("Don't put machine (instance) dependent "
                               "variables in machine info"),
                         action="store_false", default=True)
        group.add_option("", "--machine-name", dest="machine_name", type='str',
                         help="Machine name to use in submission [%default]",
                         action="store", default=platform.uname()[1])
        group.add_option("", "--submit", dest="submit_url", metavar="URLORPATH",
                         help=("autosubmit the test result to the given server "
                               "(or local instance) [%default]"),
                         type=str, default=None)
        group.add_option("", "--commit", dest="commit",
                         help=("whether the autosubmit result should be committed "
                               "[%default]"),
                         type=int, default=True)
        group.add_option("", "--output", dest="output", metavar="PATH",
                         help="write raw report data to PATH (or stdout if '-')",
                         action="store", default=None)
        group.add_option("-v", "--verbose", dest="verbose",
                         help="show verbose test results",
                         action="store_true", default=False)

        parser.add_option_group(group)

        opts, args = parser.parse_args(args)

        if len(args) != 0:
            parser.error("invalid number of arguments")

        if opts.cc is None:
            parser.error("You must specify a --cc argument.")

        # Resolve the cc_under_test path.
        opts.cc = resolve_command_path(opts.cc)

        if not lnt.testing.util.compilers.is_valid(opts.cc):
            parser.error('--cc does not point to a valid executable.')

        # Attempt to infer the cxx compiler if not given.
        if opts.cc and opts.cxx is None:
            opts.cxx = lnt.testing.util.compilers.infer_cxx_compiler(opts.cc)
            if opts.cxx is not None:
                note("inferred C++ compiler under test as: %r" % (opts.cxx,))

        # Validate options.
        if opts.cc is None:
            parser.error('--cc is required')
        if opts.cxx is None:
            parser.error('--cxx is required (and could not be inferred)')
        if opts.sandbox_path is None:
            parser.error('--sandbox is required')
        if opts.test_suite_externals is None:
            parser.error("--test-externals option is required")

        # Force the CC and CXX variables to be absolute paths.
        cc_abs = os.path.abspath(commands.which(opts.cc))
        cxx_abs = os.path.abspath(commands.which(opts.cxx))

        if not os.path.exists(cc_abs):
            parser.error("unable to determine absolute path for --cc: %r" % (
                         opts.cc,))
        if not os.path.exists(cxx_abs):
            parser.error("unable to determine absolute path for --cc: %r" % (
                         opts.cc,))
        opts.cc = cc_abs
        opts.cxx = cxx_abs

        # If no ld was set, set ld to opts.cc
        if opts.ld is None:
            opts.ld = opts.cc
        # If no ldxx was set, set ldxx to opts.cxx
        if opts.ldxx is None:
            opts.ldxx = opts.cxx

        # Set up the sandbox.
        global g_output_dir
        if not os.path.exists(opts.sandbox_path):
            print >>sys.stderr, "%s: creating sandbox: %r" % (
                timestamp(), opts.sandbox_path)
            os.mkdir(opts.sandbox_path)
        if opts.timestamp_build:
            fmt_timestamp = timestamp().replace(' ', '_').replace(':', '-')
            report_name = "test-%s" % (fmt_timestamp)
        else:
            report_name = "build"
        g_output_dir = os.path.join(os.path.abspath(opts.sandbox_path),
                                    report_name)

        try:
            os.mkdir(g_output_dir)
        except OSError(e):
            if e.errno == errno.EEXIST:
                parser.error("sandbox output directory %r already exists!" % (
                             g_output_dir,))
            else:
                raise

        # Setup log file
        global g_log

        def setup_log(output_dir):
            def stderr_log_handler():
                h = logging.StreamHandler()
                f = logging.Formatter("%(asctime)-7s: %(levelname)s: %(message)s",
                                      "%Y-%m-%d %H:%M:%S")
                h.setFormatter(f)
                return h

            def file_log_handler(path):
                h = logging.FileHandler(path, mode='w')
                f = logging.Formatter("%(asctime)-7s: %(levelname)s: %(message)s",
                                      "%Y-%m-%d %H:%M:%S")
                h.setFormatter(f)
                return h
            l = logging.Logger('compile_test')
            l.setLevel(logging.INFO)
            l.addHandler(file_log_handler(os.path.join(output_dir, 'test.log')))
            l.addHandler(stderr_log_handler())
            return l
        g_log = setup_log(g_output_dir)

        # Collect machine and run information.
        machine_info, run_info = machineinfo.get_machine_information(
            opts.use_machdep_info)

        # FIXME: Include information on test source versions.
        #
        # FIXME: Get more machine information? Cocoa.h hash, for example.

        for name, cmd in (('sys_cc_version', ('/usr/bin/gcc', '-v')),
                          ('sys_as_version', ('/usr/bin/as', '-v', '/dev/null')),
                          ('sys_ld_version', ('/usr/bin/ld', '-v')),
                          ('sys_xcodebuild', ('xcodebuild', '-version'))):
            run_info[name] = commands.capture(cmd, include_stderr=True).strip()

        # Set command line machine and run information.
        for info, params in ((machine_info, opts.machine_parameters),
                             (run_info, opts.run_parameters)):
            for entry in params:
                if '=' not in entry:
                    name, value = entry, ''
                else:
                    name, value = entry.split('=', 1)
                info[name] = value

        # Set user variables.
        variables = {}
        variables['cc'] = opts.cc
        variables['run_count'] = opts.run_count

        # Get compiler info.
        cc_info = lnt.testing.util.compilers.get_cc_info(variables['cc'])
        variables.update(cc_info)

        # Set the run order from the user, if given.
        if opts.run_order is not None:
            variables['run_order'] = opts.run_order
        else:
            # Otherwise, use the inferred run order.
            variables['run_order'] = cc_info['inferred_run_order']
            note("inferred run order to be: %r" % (variables['run_order'],))

        if opts.verbose:
            format = pprint.pformat(variables)
            msg = '\n\t'.join(['using variables:'] + format.splitlines())
            note(msg)

            format = pprint.pformat(machine_info)
            msg = '\n\t'.join(['using machine info:'] + format.splitlines())
            note(msg)

            format = pprint.pformat(run_info)
            msg = '\n\t'.join(['using run info:'] + format.splitlines())
            note(msg)

        # Compute the set of flags to test.
        if not opts.flags_to_test:
            flags_to_test = DEFAULT_FLAGS_TO_TEST
        else:
            flags_to_test = [string.split(' ')
                             for string in opts.flags_to_test]

        # Compute the set of job counts to use in full build tests.
        if not opts.jobs_to_test:
            jobs_to_test = [1, 2, 4, 8]
        else:
            jobs_to_test = opts.jobs_to_test

        # Compute the build configurations to test.
        if not opts.configs_to_test:
            configs_to_test = ['Debug', 'Release']
        else:
            configs_to_test = opts.configs_to_test

        # Compute the list of all tests.
        all_tests = list(get_tests(opts.test_suite_externals, opts.test_subdir,
                                   flags_to_test, jobs_to_test,
                                   configs_to_test))

        # Show the tests, if requested.
        if opts.show_tests:
            print >>sys.stderr, 'Available Tests'
            for name in sorted(set(name for name, _ in all_tests)):
                print >>sys.stderr, '  %s' % (name,)
            print
            raise SystemExit

        # Find the tests to run.
        if not opts.tests and not opts.test_filters:
            tests_to_run = list(all_tests)
        else:
            all_test_names = set(test[0] for test in all_tests)

            # Validate the test names.
            requested_tests = set(opts.tests)
            missing_tests = requested_tests - all_test_names
            if missing_tests:
                    parser.error(("invalid test names %s, use --show-tests to "
                                  "see available tests") %
                                 (", ".join(map(repr, missing_tests)), ))

            # Validate the test filters.
            test_filters = [re.compile(pattern)
                            for pattern in opts.test_filters]

            # Form the list of tests.
            tests_to_run = [test
                            for test in all_tests
                            if (test[0] in requested_tests or
                                [True
                                 for filter in test_filters
                                 if filter.search(test[0])])]
        if not tests_to_run:
            parser.error(
                "no tests requested (invalid --test or --test-filter options)!")

        # Ensure output directory is available.
        if not os.path.exists(g_output_dir):
            os.mkdir(g_output_dir)

        # Execute the run.
        run_info.update(variables)
        run_info['tag'] = tag = 'compile'

        testsamples = []
        start_time = datetime.utcnow()
        g_log.info('run started')
        g_log.info('using CC: %r' % opts.cc)
        g_log.info('using CXX: %r' % opts.cxx)
        for basename, test_fn in tests_to_run:
            for success, name, samples in test_fn(basename, run_info,
                                                  variables):
                g_log.info('collected samples: %r' % name)
                num_samples = len(samples)
                if num_samples:
                    samples_median = '%.4f' % (stats.median(samples),)
                    samples_mad = '%.4f' % (
                        stats.median_absolute_deviation(samples),)
                else:
                    samples_median = samples_mad = 'N/A'
                g_log.info('N=%d, median=%s, MAD=%s' % (
                    num_samples, samples_median, samples_mad))
                test_name = '%s.%s' % (tag, name)
                if not success:
                    testsamples.append(lnt.testing.TestSamples(
                                       test_name + '.status',
                                       [lnt.testing.FAIL]))
                if samples:
                    testsamples.append(lnt.testing.TestSamples(
                                       test_name, samples))
        end_time = datetime.utcnow()

        g_log.info('run complete')

        # Package up the report.
        machine = lnt.testing.Machine(opts.machine_name, machine_info)
        run = lnt.testing.Run(start_time, end_time, info=run_info)

        # Write out the report.
        lnt_report_path = os.path.join(g_output_dir, 'report.json')
        report = lnt.testing.Report(machine, run, testsamples)

        # Save report to disk for submission.
        self.print_report(report, lnt_report_path)

        # Then, also print to screen if requested.
        if opts.output is not None:
            self.print_report(report, opts.output)

        server_report = self.submit(lnt_report_path, opts)

        return server_report
コード例 #7
0
ファイル: analysis.py プロジェクト: couchbaselabs/cbnt
    def __init__(self, aggregation_fn,
                 cur_failed, prev_failed, samples, prev_samples,
                 cur_hash, prev_hash, cur_profile=None, prev_profile=None,
                 confidence_lv=0.05, bigger_is_better=False, stable_test=True):
        self.aggregation_fn = aggregation_fn

        # Special case: if we're using the minimum to aggregate, swap it for max
        # if bigger_is_better.
        if aggregation_fn == stats.safe_min and bigger_is_better:
            aggregation_fn = stats.safe_max

        self.cur_hash = cur_hash
        self.prev_hash = prev_hash
        self.cur_profile = cur_profile
        self.prev_profile = prev_profile

        if samples:
            self.current = aggregation_fn(samples)
        else:
            self.current = None

        self.previous = None
        # Compute the comparison status for the test value.
        self.delta = 0
        self.pct_delta = 0.0
        if self.current and prev_samples:
            prev_median = stats.median(prev_samples)
            self.delta, value = absmin_diff(self.current, [prev_median])
            if value != 0:
                self.pct_delta = self.delta / value
            self.previous = value

        # If we have multiple values for this run, use that to estimate the
        # distribution.
        #
        # We can get integer sample types here - for example if the field is
        # .exec.status. Make sure we don't assert by avoiding the stats
        # functions in this case.
        if samples and len(samples) > 1 and isinstance(samples[0], float):
            self.stddev = stats.standard_deviation(samples)
            self.MAD = stats.median_absolute_deviation(samples)
            self.variance = stats.variance(samples)
        else:
            self.stddev = None
            self.MAD = None
            self.variance = None

        if prev_samples and len(prev_samples) > 1 and isinstance(prev_samples[0], float):
            self.prev_stddev = stats.standard_deviation(prev_samples)
            self.prev_MAD = stats.median_absolute_deviation(prev_samples)
            self.prev_variance = stats.variance(prev_samples)
        else:
            self.prev_stddev = None
            self.prev_MAD = None
            self.prev_variance = None

        self.stddev_mean = None  # Only calculate this if needed.
        self.failed = cur_failed
        self.prev_failed = prev_failed
        self.samples = samples
        self.prev_samples = prev_samples

        self.confidence_lv = confidence_lv
        self.bigger_is_better = bigger_is_better
        self.stable_test = stable_test
コード例 #8
0
def v4_graph():
    from lnt.server.ui import util
    from lnt.testing import PASS
    from lnt.util import stats
    from lnt.external.stats import stats as ext_stats

    ts = request.get_testsuite()

    # Parse the view options.
    options = {}
    options['hide_lineplot'] = bool(request.args.get('hide_lineplot'))
    show_lineplot = not options['hide_lineplot']
    options['show_mad'] = show_mad = bool(request.args.get('show_mad'))
    options['show_stddev'] = show_stddev = bool(
        request.args.get('show_stddev'))
    options['hide_all_points'] = hide_all_points = bool(
        request.args.get('hide_all_points'))
    options['show_linear_regression'] = show_linear_regression = bool(
        request.args.get('show_linear_regression'))
    options['show_failures'] = show_failures = bool(
        request.args.get('show_failures'))
    options['normalize_by_median'] = normalize_by_median = bool(
        request.args.get('normalize_by_median'))
    options['show_moving_average'] = moving_average = bool(
        request.args.get('show_moving_average'))
    options['show_moving_median'] = moving_median = bool(
        request.args.get('show_moving_median'))
    options['moving_window_size'] = moving_window_size = int(
        request.args.get('moving_window_size', 10))
    options['hide_highlight'] = bool(request.args.get('hide_highlight'))
    show_highlight = not options['hide_highlight']

    def convert_revision(dotted):
        """Turn a version number like 489.2.10 into something
        that is ordered and sortable.
        For now 489.2.10 will be returned as a tuple of ints.
        """
        dotted = integral_rex.findall(dotted)
        return tuple([int(d) for d in dotted])

    # Load the graph parameters.
    graph_parameters = []
    for name, value in request.args.items():
        # Plots to graph are passed as::
        #
        #  plot.<unused>=<machine id>.<test id>.<field index>
        if not name.startswith(str('plot.')):
            continue

        # Ignore the extra part of the key, it is unused.
        machine_id_str, test_id_str, field_index_str = value.split('.')
        try:
            machine_id = int(machine_id_str)
            test_id = int(test_id_str)
            field_index = int(field_index_str)
        except:
            return abort(400)

        if not (0 <= field_index < len(ts.sample_fields)):
            return abort(404)

        try:
            machine = \
                ts.query(ts.Machine).filter(ts.Machine.id == machine_id).one()
            test = ts.query(ts.Test).filter(ts.Test.id == test_id).one()
            field = ts.sample_fields[field_index]
        except NoResultFound:
            return abort(404)
        graph_parameters.append((machine, test, field, field_index))

    # Order the plots by machine name, test name and then field.
    graph_parameters.sort(key=lambda (m, t, f, _): (m.name, t.name, f.name, _))

    # Extract requested mean trend.
    mean_parameter = None
    for name, value in request.args.items():
        # Mean to graph is passed as:
        #
        #  mean=<machine id>.<field index>
        if name != 'mean':
            continue

        machine_id_str, field_index_str = value.split('.')
        try:
            machine_id = int(machine_id_str)
            field_index = int(field_index_str)
        except ValueError:
            return abort(400)

        if not (0 <= field_index < len(ts.sample_fields)):
            return abort(404)

        try:
            machine = \
                ts.query(ts.Machine).filter(ts.Machine.id == machine_id).one()
        except NoResultFound:
            return abort(404)
        field = ts.sample_fields[field_index]

        mean_parameter = (machine, field)

    # Sanity check the arguments.
    if not graph_parameters and not mean_parameter:
        return render_template("error.html", message="Nothing to graph.")

    # Extract requested baselines, and their titles.
    baseline_parameters = []
    for name, value in request.args.items():
        # Baselines to graph are passed as:
        #
        #  baseline.title=<run id>
        if not name.startswith(str('baseline.')):
            continue

        baseline_title = name[len('baseline.'):]

        run_id_str = value
        try:
            run_id = int(run_id_str)
        except:
            return abort(400)

        try:
            run = ts.query(ts.Run).join(
                ts.Machine).filter(ts.Run.id == run_id).one()
        except:
            err_msg = "The run {} was not found in the database.".format(
                run_id)
            return render_template("error.html", message=err_msg)

        baseline_parameters.append((run, baseline_title))

    # Create region of interest for run data region if we are performing a
    # comparison.
    revision_range = None
    highlight_run_id = request.args.get('highlight_run')
    if show_highlight and highlight_run_id and highlight_run_id.isdigit():
        highlight_run = ts.query(
            ts.Run).filter_by(id=int(highlight_run_id)).first()
        if highlight_run is None:
            abort(404)

        # Find the neighboring runs, by order.
        prev_runs = list(ts.get_previous_runs_on_machine(highlight_run, N=1))
        if prev_runs:
            start_rev = prev_runs[0].order.llvm_project_revision
            end_rev = highlight_run.order.llvm_project_revision
            revision_range = {
                "start": convert_revision(start_rev),
                "end": convert_revision(end_rev)
            }

    # Build the graph data.
    legend = []
    graph_plots = []
    graph_datum = []
    overview_plots = []
    baseline_plots = []
    num_plots = len(graph_parameters)
    for i, (machine, test, field, field_index) in enumerate(graph_parameters):
        # Determine the base plot color.
        col = list(util.makeDarkColor(float(i) / num_plots))
        url = "/".join([str(machine.id), str(test.id), str(field_index)])
        legend.append((machine, test.name, field.name, tuple(col), url))

        # Load all the field values for this test on the same machine.
        #
        # FIXME: Don't join to Order here, aggregate this across all the tests
        # we want to load. Actually, we should just make this a single query.
        #
        # FIXME: Don't hard code field name.
        q = ts.query(field.column, ts.Order.llvm_project_revision, ts.Run.start_time, ts.Run.id).\
            join(ts.Run).join(ts.Order).\
            filter(ts.Run.machine_id == machine.id).\
            filter(ts.Sample.test == test).\
            filter(field.column != None)

        # Unless all samples requested, filter out failing tests.
        if not show_failures:
            if field.status_field:
                q = q.filter((field.status_field.column == PASS)
                             | (field.status_field.column == None))

        # Aggregate by revision.
        data = util.multidict((rev, (val, date, run_id))
                              for val, rev, date, run_id in q).items()
        data.sort(key=lambda sample: convert_revision(sample[0]))

        graph_datum.append((test.name, data, col, field, url))

        # Get baselines for this line
        num_baselines = len(baseline_parameters)
        for baseline_id, (baseline,
                          baseline_title) in enumerate(baseline_parameters):
            q_baseline = ts.query(field.column, ts.Order.llvm_project_revision, ts.Run.start_time, ts.Machine.name).\
                         join(ts.Run).join(ts.Order).join(ts.Machine).\
                         filter(ts.Run.id == baseline.id).\
                         filter(ts.Sample.test == test).\
                         filter(field.column != None)
            # In the event of many samples, use the mean of the samples as the baseline.
            samples = []
            for sample in q_baseline:
                samples.append(sample[0])
            # Skip this baseline if there is no data.
            if not samples:
                continue
            mean = sum(samples) / len(samples)
            # Darken the baseline color distinguish from non-baselines.
            # Make a color closer to the sample than its neighbour.
            color_offset = float(baseline_id) / num_baselines / 2
            my_color = (i + color_offset) / num_plots
            dark_col = list(util.makeDarkerColor(my_color))
            str_dark_col = util.toColorString(dark_col)
            baseline_plots.append({
                'color': str_dark_col,
                'lineWidth': 2,
                'yaxis': {
                    'from': mean,
                    'to': mean
                },
                'name': q_baseline[0].llvm_project_revision
            })
            baseline_name = "Baseline {} on {}".format(baseline_title,
                                                       q_baseline[0].name)
            legend.append((BaselineLegendItem(baseline_name, baseline.id),
                           test.name, field.name, dark_col))

    # Draw mean trend if requested.
    if mean_parameter:
        machine, field = mean_parameter
        test_name = 'Geometric Mean'

        col = (0, 0, 0)
        legend.append((machine, test_name, field.name, col, None))

        q = ts.query(sqlalchemy.sql.func.min(field.column),
                ts.Order.llvm_project_revision,
                sqlalchemy.sql.func.min(ts.Run.start_time)).\
            join(ts.Run).join(ts.Order).join(ts.Test).\
            filter(ts.Run.machine_id == machine.id).\
            filter(field.column != None).\
            group_by(ts.Order.llvm_project_revision, ts.Test)

        # Calculate geomean of each revision.
        data = util.multidict(
            ((rev, date), val) for val, rev, date in q).items()
        data = [(rev, [(lnt.server.reporting.analysis.calc_geomean(vals), date)
                       ]) for ((rev, date), vals) in data]

        # Sort data points according to revision number.
        data.sort(key=lambda sample: convert_revision(sample[0]))

        graph_datum.append((test_name, data, col, field, None))

    for name, data, col, field, url in graph_datum:
        # Compute the graph points.
        errorbar_data = []
        points_data = []
        pts = []
        moving_median_data = []
        moving_average_data = []

        if normalize_by_median:
            normalize_by = 1.0 / stats.median(
                [min([d[0] for d in values]) for _, values in data])
        else:
            normalize_by = 1.0

        for pos, (point_label, datapoints) in enumerate(data):
            # Get the samples.
            data = [data_date[0] for data_date in datapoints]
            # And the date on which they were taken.
            dates = [data_date[1] for data_date in datapoints]
            # Run where this point was collected.
            runs = [
                data_pts[2] for data_pts in datapoints if len(data_pts) == 3
            ]

            # When we can, map x-axis to revisions, but when that is too hard
            # use the position of the sample instead.
            rev_x = convert_revision(point_label)
            x = rev_x[0] if len(rev_x) == 1 else pos

            values = [v * normalize_by for v in data]
            aggregation_fn = min
            if field.bigger_is_better:
                aggregation_fn = max
            agg_value, agg_index = \
                aggregation_fn((value, index)
                               for (index, value) in enumerate(values))

            # Generate metadata.
            metadata = {"label": point_label}
            metadata["date"] = str(dates[agg_index])
            if runs:
                metadata["runID"] = str(runs[agg_index])

            if len(graph_datum) > 1:
                # If there are more than one plot in the graph, also label the
                # test name.
                metadata["test_name"] = name

            pts.append((x, agg_value, metadata))

            # Add the individual points, if requested.
            # For each point add a text label for the mouse over.
            if not hide_all_points:
                for i, v in enumerate(values):
                    point_metadata = dict(metadata)
                    point_metadata["date"] = str(dates[i])
                    points_data.append((x, v, point_metadata))

            # Add the standard deviation error bar, if requested.
            if show_stddev:
                mean = stats.mean(values)
                sigma = stats.standard_deviation(values)
                errorbar_data.append((x, mean, sigma))

            # Add the MAD error bar, if requested.
            if show_mad:
                med = stats.median(values)
                mad = stats.median_absolute_deviation(values, med)
                errorbar_data.append((x, med, mad))

        # Compute the moving average and or moving median of our data if requested.
        if moving_average or moving_median:
            fun = None

            def compute_moving_average(x, window, average_list, median_list):
                average_list.append((x, lnt.util.stats.mean(window)))

            def compute_moving_median(x, window, average_list, median_list):
                median_list.append((x, lnt.util.stats.median(window)))

            def compute_moving_average_and_median(x, window, average_list,
                                                  median_list):
                average_list.append((x, lnt.util.stats.mean(window)))
                median_list.append((x, lnt.util.stats.median(window)))

            if moving_average and moving_median:
                fun = compute_moving_average_and_median
            elif moving_average:
                fun = compute_moving_average
            else:
                fun = compute_moving_median

            len_pts = len(pts)
            for i in range(len_pts):
                start_index = max(0, i - moving_window_size)
                end_index = min(len_pts, i + moving_window_size)

                window_pts = [x[1] for x in pts[start_index:end_index]]
                fun(pts[i][0], window_pts, moving_average_data,
                    moving_median_data)

        # On the overview, we always show the line plot.
        overview_plots.append({"data": pts, "color": util.toColorString(col)})

        # Add the minimum line plot, if requested.
        if show_lineplot:
            plot = {"data": pts, "color": util.toColorString(col)}
            if url:
                plot["url"] = url
            graph_plots.append(plot)
        # Add regression line, if requested.
        if show_linear_regression:
            xs = [t for t, v, _ in pts]
            ys = [v for t, v, _ in pts]

            # We compute the regression line in terms of a normalized X scale.
            x_min, x_max = min(xs), max(xs)
            try:
                norm_xs = [(x - x_min) / (x_max - x_min) for x in xs]
            except ZeroDivisionError:
                norm_xs = xs

            try:
                info = ext_stats.linregress(norm_xs, ys)
            except ZeroDivisionError:
                info = None
            except ValueError:
                info = None

            if info is not None:
                slope, intercept, _, _, _ = info

                reglin_col = [c * .7 for c in col]
                reglin_pts = [(x_min, 0.0 * slope + intercept),
                              (x_max, 1.0 * slope + intercept)]
                graph_plots.insert(
                    0, {
                        "data": reglin_pts,
                        "color": util.toColorString(reglin_col),
                        "lines": {
                            "lineWidth": 2
                        },
                        "shadowSize": 4
                    })

        # Add the points plot, if used.
        if points_data:
            pts_col = (0, 0, 0)
            plot = {
                "data": points_data,
                "color": util.toColorString(pts_col),
                "lines": {
                    "show": False
                },
                "points": {
                    "show": True,
                    "radius": .25,
                    "fill": True
                }
            }
            if url:
                plot['url'] = url
            graph_plots.append(plot)

        # Add the error bar plot, if used.
        if errorbar_data:
            bar_col = [c * .7 for c in col]
            graph_plots.append({
                "data": errorbar_data,
                "lines": {
                    "show": False
                },
                "color": util.toColorString(bar_col),
                "points": {
                    "errorbars": "y",
                    "yerr": {
                        "show": True,
                        "lowerCap": "-",
                        "upperCap": "-",
                        "lineWidth": 1
                    }
                }
            })

        # Add the moving average plot, if used.
        if moving_average_data:
            col = [0.32, 0.6, 0.0]
            graph_plots.append({
                "data": moving_average_data,
                "color": util.toColorString(col)
            })

        # Add the moving median plot, if used.
        if moving_median_data:
            col = [0.75, 0.0, 1.0]
            graph_plots.append({
                "data": moving_median_data,
                "color": util.toColorString(col)
            })

    if bool(request.args.get('json')):
        json_obj = dict()
        json_obj['data'] = graph_plots
        # Flatten ORM machine objects to their string names.
        simple_type_legend = []
        for machine, test, unit, color, url in legend:
            # Flatten name, make color a dict.
            new_entry = {
                'name': machine.name,
                'test': test,
                'unit': unit,
                'color': util.toColorString(color),
                'url': url
            }
            simple_type_legend.append(new_entry)
        json_obj['legend'] = simple_type_legend
        json_obj['revision_range'] = revision_range
        json_obj['current_options'] = options
        json_obj['test_suite_name'] = ts.name
        json_obj['baselines'] = baseline_plots
        return flask.jsonify(**json_obj)

    return render_template("v4_graph.html",
                           ts=ts,
                           options=options,
                           revision_range=revision_range,
                           graph_plots=graph_plots,
                           overview_plots=overview_plots,
                           legend=legend,
                           baseline_plots=baseline_plots)