summaryrefslogtreecommitdiff
path: root/perf
diff options
context:
space:
mode:
authorChris Wilson <chris@chris-wilson.co.uk>2008-10-16 11:56:19 +0100
committerChris Wilson <chris@chris-wilson.co.uk>2008-10-19 09:36:53 +0100
commitf2ff7944264c23cbec856be3e85f240a93184f80 (patch)
tree0c14c7d36b71bcb92bf244b6eede1eec5b4aa4ea /perf
parent41c8eefc6d432ab213f6f405c3d6346adb7f7931 (diff)
[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.
Diffstat (limited to 'perf')
-rw-r--r--perf/.gitignore1
-rw-r--r--perf/Makefile.am26
-rw-r--r--perf/Makefile.win326
-rwxr-xr-xperf/cairo-perf-diff12
-rw-r--r--perf/cairo-perf-diff-files.c458
-rwxr-xr-xperf/cairo-perf-graph205
-rw-r--r--perf/cairo-perf-graph-files.c593
-rw-r--r--perf/cairo-perf-graph-widget.c450
-rw-r--r--perf/cairo-perf-graph.h63
-rw-r--r--perf/cairo-perf-report.c456
-rw-r--r--perf/cairo-perf.c1
-rw-r--r--perf/cairo-perf.h61
-rw-r--r--perf/cairo-stats.h8
13 files changed, 1862 insertions, 478 deletions
diff --git a/perf/.gitignore b/perf/.gitignore
index 858e28a7b..29ec88de7 100644
--- a/perf/.gitignore
+++ b/perf/.gitignore
@@ -2,6 +2,7 @@ TAGS
tags
cairo-perf
cairo-perf-diff-files
+cairo-perf-graph-files
valgrind-log
callgrind.out.*
index.html
diff --git a/perf/Makefile.am b/perf/Makefile.am
index 1cdcf535e..512e6d114 100644
--- a/perf/Makefile.am
+++ b/perf/Makefile.am
@@ -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
diff --git a/perf/Makefile.win32 b/perf/Makefile.win32
index 47e73c4e4..ef993a783 100644
--- a/perf/Makefile.win32
+++ b/perf/Makefile.win32
@@ -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)
diff --git a/perf/cairo-perf-diff b/perf/cairo-perf-diff
index 5094ec399..50cd1f2c6 100755
--- a/perf/cairo-perf-diff
+++ b/perf/cairo-perf-diff
@@ -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/${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"
+ 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
diff --git a/perf/cairo-perf-diff-files.c b/perf/cairo-perf-diff-files.c
index 9098c91cb..9b9f5f97d 100644
--- a/perf/cairo-perf-diff-files.c
+++ b/perf/cairo-perf-diff-files.c
@@ -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)
{
diff --git a/perf/cairo-perf-graph b/perf/cairo-perf-graph
new file mode 100755
index 000000000..4d2595329
--- /dev/null
+++ b/perf/cairo-perf-graph
@@ -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
diff --git a/perf/cairo-perf-graph-files.c b/perf/cairo-perf-graph-files.c
new file mode 100644
index 000000000..5122b9faf
--- /dev/null
+++ b/perf/cairo-perf-graph-files.c
@@ -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;
+}
diff --git a/perf/cairo-perf-graph-widget.c b/perf/cairo-perf-graph-widget.c
new file mode 100644
index 000000000..2bb48a4aa
--- /dev/null
+++ b/perf/cairo-perf-graph-widget.c
@@ -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);
+}
diff --git a/perf/cairo-perf-graph.h b/perf/cairo-perf-graph.h
new file mode 100644
index 000000000..62bf30fce
--- /dev/null
+++ b/perf/cairo-perf-graph.h
@@ -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
diff --git a/perf/cairo-perf-report.c b/perf/cairo-perf-report.c
new file mode 100644
index 000000000..79e2f923e
--- /dev/null
+++ b/perf/cairo-perf-report.c
@@ -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;
+}
+
diff --git a/perf/cairo-perf.c b/perf/cairo-perf.c
index 68e145bbd..613d15cf9 100644
--- a/perf/cairo-perf.c
+++ b/perf/cairo-perf.c
@@ -29,6 +29,7 @@
#define _GNU_SOURCE 1 /* for sched_getaffinity() */
#include "cairo-perf.h"
+#include "cairo-stats.h"
#include "cairo-boilerplate-getopt.h"
diff --git a/perf/cairo-perf.h b/perf/cairo-perf.h
index 4a75dc110..57fe85f11 100644
--- a/perf/cairo-perf.h
+++ b/perf/cairo-perf.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);
diff --git a/perf/cairo-stats.h b/perf/cairo-stats.h
index 3f8a988d9..58ff0b6f2 100644
--- a/perf/cairo-stats.h
+++ b/perf/cairo-stats.h
@@ -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,