2 * Copyright © 2006 Red Hat, Inc.
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
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
25 * Authors: Carl Worth <cworth@cworth.org>
28 #include "cairo-perf.h"
29 #include "cairo-stats.h"
31 /* We use _GNU_SOURCE for getline and strndup if available. */
46 /* 'ssize_t' does not exist in the C standard on win32.
47 * We use 'ptrdiff_t', which is nearly equivalent. */
49 typedef ptrdiff_t ssize_t
;
54 getline (char **lineptr
, size_t *n
, FILE *stream
);
57 strndup (const char *s
, size_t n
);
62 strtoll(const char *nptr
, char **endptr
, int base
);
68 /* Ad-hoc parsing, macros with a strong dependence on the calling
69 * context, and plenty of other ugliness is here. But at least it's
71 #define parse_error(...) fprintf(stderr, __VA_ARGS__); return TEST_REPORT_STATUS_ERROR;
72 #define skip_char(c) \
74 if (*s && *s == (c)) { \
77 parse_error ("expected '%c' but found '%c'", c, *s); \
80 #define skip_space() while (*s && (*s == ' ' || *s == '\t')) s++;
81 #define parse_int(result) \
83 (result) = strtol (s, &end, 10); \
84 if (*s && end != s) { \
87 parse_error("expected integer but found %s", s); \
90 #define parse_long_long(result) \
92 (result) = strtoll (s, &end, 10); \
93 if (*s && end != s) { \
96 parse_error("expected integer but found %s", s); \
99 #define parse_double(result) \
101 (result) = strtod (s, &end); \
102 if (*s && end != s) { \
105 parse_error("expected floating-point value but found %s", s); \
108 /* Here a string is simply a sequence of non-whitespace */
109 #define parse_string(result) \
111 for (end = s; *end; end++) \
112 if (isspace (*end)) \
114 (result) = strndup (s, end - s); \
115 if ((result) == NULL) { \
116 fprintf (stderr, "Out of memory.\n"); \
122 static test_report_status_t
123 test_report_parse (test_report_t
*report
, char *line
, char *configuration
)
127 cairo_bool_t is_raw
= FALSE
;
128 double min_time
, median_time
;
130 /* The code here looks funny unless you understand that these are
131 * all macro calls, (and then the code just looks sick). */
133 return TEST_REPORT_STATUS_COMMENT
;
138 return TEST_REPORT_STATUS_COMMENT
;
143 parse_int (report
->id
);
149 report
->configuration
= configuration
;
150 parse_string (report
->backend
);
151 end
= strrchr (report
->backend
, '.');
154 report
->content
= end
;
158 parse_string (report
->name
);
159 end
= strrchr (report
->name
, '.');
162 report
->size
= atoi (end
);
166 report
->samples
= NULL
;
167 report
->samples_size
= 0;
168 report
->samples_count
= 0;
171 parse_double (report
->stats
.ticks_per_ms
);
174 report
->samples_size
= 5;
175 report
->samples
= xmalloc (report
->samples_size
* sizeof (cairo_perf_ticks_t
));
176 report
->stats
.min_ticks
= 0;
178 if (report
->samples_count
== report
->samples_size
) {
179 report
->samples_size
*= 2;
180 report
->samples
= xrealloc (report
->samples
,
181 report
->samples_size
* sizeof (cairo_perf_ticks_t
));
183 parse_long_long (report
->samples
[report
->samples_count
]);
184 if (report
->samples_count
== 0) {
185 report
->stats
.min_ticks
=
186 report
->samples
[report
->samples_count
];
187 } else if (report
->stats
.min_ticks
>
188 report
->samples
[report
->samples_count
]){
189 report
->stats
.min_ticks
=
190 report
->samples
[report
->samples_count
];
192 report
->samples_count
++;
194 } while (*s
&& *s
!= '\n');
195 report
->stats
.iterations
= 0;
198 parse_double (report
->stats
.min_ticks
);
201 parse_double (min_time
);
202 report
->stats
.ticks_per_ms
= report
->stats
.min_ticks
/ min_time
;
206 parse_double (median_time
);
207 report
->stats
.median_ticks
= median_time
* report
->stats
.ticks_per_ms
;
211 parse_double (report
->stats
.std_dev
);
212 report
->stats
.std_dev
/= 100.0;
217 parse_int (report
->stats
.iterations
);
223 return TEST_REPORT_STATUS_SUCCESS
;
226 /* We conditionally provide a custom implementation of getline and strndup
227 * as needed. These aren't necessary full-fledged general purpose
228 * implementations. They just get the job done for our purposes.
231 #define POORMANS_GETLINE_BUFFER_SIZE (65536)
233 getline (char **lineptr
, size_t *n
, FILE *stream
)
237 *n
= POORMANS_GETLINE_BUFFER_SIZE
;
238 *lineptr
= (char *) malloc (*n
);
241 if (!fgets (*lineptr
, *n
, stream
))
244 if (!feof (stream
) && !strchr (*lineptr
, '\n'))
246 fprintf (stderr
, "The poor man's implementation of getline in "
247 __FILE__
" needs a bigger buffer. Perhaps it's "
248 "time for a complete implementation of getline.\n");
252 return strlen (*lineptr
);
254 #undef POORMANS_GETLINE_BUFFER_SIZE
257 strndup (const char *s
, size_t n
)
266 len
= (n
< len
? n
: len
);
267 sdup
= (char *) malloc (len
+ 1);
270 memcpy (sdup
, s
, len
);
276 #endif /* ifndef __USE_GNU */
278 /* We provide hereafter a win32 implementation of the basename
279 * and strtoll functions which are not available otherwise.
280 * The basename function is fully compliant to its GNU specs.
284 strtoll(const char *nptr
, char **endptr
, int base
)
286 return _atoi64(nptr
);
294 end
= (path
+ strlen(path
) - 1);
295 while (end
&& (end
>= path
+ 1) && (*end
== '/')) {
300 s
= strrchr(path
, '/');
311 #endif /* ifndef _MSC_VER */
314 test_report_cmp_backend_then_name (const void *a
, const void *b
)
316 const test_report_t
*a_test
= a
;
317 const test_report_t
*b_test
= b
;
321 cmp
= strcmp (a_test
->backend
, b_test
->backend
);
325 cmp
= strcmp (a_test
->content
, b_test
->content
);
329 /* A NULL name is a list-termination marker, so force it last. */
330 if (a_test
->name
== NULL
)
331 if (b_test
->name
== NULL
)
335 else if (b_test
->name
== NULL
)
338 cmp
= strcmp (a_test
->name
, b_test
->name
);
342 if (a_test
->size
< b_test
->size
)
344 if (a_test
->size
> b_test
->size
)
351 test_report_cmp_name (const void *a
, const void *b
)
353 const test_report_t
*a_test
= a
;
354 const test_report_t
*b_test
= b
;
358 /* A NULL name is a list-termination marker, so force it last. */
359 if (a_test
->name
== NULL
)
360 if (b_test
->name
== NULL
)
364 else if (b_test
->name
== NULL
)
367 cmp
= strcmp (a_test
->name
, b_test
->name
);
371 if (a_test
->size
< b_test
->size
)
373 if (a_test
->size
> b_test
->size
)
380 cairo_perf_report_sort_and_compute_stats (cairo_perf_report_t
*report
,
381 int (*cmp
) (const void*, const void*))
383 test_report_t
*base
, *next
, *last
, *t
;
386 cmp
= test_report_cmp_backend_then_name
;
388 /* First we sort, since the diff needs both lists in the same
390 qsort (report
->tests
, report
->tests_count
, sizeof (test_report_t
), cmp
);
392 /* The sorting also brings all related raw reports together so we
393 * can condense them and compute the stats.
395 base
= &report
->tests
[0];
396 last
= &report
->tests
[report
->tests_count
- 1];
397 while (base
<= last
) {
400 while (next
<= last
&&
401 test_report_cmp_backend_then_name (base
, next
) == 0)
406 unsigned int new_samples_count
= base
->samples_count
;
407 for (t
= base
+ 1; t
< next
; t
++)
408 new_samples_count
+= t
->samples_count
;
409 if (new_samples_count
> base
->samples_size
) {
410 base
->samples_size
= new_samples_count
;
411 base
->samples
= xrealloc (base
->samples
,
412 base
->samples_size
* sizeof (cairo_perf_ticks_t
));
414 for (t
= base
+ 1; t
< next
; t
++) {
415 memcpy (&base
->samples
[base
->samples_count
], t
->samples
,
416 t
->samples_count
* sizeof (cairo_perf_ticks_t
));
417 base
->samples_count
+= t
->samples_count
;
422 _cairo_stats_compute (&base
->stats
, base
->samples
, base
->samples_count
);
428 cairo_perf_report_load (cairo_perf_report_t
*report
,
429 const char *filename
,
430 int (*cmp
) (const void *, const void *))
433 test_report_status_t status
;
436 size_t line_size
= 0;
441 configuration
= xmalloc (strlen (filename
) * sizeof (char) + 1);
442 strcpy (configuration
, filename
);
443 baseName
= basename (configuration
);
444 report
->configuration
= xmalloc (strlen (baseName
) * sizeof (char) + 1);
445 strcpy (report
->configuration
, baseName
);
446 free (configuration
);
448 dot
= strrchr (report
->configuration
, '.');
452 report
->name
= filename
;
453 report
->tests_size
= 16;
454 report
->tests
= xmalloc (report
->tests_size
* sizeof (test_report_t
));
455 report
->tests_count
= 0;
457 file
= fopen (filename
, "r");
459 fprintf (stderr
, "Failed to open %s: %s\n",
460 filename
, strerror (errno
));
465 if (report
->tests_count
== report
->tests_size
) {
466 report
->tests_size
*= 2;
467 report
->tests
= xrealloc (report
->tests
,
468 report
->tests_size
* sizeof (test_report_t
));
472 if (getline (&line
, &line_size
, file
) == -1)
475 status
= test_report_parse (&report
->tests
[report
->tests_count
],
476 line
, report
->configuration
);
477 if (status
== TEST_REPORT_STATUS_ERROR
)
478 fprintf (stderr
, "Ignoring unrecognized line %d of %s:\n%s",
479 line_number
, filename
, line
);
480 if (status
== TEST_REPORT_STATUS_SUCCESS
)
481 report
->tests_count
++;
482 /* Do nothing on TEST_REPORT_STATUS_COMMENT */
490 cairo_perf_report_sort_and_compute_stats (report
, cmp
);
492 /* Add one final report with a NULL name to terminate the list. */
493 if (report
->tests_count
== report
->tests_size
) {
494 report
->tests_size
*= 2;
495 report
->tests
= xrealloc (report
->tests
,
496 report
->tests_size
* sizeof (test_report_t
));
498 report
->tests
[report
->tests_count
].name
= NULL
;