Upload Tizen2.0 source
[framework/graphics/cairo.git] / perf / cairo-perf-chart.c
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 struct chart {
41     cairo_perf_report_t *reports;
42     const char **names;
43
44     cairo_t *cr;
45     int width, height;
46     int num_tests, num_reports;
47     double min_value, max_value;
48
49     cairo_bool_t use_html;
50     cairo_bool_t relative;
51 };
52 struct color {
53     double red, green, blue;
54 };
55
56 #define FONT_SIZE 12
57 #define PAD (4)
58
59 #define MAX(a,b) ((a) > (b) ? (a) : (b))
60
61 static double
62 to_factor (double x)
63 {
64 #if 1
65     if (x > 1.)
66         return (x-1) * 100.;
67     else
68         return (1. - 1./x) * 100.;
69 #else
70     return log (x);
71 #endif
72 }
73
74 static int
75 _double_cmp (const void *_a,
76              const void *_b)
77 {
78     const double *a = _a;
79     const double *b = _b;
80
81     if (*a > *b)
82         return 1;
83     if (*a < *b)
84         return -1;
85     return 0;
86 }
87
88 static void
89 trim_outliers (double *values,
90                int     num_values,
91                double *min,
92                double *max)
93 {
94     double q1, q3, iqr;
95     double outlier_min, outlier_max;
96     int i;
97
98     /* First, identify any outliers, using the definition of "mild
99      * outliers" from:
100      *
101      *          http://en.wikipedia.org/wiki/Outliers
102      *
103      * Which is that outliers are any values less than Q1 - 1.5 * IQR
104      * or greater than Q3 + 1.5 * IQR where Q1 and Q3 are the first
105      * and third quartiles and IQR is the inter-quartile range (Q3 -
106      * Q1).
107      */
108     qsort (values, num_values,
109            sizeof (double), _double_cmp);
110
111     q1          = values[1*num_values / 6];
112     q3          = values[5*num_values / 6];
113
114     iqr = q3 - q1;
115
116     outlier_min = q1 - 3 * iqr;
117     outlier_max = q3 + 3 * iqr;
118
119     i = 0;
120     while (i < num_values && values[i] < outlier_min)
121         i++;
122     if (i == num_values)
123         return;
124
125     *min = values[i];
126
127     while (i < num_values && values[i] <= outlier_max)
128         i++;
129
130     *max = values[i-1];
131 }
132
133 static void
134 find_ranges (struct chart *chart)
135 {
136     test_report_t **tests, *min_test;
137     double *values;
138     int num_values, size_values;
139     double min = 0, max = 0;
140     double test_time;
141     int seen_non_null;
142     int num_tests = 0;
143     double slow_sum = 0, fast_sum = 0, sum;
144     int slow_count = 0, fast_count = 0;
145     int i;
146
147     num_values = 0;
148     size_values = 64;
149     values = xmalloc (size_values * sizeof (double));
150
151     tests = xmalloc (chart->num_reports * sizeof (test_report_t *));
152     for (i = 0; i < chart->num_reports; i++)
153         tests[i] = chart->reports[i].tests;
154
155     while (1) {
156         /* We expect iterations values of 0 when multiple raw reports
157          * for the same test have been condensed into the stats of the
158          * first. So we just skip these later reports that have no
159          * stats. */
160         seen_non_null = 0;
161         for (i = 0; i < chart->num_reports; i++) {
162             while (tests[i]->name && tests[i]->stats.iterations == 0)
163                 tests[i]++;
164             if (tests[i]->name)
165                 seen_non_null++;
166         }
167         if (! seen_non_null)
168             break;
169
170         num_tests++;
171
172         /* Find the minimum of all current tests, (we have to do this
173          * in case some reports don't have a particular test). */
174         for (i = 0; i < chart->num_reports; i++) {
175             if (tests[i]->name) {
176                 min_test = tests[i];
177                 break;
178             }
179         }
180         for (++i; i < chart->num_reports; i++) {
181             if (tests[i]->name && test_report_cmp_name (tests[i], min_test) < 0)
182                 min_test = tests[i];
183         }
184
185         test_time = 0;
186         for (i = 0; i < chart->num_reports; i++) {
187             double report_time = HUGE_VAL;
188
189             while (tests[i]->name &&
190                    test_report_cmp_name (tests[i], min_test) == 0)
191             {
192                 double time = tests[i]->stats.min_ticks;
193                 if (time < report_time) {
194                     time /= tests[i]->stats.ticks_per_ms;
195                     if (time < report_time)
196                         report_time = time;
197                 }
198                 tests[i]++;
199             }
200
201             if (report_time != HUGE_VAL) {
202                 if (test_time == 0)
203                     test_time = report_time;
204
205                 if (chart->relative) {
206                     if (test_time != report_time) {
207                         double v = to_factor (test_time / report_time);
208                         if (num_values == size_values) {
209                             size_values *= 2;
210                             values = xrealloc (values,
211                                                size_values * sizeof (double));
212                         }
213                         values[num_values++] = v;
214                         if (v < min)
215                             min = v;
216                         if (v > max)
217                             max = v;
218                         if (v > 0)
219                             fast_sum += v/100, fast_count++;
220                         else
221                             slow_sum += v/100, slow_count++;
222                         sum += v/100;
223                         printf ("%s %d: %f\n", min_test->name, num_values, v);
224                     }
225                 } else {
226                     if (report_time < min)
227                         min = report_time;
228                     if (report_time > max)
229                         max = report_time;
230                 }
231             }
232         }
233     }
234
235     if (chart->relative)
236         trim_outliers (values, num_values, &min, &max);
237     chart->min_value = min;
238     chart->max_value = max;
239     chart->num_tests = num_tests;
240
241     free (values);
242     free (tests);
243
244     printf ("%d: slow[%d] average: %f, fast[%d] average: %f, %f\n",
245             num_values, slow_count, slow_sum / slow_count, fast_count, fast_sum / fast_count, sum / num_values);
246 }
247
248 #define SET_COLOR(C, R, G, B) (C)->red = (R), (C)->green = (G), (C)->blue = (B)
249 static void
250 hsv_to_rgb (double        h,
251             double        s,
252             double        v,
253             struct color *color)
254 {
255     double m, n, f;
256     int i;
257
258     while (h < 0)
259         h += 6.;
260     while (h > 6.)
261         h -= 6.;
262
263     if (s < 0.)
264         s = 0.;
265     if (s > 1.)
266         s = 1.;
267
268     if (v < 0.)
269         v = 0.;
270     if (v > 1.)
271         v = 1.;
272
273     i = floor (h);
274     f = h - i;
275     if ((i & 1) == 0)
276         f = 1 - f;
277
278     m = v * (1 - s);
279     n = v * (1 - s * f);
280     switch(i){
281     default:
282     case 6:
283     case 0: SET_COLOR (color, v, n, m); break;
284     case 1: SET_COLOR (color, n, v, m); break;
285     case 2: SET_COLOR (color, m, v, n); break;
286     case 3: SET_COLOR (color, m, n, v); break;
287     case 4: SET_COLOR (color, n, m, v); break;
288     case 5: SET_COLOR (color, v, m, n); break;
289     }
290 }
291
292 static void set_report_color (struct chart *chart, int report)
293 {
294     struct color color;
295
296     hsv_to_rgb (6. / chart->num_reports * report, .7, .7, &color);
297     cairo_set_source_rgb (chart->cr, color.red, color.green, color.blue);
298 }
299
300 static void set_report_gradient (struct chart *chart, int report,
301                                  double x, double y, double w, double h)
302 {
303     struct color color;
304     cairo_pattern_t *p;
305
306     hsv_to_rgb (6. / chart->num_reports * report, .7, .7, &color);
307
308     p = cairo_pattern_create_linear (x, 0, x+w, 0);
309     cairo_pattern_add_color_stop_rgba (p, 0.0,
310                                        color.red, color.green, color.blue,
311                                        .50);
312     cairo_pattern_add_color_stop_rgba (p, 0.5,
313                                        color.red, color.green, color.blue,
314                                        .50);
315     cairo_pattern_add_color_stop_rgba (p, 1.0,
316                                        color.red, color.green, color.blue,
317                                        1.0);
318     cairo_set_source (chart->cr, p);
319     cairo_pattern_destroy (p);
320 }
321
322 static void
323 test_background (struct chart *c,
324                  int           test)
325 {
326     double dx, x;
327
328     dx = c->width / (double) c->num_tests;
329     x = dx * test;
330
331     if (test & 1)
332         cairo_set_source_rgba (c->cr, .2, .2, .2, .2);
333     else
334         cairo_set_source_rgba (c->cr, .8, .8, .8, .2);
335
336     cairo_rectangle (c->cr, floor (x), 0,
337                      floor (dx + x) - floor (x), c->height);
338     cairo_fill (c->cr);
339 }
340
341 static void
342 add_chart (struct chart *c,
343            int           test,
344            int           report,
345            double        value)
346 {
347     double dx, dy, x;
348
349     if (fabs (value) < 0.1)
350         return;
351
352     if (c->relative) {
353         cairo_text_extents_t extents;
354         char buf[80];
355         double y;
356
357         dy = (c->height/2. - PAD) / MAX (-c->min_value, c->max_value);
358         /* the first report is always skipped, as it is used as the baseline */
359         dx = c->width / (double) (c->num_tests * c->num_reports);
360         x = dx * (c->num_reports * test + report - .5);
361
362         set_report_gradient (c, report,
363                              floor (x), c->height / 2.,
364                              floor (x + dx) - floor (x),
365                              ceil (-dy*value - c->height/2.) + c->height/2.);
366
367         cairo_rectangle (c->cr,
368                          floor (x), c->height / 2.,
369                          floor (x + dx) - floor (x),
370                          ceil (-dy*value - c->height/2.) + c->height/2.);
371         cairo_fill_preserve (c->cr);
372         cairo_save (c->cr);
373         cairo_clip_preserve (c->cr);
374         set_report_color (c, report);
375         cairo_stroke (c->cr);
376         cairo_restore (c->cr);
377
378         /* Skip the label if the difference between the two is less than 0.1% */
379         if (fabs (value) < 0.1)
380                 return;
381
382         cairo_save (c->cr);
383         cairo_set_font_size (c->cr, dx - 2);
384
385         if (value < 0) {
386             sprintf (buf, "%.1f", -value/100 + 1);
387         } else {
388             sprintf (buf, "%.1f", value/100 + 1);
389         }
390         cairo_text_extents (c->cr, buf, &extents);
391
392         /* will it be clipped? */
393         y = -dy * value;
394         if (y < -c->height/2) {
395             y = -c->height/2;
396         } else if (y > c->height/2) {
397             y = c->height/2;
398         }
399
400         if (y < 0) {
401             if (y > -extents.width - 6)
402                     y -= extents.width + 6;
403         } else {
404             if (y < extents.width + 6)
405                     y += extents.width + 6;
406         }
407
408         cairo_translate (c->cr,
409                          floor (x) + (floor (x + dx) - floor (x))/2,
410                          floor (y) + c->height/2.);
411         cairo_rotate (c->cr, -M_PI/2);
412         if (y < 0) {
413             cairo_move_to (c->cr, -extents.x_bearing -extents.width - 4, -extents.y_bearing/2);
414         } else {
415             cairo_move_to (c->cr, 2, -extents.y_bearing/2);
416         }
417
418         cairo_set_source_rgb (c->cr, .95, .95, .95);
419         cairo_show_text (c->cr, buf);
420         cairo_restore (c->cr);
421     } else {
422         dy = (c->height - PAD) / c->max_value;
423         dx = c->width / (double) (c->num_tests * (c->num_reports+1));
424         x = dx * ((c->num_reports+1) * test + report + .5);
425
426         set_report_gradient (c, report,
427                          floor (x), c->height,
428                          floor (x + dx) - floor (x),
429                          floor (c->height - dy*value) - c->height);
430
431         cairo_rectangle (c->cr,
432                          floor (x), c->height,
433                          floor (x + dx) - floor (x),
434                          floor (c->height - dy*value) - c->height);
435         cairo_fill_preserve (c->cr);
436         cairo_save (c->cr);
437         cairo_clip_preserve (c->cr);
438         set_report_color (c, report);
439         cairo_stroke (c->cr);
440         cairo_restore (c->cr);
441     }
442 }
443
444 static void
445 add_label (struct chart *c,
446            int           test,
447            const char   *label)
448 {
449     cairo_text_extents_t extents;
450     double dx, x;
451
452     cairo_save (c->cr);
453     dx = c->width / (double) c->num_tests;
454     if (dx / 2 - PAD < 4)
455         return;
456     cairo_set_font_size (c->cr, dx / 2 - PAD);
457     cairo_text_extents (c->cr, label, &extents);
458
459     cairo_set_source_rgb (c->cr, .5, .5, .5);
460
461     x = (test + .5) * dx;
462     cairo_save (c->cr);
463     cairo_translate (c->cr, x, c->height - PAD / 2);
464     cairo_rotate (c->cr, -M_PI/2);
465     cairo_move_to (c->cr, 0, -extents.y_bearing/2);
466     cairo_show_text (c->cr, label);
467     cairo_restore (c->cr);
468
469     cairo_save (c->cr);
470     cairo_translate (c->cr, x, PAD / 2);
471     cairo_rotate (c->cr, -M_PI/2);
472     cairo_move_to (c->cr, -extents.width, -extents.y_bearing/2);
473     cairo_show_text (c->cr, label);
474     cairo_restore (c->cr);
475
476     cairo_restore (c->cr);
477 }
478
479 static void
480 add_base_line (struct chart *c)
481 {
482     double y;
483
484     cairo_save (c->cr);
485     cairo_set_line_width (c->cr, 2.);
486     if (c->relative) {
487         y = c->height / 2.;
488     } else {
489         y = c->height;
490     }
491     cairo_move_to (c->cr, 0, y);
492     cairo_line_to (c->cr, c->width, y);
493     cairo_set_source_rgb (c->cr, 1, 1, 1);
494     cairo_stroke (c->cr);
495     cairo_restore (c->cr);
496 }
497
498 static void
499 add_absolute_lines (struct chart *c)
500 {
501     const double dashes[] = { 2, 4 };
502     const double vlog_steps[] = { 10, 5, 4, 3, 2, 1, .5, .4, .3, .2, .1};
503     double v, y, dy;
504     unsigned int i;
505     char buf[80];
506     cairo_text_extents_t extents;
507
508     v = c->max_value / 2.;
509
510     for (i = 0; i < sizeof (vlog_steps) / sizeof (vlog_steps[0]); i++) {
511         double vlog = log (v) / log (vlog_steps[i]);
512         if (vlog > 1) {
513             v = pow (vlog_steps[i], floor (vlog));
514             goto done;
515         }
516     }
517     return;
518 done:
519
520     dy = (c->height - PAD) / c->max_value;
521
522     cairo_save (c->cr);
523     cairo_set_line_width (c->cr, 1.);
524     cairo_set_dash (c->cr, dashes, sizeof (dashes) / sizeof (dashes[0]), 0);
525
526     i = 0;
527     do {
528         y = c->height - ++i * v * dy;
529         if (y < PAD)
530             break;
531
532         cairo_set_font_size (c->cr, 8);
533
534         sprintf (buf, "%.0fs", i*v/1000);
535         cairo_text_extents (c->cr, buf, &extents);
536
537         cairo_set_source_rgba (c->cr, .75, 0, 0, .95);
538         cairo_move_to (c->cr, 1-extents.x_bearing, floor (y) - (extents.height/2 + extents.y_bearing) + .5);
539         cairo_show_text (c->cr, buf);
540
541         cairo_move_to (c->cr, c->width-extents.width-1, floor (y) - (extents.height/2 + extents.y_bearing) + .5);
542         cairo_show_text (c->cr, buf);
543
544         cairo_set_source_rgba (c->cr, .75, 0, 0, .5);
545         cairo_move_to (c->cr,
546                        ceil (extents.width + extents.x_bearing + 2),
547                        floor (y) + .5);
548         cairo_line_to (c->cr,
549                        floor (c->width - (extents.width + extents.x_bearing + 2)),
550                        floor (y) + .5);
551         cairo_stroke (c->cr);
552     } while (1);
553
554     cairo_restore (c->cr);
555 }
556
557 static void
558 add_relative_lines (struct chart *c)
559 {
560     const double dashes[] = { 2, 4 };
561     const double v_steps[] = { 10, 5, 1, .5, .1, .05, .01};
562     const int precision_steps[] = { 0, 0, 0, 1, 1, 2, 2};
563     int precision;
564     double v, y, dy, mid;
565     unsigned int i;
566     char buf[80];
567     cairo_text_extents_t extents;
568
569     v = MAX (-c->min_value, c->max_value) / 200;
570
571     for (i = 0; i < sizeof (v_steps) / sizeof (v_steps[0]); i++) {
572         if (v > v_steps[i]) {
573             v = v_steps[i];
574             precision = precision_steps[i];
575             goto done;
576         }
577     }
578     return;
579 done:
580
581     mid = c->height/2.;
582     dy = (mid - PAD) / MAX (-c->min_value, c->max_value);
583
584     cairo_save (c->cr);
585     cairo_set_line_width (c->cr, 1.);
586     cairo_set_dash (c->cr, dashes, sizeof (dashes) / sizeof (dashes[0]), 0);
587     cairo_set_font_size (c->cr, 8);
588
589     i = 0;
590     do {
591         y = ++i * v * dy * 100;
592         if (y > mid)
593             break;
594
595         sprintf (buf, "%.*fx", precision, i*v + 1);
596         cairo_text_extents (c->cr, buf, &extents);
597
598         cairo_set_source_rgba (c->cr, .75, 0, 0, .95);
599         cairo_move_to (c->cr, 1-extents.x_bearing, floor (mid + y) - (extents.height/2 + extents.y_bearing) + .5);
600         cairo_show_text (c->cr, buf);
601
602         cairo_move_to (c->cr, c->width-extents.width-1, floor (mid + y) - (extents.height/2 + extents.y_bearing) + .5);
603         cairo_show_text (c->cr, buf);
604
605         cairo_set_source_rgba (c->cr, 0, .75, 0, .95);
606         cairo_move_to (c->cr, 1-extents.x_bearing, ceil (mid - y) - (extents.height/2 + extents.y_bearing) + .5);
607         cairo_show_text (c->cr, buf);
608
609         cairo_move_to (c->cr, c->width-extents.width-1, ceil (mid - y) - (extents.height/2 + extents.y_bearing) + .5);
610         cairo_show_text (c->cr, buf);
611
612         /* trim the dashes to no obscure the labels */
613         cairo_set_source_rgba (c->cr, .75, 0, 0, .5);
614         cairo_move_to (c->cr,
615                        ceil (extents.width + extents.x_bearing + 2),
616                        floor (mid + y) + .5);
617         cairo_line_to (c->cr,
618                        floor (c->width - (extents.width + 2)),
619                        floor (mid + y) + .5);
620         cairo_stroke (c->cr);
621
622         cairo_set_source_rgba (c->cr, 0, .75, 0, .5);
623         cairo_move_to (c->cr,
624                        ceil (extents.width + extents.x_bearing + 2),
625                        ceil (mid - y) + .5);
626         cairo_line_to (c->cr,
627                        floor (c->width - (extents.width + 2)),
628                        ceil (mid - y) + .5);
629         cairo_stroke (c->cr);
630
631     } while (1);
632
633     cairo_restore (c->cr);
634 }
635
636 static void
637 add_slower_faster_guide (struct chart *c)
638 {
639     cairo_text_extents_t extents;
640
641     cairo_save (c->cr);
642
643     cairo_set_font_size (c->cr, FONT_SIZE);
644
645     cairo_text_extents (c->cr, "FASTER", &extents);
646     cairo_set_source_rgba (c->cr, 0, .75, 0, .5);
647     cairo_move_to (c->cr,
648                    c->width/4. - extents.width/2. + extents.x_bearing,
649                    1 - extents.y_bearing);
650     cairo_show_text (c->cr, "FASTER");
651     cairo_move_to (c->cr,
652                    3*c->width/4. - extents.width/2. + extents.x_bearing,
653                    1 - extents.y_bearing);
654     cairo_show_text (c->cr, "FASTER");
655
656     cairo_text_extents (c->cr, "SLOWER", &extents);
657     cairo_set_source_rgba (c->cr, .75, 0, 0, .5);
658     cairo_move_to (c->cr,
659                    c->width/4. - extents.width/2. + extents.x_bearing,
660                    c->height - 1);
661     cairo_show_text (c->cr, "SLOWER");
662     cairo_move_to (c->cr,
663                    3*c->width/4. - extents.width/2. + extents.x_bearing,
664                    c->height - 1);
665     cairo_show_text (c->cr, "SLOWER");
666
667     cairo_restore (c->cr);
668 }
669
670 static void
671 cairo_perf_reports_compare (struct chart *chart,
672                             cairo_bool_t  print)
673 {
674     test_report_t **tests, *min_test;
675     double test_time, best_time;
676     int num_test = 0;
677     int seen_non_null;
678     int i;
679
680     tests = xmalloc (chart->num_reports * sizeof (test_report_t *));
681     for (i = 0; i < chart->num_reports; i++)
682         tests[i] = chart->reports[i].tests;
683
684     if (print) {
685         if (chart->use_html) {
686             printf ("<table style=\"text-align:right\" cellspacing=\"4\">\n");
687             printf ("<tr><td></td>");
688             for (i = 0; i < chart->num_reports; i++) {
689                 printf ("<td>%s</td>", chart->names[i] ? chart->names[i] : "");
690             }
691             printf ("</tr>\n");
692         }
693     }
694
695     while (1) {
696         /* We expect iterations values of 0 when multiple raw reports
697          * for the same test have been condensed into the stats of the
698          * first. So we just skip these later reports that have no
699          * stats. */
700         seen_non_null = 0;
701         for (i = 0; i < chart->num_reports; i++) {
702             while (tests[i]->name && tests[i]->stats.iterations == 0)
703                 tests[i]++;
704             if (tests[i]->name)
705                 seen_non_null++;
706         }
707         if (! seen_non_null)
708             break;
709
710         /* Find the minimum of all current tests, (we have to do this
711          * in case some reports don't have a particular test). */
712         for (i = 0; i < chart->num_reports; i++) {
713             if (tests[i]->name) {
714                 min_test = tests[i];
715                 break;
716             }
717         }
718         for (++i; i < chart->num_reports; i++) {
719             if (tests[i]->name && test_report_cmp_name (tests[i], min_test) < 0)
720                 min_test = tests[i];
721         }
722
723         add_label (chart, num_test, min_test->name);
724         if (print) {
725             if (chart->use_html) {
726                 printf ("<tr><td>%s</td>", min_test->name);
727             } else {
728                 if (min_test->size) {
729                     printf ("%16s, size %4d:\n",
730                             min_test->name,
731                             min_test->size);
732                 } else {
733                     printf ("%26s:",
734                             min_test->name);
735                 }
736             }
737         }
738
739         test_time = 0;
740         best_time = HUGE_VAL;
741         for (i = 0; i < chart->num_reports; i++) {
742             test_report_t *initial = tests[i];
743             double report_time = HUGE_VAL;
744
745             while (tests[i]->name &&
746                    test_report_cmp_name (tests[i], min_test) == 0)
747             {
748                 double time = tests[i]->stats.min_ticks;
749                 if (time < report_time) {
750                     time /= tests[i]->stats.ticks_per_ms;
751                     if (time < report_time)
752                         report_time = time;
753                 }
754                 tests[i]++;
755             }
756
757             if (test_time == 0 && report_time != HUGE_VAL)
758                 test_time = report_time;
759             if (report_time < best_time)
760                 best_time = report_time;
761
762             tests[i] = initial;
763         }
764
765         for (i = 0; i < chart->num_reports; i++) {
766             double report_time = HUGE_VAL;
767
768             while (tests[i]->name &&
769                    test_report_cmp_name (tests[i], min_test) == 0)
770             {
771                 double time = tests[i]->stats.min_ticks;
772                 if (time > 0) {
773                     time /= tests[i]->stats.ticks_per_ms;
774                     if (time < report_time)
775                         report_time = time;
776                 }
777                 tests[i]++;
778             }
779
780             if (print) {
781                 if (chart->use_html) {
782                     if (report_time < HUGE_VAL) {
783                         if (report_time / best_time < 1.01) {
784                             printf ("<td><strong>%.1f</strong></td>", report_time/1000);
785                         } else {
786                             printf ("<td>%.1f</td>", report_time/1000);
787                         }
788                     } else {
789                         printf ("<td></td>");
790                     }
791                 } else {
792                     if (report_time < HUGE_VAL)
793                         printf (" %6.1f",  report_time/1000);
794                     else
795                         printf ("    ---");
796                 }
797             }
798
799             if (report_time < HUGE_VAL) {
800                 if (chart->relative) {
801                     add_chart (chart, num_test, i,
802                                to_factor (test_time / report_time));
803                 } else {
804                     add_chart (chart, num_test, i, report_time);
805                 }
806             }
807         }
808
809         if (print) {
810             if (chart->use_html) {
811                 printf ("</tr>\n");
812             } else {
813                 printf ("\n");
814             }
815         }
816
817         num_test++;
818     }
819     free (tests);
820
821     if (print) {
822         if (chart->use_html)
823             printf ("</table>\n");
824
825         printf ("\n");
826         for (i = 0; i < chart->num_reports; i++) {
827             if (chart->names[i]) {
828                 printf ("[%s] %s\n",
829                         chart->names[i], chart->reports[i].configuration);
830             } else {
831                 printf ("[%d] %s\n",
832                         i, chart->reports[i].configuration);
833             }
834         }
835     }
836 }
837
838 static void
839 add_legend (struct chart *chart)
840 {
841     cairo_text_extents_t extents;
842     const char *str;
843     int i, x, y;
844
845     cairo_set_font_size (chart->cr, FONT_SIZE);
846
847     x = PAD;
848     y = chart->height + PAD;
849     for (i = chart->relative; i < chart->num_reports; i++) {
850         str = chart->names[i] ?
851               chart->names[i] : chart->reports[i].configuration;
852
853         set_report_color (chart, i);
854
855         cairo_rectangle (chart->cr, x, y + 6, 8, 8);
856         cairo_fill (chart->cr);
857
858         cairo_set_source_rgb (chart->cr, 1, 1, 1);
859         cairo_move_to (chart->cr, x + 10, y + FONT_SIZE + PAD / 2.);
860         cairo_text_extents (chart->cr, str, &extents);
861         cairo_show_text (chart->cr, str);
862
863         x += 10 + 2 * PAD + ceil (extents.width);
864     }
865
866     if (chart->relative) {
867         char buf[80];
868
869         str = chart->names[0] ?
870               chart->names[0] : chart->reports[0].configuration;
871
872         sprintf (buf, "(relative to %s)", str);
873         cairo_text_extents (chart->cr, buf, &extents);
874
875         cairo_set_source_rgb (chart->cr, 1, 1, 1);
876         cairo_move_to (chart->cr,
877                        chart->width - 1 - extents.width,
878                        y + FONT_SIZE + PAD / 2.);
879         cairo_show_text (chart->cr, buf);
880     }
881 }
882
883 int
884 main (int         argc,
885       const char *argv[])
886 {
887     cairo_surface_t *surface;
888     struct chart chart;
889     test_report_t *t;
890     int i;
891
892     chart.use_html = 0;
893     chart.width = 640;
894     chart.height = 480;
895
896     chart.reports = xcalloc (argc-1, sizeof (cairo_perf_report_t));
897     chart.names = xcalloc (argc-1, sizeof (cairo_perf_report_t));
898
899     chart.num_reports = 0;
900     for (i = 1; i < argc; i++) {
901         if (strcmp (argv[i], "--html") == 0) {
902             chart.use_html = 1;
903         } else if (strncmp (argv[i], "--width=", 8) == 0) {
904             chart.width = atoi (argv[i] + 8);
905         } else if (strncmp (argv[i], "--height=", 9) == 0) {
906             chart.height = atoi (argv[i] + 9);
907         } else if (strcmp (argv[i], "--name") == 0) {
908             if (i + 1 < argc)
909                 chart.names[chart.num_reports] = argv[++i];
910         } else if (strncmp (argv[i], "--name=", 7) == 0) {
911             chart.names[chart.num_reports] = argv[i] + 7;
912         } else {
913             cairo_perf_report_load (&chart.reports[chart.num_reports++],
914                                     argv[i], i,
915                                     test_report_cmp_name);
916         }
917     }
918
919     for (chart.relative = 0; chart.relative <= 1; chart.relative++) {
920         surface = cairo_image_surface_create (CAIRO_FORMAT_ARGB32,
921                                               chart.width,
922                                               chart.height + (FONT_SIZE + PAD) + 2*PAD);
923         chart.cr = cairo_create (surface);
924         cairo_surface_destroy (surface);
925
926         cairo_set_source_rgb (chart.cr, 0, 0, 0);
927         cairo_paint (chart.cr);
928
929         find_ranges (&chart);
930
931         for (i = 0; i < chart.num_tests; i++)
932             test_background (&chart, i);
933         if (chart.relative) {
934             add_relative_lines (&chart);
935             add_slower_faster_guide (&chart);
936         } else
937             add_absolute_lines (&chart);
938
939         cairo_save (chart.cr);
940         cairo_rectangle (chart.cr, 0, 0, chart.width, chart.height);
941         cairo_clip (chart.cr);
942         cairo_perf_reports_compare (&chart, !chart.relative);
943         cairo_restore (chart.cr);
944
945         add_base_line (&chart);
946         add_legend (&chart);
947
948         cairo_surface_write_to_png (cairo_get_target (chart.cr),
949                                     chart.relative ?
950                                     "cairo-perf-chart-relative.png" :
951                                     "cairo-perf-chart-absolute.png");
952         cairo_destroy (chart.cr);
953     }
954
955     /* Pointless memory cleanup, (would be a great place for talloc) */
956     for (i = 0; i < chart.num_reports; i++) {
957         for (t = chart.reports[i].tests; t->name; t++) {
958             free (t->samples);
959             free (t->backend);
960             free (t->name);
961         }
962         free (chart.reports[i].tests);
963         free (chart.reports[i].configuration);
964     }
965     free (chart.names);
966     free (chart.reports);
967
968     return 0;
969 }