summaryrefslogtreecommitdiff
path: root/perf/cairo-analyse-trace.c
diff options
context:
space:
mode:
authorChris Wilson <chris@chris-wilson.co.uk>2011-08-14 18:11:26 +0100
committerChris Wilson <chris@chris-wilson.co.uk>2011-08-14 20:54:53 +0100
commiteee66899cdbd2d431b08b468ac2b285bb855e6da (patch)
tree08deff81060ee06329c1d5b77989713e21207502 /perf/cairo-analyse-trace.c
parentf6fc6f1ad0315d51b6b395749f8035fb7dcccbbc (diff)
downloadcairo-eee66899cdbd2d431b08b468ac2b285bb855e6da.tar.gz
Introduce cairo_surface_observer_t for performance analysis
Another logging passthrough surface that records the style of operations performed trying to categorise what is slow/fast/important. In combination with perf/cairo-analyse-trace it is very useful for understanding what a trace does. The next steps for this tool would be to identify the slow operations that the trace does. Baby steps. This should be generally useful in similar situations outside of perf/ and should be extensible to become an online performance probe. Signed-off-by: Chris Wilson <chris@chris-wilson.co.uk>
Diffstat (limited to 'perf/cairo-analyse-trace.c')
-rw-r--r--perf/cairo-analyse-trace.c646
1 files changed, 646 insertions, 0 deletions
diff --git a/perf/cairo-analyse-trace.c b/perf/cairo-analyse-trace.c
new file mode 100644
index 000000000..206ff8612
--- /dev/null
+++ b/perf/cairo-analyse-trace.c
@@ -0,0 +1,646 @@
+/* -*- Mode: c; c-basic-offset: 4; indent-tabs-mode: t; tab-width: 8; -*- */
+/*
+ * Copyright © 2006 Mozilla Corporation
+ * Copyright © 2006 Red Hat, Inc.
+ * Copyright © 2009 Chris Wilson
+ * Copyright © 2011 Intel Corporation
+ *
+ * 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 authors not be used in advertising or publicity pertaining to
+ * distribution of the software without specific, written prior
+ * permission. The authors make no representations about the
+ * suitability of this software for any purpose. It is provided "as
+ * is" without express or implied warranty.
+ *
+ * THE AUTHORS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS
+ * SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
+ * FITNESS, IN NO EVENT SHALL THE AUTHORS 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: Vladimir Vukicevic <vladimir@pobox.com>
+ * Carl Worth <cworth@cworth.org>
+ * Chris Wilson <chris@chris-wilson.co.uk>
+ */
+
+#define _GNU_SOURCE 1 /* for sched_getaffinity() and getline() */
+
+#include "../cairo-version.h" /* for the real version */
+
+#include "cairo-perf.h"
+#include "cairo-stats.h"
+
+#include "cairo-boilerplate-getopt.h"
+#include <cairo-script-interpreter.h>
+
+/* rudely reuse bits of the library... */
+#include "../src/cairo-error-private.h"
+
+/* For basename */
+#ifdef HAVE_LIBGEN_H
+#include <libgen.h>
+#endif
+#include <ctype.h> /* isspace() */
+
+#include <sys/types.h>
+#include <sys/stat.h>
+
+#ifdef _MSC_VER
+#include "dirent-win32.h"
+
+typedef SSIZE_T ssize_t;
+
+static char *
+basename_no_ext (char *path)
+{
+ static char name[_MAX_FNAME + 1];
+
+ _splitpath (path, NULL, NULL, name, NULL);
+
+ name[_MAX_FNAME] = '\0';
+
+ return name;
+}
+
+
+#else
+#include <dirent.h>
+
+static char *
+basename_no_ext (char *path)
+{
+ char *dot, *name;
+
+ name = basename (path);
+
+ dot = strchr (name, '.');
+ if (dot)
+ *dot = '\0';
+
+ return name;
+}
+
+#endif
+
+#if HAVE_UNISTD_H
+#include <unistd.h>
+#endif
+
+#include <signal.h>
+
+#if HAVE_FCFINI
+#include <fontconfig/fontconfig.h>
+#endif
+
+struct trace {
+ const cairo_boilerplate_target_t *target;
+ void *closure;
+ cairo_surface_t *surface;
+};
+
+cairo_bool_t
+cairo_perf_can_run (cairo_perf_t *perf,
+ const char *name,
+ cairo_bool_t *is_explicit)
+{
+ unsigned int i;
+ char *copy, *dot;
+ cairo_bool_t ret;
+
+ if (is_explicit)
+ *is_explicit = FALSE;
+
+ if (perf->exact_names) {
+ if (is_explicit)
+ *is_explicit = TRUE;
+ return TRUE;
+ }
+
+ if (perf->num_names == 0 && perf->num_exclude_names == 0)
+ return TRUE;
+
+ copy = xstrdup (name);
+ dot = strchr (copy, '.');
+ if (dot != NULL)
+ *dot = '\0';
+
+ if (perf->num_names) {
+ ret = TRUE;
+ for (i = 0; i < perf->num_names; i++)
+ if (strstr (copy, perf->names[i])) {
+ if (is_explicit)
+ *is_explicit = strcmp (copy, perf->names[i]) == 0;
+ goto check_exclude;
+ }
+
+ ret = FALSE;
+ goto done;
+ }
+
+check_exclude:
+ if (perf->num_exclude_names) {
+ ret = FALSE;
+ for (i = 0; i < perf->num_exclude_names; i++)
+ if (strstr (copy, perf->exclude_names[i])) {
+ if (is_explicit)
+ *is_explicit = strcmp (copy, perf->exclude_names[i]) == 0;
+ goto done;
+ }
+
+ ret = TRUE;
+ goto done;
+ }
+
+done:
+ free (copy);
+
+ return ret;
+}
+
+static cairo_surface_t *
+surface_create (void *closure,
+ cairo_content_t content,
+ double width,
+ double height,
+ long uid)
+{
+ struct trace *args = closure;
+ return cairo_surface_create_similar (args->surface, content, width, height);
+}
+
+static int user_interrupt;
+
+static void
+interrupt (int sig)
+{
+ if (user_interrupt) {
+ signal (sig, SIG_DFL);
+ raise (sig);
+ }
+
+ user_interrupt = 1;
+}
+
+static void
+describe (cairo_perf_t *perf,
+ void *closure)
+{
+ char *description = NULL;
+
+ if (perf->has_described_backend)
+ return;
+ perf->has_described_backend = TRUE;
+
+ if (perf->target->describe)
+ description = perf->target->describe (closure);
+
+ if (description == NULL)
+ return;
+
+ free (description);
+}
+
+static void
+execute (cairo_perf_t *perf,
+ struct trace *args,
+ const char *trace)
+{
+ char *trace_cpy, *name;
+ const cairo_script_interpreter_hooks_t hooks = {
+ .closure = args,
+ .surface_create = surface_create,
+ };
+
+ trace_cpy = xstrdup (trace);
+ name = basename_no_ext (trace_cpy);
+
+ if (perf->list_only) {
+ printf ("%s\n", name);
+ free (trace_cpy);
+ return;
+ }
+
+ describe (perf, args->closure);
+
+ {
+ cairo_script_interpreter_t *csi;
+ cairo_status_t status;
+ unsigned int line_no;
+
+ csi = cairo_script_interpreter_create ();
+ cairo_script_interpreter_install_hooks (csi, &hooks);
+
+ cairo_script_interpreter_run (csi, trace);
+
+ cairo_script_interpreter_finish (csi);
+
+ line_no = cairo_script_interpreter_get_line_number (csi);
+ status = cairo_script_interpreter_destroy (csi);
+ if (status) {
+ /* XXXX cairo_status_to_string is just wrong! */
+ fprintf (stderr, "Error during replay, line %d: %s\n",
+ line_no, cairo_status_to_string (status));
+ }
+ }
+ user_interrupt = 0;
+
+ free (trace_cpy);
+}
+
+static void
+usage (const char *argv0)
+{
+ fprintf (stderr,
+"Usage: %s [-l] [-r] [-v] [-i iterations] [test-names ... | traces ...]\n"
+" %s -l\n"
+"\n"
+"Run the cairo performance test suite over the given tests (all by default)\n"
+"The command-line arguments are interpreted as follows:\n"
+"\n"
+" -v verbose\n"
+" -x exclude; specify a file to read a list of traces to exclude\n"
+" -l list only; just list selected test case names without executing\n"
+"\n"
+"If test names are given they are used as sub-string matches so a command\n"
+"such as \"cairo-perf-trace firefox\" can be used to run all firefox traces.\n"
+"Alternatively, you can specify a list of filenames to execute.\n",
+ argv0, argv0);
+}
+
+#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 */
+
+static cairo_bool_t
+read_excludes (cairo_perf_t *perf,
+ const char *filename)
+{
+ FILE *file;
+ char *line = NULL;
+ size_t line_size = 0;
+ char *s, *t;
+
+ file = fopen (filename, "r");
+ if (file == NULL)
+ return FALSE;
+
+ while (getline (&line, &line_size, file) != -1) {
+ /* terminate the line at a comment marker '#' */
+ s = strchr (line, '#');
+ if (s)
+ *s = '\0';
+
+ /* whitespace delimits */
+ s = line;
+ while (*s != '\0' && isspace (*s))
+ s++;
+
+ t = s;
+ while (*t != '\0' && ! isspace (*t))
+ t++;
+
+ if (s != t) {
+ int i = perf->num_exclude_names;
+ perf->exclude_names = xrealloc (perf->exclude_names,
+ sizeof (char *) * (i+1));
+ perf->exclude_names[i] = strndup (s, t-s);
+ perf->num_exclude_names++;
+ }
+ }
+ free (line);
+
+ fclose (file);
+
+ return TRUE;
+}
+
+static void
+parse_options (cairo_perf_t *perf,
+ int argc,
+ char *argv[])
+{
+ char *end;
+ int c;
+
+ perf->list_only = FALSE;
+ perf->names = NULL;
+ perf->num_names = 0;
+ perf->exclude_names = NULL;
+ perf->num_exclude_names = 0;
+
+ while (1) {
+ c = _cairo_getopt (argc, argv, "i:x:lrvc");
+ if (c == -1)
+ break;
+
+ switch (c) {
+ case 'i':
+ perf->exact_iterations = TRUE;
+ perf->iterations = strtoul (optarg, &end, 10);
+ if (*end != '\0') {
+ fprintf (stderr, "Invalid argument for -i (not an integer): %s\n",
+ optarg);
+ exit (1);
+ }
+ break;
+ case 'l':
+ perf->list_only = TRUE;
+ break;
+ case 'x':
+ if (! read_excludes (perf, optarg)) {
+ fprintf (stderr, "Invalid argument for -x (not readable file): %s\n",
+ optarg);
+ exit (1);
+ }
+ break;
+ default:
+ fprintf (stderr, "Internal error: unhandled option: %c\n", c);
+ /* fall-through */
+ case '?':
+ usage (argv[0]);
+ exit (1);
+ }
+ }
+
+ if (optind < argc) {
+ perf->names = &argv[optind];
+ perf->num_names = argc - optind;
+ }
+}
+
+static void
+cairo_perf_fini (cairo_perf_t *perf)
+{
+ cairo_boilerplate_free_targets (perf->targets);
+ cairo_boilerplate_fini ();
+
+ free (perf->times);
+ cairo_debug_reset_static_data ();
+#if HAVE_FCFINI
+ FcFini ();
+#endif
+}
+
+static cairo_bool_t
+have_trace_filenames (cairo_perf_t *perf)
+{
+ unsigned int i;
+
+ if (perf->num_names == 0)
+ return FALSE;
+
+#if HAVE_UNISTD_H
+ for (i = 0; i < perf->num_names; i++)
+ if (access (perf->names[i], R_OK) == 0)
+ return TRUE;
+#endif
+
+ return FALSE;
+}
+
+static cairo_status_t
+print (void *closure, const unsigned char *data, unsigned int length)
+{
+ fwrite (data, length, 1, closure);
+ return CAIRO_STATUS_SUCCESS;
+}
+
+static void
+cairo_perf_trace (cairo_perf_t *perf,
+ const cairo_boilerplate_target_t *target,
+ const char *trace)
+{
+ struct trace args;
+ cairo_surface_t *real;
+
+ args.target = target;
+ real = target->create_surface (NULL,
+ CAIRO_CONTENT_COLOR_ALPHA,
+ 1, 1,
+ 1, 1,
+ CAIRO_BOILERPLATE_MODE_PERF,
+ 0,
+ &args.closure);
+ args.surface = cairo_surface_create_observer (real);
+ cairo_surface_destroy (real);
+ if (cairo_surface_status (args.surface)) {
+ fprintf (stderr,
+ "Error: Failed to create target surface: %s\n",
+ target->name);
+ return;
+ }
+
+ printf ("Observing '%s'...", trace);
+ fflush (stdout);
+
+ execute (perf, &args, trace);
+
+ printf ("\n");
+ cairo_device_observer_print (cairo_surface_get_device (args.surface),
+ print, stdout);
+ fflush (stdout);
+
+ cairo_surface_destroy (args.surface);
+
+ if (target->cleanup)
+ target->cleanup (args.closure);
+}
+
+static void
+warn_no_traces (const char *message,
+ const char *trace_dir)
+{
+ fprintf (stderr,
+"Error: %s '%s'.\n"
+"Have you cloned the cairo-traces repository and uncompressed the traces?\n"
+" git clone git://anongit.freedesktop.org/cairo-traces\n"
+" cd cairo-traces && make\n"
+"Or set the env.var CAIRO_TRACE_DIR to point to your traces?\n",
+ message, trace_dir);
+}
+
+static int
+cairo_perf_trace_dir (cairo_perf_t *perf,
+ const cairo_boilerplate_target_t *target,
+ const char *dirname)
+{
+ DIR *dir;
+ struct dirent *de;
+ int num_traces = 0;
+ cairo_bool_t force;
+ cairo_bool_t is_explicit;
+
+ dir = opendir (dirname);
+ if (dir == NULL)
+ return 0;
+
+ force = FALSE;
+ if (cairo_perf_can_run (perf, dirname, &is_explicit))
+ force = is_explicit;
+
+ while ((de = readdir (dir)) != NULL) {
+ char *trace;
+ struct stat st;
+
+ if (de->d_name[0] == '.')
+ continue;
+
+ xasprintf (&trace, "%s/%s", dirname, de->d_name);
+ if (stat (trace, &st) != 0)
+ goto next;
+
+ if (S_ISDIR(st.st_mode)) {
+ num_traces += cairo_perf_trace_dir (perf, target, trace);
+ } else {
+ const char *dot;
+
+ dot = strrchr (de->d_name, '.');
+ if (dot == NULL)
+ goto next;
+ if (strcmp (dot, ".trace"))
+ goto next;
+
+ num_traces++;
+ if (!force && ! cairo_perf_can_run (perf, de->d_name, NULL))
+ goto next;
+
+ cairo_perf_trace (perf, target, trace);
+ }
+next:
+ free (trace);
+
+ }
+ closedir (dir);
+
+ return num_traces;
+}
+
+int
+main (int argc,
+ char *argv[])
+{
+ cairo_perf_t perf;
+ const char *trace_dir = "cairo-traces:/usr/src/cairo-traces:/usr/share/cairo-traces";
+ unsigned int n;
+ int i;
+
+ parse_options (&perf, argc, argv);
+
+ signal (SIGINT, interrupt);
+
+ if (getenv ("CAIRO_TRACE_DIR") != NULL)
+ trace_dir = getenv ("CAIRO_TRACE_DIR");
+
+ perf.targets = cairo_boilerplate_get_targets (&perf.num_targets, NULL);
+ perf.times = xmalloc (perf.iterations * sizeof (cairo_perf_ticks_t));
+
+ /* do we have a list of filenames? */
+ perf.exact_names = have_trace_filenames (&perf);
+
+ for (i = 0; i < perf.num_targets; i++) {
+ const cairo_boilerplate_target_t *target = perf.targets[i];
+
+ if (! perf.list_only && ! target->is_measurable)
+ continue;
+
+ perf.target = target;
+ perf.has_described_backend = FALSE;
+
+ if (perf.exact_names) {
+ for (n = 0; n < perf.num_names; n++) {
+ struct stat st;
+
+ if (stat (perf.names[n], &st) == 0) {
+ if (S_ISDIR (st.st_mode)) {
+ cairo_perf_trace_dir (&perf, target, perf.names[n]);
+ } else
+ cairo_perf_trace (&perf, target, perf.names[n]);
+ }
+ }
+ } else {
+ int num_traces = 0;
+ const char *dir;
+
+ dir = trace_dir;
+ do {
+ char buf[1024];
+ const char *end = strchr (dir, ':');
+ if (end != NULL) {
+ memcpy (buf, dir, end-dir);
+ buf[end-dir] = '\0';
+ end++;
+
+ dir = buf;
+ }
+
+ num_traces += cairo_perf_trace_dir (&perf, target, dir);
+ dir = end;
+ } while (dir != NULL);
+
+ if (num_traces == 0) {
+ warn_no_traces ("Found no traces in", trace_dir);
+ return 1;
+ }
+ }
+
+ if (perf.list_only)
+ break;
+ }
+
+ cairo_perf_fini (&perf);
+
+ return 0;
+}