diff options
author | Chris Wilson <chris@chris-wilson.co.uk> | 2008-10-16 11:56:19 +0100 |
---|---|---|
committer | Chris Wilson <chris@chris-wilson.co.uk> | 2008-10-19 09:36:53 +0100 |
commit | f2ff7944264c23cbec856be3e85f240a93184f80 (patch) | |
tree | 0c14c7d36b71bcb92bf244b6eede1eec5b4aa4ea /perf/cairo-perf-report.c | |
parent | 41c8eefc6d432ab213f6f405c3d6346adb7f7931 (diff) | |
download | cairo-f2ff7944264c23cbec856be3e85f240a93184f80.tar.gz |
[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/cairo-perf-report.c')
-rw-r--r-- | perf/cairo-perf-report.c | 456 |
1 files changed, 456 insertions, 0 deletions
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; +} + |