blob: 8ab809057a483b82bb6491bee4121bfbe6f4ffee [file] [log] [blame]
/*
* 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;
const char *name;
name = filename;
if (name == NULL)
name = "stdin";
configuration = xmalloc (strlen (name) * sizeof (char) + 1);
strcpy (configuration, name);
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 = name;
report->tests_size = 16;
report->tests = xmalloc (report->tests_size * sizeof (test_report_t));
report->tests_count = 0;
if (filename == NULL) {
file = stdin;
} else {
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);
if (filename != NULL)
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;
}