1 /* -*- Mode: c; c-basic-offset: 4; indent-tabs-mode: t; tab-width: 8; -*- */
3 * Copyright © 2006 Mozilla Corporation
4 * Copyright © 2006 Red Hat, Inc.
5 * Copyright © 2009 Chris Wilson
7 * Permission to use, copy, modify, distribute, and sell this software
8 * and its documentation for any purpose is hereby granted without
9 * fee, provided that the above copyright notice appear in all copies
10 * and that both that copyright notice and this permission notice
11 * appear in supporting documentation, and that the name of
12 * the authors not be used in advertising or publicity pertaining to
13 * distribution of the software without specific, written prior
14 * permission. The authors make no representations about the
15 * suitability of this software for any purpose. It is provided "as
16 * is" without express or implied warranty.
18 * THE AUTHORS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS
19 * SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
20 * FITNESS, IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY SPECIAL,
21 * INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER
22 * RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
23 * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR
24 * IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
26 * Authors: Vladimir Vukicevic <vladimir@pobox.com>
27 * Carl Worth <cworth@cworth.org>
28 * Chris Wilson <chris@chris-wilson.co.uk>
31 #define _GNU_SOURCE 1 /* for sched_getaffinity() and getline() */
33 #include "../cairo-version.h" /* for the real version */
35 #include "cairo-missing.h"
36 #include "cairo-perf.h"
37 #include "cairo-stats.h"
39 #include "cairo-boilerplate-getopt.h"
40 #include <cairo-script-interpreter.h>
41 #include <cairo-types-private.h> /* for INTERNAL_SURFACE_TYPE */
43 /* rudely reuse bits of the library... */
44 #include "../src/cairo-hash-private.h"
45 #include "../src/cairo-error-private.h"
51 #include <ctype.h> /* isspace() */
53 #include <sys/types.h>
57 #include "dirent-win32.h"
60 basename_no_ext (char *path)
62 static char name[_MAX_FNAME + 1];
64 _splitpath (path, NULL, NULL, name, NULL);
66 name[_MAX_FNAME] = '\0';
76 basename_no_ext (char *path)
80 name = basename (path);
82 dot = strrchr (name, '.');
98 #include <fontconfig/fontconfig.h>
101 #define CAIRO_PERF_ITERATIONS_DEFAULT 15
102 #define CAIRO_PERF_LOW_STD_DEV 0.05
103 #define CAIRO_PERF_MIN_STD_DEV_COUNT 3
104 #define CAIRO_PERF_STABLE_STD_DEV_COUNT 3
107 const cairo_boilerplate_target_t *target;
109 cairo_surface_t *surface;
110 cairo_bool_t observe;
115 cairo_perf_can_run (cairo_perf_t *perf,
117 cairo_bool_t *is_explicit)
124 *is_explicit = FALSE;
126 if (perf->exact_names) {
132 if (perf->num_names == 0 && perf->num_exclude_names == 0)
135 copy = xstrdup (name);
136 dot = strrchr (copy, '.');
140 if (perf->num_names) {
142 for (i = 0; i < perf->num_names; i++)
143 if (strstr (copy, perf->names[i])) {
145 *is_explicit = strcmp (copy, perf->names[i]) == 0;
154 if (perf->num_exclude_names) {
156 for (i = 0; i < perf->num_exclude_names; i++)
157 if (strstr (copy, perf->exclude_names[i])) {
159 *is_explicit = strcmp (copy, perf->exclude_names[i]) == 0;
174 clear_surface (cairo_surface_t *surface)
176 cairo_t *cr = cairo_create (surface);
177 cairo_set_operator (cr, CAIRO_OPERATOR_CLEAR);
183 cairo_hash_entry_t entry;
184 cairo_content_t content;
186 cairo_surface_t *surface;
189 static cairo_hash_table_t *surface_cache;
190 static cairo_surface_t *surface_holdovers[16];
193 scache_equal (const void *A,
196 const struct scache *a = A, *b = B;
197 return a->entry.hash == b->entry.hash;
200 #define ARRAY_SIZE(A) (sizeof(A)/sizeof(A[0]))
202 scache_mark_active (cairo_surface_t *surface)
204 cairo_surface_t *t0, *t1;
207 if (surface_cache == NULL)
210 t0 = cairo_surface_reference (surface);
211 for (n = 0; n < ARRAY_SIZE (surface_holdovers); n++) {
212 if (surface_holdovers[n] == surface) {
213 surface_holdovers[n] = t0;
218 t1 = surface_holdovers[n];
219 surface_holdovers[n] = t0;
222 cairo_surface_destroy (t0);
230 if (surface_cache == NULL)
233 for (n = 0; n < ARRAY_SIZE (surface_holdovers); n++) {
234 cairo_surface_destroy (surface_holdovers[n]);
235 surface_holdovers[n] = NULL;
240 scache_remove (void *closure)
242 _cairo_hash_table_remove (surface_cache, closure);
246 static cairo_surface_t *
247 _similar_surface_create (void *closure,
248 cairo_content_t content,
253 struct trace *args = closure;
254 cairo_surface_t *surface;
255 struct scache skey, *s;
258 return cairo_surface_create_similar (args->surface,
259 content, width, height);
261 if (uid == 0 || surface_cache == NULL)
262 return args->target->create_similar (args->surface, content, width, height);
264 skey.entry.hash = uid;
265 s = _cairo_hash_table_lookup (surface_cache, &skey.entry);
267 if (s->content == content &&
271 return cairo_surface_reference (s->surface);
274 /* The surface has been resized, allow the original entry to expire
275 * as it becomes inactive.
279 surface = args->target->create_similar (args->surface, content, width, height);
280 s = malloc (sizeof (struct scache));
285 s->content = content;
288 s->surface = surface;
289 if (_cairo_hash_table_insert (surface_cache, &s->entry)) {
291 } else if (cairo_surface_set_user_data
293 (const cairo_user_data_key_t *) &surface_cache,
303 _context_create (void *closure,
304 cairo_surface_t *surface)
306 scache_mark_active (surface);
307 return cairo_create (surface);
310 static int user_interrupt;
315 if (user_interrupt) {
316 signal (sig, SIG_DFL);
324 describe (cairo_perf_t *perf,
327 char *description = NULL;
329 if (perf->has_described_backend)
331 perf->has_described_backend = TRUE;
333 if (perf->target->describe)
334 description = perf->target->describe (closure);
336 if (description == NULL)
340 printf ("[ # ] %s: %s\n", perf->target->name, description);
344 fprintf (perf->summary,
354 usage (const char *argv0)
357 "Usage: %s [-clrsv] [-i iterations] [-t tile-size] [-x exclude-file] [test-names ... | traces ...]\n"
359 "Run the cairo performance test suite over the given tests (all by default)\n"
360 "The command-line arguments are interpreted as follows:\n"
362 " -c use surface cache; keep a cache of surfaces to be reused\n"
363 " -i iterations; specify the number of iterations per test case\n"
364 " -l list only; just list selected test case names without executing\n"
365 " -r raw; display each time measurement instead of summary statistics\n"
366 " -s sync; only sum the elapsed time of the indiviual operations\n"
367 " -t tile size; draw to tiled surfaces\n"
368 " -v verbose; in raw mode also show the summaries\n"
369 " -x exclude; specify a file to read a list of traces to exclude\n"
371 "If test names are given they are used as sub-string matches so a command\n"
372 "such as \"%s firefox\" can be used to run all firefox traces.\n"
373 "Alternatively, you can specify a list of filenames to execute.\n",
378 read_excludes (cairo_perf_t *perf,
379 const char *filename)
383 size_t line_size = 0;
386 file = fopen (filename, "r");
390 while (getline (&line, &line_size, file) != -1) {
391 /* terminate the line at a comment marker '#' */
392 s = strchr (line, '#');
396 /* whitespace delimits */
398 while (*s != '\0' && isspace (*s))
402 while (*t != '\0' && ! isspace (*t))
406 int i = perf->num_exclude_names;
407 perf->exclude_names = xrealloc (perf->exclude_names,
408 sizeof (char *) * (i+1));
409 perf->exclude_names[i] = strndup (s, t-s);
410 perf->num_exclude_names++;
421 parse_options (cairo_perf_t *perf,
429 int use_surface_cache = 0;
431 if ((iters = getenv ("CAIRO_PERF_ITERATIONS")) && *iters)
432 perf->iterations = strtol (iters, NULL, 0);
434 perf->iterations = CAIRO_PERF_ITERATIONS_DEFAULT;
435 perf->exact_iterations = 0;
438 perf->observe = FALSE;
439 perf->list_only = FALSE;
443 perf->summary = stdout;
444 perf->summary_continuous = FALSE;
445 perf->exclude_names = NULL;
446 perf->num_exclude_names = 0;
449 c = _cairo_getopt (argc, argv, "ci:lrst:vx:");
455 use_surface_cache = 1;
458 perf->exact_iterations = TRUE;
459 perf->iterations = strtoul (optarg, &end, 10);
461 fprintf (stderr, "Invalid argument for -i (not an integer): %s\n",
467 perf->list_only = TRUE;
471 perf->summary = NULL;
474 perf->observe = TRUE;
477 perf->tile_size = strtoul (optarg, &end, 10);
479 fprintf (stderr, "Invalid argument for -t (not an integer): %s\n",
488 if (! read_excludes (perf, optarg)) {
489 fprintf (stderr, "Invalid argument for -x (not readable file): %s\n",
495 fprintf (stderr, "Internal error: unhandled option: %c\n", c);
503 if (perf->observe && perf->tile_size) {
504 fprintf (stderr, "Can't mix observer and tiling. Sorry.\n");
508 if (verbose && perf->summary == NULL)
509 perf->summary = stderr;
511 if (perf->summary && isatty (fileno (perf->summary)))
512 perf->summary_continuous = TRUE;
516 perf->names = &argv[optind];
517 perf->num_names = argc - optind;
520 if (use_surface_cache)
521 surface_cache = _cairo_hash_table_create (scache_equal);
525 cairo_perf_fini (cairo_perf_t *perf)
527 cairo_boilerplate_free_targets (perf->targets);
528 cairo_boilerplate_fini ();
531 cairo_debug_reset_static_data ();
538 have_trace_filenames (cairo_perf_t *perf)
542 if (perf->num_names == 0)
546 for (i = 0; i < perf->num_names; i++)
547 if (access (perf->names[i], R_OK) == 0)
555 _tiling_surface_finish (cairo_surface_t *observer,
556 cairo_surface_t *target,
559 struct trace *args = closure;
560 cairo_surface_t *surface;
561 cairo_content_t content;
566 cairo_recording_surface_get_extents (target, &r);
570 content = cairo_surface_get_content (target);
572 for (y = 0; y < h; y += args->tile_size) {
573 height = args->tile_size;
577 for (x = 0; x < w; x += args->tile_size) {
580 width = args->tile_size;
584 /* XXX to correctly observe the playback we would need
585 * to replay the target onto the observer directly.
587 surface = args->target->create_similar (args->surface,
588 content, width, height);
590 cr = cairo_create (surface);
591 cairo_set_operator (cr, CAIRO_OPERATOR_SOURCE);
592 cairo_set_source_surface (cr, target, -x, -y);
596 cairo_surface_destroy (surface);
601 static cairo_surface_t *
602 _tiling_surface_create (void *closure,
603 cairo_content_t content,
609 cairo_surface_t *surface, *observer;
615 surface = cairo_recording_surface_create (content, &r);
616 observer = cairo_surface_create_observer (surface,
617 CAIRO_SURFACE_OBSERVER_NORMAL);
618 cairo_surface_destroy (surface);
620 cairo_surface_observer_add_finish_callback (observer,
621 _tiling_surface_finish,
628 cairo_perf_trace (cairo_perf_t *perf,
629 const cairo_boilerplate_target_t *target,
632 static cairo_bool_t first_run = TRUE;
634 cairo_time_t *times, *paint, *mask, *fill, *stroke, *glyphs;
635 cairo_stats_t stats = {0.0, 0.0};
636 struct trace args = { target };
637 int low_std_dev_count;
638 char *trace_cpy, *name;
639 const cairo_script_interpreter_hooks_t hooks = {
641 perf->tile_size ? _tiling_surface_create : _similar_surface_create,
642 NULL, /* surface_destroy */
644 NULL, /* context_destroy */
645 NULL, /* show_page */
649 args.tile_size = perf->tile_size;
650 args.observe = perf->observe;
652 trace_cpy = xstrdup (trace);
653 name = basename_no_ext (trace_cpy);
655 if (perf->list_only) {
656 printf ("%s\n", name);
663 printf ("[ # ] %s.%-s %s %s %s ...\n",
664 "backend", "content", "test-size", "ticks-per-ms", "time(ticks)");
669 fprintf (perf->summary,
670 "[ # ] %8s %28s %9s %9s %9s %9s %9s %9s %5s\n",
672 "total(s)", "paint(s)", "mask(s)", "fill(s)", "stroke(s)", "glyphs(s)",
675 fprintf (perf->summary,
676 "[ # ] %8s %28s %8s %5s %5s %s\n",
677 "backend", "test", "min(s)", "median(s)",
685 paint = times + perf->iterations;
686 mask = paint + perf->iterations;
687 stroke = mask + perf->iterations;
688 fill = stroke + perf->iterations;
689 glyphs = fill + perf->iterations;
691 low_std_dev_count = 0;
692 for (i = 0; i < perf->iterations && ! user_interrupt; i++) {
693 cairo_script_interpreter_t *csi;
694 cairo_status_t status;
695 unsigned int line_no;
697 args.surface = target->create_surface (NULL,
698 CAIRO_CONTENT_COLOR_ALPHA,
701 CAIRO_BOILERPLATE_MODE_PERF,
704 cairo_surface_t *obs;
705 obs = cairo_surface_create_observer (args.surface,
706 CAIRO_SURFACE_OBSERVER_NORMAL);
707 cairo_surface_destroy (args.surface);
710 if (cairo_surface_status (args.surface)) {
712 "Error: Failed to create target surface: %s\n",
717 cairo_perf_timer_set_synchronize (target->synchronize, args.closure);
720 describe (perf, args.closure);
722 fprintf (perf->summary,
727 fflush (perf->summary);
731 csi = cairo_script_interpreter_create ();
732 cairo_script_interpreter_install_hooks (csi, &hooks);
734 if (! perf->observe) {
736 cairo_perf_timer_start ();
739 cairo_script_interpreter_run (csi, trace);
740 line_no = cairo_script_interpreter_get_line_number (csi);
742 /* Finish before querying timings in case we are using an intermediate
743 * target and so need to destroy all surfaces before rendering
746 cairo_script_interpreter_finish (csi);
749 cairo_device_t *observer = cairo_surface_get_device (args.surface);
750 times[i] = _cairo_time_from_s (1.e-9 * cairo_device_observer_elapsed (observer));
751 paint[i] = _cairo_time_from_s (1.e-9 * cairo_device_observer_paint_elapsed (observer));
752 mask[i] = _cairo_time_from_s (1.e-9 * cairo_device_observer_mask_elapsed (observer));
753 stroke[i] = _cairo_time_from_s (1.e-9 * cairo_device_observer_stroke_elapsed (observer));
754 fill[i] = _cairo_time_from_s (1.e-9 * cairo_device_observer_fill_elapsed (observer));
755 glyphs[i] = _cairo_time_from_s (1.e-9 * cairo_device_observer_glyphs_elapsed (observer));
757 clear_surface (args.surface); /* queue a write to the sync'ed surface */
758 cairo_perf_timer_stop ();
759 times[i] = cairo_perf_timer_elapsed ();
764 cairo_surface_destroy (args.surface);
767 target->cleanup (args.closure);
769 status = cairo_script_interpreter_destroy (csi);
772 fprintf (perf->summary, "Error during replay, line %d: %s\n",
774 cairo_status_to_string (status));
781 printf ("[*] %s.%s %s.%d %g",
786 _cairo_time_to_double (_cairo_time_from_s (1)) / 1000.);
787 printf (" %lld", (long long) times[i]);
789 } else if (! perf->exact_iterations) {
790 if (i > CAIRO_PERF_MIN_STD_DEV_COUNT) {
791 _cairo_stats_compute (&stats, times, i+1);
793 if (stats.std_dev <= CAIRO_PERF_LOW_STD_DEV) {
794 if (++low_std_dev_count >= CAIRO_PERF_STABLE_STD_DEV_COUNT)
797 low_std_dev_count = 0;
802 if (perf->summary && perf->summary_continuous) {
803 _cairo_stats_compute (&stats, times, i+1);
805 fprintf (perf->summary,
811 fprintf (perf->summary,
812 " %#9.3f", _cairo_time_to_s (stats.median_ticks));
814 _cairo_stats_compute (&stats, paint, i+1);
815 fprintf (perf->summary,
816 " %#9.3f", _cairo_time_to_s (stats.median_ticks));
818 _cairo_stats_compute (&stats, mask, i+1);
819 fprintf (perf->summary,
820 " %#9.3f", _cairo_time_to_s (stats.median_ticks));
822 _cairo_stats_compute (&stats, fill, i+1);
823 fprintf (perf->summary,
824 " %#9.3f", _cairo_time_to_s (stats.median_ticks));
826 _cairo_stats_compute (&stats, stroke, i+1);
827 fprintf (perf->summary,
828 " %#9.3f", _cairo_time_to_s (stats.median_ticks));
830 _cairo_stats_compute (&stats, glyphs, i+1);
831 fprintf (perf->summary,
832 " %#9.3f", _cairo_time_to_s (stats.median_ticks));
834 fprintf (perf->summary,
837 fprintf (perf->summary,
838 "%#8.3f %#8.3f %#6.2f%% %4d/%d",
839 _cairo_time_to_s (stats.min_ticks),
840 _cairo_time_to_s (stats.median_ticks),
841 stats.std_dev * 100.0,
842 stats.iterations, i+1);
844 fflush (perf->summary);
850 _cairo_stats_compute (&stats, times, i);
851 if (perf->summary_continuous) {
852 fprintf (perf->summary,
859 fprintf (perf->summary,
860 " %#9.3f", _cairo_time_to_s (stats.median_ticks));
862 _cairo_stats_compute (&stats, paint, i);
863 fprintf (perf->summary,
864 " %#9.3f", _cairo_time_to_s (stats.median_ticks));
866 _cairo_stats_compute (&stats, mask, i);
867 fprintf (perf->summary,
868 " %#9.3f", _cairo_time_to_s (stats.median_ticks));
870 _cairo_stats_compute (&stats, fill, i);
871 fprintf (perf->summary,
872 " %#9.3f", _cairo_time_to_s (stats.median_ticks));
874 _cairo_stats_compute (&stats, stroke, i);
875 fprintf (perf->summary,
876 " %#9.3f", _cairo_time_to_s (stats.median_ticks));
878 _cairo_stats_compute (&stats, glyphs, i);
879 fprintf (perf->summary,
880 " %#9.3f", _cairo_time_to_s (stats.median_ticks));
882 fprintf (perf->summary,
885 fprintf (perf->summary,
886 "%#8.3f %#8.3f %#6.2f%% %4d/%d\n",
887 _cairo_time_to_s (stats.min_ticks),
888 _cairo_time_to_s (stats.median_ticks),
889 stats.std_dev * 100.0,
890 stats.iterations, i);
892 fflush (perf->summary);
906 warn_no_traces (const char *message,
907 const char *trace_dir)
911 "Have you cloned the cairo-traces repository and uncompressed the traces?\n"
912 " git clone git://anongit.freedesktop.org/cairo-traces\n"
913 " cd cairo-traces && make\n"
914 "Or set the env.var CAIRO_TRACE_DIR to point to your traces?\n",
919 cairo_perf_trace_dir (cairo_perf_t *perf,
920 const cairo_boilerplate_target_t *target,
927 cairo_bool_t is_explicit;
929 dir = opendir (dirname);
934 if (cairo_perf_can_run (perf, dirname, &is_explicit))
937 while ((de = readdir (dir)) != NULL) {
941 if (de->d_name[0] == '.')
944 xasprintf (&trace, "%s/%s", dirname, de->d_name);
945 if (stat (trace, &st) != 0)
948 if (S_ISDIR(st.st_mode)) {
949 num_traces += cairo_perf_trace_dir (perf, target, trace);
953 dot = strrchr (de->d_name, '.');
956 if (strcmp (dot, ".trace"))
960 if (!force && ! cairo_perf_can_run (perf, de->d_name, NULL))
963 cairo_perf_trace (perf, target, trace);
979 const char *trace_dir = "cairo-traces:/usr/src/cairo-traces:/usr/share/cairo-traces";
983 parse_options (&perf, argc, argv);
985 signal (SIGINT, interrupt);
987 if (getenv ("CAIRO_TRACE_DIR") != NULL)
988 trace_dir = getenv ("CAIRO_TRACE_DIR");
990 perf.targets = cairo_boilerplate_get_targets (&perf.num_targets, NULL);
991 perf.times = xmalloc (6 * perf.iterations * sizeof (cairo_time_t));
993 /* do we have a list of filenames? */
994 perf.exact_names = have_trace_filenames (&perf);
996 for (i = 0; i < perf.num_targets; i++) {
997 const cairo_boilerplate_target_t *target = perf.targets[i];
999 if (! perf.list_only && ! target->is_measurable)
1002 perf.target = target;
1003 perf.test_number = 0;
1004 perf.has_described_backend = FALSE;
1006 if (perf.exact_names) {
1007 for (n = 0; n < perf.num_names; n++) {
1010 if (stat (perf.names[n], &st) == 0) {
1011 if (S_ISDIR (st.st_mode)) {
1012 cairo_perf_trace_dir (&perf, target, perf.names[n]);
1014 cairo_perf_trace (&perf, target, perf.names[n]);
1024 const char *end = strchr (dir, ':');
1026 memcpy (buf, dir, end-dir);
1027 buf[end-dir] = '\0';
1033 num_traces += cairo_perf_trace_dir (&perf, target, dir);
1035 } while (dir != NULL);
1037 if (num_traces == 0) {
1038 warn_no_traces ("Found no traces in", trace_dir);
1047 cairo_perf_fini (&perf);