mirror of
https://gitlab.freedesktop.org/cairo/cairo.git
synced 2026-02-03 14:50:30 +01:00
[perf] A crude tool to visualise performance changes across a series.
Generate a cairo-perf-diff graph for a series of commits in order to be able to identify significant commits. Still very crude, but minimally functional.
This commit is contained in:
parent
41c8eefc6d
commit
f2ff794426
14 changed files with 1865 additions and 478 deletions
|
|
@ -480,6 +480,9 @@ fi
|
|||
|
||||
dnl ===========================================================================
|
||||
|
||||
# We use GTK+ for some utility/debugging tools
|
||||
PKG_CHECK_MODULES(gtk, "gtk+-2.0",, AC_MSG_RESULT(no))
|
||||
|
||||
AC_CONFIG_FILES([
|
||||
Makefile
|
||||
boilerplate/Makefile
|
||||
|
|
|
|||
1
perf/.gitignore
vendored
1
perf/.gitignore
vendored
|
|
@ -2,6 +2,7 @@ TAGS
|
|||
tags
|
||||
cairo-perf
|
||||
cairo-perf-diff-files
|
||||
cairo-perf-graph-files
|
||||
valgrind-log
|
||||
callgrind.out.*
|
||||
index.html
|
||||
|
|
|
|||
|
|
@ -7,15 +7,19 @@ AM_CPPFLAGS = \
|
|||
-I$(top_builddir)/src \
|
||||
$(CAIRO_CFLAGS)
|
||||
|
||||
EXTRA_PROGRAMS += cairo-perf cairo-perf-diff-files
|
||||
EXTRA_PROGRAMS += cairo-perf cairo-perf-diff-files cairo-perf-graph-files
|
||||
EXTRA_DIST += cairo-perf-diff
|
||||
EXTRA_LTLIBRARIES += libcairoperf.la
|
||||
|
||||
LDADD = $(top_builddir)/boilerplate/libcairoboilerplate.la \
|
||||
$(top_builddir)/src/libcairo.la \
|
||||
libcairoperf.la \
|
||||
$(CAIROPERF_LIBS)
|
||||
|
||||
cairo_perf_SOURCES = \
|
||||
cairo-perf.c \
|
||||
cairo-perf.h \
|
||||
cairo-perf-cover.c \
|
||||
cairo-stats.c \
|
||||
cairo-stats.h \
|
||||
box-outline.c \
|
||||
composite-checker.c \
|
||||
fill.c \
|
||||
|
|
@ -48,14 +52,20 @@ cairo_perf_SOURCES += cairo-perf-posix.c
|
|||
endif
|
||||
endif
|
||||
|
||||
cairo_perf_diff_files_SOURCES = \
|
||||
cairo-perf-diff-files.c \
|
||||
libcairoperf_la_SOURCES = \
|
||||
cairo-perf-report.c \
|
||||
cairo-stats.c \
|
||||
cairo-stats.h
|
||||
|
||||
LDADD = $(top_builddir)/boilerplate/libcairoboilerplate.la \
|
||||
$(top_builddir)/src/libcairo.la \
|
||||
$(CAIROPERF_LIBS)
|
||||
cairo_perf_diff_files_SOURCES = \
|
||||
cairo-perf-diff-files.c
|
||||
|
||||
cairo_perf_graph_files_SOURCES = \
|
||||
cairo-perf-graph.h \
|
||||
cairo-perf-graph-files.c \
|
||||
cairo-perf-graph-widget.c
|
||||
cairo_perf_graph_files_CFLAGS = @gtk_CFLAGS@
|
||||
cairo_perf_graph_files_LDADD = @gtk_LIBS@ $(LDADD)
|
||||
|
||||
$(top_builddir)/boilerplate/libcairoboilerplate.la: $(top_builddir)/src/libcairo.la
|
||||
cd $(top_builddir)/boilerplate && $(MAKE) $(AM_MAKEFLAGS) libcairoboilerplate.la
|
||||
|
|
|
|||
|
|
@ -37,4 +37,8 @@ $(CFG)/cairo-perf.exe: $(OBJECTS)
|
|||
|
||||
cairo-perf-diff-files:
|
||||
@mkdir -p $(CFG)
|
||||
@$(CC) $(CFLAGS) -Fe"$@" cairo-perf-diff-files.c cairo-stats.c -link $(LDFLAGS)
|
||||
@$(CC) $(CFLAGS) -Fe"$@" cairo-perf-diff-files.c cairo-perf-report.c cairo-stats.c -link $(LDFLAGS)
|
||||
|
||||
cairo-perf-graph-files:
|
||||
@mkdir -p $(CFG)
|
||||
@$(CC) $(CFLAGS) -Fe"$@" cairo-perf-graph-files.c cairo-perf-report.c cairo-stats.c -link $(LDFLAGS)
|
||||
|
|
|
|||
|
|
@ -121,9 +121,16 @@ cpu_count() {
|
|||
# results from a run with an equivalent src tree.
|
||||
rev2perf() {
|
||||
rev=$1
|
||||
sha=`rev2sha $rev`
|
||||
src_tree_sha=`rev2sha $rev:src`
|
||||
perf_tree_sha=`rev2sha HEAD:perf`
|
||||
echo "$CAIRO_PERF_DIR/${perf_tree_sha}-${src_tree_sha}.perf"
|
||||
echo "$CAIRO_PERF_DIR/${sha}-${perf_tree_sha}-${src_tree_sha}.perf"
|
||||
}
|
||||
rev2perf_glob() {
|
||||
rev=$1
|
||||
src_tree_sha=`rev2sha $rev:src`
|
||||
perf_tree_sha=`rev2sha HEAD:perf`
|
||||
echo "$CAIRO_PERF_DIR/*-${perf_tree_sha}-${src_tree_sha}.perf"
|
||||
}
|
||||
|
||||
# Usage: run_cairo_perf_if_not_cached <rev> <suffix>
|
||||
|
|
@ -138,7 +145,8 @@ run_cairo_perf_if_not_cached() {
|
|||
owd=`pwd`
|
||||
sha=`rev2sha $rev`
|
||||
perf=`rev2perf $rev`
|
||||
if [ -e $perf ] && [ "$force_cairo_perf" != "true" ]; then
|
||||
glob=`rev2perf_glob $rev`
|
||||
if [ -e $glob ] && [ "$force_cairo_perf" != "true" ]; then
|
||||
return 0
|
||||
fi
|
||||
if [ ! -d $CAIRO_PERF_DIR ]; then
|
||||
|
|
|
|||
|
|
@ -27,10 +27,6 @@
|
|||
|
||||
#include "cairo-perf.h"
|
||||
|
||||
/* We use _GNU_SOURCE for getline and strndup if available. */
|
||||
#ifndef _GNU_SOURCE
|
||||
# define _GNU_SOURCE
|
||||
#endif
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
|
@ -38,50 +34,6 @@
|
|||
#include <ctype.h>
|
||||
#include <math.h>
|
||||
#include <assert.h>
|
||||
#ifdef HAVE_LIBGEN_H
|
||||
#include <libgen.h>
|
||||
#endif
|
||||
|
||||
typedef struct _test_report {
|
||||
int id;
|
||||
const char *configuration;
|
||||
char *backend;
|
||||
char *content;
|
||||
char *name;
|
||||
int size;
|
||||
|
||||
/* The samples only exists for "raw" reports */
|
||||
cairo_perf_ticks_t *samples;
|
||||
unsigned int samples_size;
|
||||
unsigned int samples_count;
|
||||
|
||||
/* The stats are either read directly or computed from samples.
|
||||
* If the stats have not yet been computed from samples, then
|
||||
* iterations will be 0. */
|
||||
cairo_stats_t stats;
|
||||
} test_report_t;
|
||||
|
||||
typedef struct _test_diff {
|
||||
test_report_t **tests;
|
||||
int num_tests;
|
||||
double min;
|
||||
double max;
|
||||
double change;
|
||||
} test_diff_t;
|
||||
|
||||
typedef struct _cairo_perf_report {
|
||||
char *configuration;
|
||||
const char *name;
|
||||
test_report_t *tests;
|
||||
int tests_size;
|
||||
int tests_count;
|
||||
} cairo_perf_report_t;
|
||||
|
||||
typedef enum {
|
||||
TEST_REPORT_STATUS_SUCCESS,
|
||||
TEST_REPORT_STATUS_COMMENT,
|
||||
TEST_REPORT_STATUS_ERROR
|
||||
} test_report_status_t;
|
||||
|
||||
typedef struct _cairo_perf_report_options {
|
||||
double min_change;
|
||||
|
|
@ -96,416 +48,6 @@ typedef struct _cairo_perf_diff_files_args {
|
|||
cairo_perf_report_options_t options;
|
||||
} cairo_perf_diff_files_args_t;
|
||||
|
||||
/* 'ssize_t' does not exist in the C standard on win32.
|
||||
* We use 'ptrdiff_t', which is nearly equivalent. */
|
||||
#ifdef _MSC_VER
|
||||
typedef ptrdiff_t ssize_t;
|
||||
#endif
|
||||
|
||||
#ifndef __USE_GNU
|
||||
static ssize_t
|
||||
getline (char **lineptr, size_t *n, FILE *stream);
|
||||
|
||||
static char *
|
||||
strndup (const char *s, size_t n);
|
||||
#endif
|
||||
|
||||
#ifdef _MSC_VER
|
||||
static long long
|
||||
strtoll(const char *nptr, char **endptr, int base);
|
||||
|
||||
static char *
|
||||
basename(char *path);
|
||||
#endif
|
||||
|
||||
/* Ad-hoc parsing, macros with a strong dependence on the calling
|
||||
* context, and plenty of other ugliness is here. But at least it's
|
||||
* not perl... */
|
||||
#define parse_error(...) fprintf(stderr, __VA_ARGS__); return TEST_REPORT_STATUS_ERROR;
|
||||
#define skip_char(c) \
|
||||
do { \
|
||||
if (*s && *s == (c)) { \
|
||||
s++; \
|
||||
} else { \
|
||||
parse_error ("expected '%c' but found '%c'", c, *s); \
|
||||
} \
|
||||
} while (0)
|
||||
#define skip_space() while (*s && (*s == ' ' || *s == '\t')) s++;
|
||||
#define parse_int(result) \
|
||||
do { \
|
||||
(result) = strtol (s, &end, 10); \
|
||||
if (*s && end != s) { \
|
||||
s = end; \
|
||||
} else { \
|
||||
parse_error("expected integer but found %s", s); \
|
||||
} \
|
||||
} while (0)
|
||||
#define parse_long_long(result) \
|
||||
do { \
|
||||
(result) = strtoll (s, &end, 10); \
|
||||
if (*s && end != s) { \
|
||||
s = end; \
|
||||
} else { \
|
||||
parse_error("expected integer but found %s", s); \
|
||||
} \
|
||||
} while (0)
|
||||
#define parse_double(result) \
|
||||
do { \
|
||||
(result) = strtod (s, &end); \
|
||||
if (*s && end != s) { \
|
||||
s = end; \
|
||||
} else { \
|
||||
parse_error("expected floating-point value but found %s", s); \
|
||||
} \
|
||||
} while (0)
|
||||
/* Here a string is simply a sequence of non-whitespace */
|
||||
#define parse_string(result) \
|
||||
do { \
|
||||
for (end = s; *end; end++) \
|
||||
if (isspace (*end)) \
|
||||
break; \
|
||||
(result) = strndup (s, end - s); \
|
||||
if ((result) == NULL) { \
|
||||
fprintf (stderr, "Out of memory.\n"); \
|
||||
exit (1); \
|
||||
} \
|
||||
s = end; \
|
||||
} while (0)
|
||||
|
||||
static test_report_status_t
|
||||
test_report_parse (test_report_t *report, char *line, char *configuration)
|
||||
{
|
||||
char *end;
|
||||
char *s = line;
|
||||
cairo_bool_t is_raw = FALSE;
|
||||
double min_time, median_time;
|
||||
|
||||
/* The code here looks funny unless you understand that these are
|
||||
* all macro calls, (and then the code just looks sick). */
|
||||
if (*s == '\n')
|
||||
return TEST_REPORT_STATUS_COMMENT;
|
||||
|
||||
skip_char ('[');
|
||||
skip_space ();
|
||||
if (*s == '#')
|
||||
return TEST_REPORT_STATUS_COMMENT;
|
||||
if (*s == '*') {
|
||||
s++;
|
||||
is_raw = TRUE;
|
||||
} else {
|
||||
parse_int (report->id);
|
||||
}
|
||||
skip_char (']');
|
||||
|
||||
skip_space ();
|
||||
|
||||
report->configuration = configuration;
|
||||
parse_string (report->backend);
|
||||
end = strrchr (report->backend, '-');
|
||||
if (*end)
|
||||
*end++ = '\0';
|
||||
report->content = end;
|
||||
|
||||
skip_space ();
|
||||
|
||||
parse_string (report->name);
|
||||
end = strrchr (report->name, '-');
|
||||
if (*end)
|
||||
*end++ = '\0';
|
||||
report->size = atoi (end);
|
||||
|
||||
skip_space ();
|
||||
|
||||
report->samples = NULL;
|
||||
report->samples_size = 0;
|
||||
report->samples_count = 0;
|
||||
|
||||
if (is_raw) {
|
||||
parse_double (report->stats.ticks_per_ms);
|
||||
skip_space ();
|
||||
|
||||
report->samples_size = 5;
|
||||
report->samples = xmalloc (report->samples_size * sizeof (cairo_perf_ticks_t));
|
||||
do {
|
||||
if (report->samples_count == report->samples_size) {
|
||||
report->samples_size *= 2;
|
||||
report->samples = xrealloc (report->samples,
|
||||
report->samples_size * sizeof (cairo_perf_ticks_t));
|
||||
}
|
||||
parse_long_long (report->samples[report->samples_count++]);
|
||||
skip_space ();
|
||||
} while (*s && *s != '\n');
|
||||
report->stats.iterations = 0;
|
||||
skip_char ('\n');
|
||||
} else {
|
||||
parse_double (report->stats.min_ticks);
|
||||
skip_space ();
|
||||
|
||||
parse_double (min_time);
|
||||
report->stats.ticks_per_ms = report->stats.min_ticks / min_time;
|
||||
|
||||
skip_space ();
|
||||
|
||||
parse_double (median_time);
|
||||
report->stats.median_ticks = median_time * report->stats.ticks_per_ms;
|
||||
|
||||
skip_space ();
|
||||
|
||||
parse_double (report->stats.std_dev);
|
||||
report->stats.std_dev /= 100.0;
|
||||
skip_char ('%');
|
||||
|
||||
skip_space ();
|
||||
|
||||
parse_int (report->stats.iterations);
|
||||
|
||||
skip_space ();
|
||||
skip_char ('\n');
|
||||
}
|
||||
|
||||
return TEST_REPORT_STATUS_SUCCESS;
|
||||
}
|
||||
|
||||
/* We conditionally provide a custom implementation of getline and strndup
|
||||
* as needed. These aren't necessary full-fledged general purpose
|
||||
* implementations. They just get the job done for our purposes.
|
||||
*/
|
||||
#ifndef __USE_GNU
|
||||
#define POORMANS_GETLINE_BUFFER_SIZE (65536)
|
||||
static ssize_t
|
||||
getline (char **lineptr, size_t *n, FILE *stream)
|
||||
{
|
||||
if (!*lineptr)
|
||||
{
|
||||
*n = POORMANS_GETLINE_BUFFER_SIZE;
|
||||
*lineptr = (char *) malloc (*n);
|
||||
}
|
||||
|
||||
if (!fgets (*lineptr, *n, stream))
|
||||
return -1;
|
||||
|
||||
if (!feof (stream) && !strchr (*lineptr, '\n'))
|
||||
{
|
||||
fprintf (stderr, "The poor man's implementation of getline in "
|
||||
__FILE__ " needs a bigger buffer. Perhaps it's "
|
||||
"time for a complete implementation of getline.\n");
|
||||
exit (0);
|
||||
}
|
||||
|
||||
return strlen (*lineptr);
|
||||
}
|
||||
#undef POORMANS_GETLINE_BUFFER_SIZE
|
||||
|
||||
static char *
|
||||
strndup (const char *s, size_t n)
|
||||
{
|
||||
size_t len;
|
||||
char *sdup;
|
||||
|
||||
if (!s)
|
||||
return NULL;
|
||||
|
||||
len = strlen (s);
|
||||
len = (n < len ? n : len);
|
||||
sdup = (char *) malloc (len + 1);
|
||||
if (sdup)
|
||||
{
|
||||
memcpy (sdup, s, len);
|
||||
sdup[len] = '\0';
|
||||
}
|
||||
|
||||
return sdup;
|
||||
}
|
||||
#endif /* ifndef __USE_GNU */
|
||||
|
||||
/* We provide hereafter a win32 implementation of the basename
|
||||
* and strtoll functions which are not available otherwise.
|
||||
* The basename function is fully compliant to its GNU specs.
|
||||
*/
|
||||
#ifdef _MSC_VER
|
||||
long long
|
||||
strtoll(const char *nptr, char **endptr, int base)
|
||||
{
|
||||
return _atoi64(nptr);
|
||||
}
|
||||
|
||||
static char *
|
||||
basename(char *path)
|
||||
{
|
||||
char *end, *s;
|
||||
|
||||
end = (path + strlen(path) - 1);
|
||||
while (end && (end >= path + 1) && (*end == '/')) {
|
||||
*end = '\0';
|
||||
end--;
|
||||
}
|
||||
|
||||
s = strrchr(path, '/');
|
||||
if (s) {
|
||||
if (s == end) {
|
||||
return s;
|
||||
} else {
|
||||
return s+1;
|
||||
}
|
||||
} else {
|
||||
return path;
|
||||
}
|
||||
}
|
||||
#endif /* ifndef _MSC_VER */
|
||||
|
||||
static int
|
||||
test_report_cmp_backend_then_name (const void *a, const void *b)
|
||||
{
|
||||
const test_report_t *a_test = a;
|
||||
const test_report_t *b_test = b;
|
||||
|
||||
int cmp;
|
||||
|
||||
cmp = strcmp (a_test->backend, b_test->backend);
|
||||
if (cmp)
|
||||
return cmp;
|
||||
|
||||
cmp = strcmp (a_test->content, b_test->content);
|
||||
if (cmp)
|
||||
return cmp;
|
||||
|
||||
/* A NULL name is a list-termination marker, so force it last. */
|
||||
if (a_test->name == NULL)
|
||||
if (b_test->name == NULL)
|
||||
return 0;
|
||||
else
|
||||
return 1;
|
||||
else if (b_test->name == NULL)
|
||||
return -1;
|
||||
|
||||
cmp = strcmp (a_test->name, b_test->name);
|
||||
if (cmp)
|
||||
return cmp;
|
||||
|
||||
if (a_test->size < b_test->size)
|
||||
return -1;
|
||||
if (a_test->size > b_test->size)
|
||||
return 1;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void
|
||||
cairo_perf_report_sort_and_compute_stats (cairo_perf_report_t *report)
|
||||
{
|
||||
test_report_t *base, *next, *last, *t;
|
||||
|
||||
/* First we sort, since the diff needs both lists in the same
|
||||
* order */
|
||||
qsort (report->tests, report->tests_count, sizeof (test_report_t),
|
||||
test_report_cmp_backend_then_name);
|
||||
|
||||
/* The sorting also brings all related raw reports together so we
|
||||
* can condense them and compute the stats.
|
||||
*/
|
||||
base = &report->tests[0];
|
||||
last = &report->tests[report->tests_count - 1];
|
||||
while (base <= last) {
|
||||
next = base+1;
|
||||
if (next <= last) {
|
||||
while (next <= last &&
|
||||
test_report_cmp_backend_then_name (base, next) == 0)
|
||||
{
|
||||
next++;
|
||||
}
|
||||
if (next != base) {
|
||||
unsigned int new_samples_count = base->samples_count;
|
||||
for (t = base + 1; t < next; t++)
|
||||
new_samples_count += t->samples_count;
|
||||
if (new_samples_count > base->samples_size) {
|
||||
base->samples_size = new_samples_count;
|
||||
base->samples = xrealloc (base->samples,
|
||||
base->samples_size * sizeof (cairo_perf_ticks_t));
|
||||
}
|
||||
for (t = base + 1; t < next; t++) {
|
||||
memcpy (&base->samples[base->samples_count], t->samples,
|
||||
t->samples_count * sizeof (cairo_perf_ticks_t));
|
||||
base->samples_count += t->samples_count;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (base->samples)
|
||||
_cairo_stats_compute (&base->stats, base->samples, base->samples_count);
|
||||
base = next;
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
cairo_perf_report_load (cairo_perf_report_t *report, const char *filename)
|
||||
{
|
||||
FILE *file;
|
||||
test_report_status_t status;
|
||||
int line_number = 0;
|
||||
char *line = NULL;
|
||||
size_t line_size = 0;
|
||||
char *configuration;
|
||||
char *dot;
|
||||
char *baseName;
|
||||
|
||||
configuration = xmalloc (strlen (filename) * sizeof (char) + 1);
|
||||
strcpy (configuration, filename);
|
||||
baseName = strdup (basename (configuration));
|
||||
report->configuration = xmalloc (strlen (filename) * sizeof (char) + 1);
|
||||
strcpy(report->configuration, baseName);
|
||||
free (configuration);
|
||||
dot = strrchr (report->configuration, '.');
|
||||
if (dot)
|
||||
*dot = '\0';
|
||||
|
||||
report->name = filename;
|
||||
report->tests_size = 16;
|
||||
report->tests = xmalloc (report->tests_size * sizeof (test_report_t));
|
||||
report->tests_count = 0;
|
||||
|
||||
file = fopen (filename, "r");
|
||||
if (file == NULL) {
|
||||
fprintf (stderr, "Failed to open %s: %s\n",
|
||||
filename, strerror (errno));
|
||||
exit (1);
|
||||
}
|
||||
|
||||
while (1) {
|
||||
if (report->tests_count == report->tests_size) {
|
||||
report->tests_size *= 2;
|
||||
report->tests = xrealloc (report->tests,
|
||||
report->tests_size * sizeof (test_report_t));
|
||||
}
|
||||
|
||||
line_number++;
|
||||
if (getline (&line, &line_size, file) == -1)
|
||||
break;
|
||||
|
||||
status = test_report_parse (&report->tests[report->tests_count],
|
||||
line, report->configuration);
|
||||
if (status == TEST_REPORT_STATUS_ERROR)
|
||||
fprintf (stderr, "Ignoring unrecognized line %d of %s:\n%s",
|
||||
line_number, filename, line);
|
||||
if (status == TEST_REPORT_STATUS_SUCCESS)
|
||||
report->tests_count++;
|
||||
/* Do nothing on TEST_REPORT_STATUS_COMMENT */
|
||||
}
|
||||
|
||||
if (line)
|
||||
free (line);
|
||||
|
||||
fclose (file);
|
||||
|
||||
cairo_perf_report_sort_and_compute_stats (report);
|
||||
|
||||
/* Add one final report with a NULL name to terminate the list. */
|
||||
if (report->tests_count == report->tests_size) {
|
||||
report->tests_size *= 2;
|
||||
report->tests = xrealloc (report->tests,
|
||||
report->tests_size * sizeof (test_report_t));
|
||||
}
|
||||
report->tests[report->tests_count].name = NULL;
|
||||
}
|
||||
|
||||
static int
|
||||
test_diff_cmp_speedup_before_slowdown (const void *a, const void *b)
|
||||
{
|
||||
|
|
|
|||
205
perf/cairo-perf-graph
Executable file
205
perf/cairo-perf-graph
Executable file
|
|
@ -0,0 +1,205 @@
|
|||
#!/bin/sh
|
||||
set -e
|
||||
|
||||
###
|
||||
### XXX Source common functions from cairo-perf-diff
|
||||
###
|
||||
|
||||
usage() {
|
||||
argv0=`basename $0`
|
||||
|
||||
cat >&2 << END
|
||||
Usage:
|
||||
As opposed to its sibling, cairo-perf-diff, cairo-perf-graph targets
|
||||
reviewing changes between series by graphically comparing the performance
|
||||
at each commit.
|
||||
|
||||
The two revisions can be any revision accepted by git. For example:
|
||||
|
||||
$argv0 1.2.0 1.2.4 # Compare performance of 1.2.0 to 1.2.4
|
||||
|
||||
Options:
|
||||
|
||||
-f, --force
|
||||
Forces cairo-perf-diff to re-run performance tests
|
||||
even if cached performance data is available.
|
||||
|
||||
-h, --html
|
||||
With this option performance changes are summarized
|
||||
as HTML table.
|
||||
|
||||
Additional options can be passed the child cairo-perf process
|
||||
by separating them with a double hyphen (--). For example, to
|
||||
examine what the impact of the latest change is on the stroke
|
||||
test you might use:
|
||||
|
||||
$argv0 HEAD -- stroke
|
||||
|
||||
The performance results are cached in .perf next to the .git directory.
|
||||
|
||||
Set CAIRO_AUTOGEN_OPTIONS to pass options to autogen for both
|
||||
builds.
|
||||
END
|
||||
|
||||
exit 1
|
||||
}
|
||||
|
||||
# First, pull off any known options
|
||||
while true; do
|
||||
case $1 in
|
||||
-f|--force) force_cairo_perf="true";;
|
||||
-h|--html) html_output="true";;
|
||||
*) break;;
|
||||
esac
|
||||
|
||||
shift
|
||||
done
|
||||
|
||||
# Then if anything is left that still looks like an option, (begins
|
||||
# with a dash), give usage to catch --help or any other -garbage
|
||||
if [ $# -eq 0 ] || [ "`echo "$1" | sed 's/^-//'`" != "$1" ]; then
|
||||
usage
|
||||
fi
|
||||
|
||||
# Finally, pick up the actual revision arguments
|
||||
old="$1"
|
||||
new="$2"
|
||||
shift 2
|
||||
|
||||
# And post-finally, pass anything after -- on to cairo-perf
|
||||
CAIRO_PERF_OPTIONS="-r -i 25"
|
||||
if [ $# -gt 0 ]; then
|
||||
if [ "$1" = "--" ]; then
|
||||
shift 1
|
||||
CAIRO_PERF_OPTIONS="$CAIRO_PERF_OPTIONS $@"
|
||||
else
|
||||
usage
|
||||
fi
|
||||
fi
|
||||
|
||||
git_setup() {
|
||||
SUBDIRECTORY_OK='Yes'
|
||||
. git-sh-setup
|
||||
CAIRO_DIR=`dirname $GIT_DIR`
|
||||
if [ "$CAIRO_DIR" = "." ]; then
|
||||
CAIRO_DIR=`pwd`
|
||||
fi
|
||||
CAIRO_PERF_DIR=$CAIRO_DIR/.perf
|
||||
}
|
||||
|
||||
rev2sha() {
|
||||
rev=$1
|
||||
git rev-parse --verify $rev || ( echo "Cannot resolve $rev as a git object" && exit 1 )
|
||||
}
|
||||
|
||||
cpu_count() {
|
||||
test -f /proc/cpuinfo &&
|
||||
grep -c '^processor[[:blank:]]\+:' /proc/cpuinfo ||
|
||||
echo 1
|
||||
}
|
||||
|
||||
# We cache performance output based on a two-part name capturing the
|
||||
# current performance test suite and the library being tested. We
|
||||
# capture these as the tree object of the perf directory in HEAD and
|
||||
# the tree object of the src directory of the revision being tested.
|
||||
#
|
||||
# This way, whenever the performance suite is updated, cached output
|
||||
# from old versions of the suite are automatically invalidated. Also,
|
||||
# if a commit just changes things outside of the src tree, (say it
|
||||
# changes the "test" test suite, or README or configure.in, or
|
||||
# whatever), cairo-perf-diff will be smart enough to still use cached
|
||||
# results from a run with an equivalent src tree.
|
||||
rev2perf() {
|
||||
rev=$1
|
||||
sha=`rev2sha $rev`
|
||||
src_tree_sha=`rev2sha $rev:src`
|
||||
perf_tree_sha=`rev2sha HEAD:perf`
|
||||
echo "$CAIRO_PERF_DIR/${sha}-${perf_tree_sha}-${src_tree_sha}.perf"
|
||||
}
|
||||
rev2perf_glob() {
|
||||
rev=$1
|
||||
src_tree_sha=`rev2sha $rev:src`
|
||||
perf_tree_sha=`rev2sha HEAD:perf`
|
||||
echo "$CAIRO_PERF_DIR/*-${perf_tree_sha}-${src_tree_sha}.perf"
|
||||
}
|
||||
|
||||
# Usage: run_cairo_perf_if_not_cached <rev> <suffix>
|
||||
# The <rev> argument must be a valid git ref-spec that can
|
||||
# be resolved to a commit. The suffix is just something
|
||||
# unique so that build directories can be separated for
|
||||
# multiple calls to this function.
|
||||
run_cairo_perf_if_not_cached() {
|
||||
rev=$1
|
||||
build_dir="build-$2"
|
||||
|
||||
owd=`pwd`
|
||||
sha=`rev2sha $rev`
|
||||
perf=`rev2perf $rev`
|
||||
glob=`rev2perf_glob $rev`
|
||||
if [ -e $glob ] && [ "$force_cairo_perf" != "true" ]; then
|
||||
return 0
|
||||
fi
|
||||
if [ ! -d $CAIRO_PERF_DIR ]; then
|
||||
echo "Creating new perf cache in $CAIRO_PERF_DIR"
|
||||
mkdir $CAIRO_PERF_DIR
|
||||
fi
|
||||
|
||||
cd $CAIRO_DIR
|
||||
boilerplate_files=`git ls-tree --name-only HEAD boilerplate/*`
|
||||
perf_files=`git ls-tree --name-only HEAD perf/*`
|
||||
cd $CAIRO_PERF_DIR
|
||||
|
||||
if [ ! -d $build_dir ]; then
|
||||
git clone -s $CAIRO_DIR $build_dir
|
||||
(cd $build_dir; git checkout -b tmp-cairo-perf-diff $sha)
|
||||
fi
|
||||
cd $build_dir
|
||||
|
||||
git checkout tmp-cairo-perf-diff
|
||||
git reset --hard $sha
|
||||
|
||||
if [ -z "$MAKEFLAGS" ]; then
|
||||
CPU_COUNT=`cpu_count`
|
||||
export MAKEFLAGS="-j`expr $CPU_COUNT + 1`"
|
||||
fi
|
||||
|
||||
if [ ! -e Makefile ]; then
|
||||
CFLAGS="-O2" ./autogen.sh $CAIRO_AUTOGEN_OPTIONS
|
||||
fi
|
||||
make CFLAGS="-O2" || (rm config.cache && make CFLAGS="-O2")
|
||||
for file in $boilerplate_files; do
|
||||
rsync $CAIRO_DIR/$file boilerplate
|
||||
done
|
||||
(cd boilerplate; make)
|
||||
for file in $perf_files; do
|
||||
rsync $CAIRO_DIR/$file perf
|
||||
done
|
||||
cd perf;
|
||||
make cairo-perf || exit 1
|
||||
echo "Running \"cairo-perf $CAIRO_PERF_OPTIONS\" against $rev. Results will be cached in:"
|
||||
echo "$perf"
|
||||
(./cairo-perf $CAIRO_PERF_OPTIONS || echo "*** Performance test crashed") >> $perf
|
||||
cd $owd
|
||||
}
|
||||
|
||||
git_setup
|
||||
|
||||
# Build cairo-perf-graph-files if not available
|
||||
if [ ! -e $CAIRO_DIR/perf/cairo-perf-graph-files ]; then
|
||||
echo "Building cairo-perf-graph-files"
|
||||
if [ "x$OS" = "xWindows_NT" ]; then
|
||||
make -f Makefile.win32 -C $CAIRO_DIR/perf/ cairo-perf-graph-files CFG=debug
|
||||
else
|
||||
make -C $CAIRO_DIR/perf/ cairo-perf-graph-files
|
||||
fi
|
||||
fi
|
||||
|
||||
revs=""
|
||||
for rev in `git rev-list --reverse $old..$new`; do
|
||||
run_cairo_perf_if_not_cached $rev rev
|
||||
perf=`rev2perf $rev`
|
||||
[ -e $perf ] && revs="$revs $perf"
|
||||
done
|
||||
|
||||
exec $CAIRO_DIR/perf/cairo-perf-graph-files $revs
|
||||
#exec $CAIRO_DIR/libtool --mode=execute gdb --args $CAIRO_DIR/perf/cairo-perf-graph-files $revs
|
||||
593
perf/cairo-perf-graph-files.c
Normal file
593
perf/cairo-perf-graph-files.c
Normal file
|
|
@ -0,0 +1,593 @@
|
|||
/*
|
||||
* Copyright © 2008 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: Chris Wilson <chris@chris-wilson.co.uk>
|
||||
*/
|
||||
|
||||
#include "cairo-perf.h"
|
||||
#include "cairo-perf-graph.h"
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
#include <errno.h>
|
||||
#include <fcntl.h>
|
||||
|
||||
#include <cairo.h>
|
||||
|
||||
static void
|
||||
usage (const char *argv0)
|
||||
{
|
||||
char const *basename = strrchr (argv0, '/');
|
||||
basename = basename ? basename+1 : argv0;
|
||||
g_printerr ("Usage: %s [options] file1 file2 [...]\n\n", basename);
|
||||
g_printerr ("Draws a graph illustrating the change in performance over a series.\n");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
enum {
|
||||
CASE_SHOWN,
|
||||
CASE_INCONSISTENT,
|
||||
CASE_BACKEND,
|
||||
CASE_CONTENT,
|
||||
CASE_NAME,
|
||||
CASE_SIZE,
|
||||
CASE_FG_COLOR,
|
||||
CASE_DATA,
|
||||
CASE_NCOLS
|
||||
};
|
||||
|
||||
static GtkTreeStore *
|
||||
cases_to_store (test_case_t *cases)
|
||||
{
|
||||
GtkTreeStore *store;
|
||||
GtkTreeIter backend_iter;
|
||||
GtkTreeIter content_iter;
|
||||
const char *backend = NULL;
|
||||
const char *content = NULL;
|
||||
|
||||
store = gtk_tree_store_new (CASE_NCOLS,
|
||||
G_TYPE_BOOLEAN, /* shown */
|
||||
G_TYPE_BOOLEAN, /* inconsistent */
|
||||
G_TYPE_STRING, /* backend */
|
||||
G_TYPE_STRING, /* content */
|
||||
G_TYPE_STRING, /* name */
|
||||
G_TYPE_INT, /* size */
|
||||
GDK_TYPE_COLOR, /* fg color */
|
||||
G_TYPE_POINTER); /* data */
|
||||
while (cases->backend != NULL) {
|
||||
GtkTreeIter iter;
|
||||
|
||||
if (backend == NULL || strcmp (backend, cases->backend)) {
|
||||
gtk_tree_store_append (store, &backend_iter, NULL);
|
||||
gtk_tree_store_set (store, &backend_iter,
|
||||
CASE_SHOWN, TRUE,
|
||||
CASE_BACKEND, cases->backend,
|
||||
-1);
|
||||
backend = cases->backend;
|
||||
content = NULL;
|
||||
}
|
||||
if (content == NULL || strcmp (content, cases->content)) {
|
||||
gtk_tree_store_append (store, &content_iter, &backend_iter);
|
||||
gtk_tree_store_set (store, &content_iter,
|
||||
CASE_SHOWN, TRUE,
|
||||
CASE_BACKEND, cases->backend,
|
||||
CASE_CONTENT, cases->content,
|
||||
-1);
|
||||
content = cases->content;
|
||||
}
|
||||
|
||||
gtk_tree_store_append (store, &iter, &content_iter);
|
||||
gtk_tree_store_set (store, &iter,
|
||||
CASE_SHOWN, TRUE,
|
||||
CASE_BACKEND, cases->backend,
|
||||
CASE_CONTENT, cases->content,
|
||||
CASE_NAME, cases->name,
|
||||
CASE_SIZE, cases->size,
|
||||
CASE_FG_COLOR, &cases->color,
|
||||
CASE_DATA, cases,
|
||||
-1);
|
||||
cases++;
|
||||
}
|
||||
|
||||
return store;
|
||||
}
|
||||
|
||||
struct _app_data {
|
||||
GtkWidget *window;
|
||||
|
||||
test_case_t *cases;
|
||||
cairo_perf_report_t *reports;
|
||||
int num_reports;
|
||||
|
||||
GtkTreeStore *case_store;
|
||||
|
||||
GIOChannel *git_io;
|
||||
GtkTextBuffer *git_buffer;
|
||||
|
||||
GtkWidget *gv;
|
||||
};
|
||||
|
||||
static void
|
||||
recurse_set_shown (GtkTreeModel *model, GtkTreeIter *parent, gboolean shown)
|
||||
{
|
||||
GtkTreeIter iter;
|
||||
|
||||
if (gtk_tree_model_iter_children (model, &iter, parent)) do {
|
||||
test_case_t *c;
|
||||
|
||||
gtk_tree_model_get (model, &iter, CASE_DATA, &c, -1);
|
||||
if (c == NULL) {
|
||||
recurse_set_shown (model, &iter, shown);
|
||||
} else if (shown != c->shown) {
|
||||
c->shown = shown;
|
||||
gtk_tree_store_set (GTK_TREE_STORE (model), &iter,
|
||||
CASE_SHOWN, shown,
|
||||
CASE_INCONSISTENT, FALSE,
|
||||
-1);
|
||||
}
|
||||
} while (gtk_tree_model_iter_next (model, &iter));
|
||||
}
|
||||
|
||||
static gboolean
|
||||
children_consistent (GtkTreeModel *model, GtkTreeIter *parent)
|
||||
{
|
||||
GtkTreeIter iter;
|
||||
gboolean first = TRUE;
|
||||
gboolean first_active;
|
||||
|
||||
if (gtk_tree_model_iter_children (model, &iter, parent)) do {
|
||||
gboolean active, inconsistent;
|
||||
|
||||
gtk_tree_model_get (model, &iter,
|
||||
CASE_INCONSISTENT, &inconsistent,
|
||||
CASE_SHOWN, &active,
|
||||
-1);
|
||||
if (inconsistent)
|
||||
return FALSE;
|
||||
|
||||
if (first) {
|
||||
first_active = active;
|
||||
first = FALSE;
|
||||
} else if (active != first_active)
|
||||
return FALSE;
|
||||
} while (gtk_tree_model_iter_next (model, &iter));
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static void
|
||||
check_consistent (GtkTreeModel *model, GtkTreeIter *child)
|
||||
{
|
||||
GtkTreeIter parent;
|
||||
|
||||
if (gtk_tree_model_iter_parent (model, &parent, child)) {
|
||||
gtk_tree_store_set (GTK_TREE_STORE (model), &parent,
|
||||
CASE_INCONSISTENT,
|
||||
! children_consistent (model, &parent),
|
||||
-1);
|
||||
check_consistent (model, &parent);
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
show_case_toggled (GtkCellRendererToggle *cell,
|
||||
gchar *str,
|
||||
struct _app_data *app)
|
||||
{
|
||||
GtkTreeModel *model;
|
||||
GtkTreePath *path;
|
||||
GtkTreeIter iter;
|
||||
test_case_t *c;
|
||||
gboolean active;
|
||||
|
||||
active = ! gtk_cell_renderer_toggle_get_active (cell);
|
||||
|
||||
model = GTK_TREE_MODEL (app->case_store);
|
||||
|
||||
path = gtk_tree_path_new_from_string (str);
|
||||
gtk_tree_model_get_iter (model, &iter, path);
|
||||
gtk_tree_path_free (path);
|
||||
|
||||
gtk_tree_store_set (app->case_store, &iter,
|
||||
CASE_SHOWN, active,
|
||||
CASE_INCONSISTENT, FALSE,
|
||||
-1);
|
||||
gtk_tree_model_get (model, &iter, CASE_DATA, &c, -1);
|
||||
if (c != NULL) {
|
||||
if (active == c->shown)
|
||||
return;
|
||||
|
||||
c->shown = active;
|
||||
} else {
|
||||
recurse_set_shown (model, &iter, active);
|
||||
}
|
||||
check_consistent (model, &iter);
|
||||
|
||||
graph_view_update_visible ((GraphView *) app->gv);
|
||||
}
|
||||
|
||||
static gboolean
|
||||
git_read (GIOChannel *io, GIOCondition cond, struct _app_data *app)
|
||||
{
|
||||
int fd;
|
||||
|
||||
fd = g_io_channel_unix_get_fd (io);
|
||||
do {
|
||||
char buf[4096];
|
||||
int len;
|
||||
GtkTextIter end;
|
||||
|
||||
len = read (fd, buf, sizeof (buf));
|
||||
if (len <= 0) {
|
||||
int err = len ? errno : 0;
|
||||
switch (err) {
|
||||
case EAGAIN:
|
||||
case EINTR:
|
||||
return TRUE;
|
||||
default:
|
||||
g_io_channel_unref (app->git_io);
|
||||
app->git_io = NULL;
|
||||
return FALSE;
|
||||
}
|
||||
}
|
||||
|
||||
gtk_text_buffer_get_end_iter (app->git_buffer, &end);
|
||||
gtk_text_buffer_insert (app->git_buffer, &end, buf, len);
|
||||
} while (TRUE);
|
||||
}
|
||||
|
||||
static void
|
||||
do_git (struct _app_data *app, char **argv)
|
||||
{
|
||||
gint output;
|
||||
GError *error = NULL;
|
||||
GtkTextIter start, stop;
|
||||
long flags;
|
||||
|
||||
if (! g_spawn_async_with_pipes (NULL, argv, NULL,
|
||||
G_SPAWN_SEARCH_PATH |
|
||||
G_SPAWN_STDERR_TO_DEV_NULL |
|
||||
G_SPAWN_FILE_AND_ARGV_ZERO,
|
||||
NULL, NULL, NULL,
|
||||
NULL, &output, NULL,
|
||||
&error))
|
||||
{
|
||||
g_error ("spawn failed: %s", error->message);
|
||||
}
|
||||
|
||||
if (app->git_io) {
|
||||
g_io_channel_shutdown (app->git_io, FALSE, NULL);
|
||||
g_io_channel_unref (app->git_io);
|
||||
}
|
||||
|
||||
gtk_text_buffer_get_bounds (app->git_buffer, &start, &stop);
|
||||
gtk_text_buffer_delete (app->git_buffer, &start, &stop);
|
||||
|
||||
flags = fcntl (output, F_GETFL);
|
||||
if ((flags & O_NONBLOCK) == 0)
|
||||
fcntl (output, F_SETFL, flags | O_NONBLOCK);
|
||||
|
||||
app->git_io = g_io_channel_unix_new (output);
|
||||
g_io_add_watch (app->git_io, G_IO_IN | G_IO_HUP, (GIOFunc) git_read, app);
|
||||
}
|
||||
|
||||
static void
|
||||
gv_report_selected (GraphView *gv, int i, struct _app_data *app)
|
||||
{
|
||||
cairo_perf_report_t *report;
|
||||
char *hyphen;
|
||||
|
||||
if (i == -1)
|
||||
return;
|
||||
|
||||
report = &app->reports[i];
|
||||
hyphen = strchr (report->configuration, '-');
|
||||
if (hyphen != NULL) {
|
||||
int len = hyphen - report->configuration;
|
||||
char *id = g_malloc (len + 1);
|
||||
char *argv[5];
|
||||
|
||||
memcpy (id, report->configuration, len);
|
||||
id[len] = '\0';
|
||||
|
||||
argv[0] = (char *) "git";
|
||||
argv[1] = (char *) "git";
|
||||
argv[2] = (char *) "show";
|
||||
argv[3] = id;
|
||||
argv[4] = NULL;
|
||||
|
||||
do_git (app, argv);
|
||||
free (id);
|
||||
}
|
||||
}
|
||||
|
||||
static GtkWidget *
|
||||
window_create (test_case_t *cases,
|
||||
cairo_perf_report_t *reports,
|
||||
int num_reports)
|
||||
{
|
||||
GtkWidget *window, *table, *w;
|
||||
GtkWidget *tv, *sw;
|
||||
GtkTreeStore *store;
|
||||
GtkTreeViewColumn *column;
|
||||
GtkCellRenderer *renderer;
|
||||
struct _app_data *data;
|
||||
|
||||
|
||||
data = g_new0 (struct _app_data, 1);
|
||||
data->cases = cases;
|
||||
data->reports = reports;
|
||||
data->num_reports = num_reports;
|
||||
|
||||
window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
|
||||
gtk_window_set_title (GTK_WINDOW (window), "Cairo Performance Graph");
|
||||
g_object_set_data_full (G_OBJECT (window),
|
||||
"app-data", data, (GDestroyNotify)g_free);
|
||||
|
||||
data->window = window;
|
||||
|
||||
table = gtk_table_new (2, 2, FALSE);
|
||||
|
||||
/* legend & show/hide lines (categorised) */
|
||||
tv = gtk_tree_view_new ();
|
||||
store = cases_to_store (cases);
|
||||
data->case_store = store;
|
||||
gtk_tree_view_set_model (GTK_TREE_VIEW (tv), GTK_TREE_MODEL (store));
|
||||
|
||||
renderer = gtk_cell_renderer_toggle_new ();
|
||||
column = gtk_tree_view_column_new_with_attributes (NULL,
|
||||
renderer,
|
||||
"active", CASE_SHOWN,
|
||||
"inconsistent", CASE_INCONSISTENT,
|
||||
NULL);
|
||||
gtk_tree_view_append_column (GTK_TREE_VIEW (tv), column);
|
||||
g_signal_connect (renderer, "toggled",
|
||||
G_CALLBACK (show_case_toggled), data);
|
||||
|
||||
renderer = gtk_cell_renderer_text_new ();
|
||||
column = gtk_tree_view_column_new_with_attributes ("Backend",
|
||||
renderer,
|
||||
"text", CASE_BACKEND,
|
||||
NULL);
|
||||
gtk_tree_view_append_column (GTK_TREE_VIEW (tv), column);
|
||||
|
||||
renderer = gtk_cell_renderer_text_new ();
|
||||
column = gtk_tree_view_column_new_with_attributes ("Content",
|
||||
renderer,
|
||||
"text", CASE_CONTENT,
|
||||
NULL);
|
||||
gtk_tree_view_append_column (GTK_TREE_VIEW (tv), column);
|
||||
|
||||
renderer = gtk_cell_renderer_text_new ();
|
||||
column = gtk_tree_view_column_new_with_attributes ("Test",
|
||||
renderer,
|
||||
"text", CASE_NAME,
|
||||
"foreground-gdk", CASE_FG_COLOR,
|
||||
NULL);
|
||||
gtk_tree_view_append_column (GTK_TREE_VIEW (tv), column);
|
||||
|
||||
renderer = gtk_cell_renderer_text_new ();
|
||||
column = gtk_tree_view_column_new_with_attributes ("Size",
|
||||
renderer,
|
||||
"text", CASE_SIZE,
|
||||
NULL);
|
||||
gtk_tree_view_append_column (GTK_TREE_VIEW (tv), column);
|
||||
|
||||
gtk_tree_view_set_rules_hint (GTK_TREE_VIEW (tv), TRUE);
|
||||
g_object_unref (store);
|
||||
|
||||
sw = gtk_scrolled_window_new (NULL, NULL);
|
||||
gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (sw),
|
||||
GTK_POLICY_NEVER,
|
||||
GTK_POLICY_AUTOMATIC);
|
||||
gtk_container_add (GTK_CONTAINER (sw), tv);
|
||||
gtk_widget_show (tv);
|
||||
gtk_table_attach (GTK_TABLE (table), sw,
|
||||
0, 1, 0, 2,
|
||||
GTK_FILL, GTK_FILL,
|
||||
4, 4);
|
||||
gtk_widget_show (sw);
|
||||
|
||||
/* the performance chart */
|
||||
w = graph_view_new ();
|
||||
data->gv = w;
|
||||
g_signal_connect (w, "report-selected",
|
||||
G_CALLBACK (gv_report_selected), data);
|
||||
graph_view_set_reports ((GraphView *)w, cases, reports, num_reports);
|
||||
gtk_table_attach (GTK_TABLE (table), w,
|
||||
1, 2, 0, 1,
|
||||
GTK_FILL | GTK_EXPAND, GTK_FILL | GTK_EXPAND,
|
||||
4, 4);
|
||||
gtk_widget_show (w);
|
||||
|
||||
/* interesting information - presumably the commit log */
|
||||
w = gtk_text_view_new ();
|
||||
data->git_buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (w));
|
||||
sw = gtk_scrolled_window_new (NULL, NULL);
|
||||
gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (sw),
|
||||
GTK_POLICY_NEVER,
|
||||
GTK_POLICY_AUTOMATIC);
|
||||
gtk_container_add (GTK_CONTAINER (sw), w);
|
||||
gtk_widget_show (w);
|
||||
gtk_table_attach (GTK_TABLE (table), sw,
|
||||
1, 2, 1, 2,
|
||||
GTK_FILL, GTK_FILL | GTK_EXPAND,
|
||||
4, 4);
|
||||
gtk_widget_show (sw);
|
||||
|
||||
gtk_container_add (GTK_CONTAINER (window), table);
|
||||
gtk_widget_show (table);
|
||||
|
||||
return window;
|
||||
}
|
||||
|
||||
static void
|
||||
name_to_color (const char *name, GdkColor *color)
|
||||
{
|
||||
gint v = g_str_hash (name);
|
||||
|
||||
color->red = ((v >> 0) & 0xff) / 384. * 0xffff;
|
||||
color->green = ((v >> 8) & 0xff) / 384. * 0xffff;
|
||||
color->blue = ((v >> 16) & 0xff) / 384. * 0xffff;
|
||||
}
|
||||
|
||||
static test_case_t *
|
||||
test_cases_from_reports (cairo_perf_report_t *reports,
|
||||
int num_reports)
|
||||
{
|
||||
test_case_t *cases, *c;
|
||||
test_report_t **tests;
|
||||
int i, j;
|
||||
int num_tests;
|
||||
|
||||
num_tests = 0;
|
||||
for (i = 0; i < num_reports; i++) {
|
||||
for (j = 0; reports[i].tests[j].name != NULL; j++)
|
||||
;
|
||||
if (j > num_tests)
|
||||
num_tests = j;
|
||||
}
|
||||
|
||||
cases = xcalloc (num_tests+1, sizeof (test_case_t));
|
||||
tests = xmalloc (num_reports * sizeof (test_report_t *));
|
||||
for (i = 0; i < num_reports; i++)
|
||||
tests[i] = reports[i].tests;
|
||||
|
||||
c = cases;
|
||||
while (1) {
|
||||
int seen_non_null;
|
||||
test_report_t *min_test;
|
||||
|
||||
/* We expect iterations values of 0 when multiple raw reports
|
||||
* for the same test have been condensed into the stats of the
|
||||
* first. So we just skip these later reports that have no
|
||||
* stats. */
|
||||
seen_non_null = 0;
|
||||
for (i = 0; i < num_reports; i++) {
|
||||
while (tests[i]->name && tests[i]->stats.iterations == 0)
|
||||
tests[i]++;
|
||||
if (tests[i]->name)
|
||||
seen_non_null++;
|
||||
}
|
||||
|
||||
if (seen_non_null < 2)
|
||||
break;
|
||||
|
||||
/* Find the minimum of all current tests, (we have to do this
|
||||
* in case some reports don't have a particular test). */
|
||||
for (i = 0; i < num_reports; i++) {
|
||||
if (tests[i]->name) {
|
||||
min_test = tests[i];
|
||||
break;
|
||||
}
|
||||
}
|
||||
for (++i; i < num_reports; i++) {
|
||||
if (tests[i]->name &&
|
||||
test_report_cmp_backend_then_name (tests[i], min_test) < 0)
|
||||
{
|
||||
min_test = tests[i];
|
||||
}
|
||||
}
|
||||
|
||||
c->min_test = min_test;
|
||||
c->backend = min_test->backend;
|
||||
c->content = min_test->content;
|
||||
c->name = min_test->name;
|
||||
c->size = min_test->size;
|
||||
c->baseline = min_test->stats.min_ticks;
|
||||
c->min = c->max = 1.;
|
||||
c->shown = TRUE;
|
||||
name_to_color (c->name, &c->color);
|
||||
|
||||
for (i = 0; i < num_reports; i++) {
|
||||
if (tests[i]->name &&
|
||||
test_report_cmp_backend_then_name (tests[i], min_test) == 0)
|
||||
{
|
||||
tests[i]++;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
for (++i; i < num_reports; i++) {
|
||||
if (tests[i]->name &&
|
||||
test_report_cmp_backend_then_name (tests[i], min_test) == 0)
|
||||
{
|
||||
double v = tests[i]->stats.min_ticks / c->baseline;
|
||||
if (v < c->min)
|
||||
c->min = v;
|
||||
if (v > c->max)
|
||||
c->max = v;
|
||||
tests[i]++;
|
||||
}
|
||||
}
|
||||
|
||||
c++;
|
||||
}
|
||||
free (tests);
|
||||
|
||||
return cases;
|
||||
}
|
||||
int
|
||||
main (int argc, char *argv[])
|
||||
{
|
||||
cairo_perf_report_t *reports;
|
||||
test_case_t *cases;
|
||||
test_report_t *t;
|
||||
int i;
|
||||
GtkWidget *window;
|
||||
|
||||
gtk_init (&argc, &argv);
|
||||
|
||||
if (argc < 3)
|
||||
usage (argv[0]);
|
||||
|
||||
reports = xmalloc ((argc-1) * sizeof (cairo_perf_report_t));
|
||||
for (i = 1; i < argc; i++ )
|
||||
cairo_perf_report_load (&reports[i-1], argv[i]);
|
||||
|
||||
cases = test_cases_from_reports (reports, argc-1);
|
||||
|
||||
window = window_create (cases, reports, argc-1);
|
||||
g_signal_connect (window, "delete-event",
|
||||
G_CALLBACK (gtk_main_quit), NULL);
|
||||
gtk_widget_show (window);
|
||||
|
||||
gtk_main ();
|
||||
|
||||
/* Pointless memory cleanup, (would be a great place for talloc) */
|
||||
free (cases);
|
||||
for (i = 0; i < argc-1; i++) {
|
||||
for (t = reports[i].tests; t->name; t++) {
|
||||
free (t->samples);
|
||||
free (t->backend);
|
||||
free (t->name);
|
||||
}
|
||||
free (reports[i].tests);
|
||||
free (reports[i].configuration);
|
||||
}
|
||||
free (reports);
|
||||
|
||||
return 0;
|
||||
}
|
||||
450
perf/cairo-perf-graph-widget.c
Normal file
450
perf/cairo-perf-graph-widget.c
Normal file
|
|
@ -0,0 +1,450 @@
|
|||
/*
|
||||
* Copyright © 2008 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: Chris Wilson <chris@chris-wilson.co.uk>
|
||||
*/
|
||||
|
||||
#include "cairo-perf.h"
|
||||
#include "cairo-perf-graph.h"
|
||||
|
||||
#include <gtk/gtk.h>
|
||||
|
||||
struct _GraphView {
|
||||
GtkWidget widget;
|
||||
|
||||
test_case_t *cases;
|
||||
cairo_perf_report_t *reports;
|
||||
int num_reports;
|
||||
double ymin, ymax;
|
||||
|
||||
int selected_report;
|
||||
};
|
||||
|
||||
typedef struct _GraphViewClass {
|
||||
GtkWidgetClass parent_class;
|
||||
} GraphViewClass;
|
||||
|
||||
static GType graph_view_get_type (void);
|
||||
|
||||
enum {
|
||||
REPORT_SELECTED,
|
||||
LAST_SIGNAL
|
||||
};
|
||||
|
||||
static guint signals[LAST_SIGNAL];
|
||||
|
||||
G_DEFINE_TYPE (GraphView, graph_view, GTK_TYPE_WIDGET)
|
||||
|
||||
static void
|
||||
draw_baseline_performance (test_case_t *cases,
|
||||
cairo_perf_report_t *reports,
|
||||
int num_reports,
|
||||
cairo_t *cr,
|
||||
const cairo_matrix_t *m)
|
||||
{
|
||||
test_report_t **tests;
|
||||
double dots[2] = { 0, 1.};
|
||||
int i;
|
||||
|
||||
tests = xmalloc (num_reports * sizeof (test_report_t *));
|
||||
for (i = 0; i < num_reports; i++)
|
||||
tests[i] = reports[i].tests;
|
||||
|
||||
while (cases->backend != NULL) {
|
||||
test_report_t *min_test;
|
||||
double baseline, last_y;
|
||||
double x, y;
|
||||
|
||||
if (! cases->shown) {
|
||||
cases++;
|
||||
continue;
|
||||
}
|
||||
|
||||
min_test = cases->min_test;
|
||||
|
||||
for (i = 0; i < num_reports; i++) {
|
||||
while (tests[i]->name &&
|
||||
test_report_cmp_backend_then_name (tests[i], min_test) < 0)
|
||||
{
|
||||
tests[i]++;
|
||||
}
|
||||
}
|
||||
|
||||
/* first the stroke */
|
||||
cairo_save (cr);
|
||||
cairo_set_line_width (cr, 2.);
|
||||
gdk_cairo_set_source_color (cr, &cases->color);
|
||||
for (i = 0; i < num_reports; i++) {
|
||||
if (tests[i]->name &&
|
||||
test_report_cmp_backend_then_name (tests[i], min_test) == 0)
|
||||
{
|
||||
baseline = tests[i]->stats.min_ticks;
|
||||
|
||||
x = i; y = 0;
|
||||
cairo_matrix_transform_point (m, &x, &y);
|
||||
x = floor (x);
|
||||
y = floor (y);
|
||||
cairo_move_to (cr, x, y);
|
||||
last_y = y;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
for (++i; i < num_reports; i++) {
|
||||
if (tests[i]->name &&
|
||||
test_report_cmp_backend_then_name (tests[i], min_test) == 0)
|
||||
{
|
||||
x = i, y = tests[i]->stats.min_ticks / baseline;
|
||||
|
||||
if (y < 1.)
|
||||
y = -1./y + 1;
|
||||
else
|
||||
y -= 1;
|
||||
|
||||
cairo_matrix_transform_point (m, &x, &y);
|
||||
x = floor (x);
|
||||
y = floor (y);
|
||||
cairo_line_to (cr, x, last_y);
|
||||
cairo_line_to (cr, x, y);
|
||||
last_y = y;
|
||||
}
|
||||
}
|
||||
{
|
||||
x = num_reports, y = 0;
|
||||
cairo_matrix_transform_point (m, &x, &y);
|
||||
x = floor (x);
|
||||
cairo_line_to (cr, x, last_y);
|
||||
}
|
||||
|
||||
cairo_set_line_width (cr, 1.);
|
||||
cairo_stroke (cr);
|
||||
|
||||
/* then draw the points */
|
||||
for (i = 0; i < num_reports; i++) {
|
||||
if (tests[i]->name &&
|
||||
test_report_cmp_backend_then_name (tests[i], min_test) == 0)
|
||||
{
|
||||
baseline = tests[i]->stats.min_ticks;
|
||||
|
||||
x = i; y = 0;
|
||||
cairo_matrix_transform_point (m, &x, &y);
|
||||
x = floor (x);
|
||||
y = floor (y);
|
||||
cairo_move_to (cr, x, y);
|
||||
cairo_close_path (cr);
|
||||
last_y = y;
|
||||
|
||||
tests[i]++;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
for (++i; i < num_reports; i++) {
|
||||
if (tests[i]->name &&
|
||||
test_report_cmp_backend_then_name (tests[i], min_test) == 0)
|
||||
{
|
||||
x = i, y = tests[i]->stats.min_ticks / baseline;
|
||||
|
||||
if (y < 1.)
|
||||
y = -1./y + 1;
|
||||
else
|
||||
y -= 1;
|
||||
|
||||
cairo_matrix_transform_point (m, &x, &y);
|
||||
x = floor (x);
|
||||
y = floor (y);
|
||||
cairo_move_to (cr, x, last_y);
|
||||
cairo_close_path (cr);
|
||||
cairo_move_to (cr, x, y);
|
||||
cairo_close_path (cr);
|
||||
last_y = y;
|
||||
|
||||
tests[i]++;
|
||||
}
|
||||
}
|
||||
{
|
||||
x = num_reports, y = 0;
|
||||
cairo_matrix_transform_point (m, &x, &y);
|
||||
x = floor (x);
|
||||
cairo_move_to (cr, x, last_y);
|
||||
cairo_close_path (cr);
|
||||
}
|
||||
cairo_set_source_rgba (cr, 0, 0, 0, .5);
|
||||
cairo_set_dash (cr, dots, 2, 0.);
|
||||
cairo_set_line_width (cr, 3.);
|
||||
cairo_set_line_cap (cr, CAIRO_LINE_CAP_ROUND);
|
||||
cairo_stroke (cr);
|
||||
cairo_restore (cr);
|
||||
|
||||
cases++;
|
||||
}
|
||||
free (tests);
|
||||
}
|
||||
|
||||
static void
|
||||
draw_hline (cairo_t *cr, const cairo_matrix_t *m, double y0, double xmin, double xmax)
|
||||
{
|
||||
double x, y;
|
||||
double py_offset;
|
||||
|
||||
py_offset = fmod (cairo_get_line_width (cr) / 2., 1.);
|
||||
|
||||
x = xmin; y = y0;
|
||||
cairo_matrix_transform_point (m, &x, &y);
|
||||
cairo_move_to (cr, floor (x), floor (y) + py_offset);
|
||||
|
||||
x = xmax; y = y0;
|
||||
cairo_matrix_transform_point (m, &x, &y);
|
||||
cairo_line_to (cr, ceil (x), floor (y) + py_offset);
|
||||
|
||||
cairo_stroke (cr);
|
||||
}
|
||||
|
||||
#define PAD 24
|
||||
static void
|
||||
graph_view_draw (GraphView *self, cairo_t *cr)
|
||||
{
|
||||
cairo_matrix_t m;
|
||||
const double dash[2] = {4, 4};
|
||||
double range;
|
||||
int i;
|
||||
|
||||
range = ceil (self->ymax) - floor (self->ymin);
|
||||
|
||||
cairo_matrix_init_translate (&m, PAD, PAD);
|
||||
cairo_matrix_scale (&m, (self->widget.allocation.width-2*PAD)/self->num_reports, (self->widget.allocation.height-2*PAD)/range);
|
||||
cairo_matrix_translate (&m, 0, - floor (self->ymin));
|
||||
|
||||
if (self->selected_report != -1) {
|
||||
cairo_save (cr); {
|
||||
double x0, x1, y;
|
||||
x0 = self->selected_report; y = 0;
|
||||
cairo_matrix_transform_point (&m, &x0, &y);
|
||||
x0 = floor (x0);
|
||||
x1 = self->selected_report + 1; y = 0;
|
||||
cairo_matrix_transform_point (&m, &x1, &y);
|
||||
x1 = ceil (x1);
|
||||
y = (x1 - x0) / 8;
|
||||
y = MIN (y, PAD / 2);
|
||||
x0 -= y;
|
||||
x1 += y;
|
||||
cairo_rectangle (cr, x0, PAD/2, x1-x0, self->widget.allocation.height-2*PAD + PAD);
|
||||
gdk_cairo_set_source_color (cr, &self->widget.style->base[GTK_STATE_SELECTED]);
|
||||
cairo_fill (cr);
|
||||
} cairo_restore (cr);
|
||||
}
|
||||
|
||||
cairo_save (cr); {
|
||||
cairo_set_source_rgb (cr, 0.7, 0.7, 0.7);
|
||||
cairo_set_line_width (cr, 2.);
|
||||
draw_hline (cr, &m, 0, 0, self->num_reports-1);
|
||||
|
||||
cairo_set_line_width (cr, 1.);
|
||||
cairo_set_dash (cr, NULL, 0, 0);
|
||||
for (i = floor (self->ymin); i <= floor (self->ymax); i++) {
|
||||
if (i != 0)
|
||||
draw_hline (cr, &m, i, 0, self->num_reports);
|
||||
}
|
||||
} cairo_restore (cr);
|
||||
|
||||
draw_baseline_performance (self->cases,
|
||||
self->reports, self->num_reports,
|
||||
cr, &m);
|
||||
|
||||
cairo_save (cr); {
|
||||
cairo_set_source_rgb (cr, 0.7, 0.7, 0.7);
|
||||
cairo_set_line_width (cr, 1.);
|
||||
cairo_set_dash (cr, dash, 2, 0);
|
||||
draw_hline (cr, &m, 0, 0, self->num_reports-1);
|
||||
} cairo_restore (cr);
|
||||
|
||||
}
|
||||
|
||||
static gboolean
|
||||
graph_view_expose (GtkWidget *w, GdkEventExpose *ev)
|
||||
{
|
||||
GraphView *self = (GraphView *) w;
|
||||
cairo_t *cr;
|
||||
|
||||
cr = gdk_cairo_create (w->window);
|
||||
gdk_cairo_region (cr, ev->region);
|
||||
cairo_clip (cr);
|
||||
|
||||
gdk_cairo_set_source_color (cr, &w->style->base[GTK_WIDGET_STATE (w)]);
|
||||
cairo_paint (cr);
|
||||
|
||||
graph_view_draw (self, cr);
|
||||
|
||||
cairo_destroy (cr);
|
||||
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
static gboolean
|
||||
graph_view_button_press (GtkWidget *w, GdkEventButton *ev)
|
||||
{
|
||||
GraphView *self = (GraphView *) w;
|
||||
cairo_matrix_t m;
|
||||
double x,y;
|
||||
int i;
|
||||
|
||||
cairo_matrix_init_translate (&m, PAD, self->widget.allocation.height-PAD);
|
||||
cairo_matrix_scale (&m, (self->widget.allocation.width-2*PAD)/self->num_reports, -(self->widget.allocation.height-2*PAD)/(self->ymax - self->ymin));
|
||||
cairo_matrix_translate (&m, 0, -self->ymin);
|
||||
cairo_matrix_invert (&m);
|
||||
|
||||
x = ev->x;
|
||||
y = ev->y;
|
||||
cairo_matrix_transform_point (&m, &x, &y);
|
||||
|
||||
i = floor (x);
|
||||
if (i < 0 || i >= self->num_reports)
|
||||
i = -1;
|
||||
|
||||
if (i != self->selected_report) {
|
||||
self->selected_report = i;
|
||||
gtk_widget_queue_draw (w);
|
||||
|
||||
g_signal_emit (w, signals[REPORT_SELECTED], 0, i);
|
||||
}
|
||||
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
static gboolean
|
||||
graph_view_button_release (GtkWidget *w, GdkEventButton *ev)
|
||||
{
|
||||
GraphView *self = (GraphView *) w;
|
||||
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
static void
|
||||
graph_view_realize (GtkWidget *widget)
|
||||
{
|
||||
GdkWindowAttr attributes;
|
||||
|
||||
GTK_WIDGET_SET_FLAGS (widget, GTK_REALIZED);
|
||||
|
||||
attributes.window_type = GDK_WINDOW_CHILD;
|
||||
attributes.x = widget->allocation.x;
|
||||
attributes.y = widget->allocation.y;
|
||||
attributes.width = widget->allocation.width;
|
||||
attributes.height = widget->allocation.height;
|
||||
attributes.wclass = GDK_INPUT_OUTPUT;
|
||||
attributes.visual = gtk_widget_get_visual (widget);
|
||||
attributes.colormap = gtk_widget_get_colormap (widget);
|
||||
attributes.event_mask = gtk_widget_get_events (widget) |
|
||||
GDK_BUTTON_PRESS_MASK |
|
||||
GDK_BUTTON_RELEASE_MASK |
|
||||
GDK_EXPOSURE_MASK;
|
||||
|
||||
widget->window = gdk_window_new (gtk_widget_get_parent_window (widget),
|
||||
&attributes,
|
||||
GDK_WA_X | GDK_WA_Y |
|
||||
GDK_WA_VISUAL | GDK_WA_COLORMAP);
|
||||
gdk_window_set_user_data (widget->window, widget);
|
||||
|
||||
widget->style = gtk_style_attach (widget->style, widget->window);
|
||||
gtk_style_set_background (widget->style, widget->window, GTK_STATE_NORMAL);
|
||||
}
|
||||
|
||||
static void
|
||||
graph_view_finalize (GObject *obj)
|
||||
{
|
||||
G_OBJECT_CLASS (graph_view_parent_class)->finalize (obj);
|
||||
}
|
||||
|
||||
static void
|
||||
graph_view_class_init (GraphViewClass *klass)
|
||||
{
|
||||
GObjectClass *object_class = (GObjectClass *) klass;
|
||||
GtkWidgetClass *widget_class = (GtkWidgetClass *) klass;
|
||||
|
||||
object_class->finalize = graph_view_finalize;
|
||||
|
||||
widget_class->realize = graph_view_realize;
|
||||
widget_class->expose_event = graph_view_expose;
|
||||
widget_class->button_press_event = graph_view_button_press;
|
||||
widget_class->button_release_event = graph_view_button_release;
|
||||
|
||||
signals[REPORT_SELECTED] =
|
||||
g_signal_new ("report-selected",
|
||||
G_TYPE_FROM_CLASS (object_class),
|
||||
G_SIGNAL_RUN_FIRST,
|
||||
0,//G_STRUCT_OFFSET (GraphView, report_selected),
|
||||
NULL, NULL,
|
||||
g_cclosure_marshal_VOID__INT,
|
||||
G_TYPE_NONE, 1, G_TYPE_INT);
|
||||
}
|
||||
|
||||
static void
|
||||
graph_view_init (GraphView *self)
|
||||
{
|
||||
self->selected_report = -1;
|
||||
}
|
||||
|
||||
GtkWidget *
|
||||
graph_view_new (void)
|
||||
{
|
||||
return g_object_new (graph_view_get_type (), NULL);
|
||||
}
|
||||
|
||||
void
|
||||
graph_view_update_visible (GraphView *gv)
|
||||
{
|
||||
double min, max;
|
||||
test_case_t *cases;
|
||||
|
||||
cases = gv->cases;
|
||||
|
||||
min = max = 1.;
|
||||
while (cases->name != NULL) {
|
||||
if (cases->shown) {
|
||||
if (cases->min < min)
|
||||
min = cases->min;
|
||||
if (cases->max > max)
|
||||
max = cases->max;
|
||||
}
|
||||
cases++;
|
||||
}
|
||||
gv->ymin = -1/min + 1;
|
||||
gv->ymax = max - 1;
|
||||
|
||||
gtk_widget_queue_draw (&gv->widget);
|
||||
}
|
||||
|
||||
void
|
||||
graph_view_set_reports (GraphView *gv,
|
||||
test_case_t *cases,
|
||||
cairo_perf_report_t *reports,
|
||||
int num_reports)
|
||||
{
|
||||
/* XXX ownership? */
|
||||
gv->cases = cases;
|
||||
gv->reports = reports;
|
||||
gv->num_reports = num_reports;
|
||||
|
||||
graph_view_update_visible (gv);
|
||||
}
|
||||
63
perf/cairo-perf-graph.h
Normal file
63
perf/cairo-perf-graph.h
Normal file
|
|
@ -0,0 +1,63 @@
|
|||
/*
|
||||
* Copyright © 2008 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: Chris Wilson <chris@chris-wilson.co.uk>
|
||||
*/
|
||||
|
||||
#ifndef CAIRO_PERF_GRAPH_H
|
||||
#define CAIRO_PERF_GRAPH_H
|
||||
|
||||
#include <gtk/gtk.h>
|
||||
|
||||
#include "cairo-perf.h"
|
||||
|
||||
typedef struct _test_case {
|
||||
const char *backend;
|
||||
const char *content;
|
||||
const char *name;
|
||||
int size;
|
||||
|
||||
test_report_t *min_test;
|
||||
|
||||
cairo_bool_t shown;
|
||||
double baseline;
|
||||
double min, max;
|
||||
GdkColor color;
|
||||
} test_case_t;
|
||||
|
||||
typedef struct _GraphView GraphView;
|
||||
|
||||
GtkWidget *
|
||||
graph_view_new (void);
|
||||
|
||||
void
|
||||
graph_view_set_reports (GraphView *gv,
|
||||
test_case_t *tests,
|
||||
cairo_perf_report_t *reports,
|
||||
int num_reports);
|
||||
|
||||
void
|
||||
graph_view_update_visible (GraphView *gv);
|
||||
|
||||
#endif
|
||||
456
perf/cairo-perf-report.c
Normal file
456
perf/cairo-perf-report.c
Normal file
|
|
@ -0,0 +1,456 @@
|
|||
/*
|
||||
* Copyright © 2006 Red Hat, Inc.
|
||||
*
|
||||
* 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>
|
||||
*/
|
||||
|
||||
#include "cairo-perf.h"
|
||||
#include "cairo-stats.h"
|
||||
|
||||
/* We use _GNU_SOURCE for getline and strndup if available. */
|
||||
#ifndef _GNU_SOURCE
|
||||
# define _GNU_SOURCE
|
||||
#endif
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <errno.h>
|
||||
#include <ctype.h>
|
||||
#include <math.h>
|
||||
#include <assert.h>
|
||||
#ifdef HAVE_LIBGEN_H
|
||||
#include <libgen.h>
|
||||
#endif
|
||||
|
||||
/* 'ssize_t' does not exist in the C standard on win32.
|
||||
* We use 'ptrdiff_t', which is nearly equivalent. */
|
||||
#ifdef _MSC_VER
|
||||
typedef ptrdiff_t ssize_t;
|
||||
#endif
|
||||
|
||||
#ifndef __USE_GNU
|
||||
static ssize_t
|
||||
getline (char **lineptr, size_t *n, FILE *stream);
|
||||
|
||||
static char *
|
||||
strndup (const char *s, size_t n);
|
||||
#endif
|
||||
|
||||
#ifdef _MSC_VER
|
||||
static long long
|
||||
strtoll(const char *nptr, char **endptr, int base);
|
||||
|
||||
static char *
|
||||
basename(char *path);
|
||||
#endif
|
||||
|
||||
/* Ad-hoc parsing, macros with a strong dependence on the calling
|
||||
* context, and plenty of other ugliness is here. But at least it's
|
||||
* not perl... */
|
||||
#define parse_error(...) fprintf(stderr, __VA_ARGS__); return TEST_REPORT_STATUS_ERROR;
|
||||
#define skip_char(c) \
|
||||
do { \
|
||||
if (*s && *s == (c)) { \
|
||||
s++; \
|
||||
} else { \
|
||||
parse_error ("expected '%c' but found '%c'", c, *s); \
|
||||
} \
|
||||
} while (0)
|
||||
#define skip_space() while (*s && (*s == ' ' || *s == '\t')) s++;
|
||||
#define parse_int(result) \
|
||||
do { \
|
||||
(result) = strtol (s, &end, 10); \
|
||||
if (*s && end != s) { \
|
||||
s = end; \
|
||||
} else { \
|
||||
parse_error("expected integer but found %s", s); \
|
||||
} \
|
||||
} while (0)
|
||||
#define parse_long_long(result) \
|
||||
do { \
|
||||
(result) = strtoll (s, &end, 10); \
|
||||
if (*s && end != s) { \
|
||||
s = end; \
|
||||
} else { \
|
||||
parse_error("expected integer but found %s", s); \
|
||||
} \
|
||||
} while (0)
|
||||
#define parse_double(result) \
|
||||
do { \
|
||||
(result) = strtod (s, &end); \
|
||||
if (*s && end != s) { \
|
||||
s = end; \
|
||||
} else { \
|
||||
parse_error("expected floating-point value but found %s", s); \
|
||||
} \
|
||||
} while (0)
|
||||
/* Here a string is simply a sequence of non-whitespace */
|
||||
#define parse_string(result) \
|
||||
do { \
|
||||
for (end = s; *end; end++) \
|
||||
if (isspace (*end)) \
|
||||
break; \
|
||||
(result) = strndup (s, end - s); \
|
||||
if ((result) == NULL) { \
|
||||
fprintf (stderr, "Out of memory.\n"); \
|
||||
exit (1); \
|
||||
} \
|
||||
s = end; \
|
||||
} while (0)
|
||||
|
||||
static test_report_status_t
|
||||
test_report_parse (test_report_t *report, char *line, char *configuration)
|
||||
{
|
||||
char *end;
|
||||
char *s = line;
|
||||
cairo_bool_t is_raw = FALSE;
|
||||
double min_time, median_time;
|
||||
|
||||
/* The code here looks funny unless you understand that these are
|
||||
* all macro calls, (and then the code just looks sick). */
|
||||
if (*s == '\n')
|
||||
return TEST_REPORT_STATUS_COMMENT;
|
||||
|
||||
skip_char ('[');
|
||||
skip_space ();
|
||||
if (*s == '#')
|
||||
return TEST_REPORT_STATUS_COMMENT;
|
||||
if (*s == '*') {
|
||||
s++;
|
||||
is_raw = TRUE;
|
||||
} else {
|
||||
parse_int (report->id);
|
||||
}
|
||||
skip_char (']');
|
||||
|
||||
skip_space ();
|
||||
|
||||
report->configuration = configuration;
|
||||
parse_string (report->backend);
|
||||
end = strrchr (report->backend, '-');
|
||||
if (*end)
|
||||
*end++ = '\0';
|
||||
report->content = end;
|
||||
|
||||
skip_space ();
|
||||
|
||||
parse_string (report->name);
|
||||
end = strrchr (report->name, '-');
|
||||
if (*end)
|
||||
*end++ = '\0';
|
||||
report->size = atoi (end);
|
||||
|
||||
skip_space ();
|
||||
|
||||
report->samples = NULL;
|
||||
report->samples_size = 0;
|
||||
report->samples_count = 0;
|
||||
|
||||
if (is_raw) {
|
||||
parse_double (report->stats.ticks_per_ms);
|
||||
skip_space ();
|
||||
|
||||
report->samples_size = 5;
|
||||
report->samples = xmalloc (report->samples_size * sizeof (cairo_perf_ticks_t));
|
||||
do {
|
||||
if (report->samples_count == report->samples_size) {
|
||||
report->samples_size *= 2;
|
||||
report->samples = xrealloc (report->samples,
|
||||
report->samples_size * sizeof (cairo_perf_ticks_t));
|
||||
}
|
||||
parse_long_long (report->samples[report->samples_count++]);
|
||||
skip_space ();
|
||||
} while (*s && *s != '\n');
|
||||
report->stats.iterations = 0;
|
||||
skip_char ('\n');
|
||||
} else {
|
||||
parse_double (report->stats.min_ticks);
|
||||
skip_space ();
|
||||
|
||||
parse_double (min_time);
|
||||
report->stats.ticks_per_ms = report->stats.min_ticks / min_time;
|
||||
|
||||
skip_space ();
|
||||
|
||||
parse_double (median_time);
|
||||
report->stats.median_ticks = median_time * report->stats.ticks_per_ms;
|
||||
|
||||
skip_space ();
|
||||
|
||||
parse_double (report->stats.std_dev);
|
||||
report->stats.std_dev /= 100.0;
|
||||
skip_char ('%');
|
||||
|
||||
skip_space ();
|
||||
|
||||
parse_int (report->stats.iterations);
|
||||
|
||||
skip_space ();
|
||||
skip_char ('\n');
|
||||
}
|
||||
|
||||
return TEST_REPORT_STATUS_SUCCESS;
|
||||
}
|
||||
|
||||
/* We conditionally provide a custom implementation of getline and strndup
|
||||
* as needed. These aren't necessary full-fledged general purpose
|
||||
* implementations. They just get the job done for our purposes.
|
||||
*/
|
||||
#ifndef __USE_GNU
|
||||
#define POORMANS_GETLINE_BUFFER_SIZE (65536)
|
||||
static ssize_t
|
||||
getline (char **lineptr, size_t *n, FILE *stream)
|
||||
{
|
||||
if (!*lineptr)
|
||||
{
|
||||
*n = POORMANS_GETLINE_BUFFER_SIZE;
|
||||
*lineptr = (char *) malloc (*n);
|
||||
}
|
||||
|
||||
if (!fgets (*lineptr, *n, stream))
|
||||
return -1;
|
||||
|
||||
if (!feof (stream) && !strchr (*lineptr, '\n'))
|
||||
{
|
||||
fprintf (stderr, "The poor man's implementation of getline in "
|
||||
__FILE__ " needs a bigger buffer. Perhaps it's "
|
||||
"time for a complete implementation of getline.\n");
|
||||
exit (0);
|
||||
}
|
||||
|
||||
return strlen (*lineptr);
|
||||
}
|
||||
#undef POORMANS_GETLINE_BUFFER_SIZE
|
||||
|
||||
static char *
|
||||
strndup (const char *s, size_t n)
|
||||
{
|
||||
size_t len;
|
||||
char *sdup;
|
||||
|
||||
if (!s)
|
||||
return NULL;
|
||||
|
||||
len = strlen (s);
|
||||
len = (n < len ? n : len);
|
||||
sdup = (char *) malloc (len + 1);
|
||||
if (sdup)
|
||||
{
|
||||
memcpy (sdup, s, len);
|
||||
sdup[len] = '\0';
|
||||
}
|
||||
|
||||
return sdup;
|
||||
}
|
||||
#endif /* ifndef __USE_GNU */
|
||||
|
||||
/* We provide hereafter a win32 implementation of the basename
|
||||
* and strtoll functions which are not available otherwise.
|
||||
* The basename function is fully compliant to its GNU specs.
|
||||
*/
|
||||
#ifdef _MSC_VER
|
||||
long long
|
||||
strtoll(const char *nptr, char **endptr, int base)
|
||||
{
|
||||
return _atoi64(nptr);
|
||||
}
|
||||
|
||||
static char *
|
||||
basename(char *path)
|
||||
{
|
||||
char *end, *s;
|
||||
|
||||
end = (path + strlen(path) - 1);
|
||||
while (end && (end >= path + 1) && (*end == '/')) {
|
||||
*end = '\0';
|
||||
end--;
|
||||
}
|
||||
|
||||
s = strrchr(path, '/');
|
||||
if (s) {
|
||||
if (s == end) {
|
||||
return s;
|
||||
} else {
|
||||
return s+1;
|
||||
}
|
||||
} else {
|
||||
return path;
|
||||
}
|
||||
}
|
||||
#endif /* ifndef _MSC_VER */
|
||||
|
||||
int
|
||||
test_report_cmp_backend_then_name (const void *a, const void *b)
|
||||
{
|
||||
const test_report_t *a_test = a;
|
||||
const test_report_t *b_test = b;
|
||||
|
||||
int cmp;
|
||||
|
||||
cmp = strcmp (a_test->backend, b_test->backend);
|
||||
if (cmp)
|
||||
return cmp;
|
||||
|
||||
cmp = strcmp (a_test->content, b_test->content);
|
||||
if (cmp)
|
||||
return cmp;
|
||||
|
||||
/* A NULL name is a list-termination marker, so force it last. */
|
||||
if (a_test->name == NULL)
|
||||
if (b_test->name == NULL)
|
||||
return 0;
|
||||
else
|
||||
return 1;
|
||||
else if (b_test->name == NULL)
|
||||
return -1;
|
||||
|
||||
cmp = strcmp (a_test->name, b_test->name);
|
||||
if (cmp)
|
||||
return cmp;
|
||||
|
||||
if (a_test->size < b_test->size)
|
||||
return -1;
|
||||
if (a_test->size > b_test->size)
|
||||
return 1;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void
|
||||
cairo_perf_report_sort_and_compute_stats (cairo_perf_report_t *report)
|
||||
{
|
||||
test_report_t *base, *next, *last, *t;
|
||||
|
||||
/* First we sort, since the diff needs both lists in the same
|
||||
* order */
|
||||
qsort (report->tests, report->tests_count, sizeof (test_report_t),
|
||||
test_report_cmp_backend_then_name);
|
||||
|
||||
/* The sorting also brings all related raw reports together so we
|
||||
* can condense them and compute the stats.
|
||||
*/
|
||||
base = &report->tests[0];
|
||||
last = &report->tests[report->tests_count - 1];
|
||||
while (base <= last) {
|
||||
next = base+1;
|
||||
if (next <= last) {
|
||||
while (next <= last &&
|
||||
test_report_cmp_backend_then_name (base, next) == 0)
|
||||
{
|
||||
next++;
|
||||
}
|
||||
if (next != base) {
|
||||
unsigned int new_samples_count = base->samples_count;
|
||||
for (t = base + 1; t < next; t++)
|
||||
new_samples_count += t->samples_count;
|
||||
if (new_samples_count > base->samples_size) {
|
||||
base->samples_size = new_samples_count;
|
||||
base->samples = xrealloc (base->samples,
|
||||
base->samples_size * sizeof (cairo_perf_ticks_t));
|
||||
}
|
||||
for (t = base + 1; t < next; t++) {
|
||||
memcpy (&base->samples[base->samples_count], t->samples,
|
||||
t->samples_count * sizeof (cairo_perf_ticks_t));
|
||||
base->samples_count += t->samples_count;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (base->samples)
|
||||
_cairo_stats_compute (&base->stats, base->samples, base->samples_count);
|
||||
base = next;
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
cairo_perf_report_load (cairo_perf_report_t *report,
|
||||
const char *filename)
|
||||
{
|
||||
FILE *file;
|
||||
test_report_status_t status;
|
||||
int line_number = 0;
|
||||
char *line = NULL;
|
||||
size_t line_size = 0;
|
||||
char *configuration;
|
||||
char *dot;
|
||||
char *baseName;
|
||||
|
||||
configuration = xmalloc (strlen (filename) * sizeof (char) + 1);
|
||||
strcpy (configuration, filename);
|
||||
baseName = strdup (basename (configuration));
|
||||
report->configuration = xmalloc (strlen (filename) * sizeof (char) + 1);
|
||||
strcpy(report->configuration, baseName);
|
||||
free (configuration);
|
||||
dot = strrchr (report->configuration, '.');
|
||||
if (dot)
|
||||
*dot = '\0';
|
||||
|
||||
report->name = filename;
|
||||
report->tests_size = 16;
|
||||
report->tests = xmalloc (report->tests_size * sizeof (test_report_t));
|
||||
report->tests_count = 0;
|
||||
|
||||
file = fopen (filename, "r");
|
||||
if (file == NULL) {
|
||||
fprintf (stderr, "Failed to open %s: %s\n",
|
||||
filename, strerror (errno));
|
||||
exit (1);
|
||||
}
|
||||
|
||||
while (1) {
|
||||
if (report->tests_count == report->tests_size) {
|
||||
report->tests_size *= 2;
|
||||
report->tests = xrealloc (report->tests,
|
||||
report->tests_size * sizeof (test_report_t));
|
||||
}
|
||||
|
||||
line_number++;
|
||||
if (getline (&line, &line_size, file) == -1)
|
||||
break;
|
||||
|
||||
status = test_report_parse (&report->tests[report->tests_count],
|
||||
line, report->configuration);
|
||||
if (status == TEST_REPORT_STATUS_ERROR)
|
||||
fprintf (stderr, "Ignoring unrecognized line %d of %s:\n%s",
|
||||
line_number, filename, line);
|
||||
if (status == TEST_REPORT_STATUS_SUCCESS)
|
||||
report->tests_count++;
|
||||
/* Do nothing on TEST_REPORT_STATUS_COMMENT */
|
||||
}
|
||||
|
||||
if (line)
|
||||
free (line);
|
||||
|
||||
fclose (file);
|
||||
|
||||
cairo_perf_report_sort_and_compute_stats (report);
|
||||
|
||||
/* Add one final report with a NULL name to terminate the list. */
|
||||
if (report->tests_count == report->tests_size) {
|
||||
report->tests_size *= 2;
|
||||
report->tests = xrealloc (report->tests,
|
||||
report->tests_size * sizeof (test_report_t));
|
||||
}
|
||||
report->tests[report->tests_count].name = NULL;
|
||||
}
|
||||
|
||||
|
|
@ -29,6 +29,7 @@
|
|||
#define _GNU_SOURCE 1 /* for sched_getaffinity() */
|
||||
|
||||
#include "cairo-perf.h"
|
||||
#include "cairo-stats.h"
|
||||
|
||||
#include "cairo-boilerplate-getopt.h"
|
||||
|
||||
|
|
|
|||
|
|
@ -32,7 +32,13 @@
|
|||
|
||||
typedef uint64_t cairo_perf_ticks_t;
|
||||
|
||||
#include "cairo-stats.h"
|
||||
typedef struct _cairo_stats {
|
||||
cairo_perf_ticks_t min_ticks;
|
||||
cairo_perf_ticks_t median_ticks;
|
||||
double ticks_per_ms;
|
||||
double std_dev;
|
||||
int iterations;
|
||||
} cairo_stats_t;
|
||||
|
||||
/* timers */
|
||||
|
||||
|
|
@ -93,6 +99,59 @@ cairo_perf_cover_sources_and_operators (cairo_perf_t *perf,
|
|||
const char *name,
|
||||
cairo_perf_func_t perf_func);
|
||||
|
||||
/* reporter convenience routines */
|
||||
|
||||
typedef struct _test_report {
|
||||
int id;
|
||||
const char *configuration;
|
||||
char *backend;
|
||||
char *content;
|
||||
char *name;
|
||||
int size;
|
||||
|
||||
/* The samples only exists for "raw" reports */
|
||||
cairo_perf_ticks_t *samples;
|
||||
unsigned int samples_size;
|
||||
unsigned int samples_count;
|
||||
|
||||
/* The stats are either read directly or computed from samples.
|
||||
* If the stats have not yet been computed from samples, then
|
||||
* iterations will be 0. */
|
||||
cairo_stats_t stats;
|
||||
} test_report_t;
|
||||
|
||||
typedef struct _test_diff {
|
||||
test_report_t **tests;
|
||||
int num_tests;
|
||||
double min;
|
||||
double max;
|
||||
double change;
|
||||
} test_diff_t;
|
||||
|
||||
typedef struct _cairo_perf_report {
|
||||
char *configuration;
|
||||
const char *name;
|
||||
test_report_t *tests;
|
||||
int tests_size;
|
||||
int tests_count;
|
||||
} cairo_perf_report_t;
|
||||
|
||||
typedef enum {
|
||||
TEST_REPORT_STATUS_SUCCESS,
|
||||
TEST_REPORT_STATUS_COMMENT,
|
||||
TEST_REPORT_STATUS_ERROR
|
||||
} test_report_status_t;
|
||||
|
||||
void
|
||||
cairo_perf_report_load (cairo_perf_report_t *report,
|
||||
const char *filename);
|
||||
|
||||
void
|
||||
cairo_perf_report_sort_and_compute_stats (cairo_perf_report_t *report);
|
||||
|
||||
int
|
||||
test_report_cmp_backend_then_name (const void *a, const void *b);
|
||||
|
||||
#define CAIRO_PERF_DECL(func) void (func) (cairo_perf_t *perf, cairo_t *cr, int width, int height)
|
||||
|
||||
CAIRO_PERF_DECL (fill);
|
||||
|
|
|
|||
|
|
@ -28,14 +28,6 @@
|
|||
|
||||
#include "cairo-perf.h"
|
||||
|
||||
typedef struct _cairo_stats {
|
||||
cairo_perf_ticks_t min_ticks;
|
||||
cairo_perf_ticks_t median_ticks;
|
||||
double ticks_per_ms;
|
||||
double std_dev;
|
||||
int iterations;
|
||||
} cairo_stats_t;
|
||||
|
||||
void
|
||||
_cairo_stats_compute (cairo_stats_t *stats,
|
||||
cairo_perf_ticks_t *values,
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue