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
#include "config.h"
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 *
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 *
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
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
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
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
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
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
239
scache_remove (void *closure)
240
{
241
    _cairo_hash_table_remove (surface_cache, closure);
242
    free (closure);
243
}
244

            
245
static cairo_surface_t *
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 *
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 *
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
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
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
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
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 ((unsigned char)*s))
411
	    s++;
412

            
413
	t = s;
414
	while (*t != '\0' && ! isspace ((unsigned char)*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
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
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
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
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 *
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
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
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
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
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

            
    signal (SIGINT, interrupt);
    if (getenv ("CAIRO_TRACE_DIR") != NULL)
	trace_dir = getenv ("CAIRO_TRACE_DIR");
    perf.targets = cairo_boilerplate_get_targets (&perf.num_targets, NULL);
    perf.times = xmalloc (6 * perf.iterations * sizeof (cairo_time_t));
    /* do we have a list of filenames? */
    perf.exact_names = have_trace_filenames (&perf);
    for (i = 0; i < perf.num_targets; i++) {
	const cairo_boilerplate_target_t *target = perf.targets[i];
	if (! perf.list_only && ! target->is_measurable)
	    continue;
	perf.target = target;
	perf.test_number = 0;
	perf.has_described_backend = FALSE;
	if (perf.exact_names) {
	    for (n = 0; n < perf.num_names; n++) {
		struct stat st;
		if (stat (perf.names[n], &st) == 0) {
		    if (S_ISDIR (st.st_mode)) {
			cairo_perf_trace_dir (&perf, target, perf.names[n]);
		    } else
			cairo_perf_trace (&perf, target, perf.names[n]);
		}
	    }
	} else {
	    int num_traces = 0;
	    const char *dir;
	    dir = trace_dir;
	    do {
		char buf[1024];
		const char *end = strchr (dir, ':');
		if (end != NULL) {
		    memcpy (buf, dir, end-dir);
		    buf[end-dir] = '\0';
		    end++;
		    dir = buf;
		}
		num_traces += cairo_perf_trace_dir (&perf, target, dir);
		dir = end;
	    } while (dir != NULL);
	    if (num_traces == 0) {
		warn_no_traces ("Found no traces in", trace_dir);
		return 1;
	    }
	}
	if (perf.list_only)
	    break;
    }
    cairo_perf_fini (&perf);
    return 0;
}