static void
cairo_perf_reports_compare (struct chart *chart,
			    cairo_bool_t  print)
{
    test_report_t **tests, *min_test;
    double test_time, best_time;
    int num_test = 0;
    int seen_non_null;
    int i;

    tests = xmalloc (chart->num_reports * sizeof (test_report_t *));
    for (i = 0; i < chart->num_reports; i++)
	tests[i] = chart->reports[i].tests;

    if (print) {
	if (chart->use_html) {
	    printf ("<table style=\"text-align:right\" cellspacing=\"4\">\n");
	    printf ("<tr><td></td>");
	    for (i = 0; i < chart->num_reports; i++) {
		printf ("<td>%s</td>", chart->names[i] ? chart->names[i] : "");
	    }
	    printf ("</tr>\n");
	}
    }

    while (1) {
	/* We expect iterations values of 0 when multiple raw reports
	 * for the same test have been condensed into the stats of the
	 * first. So we just skip these later reports that have no
	 * stats. */
	seen_non_null = 0;
	for (i = 0; i < chart->num_reports; i++) {
	    while (tests[i]->name && tests[i]->stats.iterations == 0)
		tests[i]++;
	    if (tests[i]->name)
		seen_non_null++;
	}
	if (! seen_non_null)
	    break;

	/* Find the minimum of all current tests, (we have to do this
	 * in case some reports don't have a particular test). */
	for (i = 0; i < chart->num_reports; i++) {
	    if (tests[i]->name) {
		min_test = tests[i];
		break;
	    }
	}
	for (++i; i < chart->num_reports; i++) {
	    if (tests[i]->name && test_report_cmp_name (tests[i], min_test) < 0)
		min_test = tests[i];
	}

	add_label (chart, num_test, min_test->name);
	if (print) {
	    if (chart->use_html) {
		printf ("<tr><td>%s</td>", min_test->name);
	    } else {
		if (min_test->size) {
		    printf ("%16s, size %4d:\n",
			    min_test->name,
			    min_test->size);
		} else {
		    printf ("%26s:",
			    min_test->name);
		}
	    }
	}

	test_time = 0;
	best_time = HUGE_VAL;
	for (i = 0; i < chart->num_reports; i++) {
	    test_report_t *initial = tests[i];
	    double report_time = HUGE_VAL;

	    while (tests[i]->name &&
		   test_report_cmp_name (tests[i], min_test) == 0)
	    {
		double time = tests[i]->stats.min_ticks;
		if (time < report_time) {
		    time /= tests[i]->stats.ticks_per_ms;
		    if (time < report_time)
			report_time = time;
		}
		tests[i]++;
	    }

	    if (test_time == 0 && report_time != HUGE_VAL)
		test_time = report_time;
	    if (report_time < best_time)
		best_time = report_time;

	    tests[i] = initial;
	}

	for (i = 0; i < chart->num_reports; i++) {
	    double report_time = HUGE_VAL;

	    while (tests[i]->name &&
		   test_report_cmp_name (tests[i], min_test) == 0)
	    {
		double time = tests[i]->stats.min_ticks;
		if (time > 0) {
		    time /= tests[i]->stats.ticks_per_ms;
		    if (time < report_time)
			report_time = time;
		}
		tests[i]++;
	    }

	    if (print) {
		if (chart->use_html) {
		    if (report_time < HUGE_VAL) {
			if (report_time / best_time < 1.01) {
			    printf ("<td><strong>%.1f</strong></td>", report_time/1000);
			} else {
			    printf ("<td>%.1f</td>", report_time/1000);
			}
		    } else {
			printf ("<td></td>");
		    }
		} else {
		    if (report_time < HUGE_VAL)
			printf (" %6.1f",  report_time/1000);
		    else
			printf ("    ---");
		}
	    }

	    if (report_time < HUGE_VAL) {
		if (chart->relative) {
		    add_chart (chart, num_test, i,
			       to_factor (test_time / report_time));
		} else {
		    add_chart (chart, num_test, i, report_time);
		}
	    }
	}

	if (print) {
	    if (chart->use_html) {
		printf ("</tr>\n");
	    } else {
		printf ("\n");
	    }
	}

	num_test++;
    }
    if (chart->relative) {
	add_label (chart, num_test, "(geometric mean)");
	for (i = 0; i < chart->num_reports; i++)
	    add_average (chart, num_test, i, to_factor (chart->average[i]));
    }
    free (tests);

    if (print) {
	if (chart->use_html)
	    printf ("</table>\n");

	printf ("\n");
	for (i = 0; i < chart->num_reports; i++) {
	    if (chart->names[i]) {
		printf ("[%s] %s\n",
			chart->names[i], chart->reports[i].configuration);
	    } else {
		printf ("[%d] %s\n",
			i, chart->reports[i].configuration);
	    }
	}
    }
}
static void
cairo_perf_reports_compare (cairo_perf_report_t 	*reports,
			    int 			 num_reports,
			    cairo_perf_report_options_t *options)
{
    int i;
    test_report_t **tests, *min_test;
    test_diff_t *diff, *diffs;
    int num_diffs, max_diffs;
    double max_change;
    double test_time;
    int seen_non_null;

    tests = xmalloc (num_reports * sizeof (test_report_t *));

    max_diffs = reports[0].tests_count;
    for (i = 0; i < num_reports; i++) {
	tests[i] = reports[i].tests;
	if (reports[i].tests_count > max_diffs)
	    max_diffs = reports[i].tests_count;
    }

    diff = diffs = xmalloc (max_diffs * sizeof (test_diff_t));

    num_diffs = 0;
    while (1) {
	int num_tests;

	/* We expect iterations values of 0 when multiple raw reports
	 * for the same test have been condensed into the stats of the
	 * first. So we just skip these later reports that have no
	 * stats. */
	seen_non_null = 0;
	for (i = 0; i < num_reports; i++) {
	    while (tests[i]->name && tests[i]->stats.iterations == 0)
		tests[i]++;
	    if (tests[i]->name)
		seen_non_null++;
	}
	if (! seen_non_null)
	    break;

	/* Find the minimum of all current tests, (we have to do this
	 * in case some reports don't have a particular test). */
	for (i = 0; i < num_reports; i++) {
	    if (tests[i]->name) {
		min_test = tests[i];
		break;
	    }
	}
	for (++i; i < num_reports; i++) {
	    if (tests[i]->name && test_report_cmp_name (tests[i], min_test) < 0)
		min_test = tests[i];
	}

	num_tests = 0;
	for (i = 0; i < num_reports; i++) {
	    test_report_t *test;
	    int n = 0;

	    test = tests[i];
	    while (test[n].name &&
		    test_report_cmp_name (&test[n], min_test) == 0)
	    {
		n++;
	    }

	    num_tests += n;
	}

	/* For each report that has the current test, record it into
	 * the diff structure. */
	diff->num_tests = 0;
	diff->tests = xmalloc (num_tests * sizeof (test_diff_t));
	for (i = 0; i < num_reports; i++) {
	    while (tests[i]->name &&
		    test_report_cmp_name (tests[i], min_test) == 0)
	    {
		test_time = tests[i]->stats.min_ticks;
		if (test_time > 0) {
		    test_time /= tests[i]->stats.ticks_per_ms;
		    if (diff->num_tests == 0) {
			diff->min = test_time;
			diff->max = test_time;
		    } else {
			if (test_time < diff->min)
			    diff->min = test_time;
			if (test_time > diff->max)
			    diff->max = test_time;
		    }
		    diff->tests[diff->num_tests++] = tests[i];
		}
		tests[i]++;
	    }
	}
	diff->change = diff->max / diff->min;

	diff++;
	num_diffs++;
    }
    if (num_diffs == 0)
	goto DONE;

    qsort (diffs, num_diffs, sizeof (test_diff_t), test_diff_cmp);

    max_change = 1.0;
    for (i = 0; i < num_diffs; i++) {
	if (fabs (diffs[i].change) > max_change)
	    max_change = fabs (diffs[i].change);
    }

    for (i = 0; i < num_diffs; i++) {
	diff = &diffs[i];

	/* Discard as uninteresting a change which is less than the
	 * minimum change required, (default may be overridden on
	 * command-line). */
	if (fabs (diff->change) - 1.0 < options->min_change)
	    continue;

	test_diff_print (diff, max_change, options);
    }

    for (i = 0; i < num_diffs; i++)
	free (diffs[i].tests);
 DONE:
    free (diffs);
    free (tests);
}
static void
find_ranges (struct chart *chart)
{
    test_report_t **tests, *min_test;
    double *values;
    int num_values, size_values;
    double min = 0, max = 0;
    double test_time;
    int seen_non_null;
    int num_tests = 0;
    double slow_sum = 0, fast_sum = 0, sum;
    int slow_count = 0, fast_count = 0;
    int *count;
    int i;

    num_values = 0;
    size_values = 64;
    values = xmalloc (size_values * sizeof (double));

    chart->average = xmalloc(chart->num_reports * sizeof(double));
    count = xmalloc(chart->num_reports * sizeof(int));
    for (i = 0; i < chart->num_reports; i++) {
	chart->average[i] = 0;
	count[i] = 0;
    }

    tests = xmalloc (chart->num_reports * sizeof (test_report_t *));
    for (i = 0; i < chart->num_reports; i++)
	tests[i] = chart->reports[i].tests;

    while (1) {
	/* We expect iterations values of 0 when multiple raw reports
	 * for the same test have been condensed into the stats of the
	 * first. So we just skip these later reports that have no
	 * stats. */
	seen_non_null = 0;
	for (i = 0; i < chart->num_reports; i++) {
	    while (tests[i]->name && tests[i]->stats.iterations == 0)
		tests[i]++;
	    if (tests[i]->name)
		seen_non_null++;
	}
	if (! seen_non_null)
	    break;

	num_tests++;

	/* Find the minimum of all current tests, (we have to do this
	 * in case some reports don't have a particular test). */
	for (i = 0; i < chart->num_reports; i++) {
	    if (tests[i]->name) {
		min_test = tests[i];
		break;
	    }
	}
	for (++i; i < chart->num_reports; i++) {
	    if (tests[i]->name && test_report_cmp_name (tests[i], min_test) < 0)
		min_test = tests[i];
	}

	test_time = 0;
	for (i = 0; i < chart->num_reports; i++) {
	    double report_time = HUGE_VAL;

	    while (tests[i]->name &&
		   test_report_cmp_name (tests[i], min_test) == 0)
	    {
		double time = tests[i]->stats.min_ticks;
		if (time < report_time) {
		    time /= tests[i]->stats.ticks_per_ms;
		    if (time < report_time)
			report_time = time;
		}
		tests[i]++;
	    }

	    if (report_time != HUGE_VAL) {
		if (test_time == 0)
		    test_time = report_time;

		chart->average[i] += report_time / test_time;
		count[i]++;

		if (chart->relative) {
		    if (test_time != report_time) {
			double v = to_factor (test_time / report_time);
			if (num_values == size_values) {
			    size_values *= 2;
			    values = xrealloc (values,
					       size_values * sizeof (double));
			}
			values[num_values++] = v;
			if (v < min)
			    min = v;
			if (v > max)
			    max = v;
			if (v > 0)
			    fast_sum += v/100, fast_count++;
			else
			    slow_sum += v/100, slow_count++;
			sum += v/100;
			printf ("%s %d: %f\n", min_test->name, num_values, v);
		    }
		} else {
		    if (report_time < min)
			min = report_time;
		    if (report_time > max)
			max = report_time;
		}
	    }
	}
    }

    for (i = 0; i < chart->num_reports; i++) {
	if (count[i])
	    chart->average[i] = count[i] / chart->average[i];
	else
	    chart->average[i] = 1.;
    }

    if (chart->relative)
	trim_outliers (values, num_values, &min, &max);
    chart->min_value = min;
    chart->max_value = max;
    chart->num_tests = num_tests + !!chart->relative;

    free (values);
    free (tests);
    free (count);

    printf ("%d: slow[%d] average: %f, fast[%d] average: %f, %f\n",
	    num_values, slow_count, slow_sum / slow_count, fast_count, fast_sum / fast_count, sum / num_values);
}