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 #define _GETDELIM 1/* for getline() on AIX */
29
30 #include "cairo-perf.h"
31 #include "cairo-missing.h"
32 #include "cairo-stats.h"
33
34 /* We use _GNU_SOURCE for getline and strndup if available. */
35 #ifndef _GNU_SOURCE
36 # define _GNU_SOURCE
37 #endif
38 #include <stdio.h>
39 #include <stdlib.h>
40 #include <string.h>
41 #include <errno.h>
42 #include <ctype.h>
43 #include <math.h>
44 #include <assert.h>
45 #ifdef HAVE_LIBGEN_H
46 #include <libgen.h>
47 #endif
48
49 #ifdef _MSC_VER
50 static long long
51 strtoll (const char *nptr,
52 char **endptr,
53 int base);
54
55 static char *
56 basename (char *path);
57 #endif
58
59 /* Ad-hoc parsing, macros with a strong dependence on the calling
60 * context, and plenty of other ugliness is here. But at least it's
61 * not perl... */
62 #define parse_error(...) fprintf(stderr, __VA_ARGS__); return TEST_REPORT_STATUS_ERROR;
63 #define skip_char(c) \
64 do { \
65 if (*s && *s == (c)) { \
66 s++; \
67 } else { \
68 parse_error ("expected '%c' but found '%c'", c, *s); \
69 } \
70 } while (0)
71 #define skip_space() while (*s && (*s == ' ' || *s == '\t')) s++;
72 #define parse_int(result) \
73 do { \
74 (result) = strtol (s, &end, 10); \
75 if (*s && end != s) { \
76 s = end; \
77 } else { \
78 parse_error("expected integer but found %s", s); \
79 } \
80 } while (0)
81 #define parse_long_long(result) \
82 do { \
83 (result) = strtoll (s, &end, 10); \
84 if (*s && end != s) { \
85 s = end; \
86 } else { \
87 parse_error("expected integer but found %s", s); \
88 } \
89 } while (0)
90 #define parse_double(result) \
91 do { \
92 (result) = strtod (s, &end); \
93 if (*s && end != s) { \
94 s = end; \
95 } else { \
96 parse_error("expected floating-point value but found %s", s); \
97 } \
98 } while (0)
99 /* Here a string is simply a sequence of non-whitespace */
100 #define parse_string(result) \
101 do { \
102 for (end = s; *end; end++) \
103 if (isspace (*end)) \
104 break; \
105 (result) = strndup (s, end - s); \
106 if ((result) == NULL) { \
107 fprintf (stderr, "Out of memory.\n"); \
108 exit (1); \
109 } \
110 s = end; \
111 } while (0)
112
113 static test_report_status_t
test_report_parse(test_report_t * report,int fileno,char * line,char * configuration)114 test_report_parse (test_report_t *report,
115 int fileno,
116 char *line,
117 char *configuration)
118 {
119 char *end;
120 char *s = line;
121 cairo_bool_t is_raw = FALSE;
122 double min_time, median_time;
123
124 /* The code here looks funny unless you understand that these are
125 * all macro calls, (and then the code just looks sick). */
126 if (*s == '\n')
127 return TEST_REPORT_STATUS_COMMENT;
128
129 skip_char ('[');
130 skip_space ();
131 if (*s == '#')
132 return TEST_REPORT_STATUS_COMMENT;
133 if (*s == '*') {
134 s++;
135 is_raw = TRUE;
136 } else {
137 parse_int (report->id);
138 }
139 skip_char (']');
140
141 skip_space ();
142
143 report->fileno = fileno;
144 report->configuration = configuration;
145 parse_string (report->backend);
146 end = strrchr (report->backend, '.');
147 if (end)
148 *end++ = '\0';
149 report->content = end ? end : xstrdup ("???");
150
151 skip_space ();
152
153 parse_string (report->name);
154 end = strrchr (report->name, '.');
155 if (end)
156 *end++ = '\0';
157 report->size = end ? atoi (end) : 0;
158
159 skip_space ();
160
161 report->samples = NULL;
162 report->samples_size = 0;
163 report->samples_count = 0;
164
165 if (is_raw) {
166 parse_double (report->stats.ticks_per_ms);
167 skip_space ();
168
169 report->samples_size = 5;
170 report->samples = xmalloc (report->samples_size * sizeof (cairo_time_t));
171 report->stats.min_ticks = 0;
172 do {
173 if (report->samples_count == report->samples_size) {
174 report->samples_size *= 2;
175 report->samples = xrealloc (report->samples,
176 report->samples_size * sizeof (cairo_time_t));
177 }
178 parse_long_long (report->samples[report->samples_count]);
179 if (report->samples_count == 0) {
180 report->stats.min_ticks =
181 report->samples[report->samples_count];
182 } else if (report->stats.min_ticks >
183 report->samples[report->samples_count]){
184 report->stats.min_ticks =
185 report->samples[report->samples_count];
186 }
187 report->samples_count++;
188 skip_space ();
189 } while (*s && *s != '\n');
190 report->stats.iterations = 0;
191 if (*s) skip_char ('\n');
192 } else {
193 parse_double (report->stats.min_ticks);
194 skip_space ();
195
196 parse_double (min_time);
197 report->stats.ticks_per_ms = report->stats.min_ticks / min_time;
198
199 skip_space ();
200
201 parse_double (median_time);
202 report->stats.median_ticks = median_time * report->stats.ticks_per_ms;
203
204 skip_space ();
205
206 parse_double (report->stats.std_dev);
207 report->stats.std_dev /= 100.0;
208 skip_char ('%');
209
210 skip_space ();
211
212 parse_int (report->stats.iterations);
213
214 skip_space ();
215 skip_char ('\n');
216 }
217
218 return TEST_REPORT_STATUS_SUCCESS;
219 }
220
221 /* We provide hereafter a win32 implementation of the basename
222 * and strtoll functions which are not available otherwise.
223 * The basename function is fully compliant to its GNU specs.
224 */
225 #ifdef _MSC_VER
226 long long
strtoll(const char * nptr,char ** endptr,int base)227 strtoll (const char *nptr,
228 char **endptr,
229 int base)
230 {
231 return _atoi64(nptr);
232 }
233
234 static char *
basename(char * path)235 basename (char *path)
236 {
237 char *end, *s;
238
239 end = (path + strlen(path) - 1);
240 while (end && (end >= path + 1) && (*end == '/')) {
241 *end = '\0';
242 end--;
243 }
244
245 s = strrchr(path, '/');
246 if (s) {
247 if (s == end) {
248 return s;
249 } else {
250 return s+1;
251 }
252 } else {
253 return path;
254 }
255 }
256 #endif /* ifndef _MSC_VER */
257
258 int
test_report_cmp_backend_then_name(const void * a,const void * b)259 test_report_cmp_backend_then_name (const void *a,
260 const void *b)
261 {
262 const test_report_t *a_test = a;
263 const test_report_t *b_test = b;
264
265 int cmp;
266
267 cmp = strcmp (a_test->backend, b_test->backend);
268 if (cmp)
269 return cmp;
270
271 cmp = strcmp (a_test->content, b_test->content);
272 if (cmp)
273 return cmp;
274
275 /* A NULL name is a list-termination marker, so force it last. */
276 if (a_test->name == NULL)
277 if (b_test->name == NULL)
278 return 0;
279 else
280 return 1;
281 else if (b_test->name == NULL)
282 return -1;
283
284 cmp = strcmp (a_test->name, b_test->name);
285 if (cmp)
286 return cmp;
287
288 if (a_test->size < b_test->size)
289 return -1;
290 if (a_test->size > b_test->size)
291 return 1;
292
293 return 0;
294 }
295
296 int
test_report_cmp_name(const void * a,const void * b)297 test_report_cmp_name (const void *a,
298 const void *b)
299 {
300 const test_report_t *a_test = a;
301 const test_report_t *b_test = b;
302
303 int cmp;
304
305 /* A NULL name is a list-termination marker, so force it last. */
306 if (a_test->name == NULL)
307 if (b_test->name == NULL)
308 return 0;
309 else
310 return 1;
311 else if (b_test->name == NULL)
312 return -1;
313
314 cmp = strcmp (a_test->name, b_test->name);
315 if (cmp)
316 return cmp;
317
318 if (a_test->size < b_test->size)
319 return -1;
320 if (a_test->size > b_test->size)
321 return 1;
322
323 return 0;
324 }
325
326 void
cairo_perf_report_sort_and_compute_stats(cairo_perf_report_t * report,int (* cmp)(const void *,const void *))327 cairo_perf_report_sort_and_compute_stats (cairo_perf_report_t *report,
328 int (*cmp) (const void*, const void*))
329 {
330 test_report_t *base, *next, *last, *t;
331
332 if (cmp == NULL)
333 cmp = test_report_cmp_backend_then_name;
334
335 /* First we sort, since the diff needs both lists in the same
336 * order */
337 qsort (report->tests, report->tests_count, sizeof (test_report_t), cmp);
338
339 /* The sorting also brings all related raw reports together so we
340 * can condense them and compute the stats.
341 */
342 base = &report->tests[0];
343 last = &report->tests[report->tests_count - 1];
344 while (base <= last) {
345 next = base+1;
346 if (next <= last) {
347 while (next <= last &&
348 test_report_cmp_backend_then_name (base, next) == 0)
349 {
350 next++;
351 }
352 if (next != base) {
353 unsigned int new_samples_count = base->samples_count;
354 for (t = base + 1; t < next; t++)
355 new_samples_count += t->samples_count;
356 if (new_samples_count > base->samples_size) {
357 base->samples_size = new_samples_count;
358 base->samples = xrealloc (base->samples,
359 base->samples_size * sizeof (cairo_time_t));
360 }
361 for (t = base + 1; t < next; t++) {
362 memcpy (&base->samples[base->samples_count], t->samples,
363 t->samples_count * sizeof (cairo_time_t));
364 base->samples_count += t->samples_count;
365 }
366 }
367 }
368 if (base->samples)
369 _cairo_stats_compute (&base->stats, base->samples, base->samples_count);
370 base = next;
371 }
372 }
373
374 void
cairo_perf_report_load(cairo_perf_report_t * report,const char * filename,int id,int (* cmp)(const void *,const void *))375 cairo_perf_report_load (cairo_perf_report_t *report,
376 const char *filename, int id,
377 int (*cmp) (const void *, const void *))
378 {
379 FILE *file;
380 test_report_status_t status;
381 int line_number = 0;
382 char *line = NULL;
383 size_t line_size = 0;
384 char *configuration;
385 char *dot;
386 char *baseName;
387 const char *name;
388
389 name = filename;
390 if (name == NULL)
391 name = "stdin";
392
393 configuration = xstrdup (name);
394 baseName = basename (configuration);
395 report->configuration = xstrdup (baseName);
396 free (configuration);
397
398 dot = strrchr (report->configuration, '.');
399 if (dot)
400 *dot = '\0';
401
402 report->name = name;
403 report->tests_size = 16;
404 report->tests = xmalloc (report->tests_size * sizeof (test_report_t));
405 report->tests_count = 0;
406 report->fileno = id;
407
408 if (filename == NULL) {
409 file = stdin;
410 } else {
411 file = fopen (filename, "r");
412 if (file == NULL) {
413 fprintf (stderr, "Failed to open %s: %s\n",
414 filename, strerror (errno));
415 exit (1);
416 }
417 }
418
419 while (1) {
420 if (report->tests_count == report->tests_size) {
421 report->tests_size *= 2;
422 report->tests = xrealloc (report->tests,
423 report->tests_size * sizeof (test_report_t));
424 }
425
426 line_number++;
427 if (getline (&line, &line_size, file) == -1)
428 break;
429
430 status = test_report_parse (&report->tests[report->tests_count],
431 id, line, report->configuration);
432 if (status == TEST_REPORT_STATUS_ERROR)
433 fprintf (stderr, "Ignoring unrecognized line %d of %s:\n%s",
434 line_number, filename, line);
435 if (status == TEST_REPORT_STATUS_SUCCESS)
436 report->tests_count++;
437 /* Do nothing on TEST_REPORT_STATUS_COMMENT */
438 }
439
440 free (line);
441
442 if (filename != NULL)
443 fclose (file);
444
445 cairo_perf_report_sort_and_compute_stats (report, cmp);
446
447 /* Add one final report with a NULL name to terminate the list. */
448 if (report->tests_count == report->tests_size) {
449 report->tests_size *= 2;
450 report->tests = xrealloc (report->tests,
451 report->tests_size * sizeof (test_report_t));
452 }
453 report->tests[report->tests_count].name = NULL;
454 }
455