summaryrefslogtreecommitdiff
path: root/perf/cairo-perf-chart.c
diff options
context:
space:
mode:
Diffstat (limited to 'perf/cairo-perf-chart.c')
-rw-r--r--perf/cairo-perf-chart.c751
1 files changed, 751 insertions, 0 deletions
diff --git a/perf/cairo-perf-chart.c b/perf/cairo-perf-chart.c
new file mode 100644
index 00000000..401fa593
--- /dev/null
+++ b/perf/cairo-perf-chart.c
@@ -0,0 +1,751 @@
+/*
+ * 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 void
+find_ranges (struct chart *chart)
+{
+ test_report_t **tests, *min_test;
+ double min = 0, max = 0;
+ double test_time;
+ int seen_non_null;
+ int num_tests = 0;
+ 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;
+
+ 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;
+
+ v = to_factor (test_time / report_time);
+ 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;
+ }
+ }
+ }
+ }
+
+ free (tests);
+
+ chart->min_value = min;
+ chart->max_value = max;
+ chart->num_tests = num_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) {
+ 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.);
+ } 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 - (extents.height / 2. + extents.y_bearing));
+ cairo_rotate (c->cr, -M_PI/2);
+ cairo_translate (c->cr, -extents.width, 0);
+
+ cairo_set_source_rgba (c->cr, 1, 1, 1, .5);
+ cairo_move_to (c->cr, 0, 0);
+ 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_source_rgba (c->cr, .95, .95, .95, .5);
+ cairo_move_to (c->cr, 0, floor (y) + .5);
+ cairo_line_to (c->cr, c->width, floor (y) + .5);
+ cairo_stroke (c->cr);
+
+ cairo_set_font_size (c->cr, 8);
+
+ cairo_set_source_rgba (c->cr, 0, .75, 0, .95);
+ sprintf (buf, "%.0fs", i*v/1000);
+ cairo_text_extents (c->cr, buf, &extents);
+
+ 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);
+ } while (1);
+
+ cairo_restore (c->cr);
+}
+
+static void
+add_relative_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, mid;
+ unsigned int i;
+ char buf[80];
+ cairo_text_extents_t extents;
+
+ v = MAX (-c->min_value, 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:
+
+ 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);
+
+ i = 0;
+ do {
+ y = ++i * v * dy;
+ if (y > mid)
+ break;
+
+ cairo_set_source_rgba (c->cr, .95, .95, .95, .5);
+ cairo_move_to (c->cr, 0, floor (mid + y) + .5);
+ cairo_line_to (c->cr, c->width, floor (mid + y) + .5);
+
+ cairo_move_to (c->cr, 0, ceil (mid - y) + .5);
+ cairo_line_to (c->cr, c->width, ceil (mid - y) + .5);
+ cairo_stroke (c->cr);
+
+ cairo_set_font_size (c->cr, 8);
+
+ cairo_set_source_rgba (c->cr, .75, 0, 0, .95);
+ sprintf (buf, "%.0fx", i*v/100 + 1);
+ cairo_text_extents (c->cr, buf, &extents);
+
+ 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);
+ } 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, 3 * FONT_SIZE / 2);
+
+ 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/1000);
+ }
+ }
+ }
+
+ 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 = 480;
+ chart.height = 300;
+
+ 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);
+
+ 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_perf_reports_compare (&chart, !chart.relative);
+
+ 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;
+}