cairo/perf/cairo-perf-report.c
Chris Wilson 0db9e010fa [perf] Calibrate tests to run for at least 2 seconds
By ensuring that tests take longer than a couple of seconds we eliminate
systematic errors in our measurements. However, we also effectively
eliminate the synchronisation overhead. To compensate, we attempt to
estimate the overhead by reporting the difference between a single
instance and the minimum averaged instance.
2009-08-06 10:15:40 +01:00

500 lines
13 KiB
C

/*
* 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
#if !defined (__USE_GNU) && !defined(__USE_XOPEN2K8)
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 ? end : xstrdup ("???");
skip_space ();
parse_string (report->name);
end = strrchr (report->name, '.');
if (end)
*end++ = '\0';
report->size = end ? atoi (end) : 0;
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));
report->stats.min_ticks = 0;
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]);
if (report->samples_count == 0) {
report->stats.min_ticks =
report->samples[report->samples_count];
} else if (report->stats.min_ticks >
report->samples[report->samples_count]){
report->stats.min_ticks =
report->samples[report->samples_count];
}
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.
*/
#if !defined (__USE_GNU) && !defined(__USE_XOPEN2K8)
#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;
}
int
test_report_cmp_name (const void *a, const void *b)
{
const test_report_t *a_test = a;
const test_report_t *b_test = b;
int 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,
int (*cmp) (const void*, const void*))
{
test_report_t *base, *next, *last, *t;
if (cmp == NULL)
cmp = test_report_cmp_backend_then_name;
/* First we sort, since the diff needs both lists in the same
* order */
qsort (report->tests, report->tests_count, sizeof (test_report_t), cmp);
/* 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,
int (*cmp) (const void *, const void *))
{
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 = basename (configuration);
report->configuration = xmalloc (strlen (baseName) * 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, cmp);
/* 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;
}