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