2 * Copyright © 2006 Red Hat, Inc.
3 * Copyright © 2009 Chris Wilson
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
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
26 * Authors: Carl Worth <cworth@cworth.org>
27 * Chris Wilson <chris@chris-wilson.co.uk>
30 #include "cairo-perf.h"
41 cairo_perf_report_t *reports;
46 int num_tests, num_reports;
47 double min_value, max_value;
49 cairo_bool_t use_html;
50 cairo_bool_t relative;
53 double red, green, blue;
59 #define MAX(a,b) ((a) > (b) ? (a) : (b))
68 return (1. - 1./x) * 100.;
75 _double_cmp (const void *_a,
89 trim_outliers (double *values,
95 double outlier_min, outlier_max;
98 /* First, identify any outliers, using the definition of "mild
101 * http://en.wikipedia.org/wiki/Outliers
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 -
108 qsort (values, num_values,
109 sizeof (double), _double_cmp);
111 q1 = values[1*num_values / 6];
112 q3 = values[5*num_values / 6];
116 outlier_min = q1 - 3 * iqr;
117 outlier_max = q3 + 3 * iqr;
120 while (i < num_values && values[i] < outlier_min)
127 while (i < num_values && values[i] <= outlier_max)
134 find_ranges (struct chart *chart)
136 test_report_t **tests, *min_test;
138 int num_values, size_values;
139 double min = 0, max = 0;
143 double slow_sum = 0, fast_sum = 0, sum;
144 int slow_count = 0, fast_count = 0;
149 values = xmalloc (size_values * sizeof (double));
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;
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
161 for (i = 0; i < chart->num_reports; i++) {
162 while (tests[i]->name && tests[i]->stats.iterations == 0)
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) {
180 for (++i; i < chart->num_reports; i++) {
181 if (tests[i]->name && test_report_cmp_name (tests[i], min_test) < 0)
186 for (i = 0; i < chart->num_reports; i++) {
187 double report_time = HUGE_VAL;
189 while (tests[i]->name &&
190 test_report_cmp_name (tests[i], min_test) == 0)
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)
201 if (report_time != HUGE_VAL) {
203 test_time = report_time;
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) {
210 values = xrealloc (values,
211 size_values * sizeof (double));
213 values[num_values++] = v;
219 fast_sum += v/100, fast_count++;
221 slow_sum += v/100, slow_count++;
223 printf ("%s %d: %f\n", min_test->name, num_values, v);
226 if (report_time < min)
228 if (report_time > max)
236 trim_outliers (values, num_values, &min, &max);
237 chart->min_value = min;
238 chart->max_value = max;
239 chart->num_tests = num_tests;
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);
248 #define SET_COLOR(C, R, G, B) (C)->red = (R), (C)->green = (G), (C)->blue = (B)
250 hsv_to_rgb (double h,
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;
292 static void set_report_color (struct chart *chart, int report)
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);
300 static void set_report_gradient (struct chart *chart, int report,
301 double x, double y, double w, double h)
306 hsv_to_rgb (6. / chart->num_reports * report, .7, .7, &color);
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,
312 cairo_pattern_add_color_stop_rgba (p, 0.5,
313 color.red, color.green, color.blue,
315 cairo_pattern_add_color_stop_rgba (p, 1.0,
316 color.red, color.green, color.blue,
318 cairo_set_source (chart->cr, p);
319 cairo_pattern_destroy (p);
323 test_background (struct chart *c,
328 dx = c->width / (double) c->num_tests;
332 cairo_set_source_rgba (c->cr, .2, .2, .2, .2);
334 cairo_set_source_rgba (c->cr, .8, .8, .8, .2);
336 cairo_rectangle (c->cr, floor (x), 0,
337 floor (dx + x) - floor (x), c->height);
342 add_chart (struct chart *c,
349 if (fabs (value) < 0.1)
353 cairo_text_extents_t extents;
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);
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.);
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);
373 cairo_clip_preserve (c->cr);
374 set_report_color (c, report);
375 cairo_stroke (c->cr);
376 cairo_restore (c->cr);
378 /* Skip the label if the difference between the two is less than 0.1% */
379 if (fabs (value) < 0.1)
383 cairo_set_font_size (c->cr, dx - 2);
386 sprintf (buf, "%.1f", -value/100 + 1);
388 sprintf (buf, "%.1f", value/100 + 1);
390 cairo_text_extents (c->cr, buf, &extents);
392 /* will it be clipped? */
394 if (y < -c->height/2) {
396 } else if (y > c->height/2) {
401 if (y > -extents.width - 6)
402 y -= extents.width + 6;
404 if (y < extents.width + 6)
405 y += extents.width + 6;
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);
413 cairo_move_to (c->cr, -extents.x_bearing -extents.width - 4, -extents.y_bearing/2);
415 cairo_move_to (c->cr, 2, -extents.y_bearing/2);
418 cairo_set_source_rgb (c->cr, .95, .95, .95);
419 cairo_show_text (c->cr, buf);
420 cairo_restore (c->cr);
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);
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);
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);
437 cairo_clip_preserve (c->cr);
438 set_report_color (c, report);
439 cairo_stroke (c->cr);
440 cairo_restore (c->cr);
445 add_label (struct chart *c,
449 cairo_text_extents_t extents;
453 dx = c->width / (double) c->num_tests;
454 if (dx / 2 - PAD < 4)
456 cairo_set_font_size (c->cr, dx / 2 - PAD);
457 cairo_text_extents (c->cr, label, &extents);
459 cairo_set_source_rgb (c->cr, .5, .5, .5);
461 x = (test + .5) * dx;
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);
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);
476 cairo_restore (c->cr);
480 add_base_line (struct chart *c)
485 cairo_set_line_width (c->cr, 2.);
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);
499 add_absolute_lines (struct chart *c)
501 const double dashes[] = { 2, 4 };
502 const double vlog_steps[] = { 10, 5, 4, 3, 2, 1, .5, .4, .3, .2, .1};
506 cairo_text_extents_t extents;
508 v = c->max_value / 2.;
510 for (i = 0; i < sizeof (vlog_steps) / sizeof (vlog_steps[0]); i++) {
511 double vlog = log (v) / log (vlog_steps[i]);
513 v = pow (vlog_steps[i], floor (vlog));
520 dy = (c->height - PAD) / c->max_value;
523 cairo_set_line_width (c->cr, 1.);
524 cairo_set_dash (c->cr, dashes, sizeof (dashes) / sizeof (dashes[0]), 0);
528 y = c->height - ++i * v * dy;
532 cairo_set_font_size (c->cr, 8);
534 sprintf (buf, "%.0fs", i*v/1000);
535 cairo_text_extents (c->cr, buf, &extents);
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);
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);
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),
548 cairo_line_to (c->cr,
549 floor (c->width - (extents.width + extents.x_bearing + 2)),
551 cairo_stroke (c->cr);
554 cairo_restore (c->cr);
558 add_relative_lines (struct chart *c)
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};
564 double v, y, dy, mid;
567 cairo_text_extents_t extents;
569 v = MAX (-c->min_value, c->max_value) / 200;
571 for (i = 0; i < sizeof (v_steps) / sizeof (v_steps[0]); i++) {
572 if (v > v_steps[i]) {
574 precision = precision_steps[i];
582 dy = (mid - PAD) / MAX (-c->min_value, c->max_value);
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);
591 y = ++i * v * dy * 100;
595 sprintf (buf, "%.*fx", precision, i*v + 1);
596 cairo_text_extents (c->cr, buf, &extents);
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);
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);
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);
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);
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);
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);
633 cairo_restore (c->cr);
637 add_slower_faster_guide (struct chart *c)
639 cairo_text_extents_t extents;
643 cairo_set_font_size (c->cr, FONT_SIZE);
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");
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,
661 cairo_show_text (c->cr, "SLOWER");
662 cairo_move_to (c->cr,
663 3*c->width/4. - extents.width/2. + extents.x_bearing,
665 cairo_show_text (c->cr, "SLOWER");
667 cairo_restore (c->cr);
671 cairo_perf_reports_compare (struct chart *chart,
674 test_report_t **tests, *min_test;
675 double test_time, best_time;
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;
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] : "");
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
701 for (i = 0; i < chart->num_reports; i++) {
702 while (tests[i]->name && tests[i]->stats.iterations == 0)
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) {
718 for (++i; i < chart->num_reports; i++) {
719 if (tests[i]->name && test_report_cmp_name (tests[i], min_test) < 0)
723 add_label (chart, num_test, min_test->name);
725 if (chart->use_html) {
726 printf ("<tr><td>%s</td>", min_test->name);
728 if (min_test->size) {
729 printf ("%16s, size %4d:\n",
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;
745 while (tests[i]->name &&
746 test_report_cmp_name (tests[i], min_test) == 0)
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)
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;
765 for (i = 0; i < chart->num_reports; i++) {
766 double report_time = HUGE_VAL;
768 while (tests[i]->name &&
769 test_report_cmp_name (tests[i], min_test) == 0)
771 double time = tests[i]->stats.min_ticks;
773 time /= tests[i]->stats.ticks_per_ms;
774 if (time < report_time)
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);
786 printf ("<td>%.1f</td>", report_time/1000);
789 printf ("<td></td>");
792 if (report_time < HUGE_VAL)
793 printf (" %6.1f", report_time/1000);
799 if (report_time < HUGE_VAL) {
800 if (chart->relative) {
801 add_chart (chart, num_test, i,
802 to_factor (test_time / report_time));
804 add_chart (chart, num_test, i, report_time);
810 if (chart->use_html) {
823 printf ("</table>\n");
826 for (i = 0; i < chart->num_reports; i++) {
827 if (chart->names[i]) {
829 chart->names[i], chart->reports[i].configuration);
832 i, chart->reports[i].configuration);
839 add_legend (struct chart *chart)
841 cairo_text_extents_t extents;
845 cairo_set_font_size (chart->cr, FONT_SIZE);
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;
853 set_report_color (chart, i);
855 cairo_rectangle (chart->cr, x, y + 6, 8, 8);
856 cairo_fill (chart->cr);
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);
863 x += 10 + 2 * PAD + ceil (extents.width);
866 if (chart->relative) {
869 str = chart->names[0] ?
870 chart->names[0] : chart->reports[0].configuration;
872 sprintf (buf, "(relative to %s)", str);
873 cairo_text_extents (chart->cr, buf, &extents);
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);
887 cairo_surface_t *surface;
896 chart.reports = xcalloc (argc-1, sizeof (cairo_perf_report_t));
897 chart.names = xcalloc (argc-1, sizeof (cairo_perf_report_t));
899 chart.num_reports = 0;
900 for (i = 1; i < argc; i++) {
901 if (strcmp (argv[i], "--html") == 0) {
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) {
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;
913 cairo_perf_report_load (&chart.reports[chart.num_reports++],
915 test_report_cmp_name);
919 for (chart.relative = 0; chart.relative <= 1; chart.relative++) {
920 surface = cairo_image_surface_create (CAIRO_FORMAT_ARGB32,
922 chart.height + (FONT_SIZE + PAD) + 2*PAD);
923 chart.cr = cairo_create (surface);
924 cairo_surface_destroy (surface);
926 cairo_set_source_rgb (chart.cr, 0, 0, 0);
927 cairo_paint (chart.cr);
929 find_ranges (&chart);
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);
937 add_absolute_lines (&chart);
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);
945 add_base_line (&chart);
948 cairo_surface_write_to_png (cairo_get_target (chart.cr),
950 "cairo-perf-chart-relative.png" :
951 "cairo-perf-chart-absolute.png");
952 cairo_destroy (chart.cr);
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++) {
962 free (chart.reports[i].tests);
963 free (chart.reports[i].configuration);
966 free (chart.reports);