| /* |
| * 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; |
| } |