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 "config.h"
29

            
30
#define _GETDELIM 1/* for getline() on AIX */
31

            
32
#include "cairo-perf.h"
33
#include "cairo-missing.h"
34
#include "cairo-stats.h"
35
#include "cairo-ctype-inline.h"
36

            
37
#include <stdio.h>
38
#include <stdlib.h>
39
#include <string.h>
40
#include <errno.h>
41
#include <ctype.h>
42
#include <math.h>
43
#include <assert.h>
44
#ifdef HAVE_LIBGEN_H
45
#include <libgen.h>
46
#endif
47

            
48
#ifdef _MSC_VER
49
#if _MSC_VER < 1800
50
static long long
51
strtoll (const char  *nptr,
52
	 char	    **endptr,
53
	 int	      base);
54
#endif
55

            
56
static char *
57
basename (char *path);
58
#endif
59

            
60
/* Ad-hoc parsing, macros with a strong dependence on the calling
61
 * context, and plenty of other ugliness is here.  But at least it's
62
 * not perl... */
63
#define parse_error(...) fprintf(stderr, __VA_ARGS__); return TEST_REPORT_STATUS_ERROR;
64
#define skip_char(c)							\
65
do {									\
66
    if (*s && *s == (c)) {						\
67
	s++;								\
68
    } else {								\
69
	 parse_error ("expected '%c' but found '%c'", c, *s);		\
70
    }									\
71
} while (0)
72
#define skip_space() while (*s && (*s == ' ' || *s == '\t')) s++;
73
#define parse_int(result)						\
74
do {									\
75
    (result) = strtol (s, &end, 10);					\
76
    if (*s && end != s) {						\
77
	s = end;							\
78
    } else {								\
79
	parse_error("expected integer but found %s", s);		\
80
    }									\
81
} while (0)
82
#define parse_long_long(result) 					\
83
do {									\
84
    (result) = strtoll (s, &end, 10);					\
85
    if (*s && end != s) {						\
86
	s = end;							\
87
    } else {								\
88
	parse_error("expected integer but found %s", s);		\
89
    }									\
90
} while (0)
91
#define parse_double(result)						\
92
do {									\
93
    (result) = strtod (s, &end);					\
94
    if (*s && end != s) {						\
95
	s = end;							\
96
    } else {								\
97
	parse_error("expected floating-point value but found %s", s);	\
98
    }									\
99
} while (0)
100
/* Here a string is simply a sequence of non-whitespace */
101
#define parse_string(result)						\
102
do {									\
103
    for (end = s; *end; end++)						\
104
	if (_cairo_isspace (*end))					\
105
	    break;							\
106
    (result) = strndup (s, end - s);					\
107
    if ((result) == NULL) {						\
108
	fprintf (stderr, "Out of memory.\n");				\
109
	exit (1);							\
110
    }									\
111
    s = end;								\
112
} while (0)
113

            
114
static test_report_status_t
115
test_report_parse (test_report_t *report,
116
		   int fileno,
117
		   char 	 *line,
118
		   char 	 *configuration)
119
{
120
    char *end;
121
    char *s = line;
122
    cairo_bool_t is_raw = FALSE;
123
    double min_time, median_time;
124

            
125
    /* The code here looks funny unless you understand that these are
126
     * all macro calls, (and then the code just looks sick). */
127
    if (*s == '\n')
128
	return TEST_REPORT_STATUS_COMMENT;
129

            
130
    skip_char ('[');
131
    skip_space ();
132
    if (*s == '#')
133
	return TEST_REPORT_STATUS_COMMENT;
134
    if (*s == '*') {
135
	s++;
136
	is_raw = TRUE;
137
    } else {
138
	parse_int (report->id);
139
    }
140
    skip_char (']');
141

            
142
    skip_space ();
143

            
144
    report->fileno = fileno;
145
    report->configuration = configuration;
146
    parse_string (report->backend);
147
    end = strrchr (report->backend, '.');
148
    if (end)
149
	*end++ = '\0';
150
    report->content = end ? end : xstrdup ("???");
151

            
152
    skip_space ();
153

            
154
    parse_string (report->name);
155
    end = strrchr (report->name, '.');
156
    if (end)
157
	*end++ = '\0';
158
    report->size = end ? atoi (end) : 0;
159

            
160
    skip_space ();
161

            
162
    report->samples = NULL;
163
    report->samples_size = 0;
164
    report->samples_count = 0;
165

            
166
    if (is_raw) {
167
	parse_double (report->stats.ticks_per_ms);
168
	skip_space ();
169

            
170
	report->samples_size = 5;
171
	report->samples = xmalloc (report->samples_size * sizeof (cairo_time_t));
172
	report->stats.min_ticks = 0;
173
	do {
174
	    if (report->samples_count == report->samples_size) {
175
		report->samples_size *= 2;
176
		report->samples = xrealloc (report->samples,
177
					    report->samples_size * sizeof (cairo_time_t));
178
	    }
179
	    parse_long_long (report->samples[report->samples_count]);
180
	    if (report->samples_count == 0) {
181
		report->stats.min_ticks =
182
		    report->samples[report->samples_count];
183
	    } else if (report->stats.min_ticks >
184
		       report->samples[report->samples_count]){
185
		report->stats.min_ticks =
186
		    report->samples[report->samples_count];
187
	    }
188
	    report->samples_count++;
189
	    skip_space ();
190
	} while (*s && *s != '\n');
191
	report->stats.iterations = 0;
192
	if (*s) skip_char ('\n');
193
    } else {
194
	parse_double (report->stats.min_ticks);
195
	skip_space ();
196

            
197
	parse_double (min_time);
198
	report->stats.ticks_per_ms = report->stats.min_ticks / min_time;
199

            
200
	skip_space ();
201

            
202
	parse_double (median_time);
203
	report->stats.median_ticks = median_time * report->stats.ticks_per_ms;
204

            
205
	skip_space ();
206

            
207
	parse_double (report->stats.std_dev);
208
	report->stats.std_dev /= 100.0;
209
	skip_char ('%');
210

            
211
	skip_space ();
212

            
213
	parse_int (report->stats.iterations);
214

            
215
	skip_space ();
216
	skip_char ('\n');
217
    }
218

            
219
    return TEST_REPORT_STATUS_SUCCESS;
220
}
221

            
222
/* We provide hereafter a win32 implementation of the basename
223
 * and strtoll functions which are not available otherwise.
224
 * The basename function is fully compliant to its GNU specs.
225
 */
226
#ifdef _MSC_VER
227

            
228
#if _MSC_VER < 1800
229
long long
230
strtoll (const char  *nptr,
231
	 char	    **endptr,
232
	 int	      base)
233
{
234
    return _atoi64(nptr);
235
}
236
#endif
237

            
238
static char *
239
basename (char *path)
240
{
241
    char *end, *s;
242

            
243
    end = (path + strlen(path) - 1);
244
    while (end && (end >= path + 1) && (*end == '/')) {
245
	*end = '\0';
246
	end--;
247
    }
248

            
249
    s = strrchr(path, '/');
250
    if (s) {
251
	if (s == end) {
252
	    return s;
253
	} else {
254
	    return s+1;
255
	}
256
    } else {
257
	return path;
258
    }
259
}
260
#endif /* ifndef _MSC_VER */
261

            
262
int
263
test_report_cmp_backend_then_name (const void *a,
264
				   const void *b)
265
{
266
    const test_report_t *a_test = a;
267
    const test_report_t *b_test = b;
268

            
269
    int cmp;
270

            
271
    cmp = strcmp (a_test->backend, b_test->backend);
272
    if (cmp)
273
	return cmp;
274

            
275
    cmp = strcmp (a_test->content, b_test->content);
276
    if (cmp)
277
	return cmp;
278

            
279
    /* A NULL name is a list-termination marker, so force it last. */
280
    if (a_test->name == NULL)
281
	if (b_test->name == NULL)
282
	    return 0;
283
	else
284
	    return 1;
285
    else if (b_test->name == NULL)
286
	return -1;
287

            
288
    cmp = strcmp (a_test->name, b_test->name);
289
    if (cmp)
290
	return cmp;
291

            
292
    if (a_test->size < b_test->size)
293
	return -1;
294
    if (a_test->size > b_test->size)
295
	return 1;
296

            
297
    return 0;
298
}
299

            
300
int
301
test_report_cmp_name (const void *a,
302
		      const void *b)
303
{
304
    const test_report_t *a_test = a;
305
    const test_report_t *b_test = b;
306

            
307
    int cmp;
308

            
309
    /* A NULL name is a list-termination marker, so force it last. */
310
    if (a_test->name == NULL)
311
	if (b_test->name == NULL)
312
	    return 0;
313
	else
314
	    return 1;
315
    else if (b_test->name == NULL)
316
	return -1;
317

            
318
    cmp = strcmp (a_test->name, b_test->name);
319
    if (cmp)
320
	return cmp;
321

            
322
    if (a_test->size < b_test->size)
323
	return -1;
324
    if (a_test->size > b_test->size)
325
	return 1;
326

            
327
    return 0;
328
}
329

            
330
void
331
cairo_perf_report_sort_and_compute_stats (cairo_perf_report_t *report,
332
					  int (*cmp) (const void*, const void*))
333
{
334
    test_report_t *base, *next, *last, *t;
335

            
336
    if (cmp == NULL)
337
	cmp = test_report_cmp_backend_then_name;
338

            
339
    /* First we sort, since the diff needs both lists in the same
340
     * order */
341
    qsort (report->tests, report->tests_count, sizeof (test_report_t), cmp);
342

            
343
    /* The sorting also brings all related raw reports together so we
344
     * can condense them and compute the stats.
345
     */
346
    base = &report->tests[0];
347
    last = &report->tests[report->tests_count - 1];
348
    while (base <= last) {
349
	next = base+1;
350
	if (next <= last) {
351
	    while (next <= last &&
352
		   test_report_cmp_backend_then_name (base, next) == 0)
353
	    {
354
		next++;
355
	    }
356
	    if (next != base) {
357
		unsigned int new_samples_count = base->samples_count;
358
		for (t = base + 1; t < next; t++)
359
		    new_samples_count += t->samples_count;
360
		if (new_samples_count > base->samples_size) {
361
		    base->samples_size = new_samples_count;
362
		    base->samples = xrealloc (base->samples,
363
					      base->samples_size * sizeof (cairo_time_t));
364
		}
365
		for (t = base + 1; t < next; t++) {
366
		    memcpy (&base->samples[base->samples_count], t->samples,
367
			    t->samples_count * sizeof (cairo_time_t));
368
		    base->samples_count += t->samples_count;
369
		}
370
	    }
371
	}
372
	if (base->samples)
373
	    _cairo_stats_compute (&base->stats, base->samples, base->samples_count);
374
	base = next;
375
    }
376
}
377

            
378
void
379
cairo_perf_report_load (cairo_perf_report_t *report,
380
			const char *filename, int id,
381
			int (*cmp) (const void *, const void *))
382
{
383
    FILE *file;
384
    test_report_status_t status;
385
    int line_number = 0;
386
    char *line = NULL;
387
    size_t line_size = 0;
388
    char *configuration;
389
    char *dot;
390
    char *baseName;
391
    const char *name;
392

            
393
    name = filename;
394
    if (name == NULL)
395
	name = "stdin";
396

            
397
    configuration = xstrdup (name);
398
    baseName = basename (configuration);
399
    report->configuration = xstrdup (baseName);
400
    free (configuration);
401

            
402
    dot = strrchr (report->configuration, '.');
403
    if (dot)
404
	*dot = '\0';
405

            
406
    report->name = name;
407
    report->tests_size = 16;
408
    report->tests = xmalloc (report->tests_size * sizeof (test_report_t));
409
    report->tests_count = 0;
410
    report->fileno = id;
411

            
412
    if (filename == NULL) {
413
	file = stdin;
414
    } else {
415
	file = fopen (filename, "r");
416
	if (file == NULL) {
417
	    fprintf (stderr, "Failed to open %s: %s\n",
418
		     filename, strerror (errno));
419
	    exit (1);
420
	}
421
    }
422

            
423
    while (1) {
424
	if (report->tests_count == report->tests_size) {
425
	    report->tests_size *= 2;
426
	    report->tests = xrealloc (report->tests,
427
				      report->tests_size * sizeof (test_report_t));
428
	}
429

            
430
	line_number++;
431
	if (getline (&line, &line_size, file) == -1)
432
	    break;
433

            
434
	status = test_report_parse (&report->tests[report->tests_count],
435
				    id, line, report->configuration);
436
	if (status == TEST_REPORT_STATUS_ERROR)
437
	    fprintf (stderr, "Ignoring unrecognized line %d of %s:\n%s",
438
		     line_number, filename, line);
439
	if (status == TEST_REPORT_STATUS_SUCCESS)
440
	    report->tests_count++;
441
	/* Do nothing on TEST_REPORT_STATUS_COMMENT */
442
    }
443

            
444
    free (line);
445

            
446
    if (filename != NULL)
447
	fclose (file);
448

            
449
    cairo_perf_report_sort_and_compute_stats (report, cmp);
450

            
451
    /* Add one final report with a NULL name to terminate the list. */
452
    if (report->tests_count == report->tests_size) {
453
	report->tests_size *= 2;
454
	report->tests = xrealloc (report->tests,
455
				  report->tests_size * sizeof (test_report_t));
456
    }
457
    report->tests[report->tests_count].name = NULL;
458
}