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
static void
153
cairo_perf_reports_compare (cairo_perf_report_t 	*reports,
154
			    int 			 num_reports,
155
			    cairo_perf_report_options_t *options)
156
{
157
    int i;
158
    test_report_t **tests, *min_test;
159
    test_diff_t *diff, *diffs;
160
    int num_diffs, max_diffs;
161
    double max_change;
162
    double test_time;
163
    int seen_non_null;
164

            
165
    tests = xmalloc (num_reports * sizeof (test_report_t *));
166

            
167
    max_diffs = reports[0].tests_count;
168
    for (i = 0; i < num_reports; i++) {
169
	tests[i] = reports[i].tests;
170
	if (reports[i].tests_count > max_diffs)
171
	    max_diffs = reports[i].tests_count;
172
    }
173

            
174
    diff = diffs = xmalloc (max_diffs * sizeof (test_diff_t));
175

            
176
    num_diffs = 0;
177
    while (1) {
178
	int num_tests;
179

            
180
	/* We expect iterations values of 0 when multiple raw reports
181
	 * for the same test have been condensed into the stats of the
182
	 * first. So we just skip these later reports that have no
183
	 * stats. */
184
	seen_non_null = 0;
185
	for (i = 0; i < num_reports; i++) {
186
	    while (tests[i]->name && tests[i]->stats.iterations == 0)
187
		tests[i]++;
188
	    if (tests[i]->name)
189
		seen_non_null++;
190
	}
191
	if (! seen_non_null)
192
	    break;
193

            
194
	/* Find the minimum of all current tests, (we have to do this
195
	 * in case some reports don't have a particular test). */
196
	for (i = 0; i < num_reports; i++) {
197
	    if (tests[i]->name) {
198
		min_test = tests[i];
199
		break;
200
	    }
201
	}
202
	for (++i; i < num_reports; i++) {
203
	    if (tests[i]->name && test_report_cmp_name (tests[i], min_test) < 0)
204
		min_test = tests[i];
205
	}
206

            
207
	num_tests = 0;
208
	for (i = 0; i < num_reports; i++) {
209
	    test_report_t *test;
210
	    int n = 0;
211

            
212
	    test = tests[i];
213
	    while (test[n].name &&
214
		    test_report_cmp_name (&test[n], min_test) == 0)
215
	    {
216
		n++;
217
	    }
218

            
219
	    num_tests += n;
220
	}
221

            
222
	/* For each report that has the current test, record it into
223
	 * the diff structure. */
224
	diff->num_tests = 0;
225
	diff->tests = xmalloc (num_tests * sizeof (test_diff_t));
226
	for (i = 0; i < num_reports; i++) {
227
	    while (tests[i]->name &&
228
		    test_report_cmp_name (tests[i], min_test) == 0)
229
	    {
230
		test_time = tests[i]->stats.min_ticks;
231
		if (test_time > 0) {
232
		    test_time /= tests[i]->stats.ticks_per_ms;
233
		    if (diff->num_tests == 0) {
234
			diff->min = test_time;
235
			diff->max = test_time;
236
		    } else {
237
			if (test_time < diff->min)
238
			    diff->min = test_time;
239
			if (test_time > diff->max)
240
			    diff->max = test_time;
241
		    }
242
		    diff->tests[diff->num_tests++] = tests[i];
243
		}
244
		tests[i]++;
245
	    }
246
	}
247
	diff->change = diff->max / diff->min;
248

            
249
	diff++;
250
	num_diffs++;
251
    }
252
    if (num_diffs == 0)
253
	goto DONE;
254

            
255
    qsort (diffs, num_diffs, sizeof (test_diff_t), test_diff_cmp);
256

            
257
    max_change = 1.0;
258
    for (i = 0; i < num_diffs; i++) {
259
	if (fabs (diffs[i].change) > max_change)
260
	    max_change = fabs (diffs[i].change);
261
    }
262

            
263
    for (i = 0; i < num_diffs; i++) {
264
	diff = &diffs[i];
265

            
266
	/* Discard as uninteresting a change which is less than the
267
	 * minimum change required, (default may be overridden on
268
	 * command-line). */
269
	if (fabs (diff->change) - 1.0 < options->min_change)
270
	    continue;
271

            
272
	test_diff_print (diff, max_change, options);
273
    }
274

            
275
    for (i = 0; i < num_diffs; i++)
276
	free (diffs[i].tests);
277
 DONE:
278
    free (diffs);
279
    free (tests);
280
}
281

            
282
static void
283
usage (const char *argv0)
284
{
285
    char const *basename = strrchr(argv0, '/');
286
    basename = basename ? basename+1 : argv0;
287
    fprintf (stderr,
288
	     "Usage: %s [options] file [...]\n\n",
289
	     basename);
290
    fprintf (stderr,
291
	     "Computes significant performance differences for cairo performance reports.\n"
292
	     "Each file should be the output of the cairo-perf program (or \"make perf\").\n"
293
	     "The following options are available:\n"
294
	     "\n"
295
	     "--no-utf    Use ascii stars instead of utf-8 change bars.\n"
296
	     "            Four stars are printed per factor of speedup.\n"
297
	     "\n"
298
	     "--no-bars   Don't display change bars at all.\n\n"
299
	     "\n"
300
	     "--use-ms    Use milliseconds to calculate differences.\n"
301
	     "            (instead of ticks which are hardware dependent)\n"
302
	     "\n"
303
	     "--min-change threshold[%%]\n"
304
	     "            Suppress all changes below the given threshold.\n"
305
	     "            The default threshold of 0.05 or 5%% ignores any\n"
306
	     "            speedup or slowdown of 1.05 or less. A threshold\n"
307
	     "            of 0 will cause all output to be reported.\n"
308
	);
309
    exit(1);
310
}
311

            
312
static void
313
parse_args (int 			   argc,
314
	    char const			 **argv,
315
	    cairo_perf_diff_files_args_t  *args)
316
{
317
    int i;
318

            
319
    for (i = 1; i < argc; i++) {
320
	if (strcmp (argv[i], "--no-utf") == 0) {
321
	    args->options.use_utf = 0;
322
	}
323
	else if (strcmp (argv[i], "--no-bars") == 0) {
324
	    args->options.print_change_bars = 0;
325
	}
326
	else if (strcmp (argv[i], "--min-change") == 0) {
327
	    char *end = NULL;
328
	    i++;
329
	    if (i >= argc)
330
		usage (argv[0]);
331
	    args->options.min_change = strtod (argv[i], &end);
332
	    if (*end) {
333
		if (*end == '%') {
334
		    args->options.min_change /= 100;
335
		} else {
336
		    usage (argv[0]);
337
		}
338
	    }
339
	}
340
	else {
341
	    args->num_filenames++;
342
	    args->filenames = xrealloc (args->filenames,
343
					args->num_filenames * sizeof (char *));
344
	    args->filenames[args->num_filenames - 1] = argv[i];
345
	}
346
    }
347
}
348

            
349
int
350
main (int	  argc,
351
      const char *argv[])
352
{
353
    cairo_perf_diff_files_args_t args = {
354
	NULL,			/* filenames */
355
	0,			/* num_filenames */
356
	{
357
	    0.05,		/* min change */
358
	    1,			/* use UTF-8? */
359
	    1,			/* display change bars? */
360
	}
361
    };
362
    cairo_perf_report_t *reports;
363
    test_report_t *t;
364
    int i;
365

            
366
    parse_args (argc, argv, &args);
367

            
368
    if (args.num_filenames) {
369
	reports = xcalloc (args.num_filenames, sizeof (cairo_perf_report_t));
370
	for (i = 0; i < args.num_filenames; i++) {
371
	    cairo_perf_report_load (&reports[i], args.filenames[i], i,
372
				    test_report_cmp_name);
373
	    printf ("loaded: %s, %d tests\n",
374
		    args.filenames[i], reports[i].tests_count);
375
	}
376
    } else {
377
	args.num_filenames = 1;
378
	reports = xcalloc (args.num_filenames, sizeof (cairo_perf_report_t));
379
	cairo_perf_report_load (&reports[0], NULL, 0, test_report_cmp_name);
380
    }
381

            
382
    cairo_perf_reports_compare (reports, args.num_filenames, &args.options);
383

            
384
    /* Pointless memory cleanup, (would be a great place for talloc) */
385
    free (args.filenames);
386
    for (i = 0; i < args.num_filenames; i++) {
387
	for (t = reports[i].tests; t->name; t++) {
388
	    free (t->samples);
389
	    free (t->backend);
390
	    free (t->name);
391
	}
392
	free (reports[i].tests);
393
	free (reports[i].configuration);
394
    }
395
    free (reports);
396

            
397
    return 0;
398
}