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