Tizen 2.0 Release
[framework/graphics/cairo.git] / perf / cairo-perf-diff-files.c
1 /*
2  * Copyright © 2006 Red Hat, Inc.
3  *
4  * Permission to use, copy, modify, distribute, and sell this software
5  * and its documentation for any purpose is hereby granted without
6  * fee, provided that the above copyright notice appear in all copies
7  * and that both that copyright notice and this permission notice
8  * appear in supporting documentation, and that the name of the
9  * copyright holders not be used in advertising or publicity
10  * pertaining to distribution of the software without specific,
11  * written prior permission. The copyright holders make no
12  * representations about the suitability of this software for any
13  * purpose.  It is provided "as is" without express or implied
14  * warranty.
15  *
16  * THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS
17  * SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
18  * FITNESS, IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY
19  * SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
20  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN
21  * AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING
22  * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
23  * SOFTWARE.
24  *
25  * Authors: Carl Worth <cworth@cworth.org>
26  */
27
28 #include "cairo-perf.h"
29
30 #include <stdio.h>
31 #include <stdlib.h>
32 #include <string.h>
33 #include <errno.h>
34 #include <ctype.h>
35 #include <math.h>
36 #include <assert.h>
37
38 typedef struct _cairo_perf_report_options {
39     double min_change;
40     int use_utf;
41     int print_change_bars;
42     int use_ticks;
43 } cairo_perf_report_options_t;
44
45 typedef struct _cairo_perf_diff_files_args {
46     const char **filenames;
47     int num_filenames;
48     cairo_perf_report_options_t options;
49 } cairo_perf_diff_files_args_t;
50
51 static int
52 test_diff_cmp_speedup_before_slowdown (const void *a,
53                                        const void *b)
54 {
55     const test_diff_t *a_diff = a;
56     const test_diff_t *b_diff = b;
57
58     /* First make all speedups come before all slowdowns. */
59     if (a_diff->change > 0 && b_diff->change < 0)
60         return -1;
61     if (a_diff->change < 0 && b_diff->change > 0)
62         return 1;
63
64     if (a_diff->change == b_diff->change)
65         return 0;
66
67     /* Large speedups come first. */
68     if (a_diff->change > 0) {
69             if (a_diff->change > b_diff->change)
70                     return -1;
71             else
72                     return 1;
73     }
74
75     /* Large slowdowns come last. */
76     if (a_diff->change < 0) {
77             if (a_diff->change < b_diff->change)
78                     return 1;
79             else
80                     return -1;
81     }
82
83     return 0;
84 }
85
86 static int
87 test_diff_cmp (const void *a,
88                const void *b)
89 {
90     const test_diff_t *a_diff = a;
91     const test_diff_t *b_diff = b;
92
93     /* Reverse sort by magnitude of change so larger changes come
94      * first */
95     if (a_diff->change > b_diff->change)
96         return -1;
97
98     if (a_diff->change < b_diff->change)
99         return 1;
100
101     return 0;
102 }
103
104 #define CHANGE_BAR_WIDTH 70
105 static void
106 print_change_bar (double change,
107                   double max_change,
108                   int    use_utf)
109 {
110     int units_per_cell = ceil (max_change / CHANGE_BAR_WIDTH);
111     static char const *ascii_boxes[8] = {
112         "****","***" ,"***", "**",
113         "**",  "*",   "*",   ""
114     };
115     static char const *utf_boxes[8] = {
116         "█", "▉", "▊", "▋",
117         "▌", "▍", "▎", "▏"
118     };
119     char const **boxes = use_utf ? utf_boxes : ascii_boxes;
120
121     /* For a 1.0x speedup we want a zero-size bar to show "no
122      * change". */
123     change -= 1.0;
124
125     while (change > units_per_cell) {
126         printf ("%s", boxes[0]);
127         change -= units_per_cell;
128     }
129
130     change /= units_per_cell;
131
132     if (change > 7.5/8.0)
133         printf ("%s", boxes[0]);
134     else if (change > 6.5/8.0)
135         printf ("%s", boxes[1]);
136     else if (change > 5.5/8.0)
137         printf ("%s", boxes[2]);
138     else if (change > 4.5/8.0)
139         printf ("%s", boxes[3]);
140     else if (change > 3.5/8.0)
141         printf ("%s", boxes[4]);
142     else if (change > 2.5/8.0)
143         printf ("%s", boxes[5]);
144     else if (change > 1.5/8.0)
145         printf ("%s", boxes[6]);
146     else if (change > 0.5/8.0)
147         printf ("%s", boxes[7]);
148
149     printf ("\n");
150 }
151
152 static void
153 test_diff_print_binary (test_diff_t                 *diff,
154                         double                       max_change,
155                         cairo_perf_report_options_t *options)
156 {
157     if (diff->tests[0]->size)
158         printf ("%5s-%-4s %26s-%-3d",
159                 diff->tests[0]->backend, diff->tests[0]->content,
160                 diff->tests[0]->name, diff->tests[0]->size);
161     else
162         printf ("%5s %26s", diff->tests[0]->backend, diff->tests[0]->name);
163
164     printf ("  %6.2f (%.2f %4.2f%%) -> %6.2f (%.2f %4.2f%%): %5.2fx ",
165             diff->tests[0]->stats.min_ticks / diff->tests[0]->stats.ticks_per_ms,
166             diff->tests[0]->stats.median_ticks / diff->tests[0]->stats.ticks_per_ms,
167             diff->tests[0]->stats.std_dev * 100,
168             diff->tests[1]->stats.min_ticks / diff->tests[1]->stats.ticks_per_ms,
169             diff->tests[1]->stats.median_ticks / diff->tests[1]->stats.ticks_per_ms,
170             diff->tests[1]->stats.std_dev * 100,
171             fabs (diff->change));
172
173     if (diff->change > 1.0)
174         printf ("speedup\n");
175     else
176         printf ("slowdown\n");
177
178     if (options->print_change_bars)
179         print_change_bar (fabs (diff->change), max_change,
180                           options->use_utf);
181 }
182
183 static void
184 test_diff_print_multi (test_diff_t                 *diff,
185                        double                       max_change,
186                        cairo_perf_report_options_t *options)
187 {
188     int i;
189     double test_time;
190     double change;
191
192     if (diff->tests[0]->size) {
193         printf ("%s (backend: %s-%s, size: %d)\n",
194                 diff->tests[0]->name,
195                 diff->tests[0]->backend,
196                 diff->tests[0]->content,
197                 diff->tests[0]->size);
198     } else {
199         printf ("%s (backend: %s)\n",
200                 diff->tests[0]->name,
201                 diff->tests[0]->backend);
202     }
203
204     for (i = 0; i < diff->num_tests; i++) {
205         test_time = diff->tests[i]->stats.min_ticks;
206         if (! options->use_ticks)
207             test_time /= diff->tests[i]->stats.ticks_per_ms;
208         change = diff->max / test_time;
209         printf ("[%d] %6.2f: %5.2fx ",
210                 diff->tests[i]->fileno,
211                 diff->tests[i]->stats.min_ticks / diff->tests[i]->stats.ticks_per_ms,
212                 change);
213
214         if (options->print_change_bars)
215             print_change_bar (change, max_change, options->use_utf);
216         else
217             printf("\n");
218     }
219
220     printf("\n");
221 }
222
223 #define MAX(a,b) ((a) > (b) ? (a) : (b))
224 static void
225 cairo_perf_reports_compare (cairo_perf_report_t         *reports,
226                             int                          num_reports,
227                             cairo_perf_report_options_t *options)
228 {
229     int i;
230     test_report_t **tests, *min_test;
231     test_diff_t *diff, *diffs;
232     int num_diffs, max_diffs;
233     double max_change;
234     double test_time;
235     int seen_non_null;
236     cairo_bool_t printed_speedup = FALSE;
237     cairo_bool_t printed_slowdown = FALSE;
238
239     assert (num_reports >= 2);
240
241     tests = xmalloc (num_reports * sizeof (test_report_t *));
242
243     max_diffs = reports[0].tests_count;
244     for (i = 0; i < num_reports; i++) {
245         tests[i] = reports[i].tests;
246         if (reports[i].tests_count > max_diffs)
247             max_diffs = reports[i].tests_count;
248     }
249
250     diff = diffs = xmalloc (max_diffs * sizeof (test_diff_t));
251
252     num_diffs = 0;
253     while (1) {
254         /* We expect iterations values of 0 when multiple raw reports
255          * for the same test have been condensed into the stats of the
256          * first. So we just skip these later reports that have no
257          * stats. */
258         seen_non_null = 0;
259         for (i = 0; i < num_reports; i++) {
260             while (tests[i]->name && tests[i]->stats.iterations == 0)
261                 tests[i]++;
262             if (tests[i]->name)
263                 seen_non_null++;
264         }
265
266         if (seen_non_null < 2)
267             break;
268
269         /* Find the minimum of all current tests, (we have to do this
270          * in case some reports don't have a particular test). */
271         for (i = 0; i < num_reports; i++) {
272             if (tests[i]->name) {
273                 min_test = tests[i];
274                 break;
275             }
276         }
277         for (++i; i < num_reports; i++) {
278             if (tests[i]->name &&
279                 test_report_cmp_backend_then_name (tests[i], min_test) < 0)
280             {
281                 min_test = tests[i];
282             }
283         }
284
285         /* For each report that has the current test, record it into
286          * the diff structure. */
287         diff->num_tests = 0;
288         diff->tests = xmalloc (num_reports * sizeof (test_diff_t));
289         for (i = 0; i < num_reports; i++) {
290             if (tests[i]->name &&
291                 test_report_cmp_backend_then_name (tests[i], min_test) == 0)
292             {
293                 test_time = tests[i]->stats.min_ticks;
294                 if (! options->use_ticks)
295                     test_time /= tests[i]->stats.ticks_per_ms;
296                 if (diff->num_tests == 0) {
297                     diff->min = test_time;
298                     diff->max = test_time;
299                 } else {
300                     if (test_time < diff->min)
301                         diff->min = test_time;
302                     if (test_time > diff->max)
303                         diff->max = test_time;
304                 }
305                 diff->tests[diff->num_tests++] = tests[i];
306                 tests[i]++;
307             }
308         }
309         diff->change = diff->max / diff->min;
310
311         if (num_reports == 2) {
312             double old_time, new_time;
313             if (diff->num_tests == 1) {
314                 printf ("Only in %s: %s %s\n",
315                         diff->tests[0]->configuration,
316                         diff->tests[0]->backend,
317                         diff->tests[0]->name);
318                 continue;
319             }
320             old_time = diff->tests[0]->stats.min_ticks;
321             new_time = diff->tests[1]->stats.min_ticks;
322             if (! options->use_ticks) {
323                 old_time /= diff->tests[0]->stats.ticks_per_ms;
324                 new_time /= diff->tests[1]->stats.ticks_per_ms;
325             }
326             diff->change = old_time / new_time;
327             if (diff->change < 1.0)
328                 diff->change = - 1.0 / diff->change;
329         }
330
331         diff++;
332         num_diffs++;
333     }
334     if (num_diffs == 0)
335         goto DONE;
336
337     if (num_reports == 2)
338         qsort (diffs, num_diffs, sizeof (test_diff_t),
339                test_diff_cmp_speedup_before_slowdown);
340     else
341         qsort (diffs, num_diffs, sizeof (test_diff_t), test_diff_cmp);
342
343     max_change = 1.0;
344     for (i = 0; i < num_diffs; i++) {
345         if (fabs (diffs[i].change) > max_change)
346             max_change = fabs (diffs[i].change);
347     }
348
349     if (num_reports == 2)
350         printf ("old: %s\n"
351                 "new: %s\n",
352                 diffs->tests[0]->configuration,
353                 diffs->tests[1]->configuration);
354
355     for (i = 0; i < num_diffs; i++) {
356         diff = &diffs[i];
357
358         /* Discard as uninteresting a change which is less than the
359          * minimum change required, (default may be overriden on
360          * command-line). */
361         if (fabs (diff->change) - 1.0 < options->min_change)
362             continue;
363
364         if (num_reports == 2) {
365             if (diff->change > 1.0 && ! printed_speedup) {
366                 printf ("Speedups\n"
367                         "========\n");
368                 printed_speedup = TRUE;
369             }
370             if (diff->change < 1.0 && ! printed_slowdown) {
371                 printf ("Slowdowns\n"
372                         "=========\n");
373                 printed_slowdown = TRUE;
374             }
375             test_diff_print_binary (diff, max_change, options);
376         } else {
377             test_diff_print_multi (diff, max_change, options);
378         }
379     }
380
381  DONE:
382     for (i = 0; i < num_diffs; i++)
383         free (diffs[i].tests);
384     free (diffs);
385     free (tests);
386 }
387
388 static void
389 usage (const char *argv0)
390 {
391     char const *basename = strrchr(argv0, '/');
392     basename = basename ? basename+1 : argv0;
393     fprintf (stderr,
394              "Usage: %s [options] file1 file2 [...]\n\n",
395              basename);
396     fprintf (stderr,
397              "Computes significant performance differences for cairo performance reports.\n"
398              "Each file should be the output of the cairo-perf program (or \"make perf\").\n"
399              "The following options are available:\n"
400              "\n"
401              "--no-utf    Use ascii stars instead of utf-8 change bars.\n"
402              "            Four stars are printed per factor of speedup.\n"
403              "\n"
404              "--no-bars   Don't display change bars at all.\n\n"
405              "\n"
406              "--use-ms    Use milliseconds to calculate differences.\n"
407              "            (instead of ticks which are hardware dependent)\n"
408              "\n"
409              "--min-change threshold[%%]\n"
410              "            Suppress all changes below the given threshold.\n"
411              "            The default threshold of 0.05 or 5%% ignores any\n"
412              "            speedup or slowdown of 1.05 or less. A threshold\n"
413              "            of 0 will cause all output to be reported.\n"
414         );
415     exit(1);
416 }
417
418 static void
419 parse_args (int                            argc,
420             char const                   **argv,
421             cairo_perf_diff_files_args_t  *args)
422 {
423     int i;
424
425     for (i = 1; i < argc; i++) {
426         if (strcmp (argv[i], "--no-utf") == 0) {
427             args->options.use_utf = 0;
428         }
429         else if (strcmp (argv[i], "--no-bars") == 0) {
430             args->options.print_change_bars = 0;
431         }
432         else if (strcmp (argv[i], "--use-ms") == 0) {
433             /* default */
434         }
435         else if (strcmp (argv[i], "--use-ticks") == 0) {
436             args->options.use_ticks = 1;
437         }
438         else if (strcmp (argv[i], "--min-change") == 0) {
439             char *end = NULL;
440             i++;
441             if (i >= argc)
442                 usage (argv[0]);
443             args->options.min_change = strtod (argv[i], &end);
444             if (*end) {
445                 if (*end == '%') {
446                     args->options.min_change /= 100;
447                 } else {
448                     usage (argv[0]);
449                 }
450             }
451         }
452         else {
453             args->num_filenames++;
454             args->filenames = xrealloc (args->filenames,
455                                         args->num_filenames * sizeof (char *));
456             args->filenames[args->num_filenames - 1] = argv[i];
457         }
458     }
459 }
460
461 int
462 main (int         argc,
463       const char *argv[])
464 {
465     cairo_perf_diff_files_args_t args = {
466         NULL,                   /* filenames */
467         0,                      /* num_filenames */
468         {
469             0.05,               /* min change */
470             1,                  /* use UTF-8? */
471             1,                  /* display change bars? */
472         }
473     };
474     cairo_perf_report_t *reports;
475     test_report_t *t;
476     int i;
477
478     parse_args (argc, argv, &args);
479
480     if (args.num_filenames < 2)
481         usage (argv[0]);
482
483     reports = xmalloc (args.num_filenames * sizeof (cairo_perf_report_t));
484
485     for (i = 0; i < args.num_filenames; i++ ) {
486         cairo_perf_report_load (&reports[i], args.filenames[i], i, NULL);
487         printf ("[%d] %s\n", i, args.filenames[i]);
488     }
489     printf ("\n");
490
491     cairo_perf_reports_compare (reports, args.num_filenames, &args.options);
492
493     /* Pointless memory cleanup, (would be a great place for talloc) */
494     free (args.filenames);
495     for (i = 0; i < args.num_filenames; i++) {
496         for (t = reports[i].tests; t->name; t++) {
497             free (t->samples);
498             free (t->backend);
499             free (t->name);
500         }
501         free (reports[i].tests);
502         free (reports[i].configuration);
503     }
504     free (reports);
505
506     return 0;
507 }