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
 * Copyright © 2011 Intel Corporation
7
 *
8
 * Permission to use, copy, modify, distribute, and sell this software
9
 * and its documentation for any purpose is hereby granted without
10
 * fee, provided that the above copyright notice appear in all copies
11
 * and that both that copyright notice and this permission notice
12
 * appear in supporting documentation, and that the name of
13
 * the authors not be used in advertising or publicity pertaining to
14
 * distribution of the software without specific, written prior
15
 * permission. The authors make no representations about the
16
 * suitability of this software for any purpose.  It is provided "as
17
 * is" without express or implied warranty.
18
 *
19
 * THE AUTHORS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS
20
 * SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
21
 * FITNESS, IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY SPECIAL,
22
 * INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER
23
 * RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
24
 * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR
25
 * IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
26
 *
27
 * Authors: Vladimir Vukicevic <vladimir@pobox.com>
28
 *	    Carl Worth <cworth@cworth.org>
29
 *	    Chris Wilson <chris@chris-wilson.co.uk>
30
 */
31

            
32
#include "config.h"
33

            
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-missing.h"
40

            
41
/* rudely reuse bits of the library... */
42
#include "../src/cairo-error-private.h"
43

            
44
/* For basename */
45
#ifdef HAVE_LIBGEN_H
46
#include <libgen.h>
47
#endif
48
#include <ctype.h> /* isspace() */
49

            
50
#include <sys/types.h>
51
#include <sys/stat.h>
52

            
53
#ifdef _MSC_VER
54
#include "dirent-win32.h"
55

            
56
static char *
57
basename_no_ext (char *path)
58
{
59
    static char name[_MAX_FNAME + 1];
60

            
61
    _splitpath (path, NULL, NULL, name, NULL);
62

            
63
    name[_MAX_FNAME] = '\0';
64

            
65
    return name;
66
}
67

            
68

            
69
#else
70
#include <dirent.h>
71

            
72
static char *
73
basename_no_ext (char *path)
74
{
75
    char *dot, *name;
76

            
77
    name = basename (path);
78

            
79
    dot = strchr (name, '.');
80
    if (dot)
81
	*dot = '\0';
82

            
83
    return name;
84
}
85

            
86
#endif
87

            
88
#if HAVE_UNISTD_H
89
#include <unistd.h>
90
#endif
91

            
92
#include <signal.h>
93

            
94
#if HAVE_FCFINI
95
#include <fontconfig/fontconfig.h>
96
#endif
97

            
98
struct trace {
99
    const cairo_boilerplate_target_t *target;
100
    void            *closure;
101
    cairo_surface_t *surface;
102
};
103

            
104
cairo_bool_t
105
cairo_perf_can_run (cairo_perf_t *perf,
106
		    const char	 *name,
107
		    cairo_bool_t *is_explicit)
108
{
109
    unsigned int i;
110
    char *copy, *dot;
111
    cairo_bool_t ret;
112

            
113
    if (is_explicit)
114
	*is_explicit = FALSE;
115

            
116
    if (perf->exact_names) {
117
	if (is_explicit)
118
	    *is_explicit = TRUE;
119
	return TRUE;
120
    }
121

            
122
    if (perf->num_names == 0 && perf->num_exclude_names == 0)
123
	return TRUE;
124

            
125
    copy = xstrdup (name);
126
    dot = strchr (copy, '.');
127
    if (dot != NULL)
128
	*dot = '\0';
129

            
130
    if (perf->num_names) {
131
	ret = TRUE;
132
	for (i = 0; i < perf->num_names; i++)
133
	    if (strstr (copy, perf->names[i])) {
134
		if (is_explicit)
135
		    *is_explicit = strcmp (copy, perf->names[i]) == 0;
136
		goto check_exclude;
137
	    }
138

            
139
	ret = FALSE;
140
	goto done;
141
    }
142

            
143
check_exclude:
144
    if (perf->num_exclude_names) {
145
	ret = FALSE;
146
	for (i = 0; i < perf->num_exclude_names; i++)
147
	    if (strstr (copy, perf->exclude_names[i])) {
148
		if (is_explicit)
149
		    *is_explicit = strcmp (copy, perf->exclude_names[i]) == 0;
150
		goto done;
151
	    }
152

            
153
	ret = TRUE;
154
	goto done;
155
    }
156

            
157
done:
158
    free (copy);
159

            
160
    return ret;
161
}
162

            
163
static cairo_surface_t *
164
surface_create (void		 *closure,
165
		cairo_content_t  content,
166
		double		  width,
167
		double		  height,
168
		long		  uid)
169
{
170
    struct trace *args = closure;
171
    return cairo_surface_create_similar (args->surface, content, width, height);
172
}
173

            
174
static int user_interrupt;
175

            
176
static void
177
interrupt (int sig)
178
{
179
    if (user_interrupt) {
180
	signal (sig, SIG_DFL);
181
	raise (sig);
182
    }
183

            
184
    user_interrupt = 1;
185
}
186

            
187
static void
188
describe (cairo_perf_t *perf,
189
          void *closure)
190
{
191
    char *description = NULL;
192

            
193
    if (perf->has_described_backend)
194
	    return;
195
    perf->has_described_backend = TRUE;
196

            
197
    if (perf->target->describe)
198
        description = perf->target->describe (closure);
199

            
200
    if (description == NULL)
201
        return;
202

            
203
    free (description);
204
}
205

            
206
static void
207
execute (cairo_perf_t	 *perf,
208
	 struct trace	 *args,
209
	 const char	 *trace)
210
{
211
    char *trace_cpy, *name;
212
    const cairo_script_interpreter_hooks_t hooks = {
213
	.closure = args,
214
	.surface_create = surface_create,
215
    };
216

            
217
    trace_cpy = xstrdup (trace);
218
    name = basename_no_ext (trace_cpy);
219

            
220
    if (perf->list_only) {
221
	printf ("%s\n", name);
222
	free (trace_cpy);
223
	return;
224
    }
225

            
226
    describe (perf, args->closure);
227

            
228
    {
229
	cairo_script_interpreter_t *csi;
230
	cairo_status_t status;
231
	unsigned int line_no;
232

            
233
	csi = cairo_script_interpreter_create ();
234
	cairo_script_interpreter_install_hooks (csi, &hooks);
235

            
236
	cairo_script_interpreter_run (csi, trace);
237

            
238
	cairo_script_interpreter_finish (csi);
239

            
240
	line_no = cairo_script_interpreter_get_line_number (csi);
241
	status = cairo_script_interpreter_destroy (csi);
242
	if (status) {
243
	    /* XXXX cairo_status_to_string is just wrong! */
244
	    fprintf (stderr, "Error during replay, line %d: %s\n",
245
		     line_no, cairo_status_to_string (status));
246
	}
247
    }
248
    user_interrupt = 0;
249

            
250
    free (trace_cpy);
251
}
252

            
253
static void
254
usage (const char *argv0)
255
{
256
    fprintf (stderr,
257
"Usage: %s [-l] [-i iterations] [-x exclude-file] [test-names ... | traces ...]\n"
258
"\n"
259
"Run the cairo trace analysis suite over the given tests (all by default)\n"
260
"The command-line arguments are interpreted as follows:\n"
261
"\n"
262
"  -i	iterations; specify the number of iterations per test case\n"
263
"  -l	list only; just list selected test case names without executing\n"
264
"  -x	exclude; specify a file to read a list of traces to exclude\n"
265
"\n"
266
"If test names are given they are used as sub-string matches so a command\n"
267
"such as \"%s firefox\" can be used to run all firefox traces.\n"
268
"Alternatively, you can specify a list of filenames to execute.\n",
269
	     argv0, argv0);
270
}
271

            
272
static cairo_bool_t
273
read_excludes (cairo_perf_t *perf,
274
	       const char   *filename)
275
{
276
    FILE *file;
277
    char *line = NULL;
278
    size_t line_size = 0;
279
    char *s, *t;
280

            
281
    file = fopen (filename, "r");
282
    if (file == NULL)
283
	return FALSE;
284

            
285
    while (getline (&line, &line_size, file) != -1) {
286
	/* terminate the line at a comment marker '#' */
287
	s = strchr (line, '#');
288
	if (s)
289
	    *s = '\0';
290

            
291
	/* whitespace delimits */
292
	s = line;
293
	while (*s != '\0' && isspace ((unsigned char)*s))
294
	    s++;
295

            
296
	t = s;
297
	while (*t != '\0' && ! isspace ((unsigned char)*t))
298
	    t++;
299

            
300
	if (s != t) {
301
	    int i = perf->num_exclude_names;
302
	    perf->exclude_names = xrealloc (perf->exclude_names,
303
					    sizeof (char *) * (i+1));
304
	    perf->exclude_names[i] = strndup (s, t-s);
305
	    perf->num_exclude_names++;
306
	}
307
    }
308
    free (line);
309

            
310
    fclose (file);
311

            
312
    return TRUE;
313
}
314

            
315
static void
316
parse_options (cairo_perf_t *perf,
317
	       int	     argc,
318
	       char	    *argv[])
319
{
320
    char *end;
321
    int c;
322

            
323
    perf->list_only = FALSE;
324
    perf->names = NULL;
325
    perf->num_names = 0;
326
    perf->exclude_names = NULL;
327
    perf->num_exclude_names = 0;
328

            
329
    while (1) {
330
	c = _cairo_getopt (argc, argv, "i:lx:");
331
	if (c == -1)
332
	    break;
333

            
334
	switch (c) {
335
	case 'i':
336
	    perf->exact_iterations = TRUE;
337
	    perf->iterations = strtoul (optarg, &end, 10);
338
	    if (*end != '\0') {
339
		fprintf (stderr, "Invalid argument for -i (not an integer): %s\n",
340
			 optarg);
341
		exit (1);
342
	    }
343
	    break;
344
	case 'l':
345
	    perf->list_only = TRUE;
346
	    break;
347
	case 'x':
348
	    if (! read_excludes (perf, optarg)) {
349
		fprintf (stderr, "Invalid argument for -x (not readable file): %s\n",
350
			 optarg);
351
		exit (1);
352
	    }
353
	    break;
354
	default:
355
	    fprintf (stderr, "Internal error: unhandled option: %c\n", c);
356
	    /* fall-through */
357
	case '?':
358
	    usage (argv[0]);
359
	    exit (1);
360
	}
361
    }
362

            
363
    if (optind < argc) {
364
	perf->names = &argv[optind];
365
	perf->num_names = argc - optind;
366
    }
367
}
368

            
369
static void
370
cairo_perf_fini (cairo_perf_t *perf)
371
{
372
    cairo_boilerplate_free_targets (perf->targets);
373
    cairo_boilerplate_fini ();
374

            
375
    cairo_debug_reset_static_data ();
376
#if HAVE_FCFINI
377
    FcFini ();
378
#endif
379
}
380

            
381
static cairo_bool_t
382
have_trace_filenames (cairo_perf_t *perf)
383
{
384
    unsigned int i;
385

            
386
    if (perf->num_names == 0)
387
	return FALSE;
388

            
389
#if HAVE_UNISTD_H
390
    for (i = 0; i < perf->num_names; i++)
391
	if (access (perf->names[i], R_OK) == 0)
392
	    return TRUE;
393
#endif
394

            
395
    return FALSE;
396
}
397

            
398
static cairo_status_t
399
print (void *closure, const unsigned char *data, unsigned int length)
400
{
401
    fwrite (data, length, 1, closure);
402
    return CAIRO_STATUS_SUCCESS;
403
}
404

            
405
static void
406
cairo_perf_trace (cairo_perf_t			   *perf,
407
		  const cairo_boilerplate_target_t *target,
408
		  const char			   *trace)
409
{
410
    struct trace args;
411
    cairo_surface_t *real;
412

            
413
    args.target = target;
414
    real = target->create_surface (NULL,
415
				   CAIRO_CONTENT_COLOR_ALPHA,
416
				   1, 1,
417
				   1, 1,
418
				   CAIRO_BOILERPLATE_MODE_PERF,
419
				   &args.closure);
420
    args.surface =
421
	    cairo_surface_create_observer (real,
422
					   CAIRO_SURFACE_OBSERVER_RECORD_OPERATIONS);
423
    cairo_surface_destroy (real);
424
    if (cairo_surface_status (args.surface)) {
425
	fprintf (stderr,
426
		 "Error: Failed to create target surface: %s\n",
427
		 target->name);
428
	return;
429
    }
430

            
431
    printf ("Observing '%s'...", trace);
432
    fflush (stdout);
433

            
434
    execute (perf, &args, trace);
435

            
436
    printf ("\n");
437
    cairo_device_observer_print (cairo_surface_get_device (args.surface),
438
				 print, stdout);
439
    fflush (stdout);
440

            
441
    cairo_surface_destroy (args.surface);
442

            
443
    if (target->cleanup)
444
	target->cleanup (args.closure);
445
}
446

            
447
static void
448
warn_no_traces (const char *message,
449
		const char *trace_dir)
450
{
451
    fprintf (stderr,
452
"Error: %s '%s'.\n"
453
"Have you cloned the cairo-traces repository and uncompressed the traces?\n"
454
"  git clone git://anongit.freedesktop.org/cairo-traces\n"
455
"  cd cairo-traces && make\n"
456
"Or set the env.var CAIRO_TRACE_DIR to point to your traces?\n",
457
	    message, trace_dir);
458
}
459

            
460
static int
461
cairo_perf_trace_dir (cairo_perf_t		       *perf,
462
		      const cairo_boilerplate_target_t *target,
463
		      const char		       *dirname)
464
{
465
    DIR *dir;
466
    struct dirent *de;
467
    int num_traces = 0;
468
    cairo_bool_t force;
469
    cairo_bool_t is_explicit;
470

            
471
    dir = opendir (dirname);
472
    if (dir == NULL)
473
	return 0;
474

            
475
    force = FALSE;
476
    if (cairo_perf_can_run (perf, dirname, &is_explicit))
477
	force = is_explicit;
478

            
479
    while ((de = readdir (dir)) != NULL) {
480
	char *trace;
481
	struct stat st;
482

            
483
	if (de->d_name[0] == '.')
484
	    continue;
485

            
486
	xasprintf (&trace, "%s/%s", dirname, de->d_name);
487
	if (stat (trace, &st) != 0)
488
	    goto next;
489

            
490
	if (S_ISDIR(st.st_mode)) {
491
	    num_traces += cairo_perf_trace_dir (perf, target, trace);
492
	} else {
493
	    const char *dot;
494

            
495
	    dot = strrchr (de->d_name, '.');
496
	    if (dot == NULL)
497
		goto next;
498
	    if (strcmp (dot, ".trace"))
499
		goto next;
500

            
501
	    num_traces++;
502
	    if (!force && ! cairo_perf_can_run (perf, de->d_name, NULL))
503
		goto next;
504

            
505
	    cairo_perf_trace (perf, target, trace);
506
	}
507
next:
508
	free (trace);
509

            
510
    }
511
    closedir (dir);
512

            
513
    return num_traces;
514
}
515

            
516
int
517
main (int   argc,
518
      char *argv[])
519
{
520
    cairo_perf_t perf;
521
    const char *trace_dir = "cairo-traces:/usr/src/cairo-traces:/usr/share/cairo-traces";
522
    unsigned int n;
523
    int i;
524

            
525
    parse_options (&perf, argc, argv);
526

            
527
    signal (SIGINT, interrupt);
528

            
529
    if (getenv ("CAIRO_TRACE_DIR") != NULL)
530
	trace_dir = getenv ("CAIRO_TRACE_DIR");
531

            
532
    perf.targets = cairo_boilerplate_get_targets (&perf.num_targets, NULL);
533

            
534
    /* do we have a list of filenames? */
535
    perf.exact_names = have_trace_filenames (&perf);
536

            
537
    for (i = 0; i < perf.num_targets; i++) {
538
	const cairo_boilerplate_target_t *target = perf.targets[i];
539

            
540
	if (! perf.list_only && ! target->is_measurable)
541
	    continue;
542

            
543
	perf.target = target;
544
	perf.has_described_backend = FALSE;
545

            
546
	if (perf.exact_names) {
547
	    for (n = 0; n < perf.num_names; n++) {
548
		struct stat st;
549

            
550
		if (stat (perf.names[n], &st) == 0) {
551
		    if (S_ISDIR (st.st_mode)) {
552
			cairo_perf_trace_dir (&perf, target, perf.names[n]);
553
		    } else
554
			cairo_perf_trace (&perf, target, perf.names[n]);
555
		}
556
	    }
557
	} else {
558
	    int num_traces = 0;
559
	    const char *dir;
560

            
561
	    dir = trace_dir;
562
	    do {
563
		char buf[1024];
564
		const char *end = strchr (dir, ':');
565
		if (end != NULL) {
566
		    memcpy (buf, dir, end-dir);
567
		    buf[end-dir] = '\0';
568
		    end++;
569

            
570
		    dir = buf;
571
		}
572

            
573
		num_traces += cairo_perf_trace_dir (&perf, target, dir);
574
		dir = end;
575
	    } while (dir != NULL);
576

            
577
	    if (num_traces == 0) {
578
		warn_no_traces ("Found no traces in", trace_dir);
579
		return 1;
580
	    }
581
	}
582

            
583
	if (perf.list_only)
584
	    break;
585
    }
586

            
587
    cairo_perf_fini (&perf);
588

            
589
    return 0;
590
}