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