Logo Search packages:      
Sourcecode: cairo version File versions  Download package

cairo-perf-trace.c

/* -*- 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
 *
 * 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>
#include <cairo-types-private.h> /* for INTERNAL_SURFACE_TYPE */

/* rudely reuse bits of the library... */
#include "../src/cairo-hash-private.h"
#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>
#include <dirent.h>

#if HAVE_UNISTD_H
#include <unistd.h>
#endif

#include <signal.h>

#if HAVE_FCFINI
#include <fontconfig/fontconfig.h>
#endif

#define CAIRO_PERF_ITERATIONS_DEFAULT     15
#define CAIRO_PERF_LOW_STD_DEV            0.05
#define CAIRO_PERF_MIN_STD_DEV_COUNT      3
#define CAIRO_PERF_STABLE_STD_DEV_COUNT 3

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 void
clear_surface (cairo_surface_t *surface)
{
    cairo_t *cr = cairo_create (surface);
    cairo_set_operator (cr, CAIRO_OPERATOR_CLEAR);
    cairo_paint (cr);
    cairo_destroy (cr);
}

00139 struct scache {
    cairo_hash_entry_t entry;
    cairo_content_t content;
    int width, height;
    cairo_surface_t *surface;
};

static cairo_hash_table_t *surface_cache;
static cairo_surface_t *surface_holdovers[16];

static cairo_bool_t
scache_equal (const void *A,
            const void *B)
{
    const struct scache *a = A, *b = B;
    return a->entry.hash == b->entry.hash;
}

#define ARRAY_SIZE(A) (sizeof(A)/sizeof(A[0]))
static void
scache_mark_active (cairo_surface_t *surface)
{
    cairo_surface_t *t0, *t1;
    unsigned n;

    if (surface_cache == NULL)
      return;

    t0 = cairo_surface_reference (surface);
    for (n = 0; n < ARRAY_SIZE (surface_holdovers); n++) {
      if (surface_holdovers[n] == surface) {
          surface_holdovers[n] = t0;
          t0 = surface;
          break;
      }

      t1 = surface_holdovers[n];
      surface_holdovers[n] = t0;
      t0 = t1;
    }
    cairo_surface_destroy (t0);
}

static void
scache_clear (void)
{
    unsigned n;

    if (surface_cache == NULL)
      return;

    for (n = 0; n < ARRAY_SIZE (surface_holdovers); n++) {
      cairo_surface_destroy (surface_holdovers[n]);
      surface_holdovers[n] = NULL;
    }
}

static void
scache_remove (void *closure)
{
    _cairo_hash_table_remove (surface_cache, closure);
    free (closure);
}

static cairo_surface_t *
_similar_surface_create (void        *closure,
                   cairo_content_t  content,
                   double       width,
                   double       height,
                   long         uid)
{
    cairo_surface_t *surface;
    struct scache skey, *s;

    if (uid == 0 || surface_cache == NULL)
      return cairo_surface_create_similar (closure, content, width, height);

    skey.entry.hash = uid;
    s = _cairo_hash_table_lookup (surface_cache, &skey.entry);
    if (s != NULL) {
      if (s->content == content &&
          s->width   == width   &&
          s->height  == height)
      {
          return cairo_surface_reference (s->surface);
      }

      /* The surface has been resized, allow the original entry to expire
       * as it becomes inactive.
       */
    }

    surface = cairo_surface_create_similar (closure, content, width, height);
    s = malloc (sizeof (struct scache));
    if (s == NULL)
      return surface;

    s->entry.hash = uid;
    s->content = content;
    s->width = width;
    s->height = height;
    s->surface = surface;
    if (_cairo_hash_table_insert (surface_cache, &s->entry)) {
      free (s);
    } else if (cairo_surface_set_user_data
             (surface,
            (const cairo_user_data_key_t *) &surface_cache,
            s, scache_remove))
    {
      scache_remove (s);
    }

    return surface;
}

static cairo_t *
_context_create (void          *closure,
             cairo_surface_t *surface)
{
    scache_mark_active (surface);
    return cairo_create (surface);
}

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->target->describe)
        description = perf->target->describe (closure);

    if (description == NULL)
        return;

    if (perf->raw) {
        printf ("[ # ] %s: %s\n", perf->target->name, description);
    }

    if (perf->summary) {
        fprintf (perf->summary,
                 "[ # ] %8s: %s\n",
                 perf->target->name,
                 description);
    }

    free (description);
}

static void
execute (cairo_perf_t    *perf,
         void            *closure,
       cairo_surface_t *target,
       const char  *trace)
{
    static cairo_bool_t first_run = TRUE;
    unsigned int i;
    cairo_perf_ticks_t *times;
    cairo_stats_t stats = {0.0, 0.0};
    int low_std_dev_count;
    char *trace_cpy, *name, *dot;
    const cairo_script_interpreter_hooks_t hooks = {
      .closure = target,
      .surface_create = _similar_surface_create,
      .context_create = _context_create
    };

    trace_cpy = xstrdup (trace);
    name = basename (trace_cpy);
    dot = strchr (name, '.');
    if (dot)
      *dot = '\0';

    if (perf->list_only) {
      printf ("%s\n", name);
      free (trace_cpy);
      return;
    }

    if (first_run) {
      if (perf->raw) {
          printf ("[ # ] %s.%-s %s %s %s ...\n",
                "backend", "content", "test-size", "ticks-per-ms", "time(ticks)");
      }

      if (perf->summary) {
          fprintf (perf->summary,
                 "[ # ] %8s %28s %8s %5s %5s %s\n",
                 "backend", "test", "min(s)", "median(s)",
                 "stddev.", "count");
      }
      first_run = FALSE;
    }

    describe (perf, closure);

    times = perf->times;

    if (perf->summary) {
      fprintf (perf->summary,
             "[%3d] %8s %28s ",
             perf->test_number,
             perf->target->name,
             name);
      fflush (perf->summary);
    }

    low_std_dev_count = 0;
    for (i = 0; i < perf->iterations && ! user_interrupt; i++) {
      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_perf_yield ();
      cairo_perf_timer_start ();

      cairo_script_interpreter_run (csi, trace);
      clear_surface (target); /* queue a write to the sync'ed surface */

      cairo_perf_timer_stop ();
      times[i] = cairo_perf_timer_elapsed ();

      cairo_script_interpreter_finish (csi);
      scache_clear ();

      line_no = cairo_script_interpreter_get_line_number (csi);
      status = cairo_script_interpreter_destroy (csi);
      if (status) {
          if (perf->summary) {
            fprintf (perf->summary, "Error during replay, line %d: %s\n",
                   line_no,
                   cairo_status_to_string (status));
          }
          goto out;
      }

      if (perf->raw) {
          if (i == 0)
            printf ("[*] %s.%s %s.%d %g",
                  perf->target->name,
                  "rgba",
                  name,
                  0,
                  cairo_perf_ticks_per_second () / 1000.0);
          printf (" %lld", (long long) times[i]);
          fflush (stdout);
      } else if (! perf->exact_iterations) {
          if (i > CAIRO_PERF_MIN_STD_DEV_COUNT) {
            _cairo_stats_compute (&stats, times, i+1);

            if (stats.std_dev <= CAIRO_PERF_LOW_STD_DEV) {
                if (++low_std_dev_count >= CAIRO_PERF_STABLE_STD_DEV_COUNT)
                  break;
            } else {
                low_std_dev_count = 0;
            }
          }
      }

      if (perf->summary && perf->summary_continuous) {
          _cairo_stats_compute (&stats, times, i+1);

          fprintf (perf->summary,
                 "\r[%3d] %8s %28s ",
                 perf->test_number,
                 perf->target->name,
                 name);
          fprintf (perf->summary,
                 "%#8.3f %#8.3f %#6.2f%% %4d/%d",
                 (double) stats.min_ticks / cairo_perf_ticks_per_second (),
                 (double) stats.median_ticks / cairo_perf_ticks_per_second (),
                 stats.std_dev * 100.0,
                 stats.iterations, i+1);
          fflush (perf->summary);
      }
    }
    user_interrupt = 0;

    if (perf->summary) {
      _cairo_stats_compute (&stats, times, i);
      if (perf->summary_continuous) {
          fprintf (perf->summary,
                 "\r[%3d] %8s %28s ",
                 perf->test_number,
                 perf->target->name,
                 name);
      }
      fprintf (perf->summary,
             "%#8.3f %#8.3f %#6.2f%% %4d/%d\n",
             (double) stats.min_ticks / cairo_perf_ticks_per_second (),
             (double) stats.median_ticks / cairo_perf_ticks_per_second (),
             stats.std_dev * 100.0,
             stats.iterations, i);
      fflush (perf->summary);
    }

out:
    if (perf->raw) {
      printf ("\n");
      fflush (stdout);
    }

    perf->test_number++;
    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"
"  -r raw; display each time measurement instead of summary statistics\n"
"  -v verbose; in raw mode also show the summaries\n"
"  -i iterations; specify the number of iterations per test case\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++;
      }
    }
    if (line != NULL)
      free (line);

    fclose (file);

    return TRUE;
}

static void
parse_options (cairo_perf_t *perf,
             int       argc,
             char     *argv[])
{
    int c;
    const char *iters;
    char *end;
    int verbose = 0;
    int use_surface_cache = 0;

    if ((iters = getenv ("CAIRO_PERF_ITERATIONS")) && *iters)
      perf->iterations = strtol (iters, NULL, 0);
    else
      perf->iterations = CAIRO_PERF_ITERATIONS_DEFAULT;
    perf->exact_iterations = 0;

    perf->raw = FALSE;
    perf->list_only = FALSE;
    perf->names = NULL;
    perf->num_names = 0;
    perf->summary = stdout;
    perf->summary_continuous = FALSE;
    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 'r':
          perf->raw = TRUE;
          perf->summary = NULL;
          break;
      case 'v':
          verbose = 1;
          break;
      case 'c':
          use_surface_cache = 1;
          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 (verbose && perf->summary == NULL)
      perf->summary = stderr;
#if HAVE_UNISTD_H
    if (perf->summary && isatty (fileno (perf->summary)))
      perf->summary_continuous = TRUE;
#endif

    if (optind < argc) {
      perf->names = &argv[optind];
      perf->num_names = argc - optind;
    }

    if (use_surface_cache)
      surface_cache = _cairo_hash_table_create (scache_equal);
}

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 void
cairo_perf_trace (cairo_perf_t                     *perf,
              const cairo_boilerplate_target_t *target,
              const char                     *trace)
{
    cairo_surface_t *surface;
    void *closure;

    surface = (target->create_surface) (NULL,
                              CAIRO_CONTENT_COLOR_ALPHA,
                              1, 1,
                              1, 1,
                              CAIRO_BOILERPLATE_MODE_PERF,
                              0,
                              &closure);
    if (surface == NULL) {
      fprintf (stderr,
             "Error: Failed to create target surface: %s\n",
             target->name);
      return;
    }

    cairo_perf_timer_set_synchronize (target->synchronize, closure);

    execute (perf, closure, surface, trace);

    cairo_surface_destroy (surface);

    if (target->cleanup)
      target->cleanup (closure);

    cairo_debug_reset_static_data ();
#if HAVE_FCFINI
    FcFini ();
#endif
}

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.test_number = 0;

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

cairo_status_t
_cairo_error (cairo_status_t status)
{
    return status;
}

Generated by  Doxygen 1.6.0   Back to index