diff options
Diffstat (limited to 'perf/cairo-perf-chart.c')
-rw-r--r-- | perf/cairo-perf-chart.c | 884 |
1 files changed, 884 insertions, 0 deletions
diff --git a/perf/cairo-perf-chart.c b/perf/cairo-perf-chart.c new file mode 100644 index 0000000..433491e --- /dev/null +++ b/perf/cairo-perf-chart.c @@ -0,0 +1,884 @@ +/* + * Copyright © 2006 Red Hat, Inc. + * Copyright © 2009 Chris Wilson + * + * Permission to use, copy, modify, distribute, and sell this software + * and its documentation for any purpose is hereby granted without + * fee, provided that the above copyright notice appear in all copies + * and that both that copyright notice and this permission notice + * appear in supporting documentation, and that the name of the + * copyright holders not be used in advertising or publicity + * pertaining to distribution of the software without specific, + * written prior permission. The copyright holders make no + * representations about the suitability of this software for any + * purpose. It is provided "as is" without express or implied + * warranty. + * + * THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS + * SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS, IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY + * SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN + * AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING + * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS + * SOFTWARE. + * + * Authors: Carl Worth <cworth@cworth.org> + * Chris Wilson <chris@chris-wilson.co.uk> + */ + +#include "cairo-perf.h" + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <errno.h> +#include <ctype.h> +#include <math.h> +#include <assert.h> + +struct chart { + cairo_perf_report_t *reports; + const char **names; + + cairo_t *cr; + int width, height; + int num_tests, num_reports; + double min_value, max_value; + + cairo_bool_t use_html; + cairo_bool_t relative; +}; +struct color { + double red, green, blue; +}; + +#define FONT_SIZE 12 +#define PAD (FONT_SIZE/2+1) + +#define MAX(a,b) ((a) > (b) ? (a) : (b)) + +static double +to_factor (double x) +{ +#if 1 + if (x > 1.) + return (x-1) * 100.; + else + return (1. - 1./x) * 100.; +#else + return log (x); +#endif +} + +static int +_double_cmp (const void *_a, const void *_b) +{ + const double *a = _a; + const double *b = _b; + + if (*a > *b) + return 1; + if (*a < *b) + return -1; + return 0; +} + +static void +trim_outliers (double *values, int num_values, + double *min, double *max) +{ + double q1, q3, iqr; + double outlier_min, outlier_max; + int i; + + /* First, identify any outliers, using the definition of "mild + * outliers" from: + * + * http://en.wikipedia.org/wiki/Outliers + * + * Which is that outliers are any values less than Q1 - 1.5 * IQR + * or greater than Q3 + 1.5 * IQR where Q1 and Q3 are the first + * and third quartiles and IQR is the inter-quartile range (Q3 - + * Q1). + */ + qsort (values, num_values, + sizeof (double), _double_cmp); + + q1 = values[1*num_values / 4]; + q3 = values[3*num_values / 4]; + + iqr = q3 - q1; + + outlier_min = q1 - 1.5 * iqr; + outlier_max = q3 + 1.5 * iqr; + + i = 0; + while (i < num_values && values[i] < outlier_min) + i++; + if (i == num_values) + return; + + *min = values[i]; + + while (i < num_values && values[i] <= outlier_max) + i++; + + *max = values[i-1]; +} + +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; + int i; + + num_values = 0; + size_values = 64; + values = xmalloc (size_values * sizeof (double)); + + 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; + + if (chart->relative) { + 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; + } else { + if (report_time < min) + min = report_time; + if (report_time > max) + max = report_time; + } + } + } + } + + if (chart->relative) + trim_outliers (values, num_values, &min, &max); + chart->min_value = min; + chart->max_value = max; + chart->num_tests = num_tests; + + free (values); + free (tests); +} + +#define SET_COLOR(C, R, G, B) (C)->red = (R), (C)->green = (G), (C)->blue = (B) +static void +hsv_to_rgb (double h, double s, double v, struct color *color) +{ + double m, n, f; + int i; + + while (h < 0) + h += 6.; + while (h > 6.) + h -= 6.; + + if (s < 0.) + s = 0.; + if (s > 1.) + s = 1.; + + if (v < 0.) + v = 0.; + if (v > 1.) + v = 1.; + + i = floor (h); + f = h - i; + if ((i & 1) == 0) + f = 1 - f; + + m = v * (1 - s); + n = v * (1 - s * f); + switch(i){ + default: + case 6: + case 0: SET_COLOR (color, v, n, m); break; + case 1: SET_COLOR (color, n, v, m); break; + case 2: SET_COLOR (color, m, v, n); break; + case 3: SET_COLOR (color, m, n, v); break; + case 4: SET_COLOR (color, n, m, v); break; + case 5: SET_COLOR (color, v, m, n); break; + } +} + +static void set_report_color (struct chart *chart, int report) +{ + struct color color; + + hsv_to_rgb (6. / chart->num_reports * report, .7, .7, &color); + cairo_set_source_rgb (chart->cr, color.red, color.green, color.blue); +} + +static void +test_background (struct chart *c, int test) +{ + double dx, x; + + dx = c->width / (double) c->num_tests; + x = dx * test; + + if (test & 1) + cairo_set_source_rgba (c->cr, .2, .2, .2, .2); + else + cairo_set_source_rgba (c->cr, .8, .8, .8, .2); + + cairo_rectangle (c->cr, floor (x), 0, + floor (dx + x) - floor (x), c->height); + cairo_fill (c->cr); +} + +static void +add_chart (struct chart *c, int test, int report, double value) +{ + double dx, dy, x; + + if (fabs (value) < 0.1) + return; + + set_report_color (c, report); + + if (c->relative) { + cairo_text_extents_t extents; + cairo_bool_t show_label; + char buf[80]; + double y; + + dy = (c->height/2. - PAD) / MAX (-c->min_value, c->max_value); + /* the first report is always skipped, as it is used as the baseline */ + dx = c->width / (double) (c->num_tests * c->num_reports); + x = dx * (c->num_reports * test + report - .5); + + cairo_rectangle (c->cr, + floor (x), c->height / 2., + floor (x + dx) - floor (x), + ceil (-dy*value - c->height/2.) + c->height/2.); + cairo_fill (c->cr); + + cairo_save (c->cr); + cairo_set_font_size (c->cr, dx - 2); + + if (value < 0) { + sprintf (buf, "%.1f", value/100 - 1); + } else { + sprintf (buf, "%.1f", value/100 + 1); + } + cairo_text_extents (c->cr, buf, &extents); + + /* will it be clipped? */ + y = -dy * value; + if (y < -c->height/2) { + y = -c->height/2; + } else if (y > c->height/2) { + y = c->height/2; + } + + cairo_translate (c->cr, + floor (x) + (floor (x + dx) - floor (x))/2, + floor (y) + c->height/2.); + cairo_rotate (c->cr, -M_PI/2); + if (y < 0) { + cairo_move_to (c->cr, -extents.x_bearing -extents.width - 4, -extents.y_bearing/2); + show_label = y < -extents.width - 6; + } else { + cairo_move_to (c->cr, 2, -extents.y_bearing/2); + show_label = y > extents.width + 6; + } + + cairo_set_source_rgb (c->cr, .95, .95, .95); + if (show_label) + cairo_show_text (c->cr, buf); + cairo_restore (c->cr); + } else { + dy = (c->height - PAD) / c->max_value; + dx = c->width / (double) (c->num_tests * (c->num_reports+1)); + x = dx * ((c->num_reports+1) * test + report + .5); + + cairo_rectangle (c->cr, + floor (x), c->height, + floor (x + dx) - floor (x), + floor (c->height - dy*value) - c->height); + cairo_fill (c->cr); + } +} + +static void +add_label (struct chart *c, int test, const char *label) +{ + cairo_text_extents_t extents; + double dx, x; + + cairo_save (c->cr); + dx = c->width / (double) c->num_tests; + if (dx / 2 - PAD < 6) + return; + cairo_set_font_size (c->cr, dx / 2 - PAD); + cairo_text_extents (c->cr, label, &extents); + + x = (test + .5) * dx; + cairo_translate (c->cr, x, PAD / 2); + cairo_rotate (c->cr, -M_PI/2); + + cairo_set_source_rgb (c->cr, .5, .5, .5); + cairo_move_to (c->cr, -extents.width, -extents.y_bearing/2); + cairo_show_text (c->cr, label); + cairo_restore (c->cr); +} + +static void +add_base_line (struct chart *c) +{ + double y; + + cairo_save (c->cr); + cairo_set_line_width (c->cr, 2.); + if (c->relative) { + y = c->height / 2.; + } else { + y = c->height; + } + cairo_move_to (c->cr, 0, y); + cairo_line_to (c->cr, c->width, y); + cairo_set_source_rgb (c->cr, 1, 1, 1); + cairo_stroke (c->cr); + cairo_restore (c->cr); +} + +static void +add_absolute_lines (struct chart *c) +{ + const double dashes[] = { 2, 4 }; + const double vlog_steps[] = { 10, 5, 4, 3, 2, 1, .5, .4, .3, .2, .1}; + double v, y, dy; + unsigned int i; + char buf[80]; + cairo_text_extents_t extents; + + v = c->max_value / 2.; + + for (i = 0; i < sizeof (vlog_steps) / sizeof (vlog_steps[0]); i++) { + double vlog = log (v) / log (vlog_steps[i]); + if (vlog > 1) { + v = pow (vlog_steps[i], floor (vlog)); + goto done; + } + } + return; +done: + + dy = (c->height - PAD) / c->max_value; + + cairo_save (c->cr); + cairo_set_line_width (c->cr, 1.); + cairo_set_dash (c->cr, dashes, sizeof (dashes) / sizeof (dashes[0]), 0); + + i = 0; + do { + y = c->height - ++i * v * dy; + if (y < PAD) + break; + + cairo_set_font_size (c->cr, 8); + + sprintf (buf, "%.0fs", i*v/1000); + cairo_text_extents (c->cr, buf, &extents); + + cairo_set_source_rgba (c->cr, .75, 0, 0, .95); + cairo_move_to (c->cr, -extents.x_bearing, floor (y) - (extents.height/2 + extents.y_bearing) + .5); + cairo_show_text (c->cr, buf); + + cairo_move_to (c->cr, c->width-extents.width+extents.x_bearing, floor (y) - (extents.height/2 + extents.y_bearing) + .5); + cairo_show_text (c->cr, buf); + + cairo_set_source_rgba (c->cr, .75, 0, 0, .5); + cairo_move_to (c->cr, + ceil (extents.width + extents.x_bearing + 2), + floor (y) + .5); + cairo_line_to (c->cr, + floor (c->width - (extents.width + extents.x_bearing + 2)), + floor (y) + .5); + cairo_stroke (c->cr); + } while (1); + + cairo_restore (c->cr); +} + +static void +add_relative_lines (struct chart *c) +{ + const double dashes[] = { 2, 4 }; + const double v_steps[] = { 10, 5, 1, .5, .1, .05, .01}; + const int precision_steps[] = { 0, 0, 0, 1, 1, 2, 2}; + int precision; + double v, y, dy, mid; + unsigned int i; + char buf[80]; + cairo_text_extents_t extents; + + v = MAX (-c->min_value, c->max_value) / 200; + + for (i = 0; i < sizeof (v_steps) / sizeof (v_steps[0]); i++) { + if (v > v_steps[i]) { + v = v_steps[i]; + precision = precision_steps[i]; + goto done; + } + } + return; +done: + + mid = c->height/2.; + dy = (mid - PAD) / MAX (-c->min_value, c->max_value); + + cairo_save (c->cr); + cairo_set_line_width (c->cr, 1.); + cairo_set_dash (c->cr, dashes, sizeof (dashes) / sizeof (dashes[0]), 0); + cairo_set_font_size (c->cr, 8); + + i = 0; + do { + y = ++i * v * dy * 100; + if (y > mid) + break; + + sprintf (buf, "%.*fx", precision, i*v + 1); + cairo_text_extents (c->cr, buf, &extents); + + cairo_set_source_rgba (c->cr, .75, 0, 0, .95); + cairo_move_to (c->cr, -extents.x_bearing, floor (mid + y) - (extents.height/2 + extents.y_bearing)+ .5); + cairo_show_text (c->cr, buf); + + cairo_move_to (c->cr, c->width-extents.width+extents.x_bearing, floor (mid + y) - (extents.height/2 + extents.y_bearing)+ .5); + cairo_show_text (c->cr, buf); + + cairo_set_source_rgba (c->cr, 0, .75, 0, .95); + cairo_move_to (c->cr, -extents.x_bearing, ceil (mid - y) - (extents.height/2 + extents.y_bearing)+ .5); + cairo_show_text (c->cr, buf); + + cairo_move_to (c->cr, c->width-extents.width+extents.x_bearing, ceil (mid - y) - (extents.height/2 + extents.y_bearing)+ .5); + cairo_show_text (c->cr, buf); + + /* trim the dashes to no obscure the labels */ + cairo_set_source_rgba (c->cr, .75, 0, 0, .5); + cairo_move_to (c->cr, + ceil (extents.width + extents.x_bearing + 2), + floor (mid + y) + .5); + cairo_line_to (c->cr, + floor (c->width - (extents.width + extents.x_bearing + 2)), + floor (mid + y) + .5); + cairo_stroke (c->cr); + + cairo_set_source_rgba (c->cr, 0, .75, 0, .5); + cairo_move_to (c->cr, + ceil (extents.width + extents.x_bearing + 2), + ceil (mid - y) + .5); + cairo_line_to (c->cr, + floor (c->width - (extents.width + extents.x_bearing + 2)), + ceil (mid - y) + .5); + cairo_stroke (c->cr); + + } while (1); + + cairo_restore (c->cr); +} + +static void +add_slower_faster_guide (struct chart *c) +{ + cairo_text_extents_t extents; + + cairo_save (c->cr); + + cairo_set_font_size (c->cr, FONT_SIZE); + + cairo_text_extents (c->cr, "FASTER", &extents); + cairo_set_source_rgba (c->cr, 0, .75, 0, .5); + cairo_move_to (c->cr, + c->width/4. - extents.width/2. + extents.x_bearing, + 1 - extents.y_bearing); + cairo_show_text (c->cr, "FASTER"); + cairo_move_to (c->cr, + 3*c->width/4. - extents.width/2. + extents.x_bearing, + 1 - extents.y_bearing); + cairo_show_text (c->cr, "FASTER"); + + cairo_text_extents (c->cr, "SLOWER", &extents); + cairo_set_source_rgba (c->cr, .75, 0, 0, .5); + cairo_move_to (c->cr, + c->width/4. - extents.width/2. + extents.x_bearing, + c->height - 1); + cairo_show_text (c->cr, "SLOWER"); + cairo_move_to (c->cr, + 3*c->width/4. - extents.width/2. + extents.x_bearing, + c->height - 1); + cairo_show_text (c->cr, "SLOWER"); + + cairo_restore (c->cr); +} + +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++; + } + 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 +add_legend (struct chart *chart) +{ + cairo_text_extents_t extents; + const char *str; + int i, x, y; + + cairo_set_font_size (chart->cr, FONT_SIZE); + + x = PAD; + y = chart->height + PAD; + for (i = chart->relative; i < chart->num_reports; i++) { + str = chart->names[i] ? + chart->names[i] : chart->reports[i].configuration; + + set_report_color (chart, i); + + cairo_rectangle (chart->cr, x, y + 6, 8, 8); + cairo_fill (chart->cr); + + cairo_set_source_rgb (chart->cr, 1, 1, 1); + cairo_move_to (chart->cr, x + 10, y + FONT_SIZE + PAD / 2.); + cairo_text_extents (chart->cr, str, &extents); + cairo_show_text (chart->cr, str); + + x += 10 + 2 * PAD + ceil (extents.width); + } + + if (chart->relative) { + char buf[80]; + + str = chart->names[0] ? + chart->names[0] : chart->reports[0].configuration; + + sprintf (buf, "(relative to %s)", str); + cairo_text_extents (chart->cr, buf, &extents); + + cairo_set_source_rgb (chart->cr, 1, 1, 1); + cairo_move_to (chart->cr, + chart->width - 1 - extents.width, + y + FONT_SIZE + PAD / 2.); + cairo_show_text (chart->cr, buf); + } +} + +int +main (int argc, const char *argv[]) +{ + cairo_surface_t *surface; + struct chart chart; + test_report_t *t; + int i; + + chart.use_html = 0; + chart.width = 640; + chart.height = 480; + + chart.reports = xcalloc (argc-1, sizeof (cairo_perf_report_t)); + chart.names = xcalloc (argc-1, sizeof (cairo_perf_report_t)); + + chart.num_reports = 0; + for (i = 1; i < argc; i++) { + if (strcmp (argv[i], "--html") == 0) { + chart.use_html = 1; + } else if (strncmp (argv[i], "--width=", 8) == 0) { + chart.width = atoi (argv[i] + 8); + } else if (strncmp (argv[i], "--height=", 9) == 0) { + chart.height = atoi (argv[i] + 9); + } else if (strcmp (argv[i], "--name") == 0) { + if (i + 1 < argc) + chart.names[chart.num_reports] = argv[++i]; + } else if (strncmp (argv[i], "--name=", 7) == 0) { + chart.names[chart.num_reports] = argv[i] + 7; + } else { + cairo_perf_report_load (&chart.reports[chart.num_reports++], + argv[i], + test_report_cmp_name); + } + } + + for (chart.relative = 0; chart.relative <= 1; chart.relative++) { + surface = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, + chart.width, + chart.height + (FONT_SIZE + PAD) + 2*PAD); + chart.cr = cairo_create (surface); + cairo_surface_destroy (surface); + + cairo_set_source_rgb (chart.cr, 0, 0, 0); + cairo_paint (chart.cr); + + find_ranges (&chart); + + for (i = 0; i < chart.num_tests; i++) + test_background (&chart, i); + if (chart.relative) { + add_relative_lines (&chart); + add_slower_faster_guide (&chart); + } else + add_absolute_lines (&chart); + + cairo_save (chart.cr); + cairo_rectangle (chart.cr, 0, 0, chart.width, chart.height); + cairo_clip (chart.cr); + cairo_perf_reports_compare (&chart, !chart.relative); + cairo_restore (chart.cr); + + add_base_line (&chart); + add_legend (&chart); + + cairo_surface_write_to_png (cairo_get_target (chart.cr), + chart.relative ? + "cairo-perf-chart-relative.png" : + "cairo-perf-chart-absolute.png"); + cairo_destroy (chart.cr); + } + + /* Pointless memory cleanup, (would be a great place for talloc) */ + for (i = 0; i < chart.num_reports; i++) { + for (t = chart.reports[i].tests; t->name; t++) { + free (t->samples); + free (t->backend); + free (t->name); + } + free (chart.reports[i].tests); + free (chart.reports[i].configuration); + } + free (chart.names); + free (chart.reports); + + return 0; +} |