/* * 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 */ #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 #include #include #include #include #include #include #ifdef HAVE_LIBGEN_H #include #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; }