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