Tizen 2.0 Release
[framework/graphics/cairo.git] / perf / cairo-analyse-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  * 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 #define _GNU_SOURCE 1   /* for sched_getaffinity() and getline() */
33
34 #include "../cairo-version.h" /* for the real version */
35
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-missing.h"
42
43 /* rudely reuse bits of the library... */
44 #include "../src/cairo-error-private.h"
45
46 /* For basename */
47 #ifdef HAVE_LIBGEN_H
48 #include <libgen.h>
49 #endif
50 #include <ctype.h> /* isspace() */
51
52 #include <sys/types.h>
53 #include <sys/stat.h>
54
55 #ifdef _MSC_VER
56 #include "dirent-win32.h"
57
58 static char *
59 basename_no_ext (char *path)
60 {
61     static char name[_MAX_FNAME + 1];
62
63     _splitpath (path, NULL, NULL, name, NULL);
64
65     name[_MAX_FNAME] = '\0';
66
67     return name;
68 }
69
70
71 #else
72 #include <dirent.h>
73
74 static char *
75 basename_no_ext (char *path)
76 {
77     char *dot, *name;
78
79     name = basename (path);
80
81     dot = strchr (name, '.');
82     if (dot)
83         *dot = '\0';
84
85     return name;
86 }
87
88 #endif
89
90 #if HAVE_UNISTD_H
91 #include <unistd.h>
92 #endif
93
94 #include <signal.h>
95
96 #if HAVE_FCFINI
97 #include <fontconfig/fontconfig.h>
98 #endif
99
100 struct trace {
101     const cairo_boilerplate_target_t *target;
102     void            *closure;
103     cairo_surface_t *surface;
104 };
105
106 cairo_bool_t
107 cairo_perf_can_run (cairo_perf_t *perf,
108                     const char   *name,
109                     cairo_bool_t *is_explicit)
110 {
111     unsigned int i;
112     char *copy, *dot;
113     cairo_bool_t ret;
114
115     if (is_explicit)
116         *is_explicit = FALSE;
117
118     if (perf->exact_names) {
119         if (is_explicit)
120             *is_explicit = TRUE;
121         return TRUE;
122     }
123
124     if (perf->num_names == 0 && perf->num_exclude_names == 0)
125         return TRUE;
126
127     copy = xstrdup (name);
128     dot = strchr (copy, '.');
129     if (dot != NULL)
130         *dot = '\0';
131
132     if (perf->num_names) {
133         ret = TRUE;
134         for (i = 0; i < perf->num_names; i++)
135             if (strstr (copy, perf->names[i])) {
136                 if (is_explicit)
137                     *is_explicit = strcmp (copy, perf->names[i]) == 0;
138                 goto check_exclude;
139             }
140
141         ret = FALSE;
142         goto done;
143     }
144
145 check_exclude:
146     if (perf->num_exclude_names) {
147         ret = FALSE;
148         for (i = 0; i < perf->num_exclude_names; i++)
149             if (strstr (copy, perf->exclude_names[i])) {
150                 if (is_explicit)
151                     *is_explicit = strcmp (copy, perf->exclude_names[i]) == 0;
152                 goto done;
153             }
154
155         ret = TRUE;
156         goto done;
157     }
158
159 done:
160     free (copy);
161
162     return ret;
163 }
164
165 static cairo_surface_t *
166 surface_create (void             *closure,
167                 cairo_content_t  content,
168                 double            width,
169                 double            height,
170                 long              uid)
171 {
172     struct trace *args = closure;
173     return cairo_surface_create_similar (args->surface, content, width, height);
174 }
175
176 static int user_interrupt;
177
178 static void
179 interrupt (int sig)
180 {
181     if (user_interrupt) {
182         signal (sig, SIG_DFL);
183         raise (sig);
184     }
185
186     user_interrupt = 1;
187 }
188
189 static void
190 describe (cairo_perf_t *perf,
191           void *closure)
192 {
193     char *description = NULL;
194
195     if (perf->has_described_backend)
196             return;
197     perf->has_described_backend = TRUE;
198
199     if (perf->target->describe)
200         description = perf->target->describe (closure);
201
202     if (description == NULL)
203         return;
204
205     free (description);
206 }
207
208 static void
209 execute (cairo_perf_t    *perf,
210          struct trace    *args,
211          const char      *trace)
212 {
213     char *trace_cpy, *name;
214     const cairo_script_interpreter_hooks_t hooks = {
215         .closure = args,
216         .surface_create = surface_create,
217     };
218
219     trace_cpy = xstrdup (trace);
220     name = basename_no_ext (trace_cpy);
221
222     if (perf->list_only) {
223         printf ("%s\n", name);
224         free (trace_cpy);
225         return;
226     }
227
228     describe (perf, args->closure);
229
230     {
231         cairo_script_interpreter_t *csi;
232         cairo_status_t status;
233         unsigned int line_no;
234
235         csi = cairo_script_interpreter_create ();
236         cairo_script_interpreter_install_hooks (csi, &hooks);
237
238         cairo_script_interpreter_run (csi, trace);
239
240         cairo_script_interpreter_finish (csi);
241
242         line_no = cairo_script_interpreter_get_line_number (csi);
243         status = cairo_script_interpreter_destroy (csi);
244         if (status) {
245             /* XXXX cairo_status_to_string is just wrong! */
246             fprintf (stderr, "Error during replay, line %d: %s\n",
247                      line_no, cairo_status_to_string (status));
248         }
249     }
250     user_interrupt = 0;
251
252     free (trace_cpy);
253 }
254
255 static void
256 usage (const char *argv0)
257 {
258     fprintf (stderr,
259 "Usage: %s [-l] [-i iterations] [-x exclude-file] [test-names ... | traces ...]\n"
260 "\n"
261 "Run the cairo trace analysis suite over the given tests (all by default)\n"
262 "The command-line arguments are interpreted as follows:\n"
263 "\n"
264 "  -i   iterations; specify the number of iterations per test case\n"
265 "  -l   list only; just list selected test case names without executing\n"
266 "  -x   exclude; specify a file to read a list of traces to exclude\n"
267 "\n"
268 "If test names are given they are used as sub-string matches so a command\n"
269 "such as \"%s firefox\" can be used to run all firefox traces.\n"
270 "Alternatively, you can specify a list of filenames to execute.\n",
271              argv0, argv0);
272 }
273
274 static cairo_bool_t
275 read_excludes (cairo_perf_t *perf,
276                const char   *filename)
277 {
278     FILE *file;
279     char *line = NULL;
280     size_t line_size = 0;
281     char *s, *t;
282
283     file = fopen (filename, "r");
284     if (file == NULL)
285         return FALSE;
286
287     while (getline (&line, &line_size, file) != -1) {
288         /* terminate the line at a comment marker '#' */
289         s = strchr (line, '#');
290         if (s)
291             *s = '\0';
292
293         /* whitespace delimits */
294         s = line;
295         while (*s != '\0' && isspace (*s))
296             s++;
297
298         t = s;
299         while (*t != '\0' && ! isspace (*t))
300             t++;
301
302         if (s != t) {
303             int i = perf->num_exclude_names;
304             perf->exclude_names = xrealloc (perf->exclude_names,
305                                             sizeof (char *) * (i+1));
306             perf->exclude_names[i] = strndup (s, t-s);
307             perf->num_exclude_names++;
308         }
309     }
310     free (line);
311
312     fclose (file);
313
314     return TRUE;
315 }
316
317 static void
318 parse_options (cairo_perf_t *perf,
319                int           argc,
320                char         *argv[])
321 {
322     char *end;
323     int c;
324
325     perf->list_only = FALSE;
326     perf->names = NULL;
327     perf->num_names = 0;
328     perf->exclude_names = NULL;
329     perf->num_exclude_names = 0;
330
331     while (1) {
332         c = _cairo_getopt (argc, argv, "i:lx:");
333         if (c == -1)
334             break;
335
336         switch (c) {
337         case 'i':
338             perf->exact_iterations = TRUE;
339             perf->iterations = strtoul (optarg, &end, 10);
340             if (*end != '\0') {
341                 fprintf (stderr, "Invalid argument for -i (not an integer): %s\n",
342                          optarg);
343                 exit (1);
344             }
345             break;
346         case 'l':
347             perf->list_only = TRUE;
348             break;
349         case 'x':
350             if (! read_excludes (perf, optarg)) {
351                 fprintf (stderr, "Invalid argument for -x (not readable file): %s\n",
352                          optarg);
353                 exit (1);
354             }
355             break;
356         default:
357             fprintf (stderr, "Internal error: unhandled option: %c\n", c);
358             /* fall-through */
359         case '?':
360             usage (argv[0]);
361             exit (1);
362         }
363     }
364
365     if (optind < argc) {
366         perf->names = &argv[optind];
367         perf->num_names = argc - optind;
368     }
369 }
370
371 static void
372 cairo_perf_fini (cairo_perf_t *perf)
373 {
374     cairo_boilerplate_free_targets (perf->targets);
375     cairo_boilerplate_fini ();
376
377     cairo_debug_reset_static_data ();
378 #if HAVE_FCFINI
379     FcFini ();
380 #endif
381 }
382
383 static cairo_bool_t
384 have_trace_filenames (cairo_perf_t *perf)
385 {
386     unsigned int i;
387
388     if (perf->num_names == 0)
389         return FALSE;
390
391 #if HAVE_UNISTD_H
392     for (i = 0; i < perf->num_names; i++)
393         if (access (perf->names[i], R_OK) == 0)
394             return TRUE;
395 #endif
396
397     return FALSE;
398 }
399
400 static cairo_status_t
401 print (void *closure, const unsigned char *data, unsigned int length)
402 {
403     fwrite (data, length, 1, closure);
404     return CAIRO_STATUS_SUCCESS;
405 }
406
407 static void
408 cairo_perf_trace (cairo_perf_t                     *perf,
409                   const cairo_boilerplate_target_t *target,
410                   const char                       *trace)
411 {
412     struct trace args;
413     cairo_surface_t *real;
414
415     args.target = target;
416     real = target->create_surface (NULL,
417                                    CAIRO_CONTENT_COLOR_ALPHA,
418                                    1, 1,
419                                    1, 1,
420                                    CAIRO_BOILERPLATE_MODE_PERF,
421                                    &args.closure);
422     args.surface =
423             cairo_surface_create_observer (real,
424                                            CAIRO_SURFACE_OBSERVER_RECORD_OPERATIONS);
425     cairo_surface_destroy (real);
426     if (cairo_surface_status (args.surface)) {
427         fprintf (stderr,
428                  "Error: Failed to create target surface: %s\n",
429                  target->name);
430         return;
431     }
432
433     printf ("Observing '%s'...", trace);
434     fflush (stdout);
435
436     execute (perf, &args, trace);
437
438     printf ("\n");
439     cairo_device_observer_print (cairo_surface_get_device (args.surface),
440                                  print, stdout);
441     fflush (stdout);
442
443     cairo_surface_destroy (args.surface);
444
445     if (target->cleanup)
446         target->cleanup (args.closure);
447 }
448
449 static void
450 warn_no_traces (const char *message,
451                 const char *trace_dir)
452 {
453     fprintf (stderr,
454 "Error: %s '%s'.\n"
455 "Have you cloned the cairo-traces repository and uncompressed the traces?\n"
456 "  git clone git://anongit.freedesktop.org/cairo-traces\n"
457 "  cd cairo-traces && make\n"
458 "Or set the env.var CAIRO_TRACE_DIR to point to your traces?\n",
459             message, trace_dir);
460 }
461
462 static int
463 cairo_perf_trace_dir (cairo_perf_t                     *perf,
464                       const cairo_boilerplate_target_t *target,
465                       const char                       *dirname)
466 {
467     DIR *dir;
468     struct dirent *de;
469     int num_traces = 0;
470     cairo_bool_t force;
471     cairo_bool_t is_explicit;
472
473     dir = opendir (dirname);
474     if (dir == NULL)
475         return 0;
476
477     force = FALSE;
478     if (cairo_perf_can_run (perf, dirname, &is_explicit))
479         force = is_explicit;
480
481     while ((de = readdir (dir)) != NULL) {
482         char *trace;
483         struct stat st;
484
485         if (de->d_name[0] == '.')
486             continue;
487
488         xasprintf (&trace, "%s/%s", dirname, de->d_name);
489         if (stat (trace, &st) != 0)
490             goto next;
491
492         if (S_ISDIR(st.st_mode)) {
493             num_traces += cairo_perf_trace_dir (perf, target, trace);
494         } else {
495             const char *dot;
496
497             dot = strrchr (de->d_name, '.');
498             if (dot == NULL)
499                 goto next;
500             if (strcmp (dot, ".trace"))
501                 goto next;
502
503             num_traces++;
504             if (!force && ! cairo_perf_can_run (perf, de->d_name, NULL))
505                 goto next;
506
507             cairo_perf_trace (perf, target, trace);
508         }
509 next:
510         free (trace);
511
512     }
513     closedir (dir);
514
515     return num_traces;
516 }
517
518 int
519 main (int   argc,
520       char *argv[])
521 {
522     cairo_perf_t perf;
523     const char *trace_dir = "cairo-traces:/usr/src/cairo-traces:/usr/share/cairo-traces";
524     unsigned int n;
525     int i;
526
527     parse_options (&perf, argc, argv);
528
529     signal (SIGINT, interrupt);
530
531     if (getenv ("CAIRO_TRACE_DIR") != NULL)
532         trace_dir = getenv ("CAIRO_TRACE_DIR");
533
534     perf.targets = cairo_boilerplate_get_targets (&perf.num_targets, NULL);
535
536     /* do we have a list of filenames? */
537     perf.exact_names = have_trace_filenames (&perf);
538
539     for (i = 0; i < perf.num_targets; i++) {
540         const cairo_boilerplate_target_t *target = perf.targets[i];
541
542         if (! perf.list_only && ! target->is_measurable)
543             continue;
544
545         perf.target = target;
546         perf.has_described_backend = FALSE;
547
548         if (perf.exact_names) {
549             for (n = 0; n < perf.num_names; n++) {
550                 struct stat st;
551
552                 if (stat (perf.names[n], &st) == 0) {
553                     if (S_ISDIR (st.st_mode)) {
554                         cairo_perf_trace_dir (&perf, target, perf.names[n]);
555                     } else
556                         cairo_perf_trace (&perf, target, perf.names[n]);
557                 }
558             }
559         } else {
560             int num_traces = 0;
561             const char *dir;
562
563             dir = trace_dir;
564             do {
565                 char buf[1024];
566                 const char *end = strchr (dir, ':');
567                 if (end != NULL) {
568                     memcpy (buf, dir, end-dir);
569                     buf[end-dir] = '\0';
570                     end++;
571
572                     dir = buf;
573                 }
574
575                 num_traces += cairo_perf_trace_dir (&perf, target, dir);
576                 dir = end;
577             } while (dir != NULL);
578
579             if (num_traces == 0) {
580                 warn_no_traces ("Found no traces in", trace_dir);
581                 return 1;
582             }
583         }
584
585         if (perf.list_only)
586             break;
587     }
588
589     cairo_perf_fini (&perf);
590
591     return 0;
592 }