| /* |
| * 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; |
| } |