1 /* -*- Mode: c; c-basic-offset: 4; indent-tabs-mode: t; tab-width: 8; -*- */
2 /*
3  * Copyright © 2006 Mozilla Corporation
4  * Copyright © 2006 Red Hat, Inc.
5  * Copyright © 2009 Chris Wilson
6  *
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.
17  *
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.
25  *
26  * Authors: Vladimir Vukicevic <vladimir@pobox.com>
27  *	    Carl Worth <cworth@cworth.org>
28  *	    Chris Wilson <chris@chris-wilson.co.uk>
29  */
30 
31 #define _GNU_SOURCE 1	/* for sched_getaffinity() and getline() */
32 
33 #include "cairo-missing.h"
34 #include "cairo-perf.h"
35 #include "cairo-stats.h"
36 
37 #include "cairo-boilerplate-getopt.h"
38 #include <cairo-script-interpreter.h>
39 #include <cairo-types-private.h> /* for INTERNAL_SURFACE_TYPE */
40 
41 /* rudely reuse bits of the library... */
42 #include "../src/cairo-hash-private.h"
43 #include "../src/cairo-error-private.h"
44 
45 /* For basename */
46 #ifdef HAVE_LIBGEN_H
47 #include <libgen.h>
48 #endif
49 #include <ctype.h> /* isspace() */
50 
51 #include <sys/types.h>
52 #include <sys/stat.h>
53 
54 #ifdef _MSC_VER
55 #include "dirent-win32.h"
56 
57 static char *
basename_no_ext(char * path)58 basename_no_ext (char *path)
59 {
60     static char name[_MAX_FNAME + 1];
61 
62     _splitpath (path, NULL, NULL, name, NULL);
63 
64     name[_MAX_FNAME] = '\0';
65 
66     return name;
67 }
68 
69 
70 #else
71 #include <dirent.h>
72 
73 static char *
basename_no_ext(char * path)74 basename_no_ext (char *path)
75 {
76     char *dot, *name;
77 
78     name = basename (path);
79 
80     dot = strrchr (name, '.');
81     if (dot)
82 	*dot = '\0';
83 
84     return name;
85 }
86 
87 #endif
88 
89 #if HAVE_UNISTD_H
90 #include <unistd.h>
91 #endif
92 
93 #include <signal.h>
94 
95 #if HAVE_FCFINI
96 #include <fontconfig/fontconfig.h>
97 #endif
98 
99 #define CAIRO_PERF_ITERATIONS_DEFAULT	15
100 #define CAIRO_PERF_LOW_STD_DEV		0.05
101 #define CAIRO_PERF_MIN_STD_DEV_COUNT	3
102 #define CAIRO_PERF_STABLE_STD_DEV_COUNT 3
103 
104 struct trace {
105     const cairo_boilerplate_target_t *target;
106     void            *closure;
107     cairo_surface_t *surface;
108     cairo_bool_t observe;
109     int tile_size;
110 };
111 
112 cairo_bool_t
cairo_perf_can_run(cairo_perf_t * perf,const char * name,cairo_bool_t * is_explicit)113 cairo_perf_can_run (cairo_perf_t *perf,
114 		    const char	 *name,
115 		    cairo_bool_t *is_explicit)
116 {
117     unsigned int i;
118     char *copy, *dot;
119     cairo_bool_t ret;
120 
121     if (is_explicit)
122 	*is_explicit = FALSE;
123 
124     if (perf->exact_names) {
125 	if (is_explicit)
126 	    *is_explicit = TRUE;
127 	return TRUE;
128     }
129 
130     if (perf->num_names == 0 && perf->num_exclude_names == 0)
131 	return TRUE;
132 
133     copy = xstrdup (name);
134     dot = strrchr (copy, '.');
135     if (dot != NULL)
136 	*dot = '\0';
137 
138     if (perf->num_names) {
139 	ret = TRUE;
140 	for (i = 0; i < perf->num_names; i++)
141 	    if (strstr (copy, perf->names[i])) {
142 		if (is_explicit)
143 		    *is_explicit = strcmp (copy, perf->names[i]) == 0;
144 		goto check_exclude;
145 	    }
146 
147 	ret = FALSE;
148 	goto done;
149     }
150 
151 check_exclude:
152     if (perf->num_exclude_names) {
153 	ret = FALSE;
154 	for (i = 0; i < perf->num_exclude_names; i++)
155 	    if (strstr (copy, perf->exclude_names[i])) {
156 		if (is_explicit)
157 		    *is_explicit = strcmp (copy, perf->exclude_names[i]) == 0;
158 		goto done;
159 	    }
160 
161 	ret = TRUE;
162 	goto done;
163     }
164 
165 done:
166     free (copy);
167 
168     return ret;
169 }
170 
171 static void
fill_surface(cairo_surface_t * surface)172 fill_surface (cairo_surface_t *surface)
173 {
174     cairo_t *cr = cairo_create (surface);
175     /* This needs to be an operation that the backends can't optimise away */
176     cairo_set_source_rgba (cr, 0.5, 0.5, 0.5, 0.5);
177     cairo_set_operator (cr, CAIRO_OPERATOR_OVER);
178     cairo_paint (cr);
179     cairo_destroy (cr);
180 }
181 
182 struct scache {
183     cairo_hash_entry_t entry;
184     cairo_content_t content;
185     int width, height;
186     cairo_surface_t *surface;
187 };
188 
189 static cairo_hash_table_t *surface_cache;
190 static cairo_surface_t *surface_holdovers[16];
191 
192 static cairo_bool_t
scache_equal(const void * A,const void * B)193 scache_equal (const void *A,
194 	      const void *B)
195 {
196     const struct scache *a = A, *b = B;
197     return a->entry.hash == b->entry.hash;
198 }
199 
200 static void
scache_mark_active(cairo_surface_t * surface)201 scache_mark_active (cairo_surface_t *surface)
202 {
203     cairo_surface_t *t0, *t1;
204     unsigned n;
205 
206     if (surface_cache == NULL)
207 	return;
208 
209     t0 = cairo_surface_reference (surface);
210     for (n = 0; n < ARRAY_LENGTH (surface_holdovers); n++) {
211 	if (surface_holdovers[n] == surface) {
212 	    surface_holdovers[n] = t0;
213 	    t0 = surface;
214 	    break;
215 	}
216 
217 	t1 = surface_holdovers[n];
218 	surface_holdovers[n] = t0;
219 	t0 = t1;
220     }
221     cairo_surface_destroy (t0);
222 }
223 
224 static void
scache_clear(void)225 scache_clear (void)
226 {
227     unsigned n;
228 
229     if (surface_cache == NULL)
230 	return;
231 
232     for (n = 0; n < ARRAY_LENGTH (surface_holdovers); n++) {
233 	cairo_surface_destroy (surface_holdovers[n]);
234 	surface_holdovers[n] = NULL;
235     }
236 }
237 
238 static void
scache_remove(void * closure)239 scache_remove (void *closure)
240 {
241     _cairo_hash_table_remove (surface_cache, closure);
242     free (closure);
243 }
244 
245 static cairo_surface_t *
_similar_surface_create(void * closure,cairo_content_t content,double width,double height,long uid)246 _similar_surface_create (void		 *closure,
247 			 cairo_content_t  content,
248 			 double		  width,
249 			 double		  height,
250 			 long		  uid)
251 {
252     struct trace *args = closure;
253     cairo_surface_t *surface;
254     struct scache skey, *s;
255 
256     if (args->observe)
257 	    return cairo_surface_create_similar (args->surface,
258 						 content, width, height);
259 
260     if (uid == 0 || surface_cache == NULL)
261 	return args->target->create_similar (args->surface, content, width, height);
262 
263     skey.entry.hash = uid;
264     s = _cairo_hash_table_lookup (surface_cache, &skey.entry);
265     if (s != NULL) {
266 	if (s->content == content &&
267 	    s->width   == width   &&
268 	    s->height  == height)
269 	{
270 	    return cairo_surface_reference (s->surface);
271 	}
272 
273 	/* The surface has been resized, allow the original entry to expire
274 	 * as it becomes inactive.
275 	 */
276     }
277 
278     surface = args->target->create_similar (args->surface, content, width, height);
279     s = malloc (sizeof (struct scache));
280     if (s == NULL)
281 	return surface;
282 
283     s->entry.hash = uid;
284     s->content = content;
285     s->width = width;
286     s->height = height;
287     s->surface = surface;
288     if (_cairo_hash_table_insert (surface_cache, &s->entry)) {
289 	free (s);
290     } else if (cairo_surface_set_user_data
291 	       (surface,
292 		(const cairo_user_data_key_t *) &surface_cache,
293 		s, scache_remove))
294     {
295 	scache_remove (s);
296     }
297 
298     return surface;
299 }
300 
301 static cairo_surface_t *
_source_image_create(void * closure,cairo_format_t format,int width,int height,long uid)302 _source_image_create (void		*closure,
303 		      cairo_format_t	 format,
304 		      int		 width,
305 		      int		 height,
306 		      long		 uid)
307 {
308     struct trace *args = closure;
309 
310     return cairo_surface_create_similar_image (args->surface,
311 					       format, width, height);
312 }
313 
314 static cairo_t *
_context_create(void * closure,cairo_surface_t * surface)315 _context_create (void		 *closure,
316 		 cairo_surface_t *surface)
317 {
318     scache_mark_active (surface);
319     return cairo_create (surface);
320 }
321 
322 static int user_interrupt;
323 
324 static void
interrupt(int sig)325 interrupt (int sig)
326 {
327     if (user_interrupt) {
328 	signal (sig, SIG_DFL);
329 	raise (sig);
330     }
331 
332     user_interrupt = 1;
333 }
334 
335 static void
describe(cairo_perf_t * perf,void * closure)336 describe (cairo_perf_t *perf,
337           void *closure)
338 {
339     char *description = NULL;
340 
341     if (perf->has_described_backend)
342 	    return;
343     perf->has_described_backend = TRUE;
344 
345     if (perf->target->describe)
346         description = perf->target->describe (closure);
347 
348     if (description == NULL)
349         return;
350 
351     if (perf->raw) {
352         printf ("[ # ] %s: %s\n", perf->target->name, description);
353     }
354 
355     if (perf->summary) {
356         fprintf (perf->summary,
357                  "[ # ] %8s: %s\n",
358                  perf->target->name,
359                  description);
360     }
361 
362     free (description);
363 }
364 
365 static void
usage(const char * argv0)366 usage (const char *argv0)
367 {
368     fprintf (stderr,
369 "Usage: %s [-clrsv] [-i iterations] [-t tile-size] [-x exclude-file] [test-names ... | traces ...]\n"
370 "\n"
371 "Run the cairo performance test suite over the given tests (all by default)\n"
372 "The command-line arguments are interpreted as follows:\n"
373 "\n"
374 "  -c	use surface cache; keep a cache of surfaces to be reused\n"
375 "  -i	iterations; specify the number of iterations per test case\n"
376 "  -l	list only; just list selected test case names without executing\n"
377 "  -r	raw; display each time measurement instead of summary statistics\n"
378 "  -s	sync; only sum the elapsed time of the individual operations\n"
379 "  -t	tile size; draw to tiled surfaces\n"
380 "  -v	verbose; in raw mode also show the summaries\n"
381 "  -x	exclude; specify a file to read a list of traces to exclude\n"
382 "\n"
383 "If test names are given they are used as sub-string matches so a command\n"
384 "such as \"%s firefox\" can be used to run all firefox traces.\n"
385 "Alternatively, you can specify a list of filenames to execute.\n",
386 	     argv0, argv0);
387 }
388 
389 static cairo_bool_t
read_excludes(cairo_perf_t * perf,const char * filename)390 read_excludes (cairo_perf_t *perf,
391 	       const char   *filename)
392 {
393     FILE *file;
394     char *line = NULL;
395     size_t line_size = 0;
396     char *s, *t;
397 
398     file = fopen (filename, "r");
399     if (file == NULL)
400 	return FALSE;
401 
402     while (getline (&line, &line_size, file) != -1) {
403 	/* terminate the line at a comment marker '#' */
404 	s = strchr (line, '#');
405 	if (s)
406 	    *s = '\0';
407 
408 	/* whitespace delimits */
409 	s = line;
410 	while (*s != '\0' && isspace (*s))
411 	    s++;
412 
413 	t = s;
414 	while (*t != '\0' && ! isspace (*t))
415 	    t++;
416 
417 	if (s != t) {
418 	    int i = perf->num_exclude_names;
419 	    perf->exclude_names = xrealloc (perf->exclude_names,
420 					    sizeof (char *) * (i+1));
421 	    perf->exclude_names[i] = strndup (s, t-s);
422 	    perf->num_exclude_names++;
423 	}
424     }
425     free (line);
426 
427     fclose (file);
428 
429     return TRUE;
430 }
431 
432 static void
parse_options(cairo_perf_t * perf,int argc,char * argv[])433 parse_options (cairo_perf_t *perf,
434 	       int	     argc,
435 	       char	    *argv[])
436 {
437     int c;
438     const char *iters;
439     char *end;
440     int verbose = 0;
441     int use_surface_cache = 0;
442 
443     if ((iters = getenv ("CAIRO_PERF_ITERATIONS")) && *iters)
444 	perf->iterations = strtol (iters, NULL, 0);
445     else
446 	perf->iterations = CAIRO_PERF_ITERATIONS_DEFAULT;
447     perf->exact_iterations = 0;
448 
449     perf->raw = FALSE;
450     perf->observe = FALSE;
451     perf->list_only = FALSE;
452     perf->tile_size = 0;
453     perf->names = NULL;
454     perf->num_names = 0;
455     perf->summary = stdout;
456     perf->summary_continuous = FALSE;
457     perf->exclude_names = NULL;
458     perf->num_exclude_names = 0;
459 
460     while (1) {
461 	c = _cairo_getopt (argc, argv, "ci:lrst:vx:");
462 	if (c == -1)
463 	    break;
464 
465 	switch (c) {
466 	case 'c':
467 	    use_surface_cache = 1;
468 	    break;
469 	case 'i':
470 	    perf->exact_iterations = TRUE;
471 	    perf->iterations = strtoul (optarg, &end, 10);
472 	    if (*end != '\0') {
473 		fprintf (stderr, "Invalid argument for -i (not an integer): %s\n",
474 			 optarg);
475 		exit (1);
476 	    }
477 	    break;
478 	case 'l':
479 	    perf->list_only = TRUE;
480 	    break;
481 	case 'r':
482 	    perf->raw = TRUE;
483 	    perf->summary = NULL;
484 	    break;
485 	case 's':
486 	    perf->observe = TRUE;
487 	    break;
488 	case 't':
489 	    perf->tile_size = strtoul (optarg, &end, 10);
490 	    if (*end != '\0') {
491 		fprintf (stderr, "Invalid argument for -t (not an integer): %s\n",
492 			 optarg);
493 		exit (1);
494 	    }
495 	    break;
496 	case 'v':
497 	    verbose = 1;
498 	    break;
499 	case 'x':
500 	    if (! read_excludes (perf, optarg)) {
501 		fprintf (stderr, "Invalid argument for -x (not readable file): %s\n",
502 			 optarg);
503 		exit (1);
504 	    }
505 	    break;
506 	default:
507 	    fprintf (stderr, "Internal error: unhandled option: %c\n", c);
508 	    /* fall-through */
509 	case '?':
510 	    usage (argv[0]);
511 	    exit (1);
512 	}
513     }
514 
515     if (perf->observe && perf->tile_size) {
516 	fprintf (stderr, "Can't mix observer and tiling. Sorry.\n");
517 	exit (1);
518     }
519 
520     if (verbose && perf->summary == NULL)
521 	perf->summary = stderr;
522 #if HAVE_UNISTD_H
523     if (perf->summary && isatty (fileno (perf->summary)))
524 	perf->summary_continuous = TRUE;
525 #endif
526 
527     if (optind < argc) {
528 	perf->names = &argv[optind];
529 	perf->num_names = argc - optind;
530     }
531 
532     if (use_surface_cache)
533 	surface_cache = _cairo_hash_table_create (scache_equal);
534 }
535 
536 static void
cairo_perf_fini(cairo_perf_t * perf)537 cairo_perf_fini (cairo_perf_t *perf)
538 {
539     cairo_boilerplate_free_targets (perf->targets);
540     cairo_boilerplate_fini ();
541 
542     free (perf->times);
543     cairo_debug_reset_static_data ();
544 #if HAVE_FCFINI
545     FcFini ();
546 #endif
547 }
548 
549 static cairo_bool_t
have_trace_filenames(cairo_perf_t * perf)550 have_trace_filenames (cairo_perf_t *perf)
551 {
552     unsigned int i;
553 
554     if (perf->num_names == 0)
555 	return FALSE;
556 
557 #if HAVE_UNISTD_H
558     for (i = 0; i < perf->num_names; i++)
559 	if (access (perf->names[i], R_OK) == 0)
560 	    return TRUE;
561 #endif
562 
563     return FALSE;
564 }
565 
566 static void
_tiling_surface_finish(cairo_surface_t * observer,cairo_surface_t * target,void * closure)567 _tiling_surface_finish (cairo_surface_t *observer,
568 			cairo_surface_t *target,
569 			void *closure)
570 {
571     struct trace *args = closure;
572     cairo_surface_t *surface;
573     cairo_content_t content;
574     cairo_rectangle_t r;
575     int width, height;
576     int x, y, w, h;
577 
578     cairo_recording_surface_get_extents (target, &r);
579     w = r.width;
580     h = r.height;
581 
582     content = cairo_surface_get_content (target);
583 
584     for (y = 0; y < h; y += args->tile_size) {
585 	height = args->tile_size;
586 	if (y + height > h)
587 	    height = h - y;
588 
589 	for (x = 0; x < w; x += args->tile_size) {
590 	    cairo_t *cr;
591 
592 	    width = args->tile_size;
593 	    if (x + width > w)
594 		width = w - x;
595 
596 	    /* XXX to correctly observe the playback we would need
597 	     * to replay the target onto the observer directly.
598 	     */
599 	    surface = args->target->create_similar (args->surface,
600 						    content, width, height);
601 
602 	    cr = cairo_create (surface);
603 	    cairo_set_operator (cr, CAIRO_OPERATOR_SOURCE);
604 	    cairo_set_source_surface (cr, target, -x, -y);
605 	    cairo_paint (cr);
606 	    cairo_destroy (cr);
607 
608 	    cairo_surface_destroy (surface);
609 	}
610     }
611 }
612 
613 static cairo_surface_t *
_tiling_surface_create(void * closure,cairo_content_t content,double width,double height,long uid)614 _tiling_surface_create (void		 *closure,
615 			cairo_content_t  content,
616 			double		  width,
617 			double		  height,
618 			long		  uid)
619 {
620     cairo_rectangle_t r;
621     cairo_surface_t *surface, *observer;
622 
623     r.x = r.y = 0;
624     r.width = width;
625     r.height = height;
626 
627     surface = cairo_recording_surface_create (content, &r);
628     observer = cairo_surface_create_observer (surface,
629 					      CAIRO_SURFACE_OBSERVER_NORMAL);
630     cairo_surface_destroy (surface);
631 
632     cairo_surface_observer_add_finish_callback (observer,
633 						_tiling_surface_finish,
634 						closure);
635 
636     return observer;
637 }
638 
639 static void
cairo_perf_trace(cairo_perf_t * perf,const cairo_boilerplate_target_t * target,const char * trace)640 cairo_perf_trace (cairo_perf_t			   *perf,
641 		  const cairo_boilerplate_target_t *target,
642 		  const char			   *trace)
643 {
644     static cairo_bool_t first_run = TRUE;
645     unsigned int i;
646     cairo_time_t *times, *paint, *mask, *fill, *stroke, *glyphs;
647     cairo_stats_t stats = {0.0, 0.0};
648     struct trace args = { target };
649     int low_std_dev_count;
650     char *trace_cpy, *name;
651     const cairo_script_interpreter_hooks_t hooks = {
652 	&args,
653 	perf->tile_size ? _tiling_surface_create : _similar_surface_create,
654 	NULL, /* surface_destroy */
655 	_context_create,
656 	NULL, /* context_destroy */
657 	NULL, /* show_page */
658 	NULL, /* copy_page */
659 	_source_image_create,
660     };
661 
662     args.tile_size = perf->tile_size;
663     args.observe = perf->observe;
664 
665     trace_cpy = xstrdup (trace);
666     name = basename_no_ext (trace_cpy);
667 
668     if (perf->list_only) {
669 	printf ("%s\n", name);
670 	free (trace_cpy);
671 	return;
672     }
673 
674     if (first_run) {
675 	if (perf->raw) {
676 	    printf ("[ # ] %s.%-s %s %s %s ...\n",
677 		    "backend", "content", "test-size", "ticks-per-ms", "time(ticks)");
678 	}
679 
680 	if (perf->summary) {
681 	    if (perf->observe) {
682 		fprintf (perf->summary,
683 			 "[ # ] %8s %28s  %9s %9s %9s %9s %9s %9s %5s\n",
684 			 "backend", "test",
685 			 "total(s)", "paint(s)", "mask(s)", "fill(s)", "stroke(s)", "glyphs(s)",
686 			 "count");
687 	    } else {
688 		fprintf (perf->summary,
689 			 "[ # ] %8s %28s %8s %5s %5s %s\n",
690 			 "backend", "test", "min(s)", "median(s)",
691 			 "stddev.", "count");
692 	    }
693 	}
694 	first_run = FALSE;
695     }
696 
697     times = perf->times;
698     paint = times + perf->iterations;
699     mask = paint + perf->iterations;
700     stroke = mask + perf->iterations;
701     fill = stroke + perf->iterations;
702     glyphs = fill + perf->iterations;
703 
704     low_std_dev_count = 0;
705     for (i = 0; i < perf->iterations && ! user_interrupt; i++) {
706 	cairo_script_interpreter_t *csi;
707 	cairo_status_t status;
708 	unsigned int line_no;
709 
710 	args.surface = target->create_surface (NULL,
711 					       CAIRO_CONTENT_COLOR_ALPHA,
712 					       1, 1,
713 					       1, 1,
714 					       CAIRO_BOILERPLATE_MODE_PERF,
715 					       &args.closure);
716 	fill_surface(args.surface); /* remove any clear flags */
717 
718 	if (perf->observe) {
719 	    cairo_surface_t *obs;
720 	    obs = cairo_surface_create_observer (args.surface,
721 						 CAIRO_SURFACE_OBSERVER_NORMAL);
722 	    cairo_surface_destroy (args.surface);
723 	    args.surface = obs;
724 	}
725 	if (cairo_surface_status (args.surface)) {
726 	    fprintf (stderr,
727 		     "Error: Failed to create target surface: %s\n",
728 		     target->name);
729 	    return;
730 	}
731 
732 	cairo_perf_timer_set_synchronize (target->synchronize, args.closure);
733 
734 	if (i == 0) {
735 	    describe (perf, args.closure);
736 	    if (perf->summary) {
737 		fprintf (perf->summary,
738 			 "[%3d] %8s %28s ",
739 			 perf->test_number,
740 			 perf->target->name,
741 			 name);
742 		fflush (perf->summary);
743 	    }
744 	}
745 
746 	csi = cairo_script_interpreter_create ();
747 	cairo_script_interpreter_install_hooks (csi, &hooks);
748 
749 	if (! perf->observe) {
750 	    cairo_perf_yield ();
751 	    cairo_perf_timer_start ();
752 	}
753 
754 	cairo_script_interpreter_run (csi, trace);
755 	line_no = cairo_script_interpreter_get_line_number (csi);
756 
757 	/* Finish before querying timings in case we are using an intermediate
758 	 * target and so need to destroy all surfaces before rendering
759 	 * commences.
760 	 */
761 	cairo_script_interpreter_finish (csi);
762 
763 	if (perf->observe) {
764 	    cairo_device_t *observer = cairo_surface_get_device (args.surface);
765 	    times[i] = _cairo_time_from_s (1.e-9 * cairo_device_observer_elapsed (observer));
766 	    paint[i] = _cairo_time_from_s (1.e-9 * cairo_device_observer_paint_elapsed (observer));
767 	    mask[i] = _cairo_time_from_s (1.e-9 * cairo_device_observer_mask_elapsed (observer));
768 	    stroke[i] = _cairo_time_from_s (1.e-9 * cairo_device_observer_stroke_elapsed (observer));
769 	    fill[i] = _cairo_time_from_s (1.e-9 * cairo_device_observer_fill_elapsed (observer));
770 	    glyphs[i] = _cairo_time_from_s (1.e-9 * cairo_device_observer_glyphs_elapsed (observer));
771 	} else {
772 	    fill_surface (args.surface); /* queue a write to the sync'ed surface */
773 	    cairo_perf_timer_stop ();
774 	    times[i] = cairo_perf_timer_elapsed ();
775 	}
776 
777 	scache_clear ();
778 
779 	cairo_surface_destroy (args.surface);
780 
781 	if (target->cleanup)
782 	    target->cleanup (args.closure);
783 
784 	status = cairo_script_interpreter_destroy (csi);
785 	if (status) {
786 	    if (perf->summary) {
787 		fprintf (perf->summary, "Error during replay, line %d: %s\n",
788 			 line_no,
789 			 cairo_status_to_string (status));
790 	    }
791 	    goto out;
792 	}
793 
794 	if (perf->raw) {
795 	    if (i == 0)
796 		printf ("[*] %s.%s %s.%d %g",
797 			perf->target->name,
798 			"rgba",
799 			name,
800 			0,
801 			_cairo_time_to_double (_cairo_time_from_s (1)) / 1000.);
802 	    printf (" %lld", (long long) times[i]);
803 	    fflush (stdout);
804 	} else if (! perf->exact_iterations) {
805 	    if (i > CAIRO_PERF_MIN_STD_DEV_COUNT) {
806 		_cairo_stats_compute (&stats, times, i+1);
807 
808 		if (stats.std_dev <= CAIRO_PERF_LOW_STD_DEV) {
809 		    if (++low_std_dev_count >= CAIRO_PERF_STABLE_STD_DEV_COUNT)
810 			break;
811 		} else {
812 		    low_std_dev_count = 0;
813 		}
814 	    }
815 	}
816 
817 	if (perf->summary && perf->summary_continuous) {
818 	    _cairo_stats_compute (&stats, times, i+1);
819 
820 	    fprintf (perf->summary,
821 		     "\r[%3d] %8s %28s ",
822 		     perf->test_number,
823 		     perf->target->name,
824 		     name);
825 	    if (perf->observe) {
826 		fprintf (perf->summary,
827 			 " %#9.3f", _cairo_time_to_s (stats.median_ticks));
828 
829 		_cairo_stats_compute (&stats, paint, i+1);
830 		fprintf (perf->summary,
831 			 " %#9.3f", _cairo_time_to_s (stats.median_ticks));
832 
833 		_cairo_stats_compute (&stats, mask, i+1);
834 		fprintf (perf->summary,
835 			 " %#9.3f", _cairo_time_to_s (stats.median_ticks));
836 
837 		_cairo_stats_compute (&stats, fill, i+1);
838 		fprintf (perf->summary,
839 			 " %#9.3f", _cairo_time_to_s (stats.median_ticks));
840 
841 		_cairo_stats_compute (&stats, stroke, i+1);
842 		fprintf (perf->summary,
843 			 " %#9.3f", _cairo_time_to_s (stats.median_ticks));
844 
845 		_cairo_stats_compute (&stats, glyphs, i+1);
846 		fprintf (perf->summary,
847 			 " %#9.3f", _cairo_time_to_s (stats.median_ticks));
848 
849 		fprintf (perf->summary,
850 			 " %5d", i+1);
851 	    } else {
852 		fprintf (perf->summary,
853 			 "%#8.3f %#8.3f %#6.2f%% %4d/%d",
854 			 _cairo_time_to_s (stats.min_ticks),
855 			 _cairo_time_to_s (stats.median_ticks),
856 			 stats.std_dev * 100.0,
857 			 stats.iterations, i+1);
858 	    }
859 	    fflush (perf->summary);
860 	}
861     }
862     user_interrupt = 0;
863 
864     if (perf->summary) {
865 	_cairo_stats_compute (&stats, times, i);
866 	if (perf->summary_continuous) {
867 	    fprintf (perf->summary,
868 		     "\r[%3d] %8s %28s ",
869 		     perf->test_number,
870 		     perf->target->name,
871 		     name);
872 	}
873 	if (perf->observe) {
874 	    fprintf (perf->summary,
875 		     " %#9.3f", _cairo_time_to_s (stats.median_ticks));
876 
877 	    _cairo_stats_compute (&stats, paint, i);
878 	    fprintf (perf->summary,
879 		     " %#9.3f", _cairo_time_to_s (stats.median_ticks));
880 
881 	    _cairo_stats_compute (&stats, mask, i);
882 	    fprintf (perf->summary,
883 		     " %#9.3f", _cairo_time_to_s (stats.median_ticks));
884 
885 	    _cairo_stats_compute (&stats, fill, i);
886 	    fprintf (perf->summary,
887 		     " %#9.3f", _cairo_time_to_s (stats.median_ticks));
888 
889 	    _cairo_stats_compute (&stats, stroke, i);
890 	    fprintf (perf->summary,
891 		     " %#9.3f", _cairo_time_to_s (stats.median_ticks));
892 
893 	    _cairo_stats_compute (&stats, glyphs, i);
894 	    fprintf (perf->summary,
895 		     " %#9.3f", _cairo_time_to_s (stats.median_ticks));
896 
897 	    fprintf (perf->summary,
898 		     " %5d\n", i);
899 	} else {
900 	    fprintf (perf->summary,
901 		     "%#8.3f %#8.3f %#6.2f%% %4d/%d\n",
902 		     _cairo_time_to_s (stats.min_ticks),
903 		     _cairo_time_to_s (stats.median_ticks),
904 		     stats.std_dev * 100.0,
905 		     stats.iterations, i);
906 	}
907 	fflush (perf->summary);
908     }
909 
910 out:
911     if (perf->raw) {
912 	printf ("\n");
913 	fflush (stdout);
914     }
915 
916     perf->test_number++;
917     free (trace_cpy);
918 }
919 
920 static void
warn_no_traces(const char * message,const char * trace_dir)921 warn_no_traces (const char *message,
922 		const char *trace_dir)
923 {
924     fprintf (stderr,
925 "Error: %s '%s'.\n"
926 "Have you cloned the cairo-traces repository and uncompressed the traces?\n"
927 "  git clone git://anongit.freedesktop.org/cairo-traces\n"
928 "  cd cairo-traces && make\n"
929 "Or set the env.var CAIRO_TRACE_DIR to point to your traces?\n",
930 	    message, trace_dir);
931 }
932 
933 static int
cairo_perf_trace_dir(cairo_perf_t * perf,const cairo_boilerplate_target_t * target,const char * dirname)934 cairo_perf_trace_dir (cairo_perf_t		       *perf,
935 		      const cairo_boilerplate_target_t *target,
936 		      const char		       *dirname)
937 {
938     DIR *dir;
939     struct dirent *de;
940     int num_traces = 0;
941     cairo_bool_t force;
942     cairo_bool_t is_explicit;
943 
944     dir = opendir (dirname);
945     if (dir == NULL)
946 	return 0;
947 
948     force = FALSE;
949     if (cairo_perf_can_run (perf, dirname, &is_explicit))
950 	force = is_explicit;
951 
952     while ((de = readdir (dir)) != NULL) {
953 	char *trace;
954 	struct stat st;
955 
956 	if (de->d_name[0] == '.')
957 	    continue;
958 
959 	xasprintf (&trace, "%s/%s", dirname, de->d_name);
960 	if (stat (trace, &st) != 0)
961 	    goto next;
962 
963 	if (S_ISDIR(st.st_mode)) {
964 	    num_traces += cairo_perf_trace_dir (perf, target, trace);
965 	} else {
966 	    const char *dot;
967 
968 	    dot = strrchr (de->d_name, '.');
969 	    if (dot == NULL)
970 		goto next;
971 	    if (strcmp (dot, ".trace"))
972 		goto next;
973 
974 	    num_traces++;
975 	    if (!force && ! cairo_perf_can_run (perf, de->d_name, NULL))
976 		goto next;
977 
978 	    cairo_perf_trace (perf, target, trace);
979 	}
980 next:
981 	free (trace);
982 
983     }
984     closedir (dir);
985 
986     return num_traces;
987 }
988 
989 int
main(int argc,char * argv[])990 main (int   argc,
991       char *argv[])
992 {
993     cairo_perf_t perf;
994     const char *trace_dir = "cairo-traces:/usr/src/cairo-traces:/usr/share/cairo-traces";
995     unsigned int n;
996     int i;
997 
998     parse_options (&perf, argc, argv);
999 
1000     signal (SIGINT, interrupt);
1001 
1002     if (getenv ("CAIRO_TRACE_DIR") != NULL)
1003 	trace_dir = getenv ("CAIRO_TRACE_DIR");
1004 
1005     perf.targets = cairo_boilerplate_get_targets (&perf.num_targets, NULL);
1006     perf.times = xmalloc (6 * perf.iterations * sizeof (cairo_time_t));
1007 
1008     /* do we have a list of filenames? */
1009     perf.exact_names = have_trace_filenames (&perf);
1010 
1011     for (i = 0; i < perf.num_targets; i++) {
1012 	const cairo_boilerplate_target_t *target = perf.targets[i];
1013 
1014 	if (! perf.list_only && ! target->is_measurable)
1015 	    continue;
1016 
1017 	perf.target = target;
1018 	perf.test_number = 0;
1019 	perf.has_described_backend = FALSE;
1020 
1021 	if (perf.exact_names) {
1022 	    for (n = 0; n < perf.num_names; n++) {
1023 		struct stat st;
1024 
1025 		if (stat (perf.names[n], &st) == 0) {
1026 		    if (S_ISDIR (st.st_mode)) {
1027 			cairo_perf_trace_dir (&perf, target, perf.names[n]);
1028 		    } else
1029 			cairo_perf_trace (&perf, target, perf.names[n]);
1030 		}
1031 	    }
1032 	} else {
1033 	    int num_traces = 0;
1034 	    const char *dir;
1035 
1036 	    dir = trace_dir;
1037 	    do {
1038 		char buf[1024];
1039 		const char *end = strchr (dir, ':');
1040 		if (end != NULL) {
1041 		    memcpy (buf, dir, end-dir);
1042 		    buf[end-dir] = '\0';
1043 		    end++;
1044 
1045 		    dir = buf;
1046 		}
1047 
1048 		num_traces += cairo_perf_trace_dir (&perf, target, dir);
1049 		dir = end;
1050 	    } while (dir != NULL);
1051 
1052 	    if (num_traces == 0) {
1053 		warn_no_traces ("Found no traces in", trace_dir);
1054 		return 1;
1055 	    }
1056 	}
1057 
1058 	if (perf.list_only)
1059 	    break;
1060     }
1061 
1062     cairo_perf_fini (&perf);
1063 
1064     return 0;
1065 }
1066