2009-08-19 12:36:56 +01:00
|
|
|
/*
|
|
|
|
|
* 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>
|
2010-06-24 14:59:18 +03:00
|
|
|
* Chris Wilson <chris@chris-wilson.co.uk>
|
2009-08-19 12:36:56 +01:00
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
#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
|
|
|
|
|
}
|
|
|
|
|
|
2009-08-30 12:33:40 +01:00
|
|
|
static int
|
2010-06-24 14:59:18 +03:00
|
|
|
_double_cmp (const void *_a,
|
|
|
|
|
const void *_b)
|
2009-08-30 12:33:40 +01:00
|
|
|
{
|
|
|
|
|
const double *a = _a;
|
|
|
|
|
const double *b = _b;
|
|
|
|
|
|
|
|
|
|
if (*a > *b)
|
|
|
|
|
return 1;
|
|
|
|
|
if (*a < *b)
|
|
|
|
|
return -1;
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void
|
2010-06-24 14:59:18 +03:00
|
|
|
trim_outliers (double *values,
|
|
|
|
|
int num_values,
|
|
|
|
|
double *min,
|
|
|
|
|
double *max)
|
2009-08-30 12:33:40 +01:00
|
|
|
{
|
|
|
|
|
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];
|
|
|
|
|
}
|
|
|
|
|
|
2009-08-19 12:36:56 +01:00
|
|
|
static void
|
|
|
|
|
find_ranges (struct chart *chart)
|
|
|
|
|
{
|
|
|
|
|
test_report_t **tests, *min_test;
|
2009-08-30 12:33:40 +01:00
|
|
|
double *values;
|
|
|
|
|
int num_values, size_values;
|
2009-08-19 12:36:56 +01:00
|
|
|
double min = 0, max = 0;
|
|
|
|
|
double test_time;
|
|
|
|
|
int seen_non_null;
|
|
|
|
|
int num_tests = 0;
|
|
|
|
|
int i;
|
|
|
|
|
|
2009-08-30 12:33:40 +01:00
|
|
|
num_values = 0;
|
|
|
|
|
size_values = 64;
|
|
|
|
|
values = xmalloc (size_values * sizeof (double));
|
|
|
|
|
|
2009-08-19 12:36:56 +01:00
|
|
|
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) {
|
2009-08-30 12:33:40 +01:00
|
|
|
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;
|
2009-08-19 12:36:56 +01:00
|
|
|
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;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2009-08-30 12:33:40 +01:00
|
|
|
if (chart->relative)
|
|
|
|
|
trim_outliers (values, num_values, &min, &max);
|
2009-08-19 12:36:56 +01:00
|
|
|
chart->min_value = min;
|
|
|
|
|
chart->max_value = max;
|
|
|
|
|
chart->num_tests = num_tests;
|
2009-08-30 12:33:40 +01:00
|
|
|
|
|
|
|
|
free (values);
|
|
|
|
|
free (tests);
|
2009-08-19 12:36:56 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#define SET_COLOR(C, R, G, B) (C)->red = (R), (C)->green = (G), (C)->blue = (B)
|
|
|
|
|
static void
|
2010-06-24 14:59:18 +03:00
|
|
|
hsv_to_rgb (double h,
|
|
|
|
|
double s,
|
|
|
|
|
double v,
|
|
|
|
|
struct color *color)
|
2009-08-19 12:36:56 +01:00
|
|
|
{
|
|
|
|
|
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
|
2010-06-24 14:59:18 +03:00
|
|
|
test_background (struct chart *c,
|
|
|
|
|
int test)
|
2009-08-19 12:36:56 +01:00
|
|
|
{
|
|
|
|
|
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,
|
2010-06-24 14:59:18 +03:00
|
|
|
floor (dx + x) - floor (x), c->height);
|
2009-08-19 12:36:56 +01:00
|
|
|
cairo_fill (c->cr);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void
|
2010-06-24 14:59:18 +03:00
|
|
|
add_chart (struct chart *c,
|
|
|
|
|
int test,
|
|
|
|
|
int report,
|
|
|
|
|
double value)
|
2009-08-19 12:36:56 +01:00
|
|
|
{
|
|
|
|
|
double dx, dy, x;
|
|
|
|
|
|
|
|
|
|
if (fabs (value) < 0.1)
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
set_report_color (c, report);
|
|
|
|
|
|
|
|
|
|
if (c->relative) {
|
2009-08-30 12:33:40 +01:00
|
|
|
cairo_text_extents_t extents;
|
|
|
|
|
cairo_bool_t show_label;
|
|
|
|
|
char buf[80];
|
|
|
|
|
double y;
|
|
|
|
|
|
2009-08-19 12:36:56 +01:00
|
|
|
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.);
|
2009-08-30 12:33:40 +01:00
|
|
|
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);
|
2009-08-19 12:36:56 +01:00
|
|
|
} 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);
|
2009-08-30 12:33:40 +01:00
|
|
|
cairo_fill (c->cr);
|
2009-08-19 12:36:56 +01:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void
|
2010-06-24 14:59:18 +03:00
|
|
|
add_label (struct chart *c,
|
|
|
|
|
int test,
|
|
|
|
|
const char *label)
|
2009-08-19 12:36:56 +01:00
|
|
|
{
|
|
|
|
|
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;
|
2009-08-30 12:33:40 +01:00
|
|
|
cairo_translate (c->cr, x, PAD / 2);
|
2009-08-19 12:36:56 +01:00
|
|
|
cairo_rotate (c->cr, -M_PI/2);
|
|
|
|
|
|
2009-08-30 12:33:40 +01:00
|
|
|
cairo_set_source_rgb (c->cr, .5, .5, .5);
|
|
|
|
|
cairo_move_to (c->cr, -extents.width, -extents.y_bearing/2);
|
2009-08-19 12:36:56 +01:00
|
|
|
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);
|
|
|
|
|
|
2009-08-30 12:33:40 +01:00
|
|
|
cairo_set_source_rgba (c->cr, .75, 0, 0, .95);
|
2009-08-19 12:36:56 +01:00
|
|
|
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);
|
2009-08-30 12:33:40 +01:00
|
|
|
|
|
|
|
|
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);
|
2009-08-19 12:36:56 +01:00
|
|
|
} while (1);
|
|
|
|
|
|
|
|
|
|
cairo_restore (c->cr);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
|
add_relative_lines (struct chart *c)
|
|
|
|
|
{
|
|
|
|
|
const double dashes[] = { 2, 4 };
|
2009-08-30 12:33:40 +01:00
|
|
|
const double v_steps[] = { 10, 5, 1, .5, .1, .05, .01};
|
|
|
|
|
const int precision_steps[] = { 0, 0, 0, 1, 1, 2, 2};
|
|
|
|
|
int precision;
|
2009-08-19 12:36:56 +01:00
|
|
|
double v, y, dy, mid;
|
|
|
|
|
unsigned int i;
|
|
|
|
|
char buf[80];
|
|
|
|
|
cairo_text_extents_t extents;
|
|
|
|
|
|
2009-08-30 12:33:40 +01:00
|
|
|
v = MAX (-c->min_value, c->max_value) / 200;
|
2009-08-19 12:36:56 +01:00
|
|
|
|
2009-08-30 12:33:40 +01:00
|
|
|
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];
|
2009-08-19 12:36:56 +01:00
|
|
|
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);
|
2009-08-30 12:33:40 +01:00
|
|
|
cairo_set_font_size (c->cr, 8);
|
2009-08-19 12:36:56 +01:00
|
|
|
|
|
|
|
|
i = 0;
|
|
|
|
|
do {
|
2009-08-30 12:33:40 +01:00
|
|
|
y = ++i * v * dy * 100;
|
2009-08-19 12:36:56 +01:00
|
|
|
if (y > mid)
|
|
|
|
|
break;
|
|
|
|
|
|
2009-08-30 12:33:40 +01:00
|
|
|
sprintf (buf, "%.*fx", precision, i*v + 1);
|
2009-08-19 12:36:56 +01:00
|
|
|
cairo_text_extents (c->cr, buf, &extents);
|
|
|
|
|
|
2009-08-30 12:33:40 +01:00
|
|
|
cairo_set_source_rgba (c->cr, .75, 0, 0, .95);
|
2009-08-19 12:36:56 +01:00
|
|
|
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);
|
2009-08-30 12:33:40 +01:00
|
|
|
|
|
|
|
|
/* 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);
|
|
|
|
|
|
2009-08-19 12:36:56 +01:00
|
|
|
} while (1);
|
|
|
|
|
|
|
|
|
|
cairo_restore (c->cr);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
|
add_slower_faster_guide (struct chart *c)
|
|
|
|
|
{
|
|
|
|
|
cairo_text_extents_t extents;
|
|
|
|
|
|
|
|
|
|
cairo_save (c->cr);
|
|
|
|
|
|
2009-08-30 12:33:40 +01:00
|
|
|
cairo_set_font_size (c->cr, FONT_SIZE);
|
2009-08-19 12:36:56 +01:00
|
|
|
|
|
|
|
|
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
|
2010-06-24 14:59:18 +03:00
|
|
|
cairo_perf_reports_compare (struct chart *chart,
|
|
|
|
|
cairo_bool_t print)
|
2009-08-19 12:36:56 +01:00
|
|
|
{
|
|
|
|
|
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 {
|
2009-08-30 12:33:40 +01:00
|
|
|
add_chart (chart, num_test, i, report_time);
|
2009-08-19 12:36:56 +01:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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
|
2010-06-24 14:59:18 +03:00
|
|
|
main (int argc,
|
|
|
|
|
const char *argv[])
|
2009-08-19 12:36:56 +01:00
|
|
|
{
|
|
|
|
|
cairo_surface_t *surface;
|
|
|
|
|
struct chart chart;
|
|
|
|
|
test_report_t *t;
|
|
|
|
|
int i;
|
|
|
|
|
|
|
|
|
|
chart.use_html = 0;
|
2009-08-30 12:33:40 +01:00
|
|
|
chart.width = 640;
|
|
|
|
|
chart.height = 480;
|
2009-08-19 12:36:56 +01:00
|
|
|
|
|
|
|
|
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++],
|
2010-06-24 14:59:18 +03:00
|
|
|
argv[i],
|
2009-08-19 12:36:56 +01:00
|
|
|
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);
|
|
|
|
|
|
2009-08-30 12:33:40 +01:00
|
|
|
cairo_set_source_rgb (chart.cr, 0, 0, 0);
|
|
|
|
|
cairo_paint (chart.cr);
|
|
|
|
|
|
2009-08-19 12:36:56 +01:00
|
|
|
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);
|
|
|
|
|
|
2009-08-30 12:33:40 +01:00
|
|
|
cairo_save (chart.cr);
|
|
|
|
|
cairo_rectangle (chart.cr, 0, 0, chart.width, chart.height);
|
|
|
|
|
cairo_clip (chart.cr);
|
2009-08-19 12:36:56 +01:00
|
|
|
cairo_perf_reports_compare (&chart, !chart.relative);
|
2009-08-30 12:33:40 +01:00
|
|
|
cairo_restore (chart.cr);
|
2009-08-19 12:36:56 +01:00
|
|
|
|
|
|
|
|
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;
|
|
|
|
|
}
|