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
    double *average;
49

            
50
    cairo_bool_t use_html;
51
    cairo_bool_t relative;
52
};
53
struct color {
54
    double red, green, blue;
55
};
56

            
57
#define FONT_SIZE 12
58
#define PAD (4)
59

            
60
static double
61
to_factor (double x)
62
{
63
#if 1
64
    if (x > 1.)
65
	return (x-1) * 100.;
66
    else
67
	return (1. - 1./x) * 100.;
68
#else
69
    return log (x);
70
#endif
71
}
72

            
73
static int
74
_double_cmp (const void *_a,
75
	     const void *_b)
76
{
77
    const double *a = _a;
78
    const double *b = _b;
79

            
80
    if (*a > *b)
81
	return 1;
82
    if (*a < *b)
83
	return -1;
84
    return 0;
85
}
86

            
87
static void
88
trim_outliers (double *values,
89
	       int     num_values,
90
	       double *min,
91
	       double *max)
92
{
93
    double q1, q3, iqr;
94
    double outlier_min, outlier_max;
95
    int i;
96

            
97
    /* First, identify any outliers, using the definition of "mild
98
     * outliers" from:
99
     *
100
     *		http://en.wikipedia.org/wiki/Outliers
101
     *
102
     * Which is that outliers are any values less than Q1 - 1.5 * IQR
103
     * or greater than Q3 + 1.5 * IQR where Q1 and Q3 are the first
104
     * and third quartiles and IQR is the inter-quartile range (Q3 -
105
     * Q1).
106
     */
107
    qsort (values, num_values,
108
	   sizeof (double), _double_cmp);
109

            
110
    q1		= values[1*num_values / 6];
111
    q3		= values[5*num_values / 6];
112

            
113
    iqr = q3 - q1;
114

            
115
    outlier_min = q1 - 3 * iqr;
116
    outlier_max = q3 + 3 * iqr;
117

            
118
    i = 0;
119
    while (i < num_values && values[i] < outlier_min)
120
	i++;
121
    if (i == num_values)
122
	return;
123

            
124
    *min = values[i];
125

            
126
    while (i < num_values && values[i] <= outlier_max)
127
	i++;
128

            
129
    *max = values[i-1];
130
}
131

            
132
static void
133
find_ranges (struct chart *chart)
134
{
135
    test_report_t **tests, *min_test;
136
    double *values;
137
    int num_values, size_values;
138
    double min = 0, max = 0;
139
    double test_time;
140
    int seen_non_null;
141
    int num_tests = 0;
142
    double slow_sum = 0, fast_sum = 0, sum = 0;
143
    int slow_count = 0, fast_count = 0;
144
    int *count;
145
    int i;
146

            
147
    num_values = 0;
148
    size_values = 64;
149
    values = xmalloc (size_values * sizeof (double));
150

            
151
    chart->average = xmalloc(chart->num_reports * sizeof(double));
152
    count = xmalloc(chart->num_reports * sizeof(int));
153
    for (i = 0; i < chart->num_reports; i++) {
154
	chart->average[i] = 0;
155
	count[i] = 0;
156
    }
157

            
158
    tests = xmalloc (chart->num_reports * sizeof (test_report_t *));
159
    for (i = 0; i < chart->num_reports; i++)
160
	tests[i] = chart->reports[i].tests;
161

            
162
    while (1) {
163
	/* We expect iterations values of 0 when multiple raw reports
164
	 * for the same test have been condensed into the stats of the
165
	 * first. So we just skip these later reports that have no
166
	 * stats. */
167
	seen_non_null = 0;
168
	for (i = 0; i < chart->num_reports; i++) {
169
	    while (tests[i]->name && tests[i]->stats.iterations == 0)
170
		tests[i]++;
171
	    if (tests[i]->name)
172
		seen_non_null++;
173
	}
174
	if (! seen_non_null)
175
	    break;
176

            
177
	num_tests++;
178

            
179
	/* Find the minimum of all current tests, (we have to do this
180
	 * in case some reports don't have a particular test). */
181
	for (i = 0; i < chart->num_reports; i++) {
182
	    if (tests[i]->name) {
183
		min_test = tests[i];
184
		break;
185
	    }
186
	}
187
	for (++i; i < chart->num_reports; i++) {
188
	    if (tests[i]->name && test_report_cmp_name (tests[i], min_test) < 0)
189
		min_test = tests[i];
190
	}
191

            
192
	test_time = 0;
193
	for (i = 0; i < chart->num_reports; i++) {
194
	    double report_time = HUGE_VAL;
195

            
196
	    while (tests[i]->name &&
197
		   test_report_cmp_name (tests[i], min_test) == 0)
198
	    {
199
		double time = tests[i]->stats.min_ticks;
200
		if (time < report_time) {
201
		    time /= tests[i]->stats.ticks_per_ms;
202
		    if (time < report_time)
203
			report_time = time;
204
		}
205
		tests[i]++;
206
	    }
207

            
208
	    if (report_time != HUGE_VAL) {
209
		if (test_time == 0)
210
		    test_time = report_time;
211

            
212
		chart->average[i] += report_time / test_time;
213
		count[i]++;
214

            
215
		if (chart->relative) {
216
		    if (test_time != report_time) {
217
			double v = to_factor (test_time / report_time);
218
			if (num_values == size_values) {
219
			    size_values *= 2;
220
			    values = xrealloc (values,
221
					       size_values * sizeof (double));
222
			}
223
			values[num_values++] = v;
224
			if (v < min)
225
			    min = v;
226
			if (v > max)
227
			    max = v;
228
			if (v > 0)
229
			    fast_sum += v/100, fast_count++;
230
			else
231
			    slow_sum += v/100, slow_count++;
232
			sum += v/100;
233
			printf ("%s %d: %f\n", min_test->name, num_values, v);
234
		    }
235
		} else {
236
		    if (report_time < min)
237
			min = report_time;
238
		    if (report_time > max)
239
			max = report_time;
240
		}
241
	    }
242
	}
243
    }
244

            
245
    for (i = 0; i < chart->num_reports; i++) {
246
	if (count[i])
247
	    chart->average[i] = count[i] / chart->average[i];
248
	else
249
	    chart->average[i] = 1.;
250
    }
251

            
252
    if (chart->relative)
253
	trim_outliers (values, num_values, &min, &max);
254
    chart->min_value = min;
255
    chart->max_value = max;
256
    chart->num_tests = num_tests + !!chart->relative;
257

            
258
    free (values);
259
    free (tests);
260
    free (count);
261

            
262
    printf ("%d: slow[%d] average: %f, fast[%d] average: %f, %f\n",
263
	    num_values, slow_count, slow_sum / slow_count, fast_count, fast_sum / fast_count, sum / num_values);
264
}
265

            
266
#define SET_COLOR(C, R, G, B) (C)->red = (R), (C)->green = (G), (C)->blue = (B)
267
static void
268
hsv_to_rgb (double	  h,
269
	    double	  s,
270
	    double	  v,
271
	    struct color *color)
272
{
273
    double m, n, f;
274
    int i;
275

            
276
    while (h < 0)
277
	h += 6.;
278
    while (h > 6.)
279
	h -= 6.;
280

            
281
    if (s < 0.)
282
	s = 0.;
283
    if (s > 1.)
284
	s = 1.;
285

            
286
    if (v < 0.)
287
	v = 0.;
288
    if (v > 1.)
289
	v = 1.;
290

            
291
    i = floor (h);
292
    f = h - i;
293
    if ((i & 1) == 0)
294
	f = 1 - f;
295

            
296
    m = v * (1 - s);
297
    n = v * (1 - s * f);
298
    switch(i){
299
    default:
300
    case 6:
301
    case 0: SET_COLOR (color, v, n, m); break;
302
    case 1: SET_COLOR (color, n, v, m); break;
303
    case 2: SET_COLOR (color, m, v, n); break;
304
    case 3: SET_COLOR (color, m, n, v); break;
305
    case 4: SET_COLOR (color, n, m, v); break;
306
    case 5: SET_COLOR (color, v, m, n); break;
307
    }
308
}
309

            
310
static void set_report_color (struct chart *chart, int report)
311
{
312
    struct color color;
313

            
314
    hsv_to_rgb (6. / chart->num_reports * report, .7, .7, &color);
315
    cairo_set_source_rgb (chart->cr, color.red, color.green, color.blue);
316
}
317

            
318
static void set_report_gradient (struct chart *chart, int report,
319
				 double x, double y, double w, double h)
320
{
321
    struct color color;
322
    cairo_pattern_t *p;
323

            
324
    hsv_to_rgb (6. / chart->num_reports * report, .7, .7, &color);
325

            
326
    p = cairo_pattern_create_linear (x, 0, x+w, 0);
327
    cairo_pattern_add_color_stop_rgba (p, 0.0,
328
				       color.red, color.green, color.blue,
329
				       .50);
330
    cairo_pattern_add_color_stop_rgba (p, 0.5,
331
				       color.red, color.green, color.blue,
332
				       .50);
333
    cairo_pattern_add_color_stop_rgba (p, 1.0,
334
				       color.red, color.green, color.blue,
335
				       1.0);
336
    cairo_set_source (chart->cr, p);
337
    cairo_pattern_destroy (p);
338
}
339

            
340
static void
341
test_background (struct chart *c,
342
		 int	       test)
343
{
344
    double dx, x;
345

            
346
    dx = c->width / (double) c->num_tests;
347
    x = dx * test;
348

            
349
    if (test & 1)
350
	cairo_set_source_rgba (c->cr, .2, .2, .2, .2);
351
    else
352
	cairo_set_source_rgba (c->cr, .8, .8, .8, .2);
353

            
354
    cairo_rectangle (c->cr, floor (x), 0,
355
		     floor (dx + x) - floor (x), c->height);
356
    cairo_fill (c->cr);
357
}
358

            
359
static void
360
add_chart (struct chart *c,
361
	   int		 test,
362
	   int		 report,
363
	   double	 value)
364
{
365
    double dx, dy, x;
366

            
367
    if (fabs (value) < 0.1)
368
	return;
369

            
370
    if (c->relative) {
371
	cairo_text_extents_t extents;
372
	char buf[80];
373
	double y;
374

            
375
	dy = (c->height/2. - PAD) / MAX (-c->min_value, c->max_value);
376
	/* the first report is always skipped, as it is used as the baseline */
377
	dx = c->width / (double) (c->num_tests * c->num_reports);
378
	x = dx * (c->num_reports * test + report - .5);
379

            
380
	cairo_rectangle (c->cr,
381
			 floor (x), c->height / 2.,
382
			 floor (x + dx) - floor (x),
383
			 ceil (-dy*value - c->height/2.) + c->height/2.);
384
	if (dx < 5) {
385
	    set_report_color (c, report);
386
	    cairo_fill (c->cr);
387
	} else {
388
	    set_report_gradient (c, report,
389
				 floor (x), c->height / 2.,
390
				 floor (x + dx) - floor (x),
391
				 ceil (-dy*value - c->height/2.) + c->height/2.);
392

            
393
	    cairo_fill_preserve (c->cr);
394
	    cairo_save (c->cr);
395
	    cairo_clip_preserve (c->cr);
396
	    set_report_color (c, report);
397
	    cairo_stroke (c->cr);
398
	    cairo_restore (c->cr);
399
	}
400

            
401
	/* Skip the label if the difference between the two is less than 0.1% */
402
	if (fabs (value) < 0.1)
403
		return;
404

            
405
	cairo_save (c->cr);
406
	cairo_set_font_size (c->cr, dx - 2);
407

            
408
	if (value < 0) {
409
	    sprintf (buf, "%.1f", -value/100 + 1);
410
	} else {
411
	    sprintf (buf, "%.1f", value/100 + 1);
412
	}
413
	cairo_text_extents (c->cr, buf, &extents);
414

            
415
	/* will it be clipped? */
416
	y = -dy * value;
417
	if (y < -c->height/2) {
418
	    y = -c->height/2;
419
	} else if (y > c->height/2) {
420
	    y = c->height/2;
421
	}
422

            
423
	if (y < 0) {
424
	    if (y > -extents.width - 6)
425
		    y -= extents.width + 6;
426
	} else {
427
	    if (y < extents.width + 6)
428
		    y += extents.width + 6;
429
	}
430

            
431
	cairo_translate (c->cr,
432
			 floor (x) + (floor (x + dx) - floor (x))/2,
433
			 floor (y) + c->height/2.);
434
	cairo_rotate (c->cr, -M_PI/2);
435
	if (y < 0) {
436
	    cairo_move_to (c->cr, -extents.x_bearing -extents.width - 4, -extents.y_bearing/2);
437
	} else {
438
	    cairo_move_to (c->cr, 2, -extents.y_bearing/2);
439
	}
440

            
441
	cairo_set_source_rgb (c->cr, .95, .95, .95);
442
	cairo_show_text (c->cr, buf);
443
	cairo_restore (c->cr);
444
    } else {
445
	dy = (c->height - PAD) / c->max_value;
446
	dx = c->width / (double) (c->num_tests * (c->num_reports+1));
447
	x = dx * ((c->num_reports+1) * test + report + .5);
448

            
449
	cairo_rectangle (c->cr,
450
			 floor (x), c->height,
451
			 floor (x + dx) - floor (x),
452
			 floor (c->height - dy*value) - c->height);
453
	if (dx < 5) {
454
	    set_report_color (c, report);
455
	    cairo_fill (c->cr);
456
	} else {
457
	    set_report_gradient (c, report,
458
				 floor (x), c->height,
459
				 floor (x + dx) - floor (x),
460
				 floor (c->height - dy*value) - c->height);
461
	    cairo_fill_preserve (c->cr);
462
	    cairo_save (c->cr);
463
	    cairo_clip_preserve (c->cr);
464
	    set_report_color (c, report);
465
	    cairo_stroke (c->cr);
466
	    cairo_restore (c->cr);
467
	}
468
    }
469
}
470

            
471
static void
472
add_average (struct chart *c,
473
	     int		 test,
474
	     int		 report,
475
	     double	 value)
476
{
477
    double dx, dy, x;
478
    cairo_text_extents_t extents;
479
    char buf[80];
480
    double y;
481

            
482
    if (fabs (value) < 0.1)
483
	return;
484

            
485
    dy = (c->height/2. - PAD) / MAX (-c->min_value, c->max_value);
486
    /* the first report is always skipped, as it is used as the baseline */
487
    dx = c->width / (double) (c->num_tests * c->num_reports);
488
    x = dx * (c->num_reports * test + report - .5);
489

            
490
    cairo_rectangle (c->cr,
491
		     floor (x), c->height / 2.,
492
		     floor (x + dx) - floor (x),
493
		     ceil (-dy*value - c->height/2.) + c->height/2.);
494
    if (dx < 5) {
495
	set_report_color (c, report);
496
	cairo_fill (c->cr);
497
    } else {
498
	set_report_gradient (c, report,
499
			     floor (x), c->height / 2.,
500
			     floor (x + dx) - floor (x),
501
			     ceil (-dy*value - c->height/2.) + c->height/2.);
502

            
503
	cairo_fill_preserve (c->cr);
504
	cairo_save (c->cr);
505
	cairo_clip_preserve (c->cr);
506
	set_report_color (c, report);
507
	cairo_stroke (c->cr);
508
	cairo_restore (c->cr);
509
    }
510

            
511
    /* Skip the label if the difference between the two is less than 0.1% */
512
    if (fabs (value) < 0.1)
513
	return;
514

            
515
    cairo_save (c->cr);
516
    cairo_set_font_size (c->cr, dx - 2);
517

            
518
    if (value < 0) {
519
	sprintf (buf, "%.1f", -value/100 + 1);
520
    } else {
521
	sprintf (buf, "%.1f", value/100 + 1);
522
    }
523
    cairo_text_extents (c->cr, buf, &extents);
524

            
525
    /* will it be clipped? */
526
    y = -dy * value;
527
    if (y < -c->height/2) {
528
	y = -c->height/2;
529
    } else if (y > c->height/2) {
530
	y = c->height/2;
531
    }
532

            
533
    if (y < 0) {
534
	if (y > -extents.width - 6)
535
	    y -= extents.width + 6;
536
    } else {
537
	if (y < extents.width + 6)
538
	    y += extents.width + 6;
539
    }
540

            
541
    cairo_translate (c->cr,
542
		     floor (x) + (floor (x + dx) - floor (x))/2,
543
		     floor (y) + c->height/2.);
544
    cairo_rotate (c->cr, -M_PI/2);
545
    if (y < 0) {
546
	cairo_move_to (c->cr, -extents.x_bearing -extents.width - 4, -extents.y_bearing/2);
547
    } else {
548
	cairo_move_to (c->cr, 2, -extents.y_bearing/2);
549
    }
550

            
551
    cairo_set_source_rgb (c->cr, .95, .95, .95);
552
    cairo_show_text (c->cr, buf);
553
    cairo_restore (c->cr);
554
}
555

            
556
static void
557
add_label (struct chart *c,
558
	   int		 test,
559
	   const char	*label)
560
{
561
    cairo_text_extents_t extents;
562
    double dx, x;
563

            
564
    cairo_save (c->cr);
565
    dx = c->width / (double) c->num_tests;
566
    if (dx / 2 - PAD < 4)
567
	return;
568
    cairo_set_font_size (c->cr, dx / 2 - PAD);
569
    cairo_text_extents (c->cr, label, &extents);
570

            
571
    cairo_set_source_rgb (c->cr, .5, .5, .5);
572

            
573
    x = (test + .5) * dx;
574
    cairo_save (c->cr);
575
    cairo_translate (c->cr, x, c->height - PAD / 2);
576
    cairo_rotate (c->cr, -M_PI/2);
577
    cairo_move_to (c->cr, 0, -extents.y_bearing/2);
578
    cairo_show_text (c->cr, label);
579
    cairo_restore (c->cr);
580

            
581
    cairo_save (c->cr);
582
    cairo_translate (c->cr, x, PAD / 2);
583
    cairo_rotate (c->cr, -M_PI/2);
584
    cairo_move_to (c->cr, -extents.width, -extents.y_bearing/2);
585
    cairo_show_text (c->cr, label);
586
    cairo_restore (c->cr);
587

            
588
    cairo_restore (c->cr);
589
}
590

            
591
static void
592
add_base_line (struct chart *c)
593
{
594
    double y;
595

            
596
    cairo_save (c->cr);
597
    cairo_set_line_width (c->cr, 2.);
598
    if (c->relative) {
599
	y = c->height / 2.;
600
    } else {
601
	y = c->height;
602
    }
603
    cairo_move_to (c->cr, 0, y);
604
    cairo_line_to (c->cr, c->width, y);
605
    cairo_set_source_rgb (c->cr, 1, 1, 1);
606
    cairo_stroke (c->cr);
607
    cairo_restore (c->cr);
608
}
609

            
610
static void
611
add_absolute_lines (struct chart *c)
612
{
613
    const double dashes[] = { 2, 4 };
614
    const double vlog_steps[] = { 10, 5, 4, 3, 2, 1, .5, .4, .3, .2, .1};
615
    double v, y, dy;
616
    unsigned int i;
617
    char buf[80];
618
    cairo_text_extents_t extents;
619

            
620
    v = c->max_value / 2.;
621

            
622
    for (i = 0; i < sizeof (vlog_steps) / sizeof (vlog_steps[0]); i++) {
623
	double vlog = log (v) / log (vlog_steps[i]);
624
	if (vlog > 1) {
625
	    v = pow (vlog_steps[i], floor (vlog));
626
	    goto done;
627
	}
628
    }
629
    return;
630
done:
631

            
632
    dy = (c->height - PAD) / c->max_value;
633

            
634
    cairo_save (c->cr);
635
    cairo_set_line_width (c->cr, 1.);
636
    cairo_set_dash (c->cr, dashes, sizeof (dashes) / sizeof (dashes[0]), 0);
637

            
638
    i = 0;
639
    do {
640
	y = c->height - ++i * v * dy;
641
	if (y < PAD)
642
	    break;
643

            
644
	cairo_set_font_size (c->cr, 8);
645

            
646
	sprintf (buf, "%.0fs", i*v/1000);
647
	cairo_text_extents (c->cr, buf, &extents);
648

            
649
	cairo_set_source_rgba (c->cr, .75, 0, 0, .95);
650
	cairo_move_to (c->cr, 1-extents.x_bearing, floor (y) - (extents.height/2 + extents.y_bearing) + .5);
651
	cairo_show_text (c->cr, buf);
652

            
653
	cairo_move_to (c->cr, c->width-extents.width-1, floor (y) - (extents.height/2 + extents.y_bearing) + .5);
654
	cairo_show_text (c->cr, buf);
655

            
656
	cairo_set_source_rgba (c->cr, .75, 0, 0, .5);
657
	cairo_move_to (c->cr,
658
		       ceil (extents.width + extents.x_bearing + 2),
659
		       floor (y) + .5);
660
	cairo_line_to (c->cr,
661
		       floor (c->width - (extents.width + extents.x_bearing + 2)),
662
		       floor (y) + .5);
663
	cairo_stroke (c->cr);
664
    } while (1);
665

            
666
    cairo_restore (c->cr);
667
}
668

            
669
static void
670
add_relative_lines (struct chart *c)
671
{
672
    const double dashes[] = { 2, 4 };
673
    const double v_steps[] = { 10, 5, 1, .5, .1, .05, .01};
674
    const int precision_steps[] = { 0, 0, 0, 1, 1, 2, 2};
675
    int precision;
676
    double v, y, dy, mid;
677
    unsigned int i;
678
    char buf[80];
679
    cairo_text_extents_t extents;
680

            
681
    v = MAX (-c->min_value, c->max_value) / 200;
682

            
683
    for (i = 0; i < sizeof (v_steps) / sizeof (v_steps[0]); i++) {
684
	if (v > v_steps[i]) {
685
	    v = v_steps[i];
686
	    precision = precision_steps[i];
687
	    goto done;
688
	}
689
    }
690
    return;
691
done:
692

            
693
    mid = c->height/2.;
694
    dy = (mid - PAD) / MAX (-c->min_value, c->max_value);
695

            
696
    cairo_save (c->cr);
697
    cairo_set_line_width (c->cr, 1.);
698
    cairo_set_dash (c->cr, dashes, sizeof (dashes) / sizeof (dashes[0]), 0);
699
    cairo_set_font_size (c->cr, 8);
700

            
701
    i = 0;
702
    do {
703
	y = ++i * v * dy * 100;
704
	if (y > mid)
705
	    break;
706

            
707
	sprintf (buf, "%.*fx", precision, i*v + 1);
708
	cairo_text_extents (c->cr, buf, &extents);
709

            
710
	cairo_set_source_rgba (c->cr, .75, 0, 0, .95);
711
	cairo_move_to (c->cr, 1-extents.x_bearing, floor (mid + y) - (extents.height/2 + extents.y_bearing) + .5);
712
	cairo_show_text (c->cr, buf);
713

            
714
	cairo_move_to (c->cr, c->width-extents.width-1, floor (mid + y) - (extents.height/2 + extents.y_bearing) + .5);
715
	cairo_show_text (c->cr, buf);
716

            
717
	cairo_set_source_rgba (c->cr, 0, .75, 0, .95);
718
	cairo_move_to (c->cr, 1-extents.x_bearing, ceil (mid - y) - (extents.height/2 + extents.y_bearing) + .5);
719
	cairo_show_text (c->cr, buf);
720

            
721
	cairo_move_to (c->cr, c->width-extents.width-1, ceil (mid - y) - (extents.height/2 + extents.y_bearing) + .5);
722
	cairo_show_text (c->cr, buf);
723

            
724
	/* trim the dashes to no obscure the labels */
725
	cairo_set_source_rgba (c->cr, .75, 0, 0, .5);
726
	cairo_move_to (c->cr,
727
		       ceil (extents.width + extents.x_bearing + 2),
728
		       floor (mid + y) + .5);
729
	cairo_line_to (c->cr,
730
		       floor (c->width - (extents.width + 2)),
731
		       floor (mid + y) + .5);
732
	cairo_stroke (c->cr);
733

            
734
	cairo_set_source_rgba (c->cr, 0, .75, 0, .5);
735
	cairo_move_to (c->cr,
736
		       ceil (extents.width + extents.x_bearing + 2),
737
		       ceil (mid - y) + .5);
738
	cairo_line_to (c->cr,
739
		       floor (c->width - (extents.width + 2)),
740
		       ceil (mid - y) + .5);
741
	cairo_stroke (c->cr);
742

            
743
    } while (1);
744

            
745
    cairo_restore (c->cr);
746
}
747

            
748
static void
749
add_slower_faster_guide (struct chart *c)
750
{
751
    cairo_text_extents_t extents;
752

            
753
    cairo_save (c->cr);
754

            
755
    cairo_set_font_size (c->cr, FONT_SIZE);
756

            
757
    cairo_text_extents (c->cr, "FASTER", &extents);
758
    cairo_set_source_rgba (c->cr, 0, .75, 0, .5);
759
    cairo_move_to (c->cr,
760
		   c->width/4. - extents.width/2. + extents.x_bearing,
761
		   1 - extents.y_bearing);
762
    cairo_show_text (c->cr, "FASTER");
763
    cairo_move_to (c->cr,
764
		   3*c->width/4. - extents.width/2. + extents.x_bearing,
765
		   1 - extents.y_bearing);
766
    cairo_show_text (c->cr, "FASTER");
767

            
768
    cairo_text_extents (c->cr, "SLOWER", &extents);
769
    cairo_set_source_rgba (c->cr, .75, 0, 0, .5);
770
    cairo_move_to (c->cr,
771
		   c->width/4. - extents.width/2. + extents.x_bearing,
772
		   c->height - 1);
773
    cairo_show_text (c->cr, "SLOWER");
774
    cairo_move_to (c->cr,
775
		   3*c->width/4. - extents.width/2. + extents.x_bearing,
776
		   c->height - 1);
777
    cairo_show_text (c->cr, "SLOWER");
778

            
779
    cairo_restore (c->cr);
780
}
781

            
782
static void
783
cairo_perf_reports_compare (struct chart *chart,
784
			    cairo_bool_t  print)
785
{
786
    test_report_t **tests, *min_test;
787
    double test_time, best_time;
788
    int num_test = 0;
789
    int seen_non_null;
790
    int i;
791

            
792
    tests = xmalloc (chart->num_reports * sizeof (test_report_t *));
793
    for (i = 0; i < chart->num_reports; i++)
794
	tests[i] = chart->reports[i].tests;
795

            
796
    if (print) {
797
	if (chart->use_html) {
798
	    printf ("<table style=\"text-align:right\" cellspacing=\"4\">\n");
799
	    printf ("<tr><td></td>");
800
	    for (i = 0; i < chart->num_reports; i++) {
801
		printf ("<td>%s</td>", chart->names[i] ? chart->names[i] : "");
802
	    }
803
	    printf ("</tr>\n");
804
	}
805
    }
806

            
807
    while (1) {
808
	/* We expect iterations values of 0 when multiple raw reports
809
	 * for the same test have been condensed into the stats of the
810
	 * first. So we just skip these later reports that have no
811
	 * stats. */
812
	seen_non_null = 0;
813
	for (i = 0; i < chart->num_reports; i++) {
814
	    while (tests[i]->name && tests[i]->stats.iterations == 0)
815
		tests[i]++;
816
	    if (tests[i]->name)
817
		seen_non_null++;
818
	}
819
	if (! seen_non_null)
820
	    break;
821

            
822
	/* Find the minimum of all current tests, (we have to do this
823
	 * in case some reports don't have a particular test). */
824
	for (i = 0; i < chart->num_reports; i++) {
825
	    if (tests[i]->name) {
826
		min_test = tests[i];
827
		break;
828
	    }
829
	}
830
	for (++i; i < chart->num_reports; i++) {
831
	    if (tests[i]->name && test_report_cmp_name (tests[i], min_test) < 0)
832
		min_test = tests[i];
833
	}
834

            
835
	add_label (chart, num_test, min_test->name);
836
	if (print) {
837
	    if (chart->use_html) {
838
		printf ("<tr><td>%s</td>", min_test->name);
839
	    } else {
840
		if (min_test->size) {
841
		    printf ("%16s, size %4d:\n",
842
			    min_test->name,
843
			    min_test->size);
844
		} else {
845
		    printf ("%26s:",
846
			    min_test->name);
847
		}
848
	    }
849
	}
850

            
851
	test_time = 0;
852
	best_time = HUGE_VAL;
853
	for (i = 0; i < chart->num_reports; i++) {
854
	    test_report_t *initial = tests[i];
855
	    double report_time = HUGE_VAL;
856

            
857
	    while (tests[i]->name &&
858
		   test_report_cmp_name (tests[i], min_test) == 0)
859
	    {
860
		double time = tests[i]->stats.min_ticks;
861
		if (time < report_time) {
862
		    time /= tests[i]->stats.ticks_per_ms;
863
		    if (time < report_time)
864
			report_time = time;
865
		}
866
		tests[i]++;
867
	    }
868

            
869
	    if (test_time == 0 && report_time != HUGE_VAL)
870
		test_time = report_time;
871
	    if (report_time < best_time)
872
		best_time = report_time;
873

            
874
	    tests[i] = initial;
875
	}
876

            
877
	for (i = 0; i < chart->num_reports; i++) {
878
	    double report_time = HUGE_VAL;
879

            
880
	    while (tests[i]->name &&
881
		   test_report_cmp_name (tests[i], min_test) == 0)
882
	    {
883
		double time = tests[i]->stats.min_ticks;
884
		if (time > 0) {
885
		    time /= tests[i]->stats.ticks_per_ms;
886
		    if (time < report_time)
887
			report_time = time;
888
		}
889
		tests[i]++;
890
	    }
891

            
892
	    if (print) {
893
		if (chart->use_html) {
894
		    if (report_time < HUGE_VAL) {
895
			if (report_time / best_time < 1.01) {
896
			    printf ("<td><strong>%.1f</strong></td>", report_time/1000);
897
			} else {
898
			    printf ("<td>%.1f</td>", report_time/1000);
899
			}
900
		    } else {
901
			printf ("<td></td>");
902
		    }
903
		} else {
904
		    if (report_time < HUGE_VAL)
905
			printf (" %6.1f",  report_time/1000);
906
		    else
907
			printf ("    ---");
908
		}
909
	    }
910

            
911
	    if (report_time < HUGE_VAL) {
912
		if (chart->relative) {
913
		    add_chart (chart, num_test, i,
914
			       to_factor (test_time / report_time));
915
		} else {
916
		    add_chart (chart, num_test, i, report_time);
917
		}
918
	    }
919
	}
920

            
921
	if (print) {
922
	    if (chart->use_html) {
923
		printf ("</tr>\n");
924
	    } else {
925
		printf ("\n");
926
	    }
927
	}
928

            
929
	num_test++;
930
    }
931
    if (chart->relative) {
932
	add_label (chart, num_test, "(geometric mean)");
933
	for (i = 0; i < chart->num_reports; i++)
934
	    add_average (chart, num_test, i, to_factor (chart->average[i]));
935
    }
936
    free (tests);
937

            
938
    if (print) {
939
	if (chart->use_html)
940
	    printf ("</table>\n");
941

            
942
	printf ("\n");
943
	for (i = 0; i < chart->num_reports; i++) {
944
	    if (chart->names[i]) {
945
		printf ("[%s] %s\n",
946
			chart->names[i], chart->reports[i].configuration);
947
	    } else {
948
		printf ("[%d] %s\n",
949
			i, chart->reports[i].configuration);
950
	    }
951
	}
952
    }
953
}
954

            
955
static void
956
add_legend (struct chart *chart)
957
{
958
    cairo_text_extents_t extents;
959
    const char *str;
960
    int i, x, y;
961

            
962
    cairo_set_font_size (chart->cr, FONT_SIZE);
963

            
964
    x = PAD;
965
    y = chart->height + PAD;
966
    for (i = chart->relative; i < chart->num_reports; i++) {
967
	str = chart->names[i] ?
968
	      chart->names[i] : chart->reports[i].configuration;
969

            
970
	set_report_color (chart, i);
971

            
972
	cairo_rectangle (chart->cr, x, y + 6, 8, 8);
973
	cairo_fill (chart->cr);
974

            
975
	cairo_set_source_rgb (chart->cr, 1, 1, 1);
976
	cairo_move_to (chart->cr, x + 10, y + FONT_SIZE + PAD / 2.);
977
	cairo_text_extents (chart->cr, str, &extents);
978
	cairo_show_text (chart->cr, str);
979

            
980
	x += 10 + 2 * PAD + ceil (extents.width);
981
    }
982

            
983
    if (chart->relative) {
984
	char buf[80];
985

            
986
	str = chart->names[0] ?
987
	      chart->names[0] : chart->reports[0].configuration;
988

            
989
	sprintf (buf, "(relative to %s)", str);
990
	cairo_text_extents (chart->cr, buf, &extents);
991

            
992
	cairo_set_source_rgb (chart->cr, 1, 1, 1);
993
	cairo_move_to (chart->cr,
994
		       chart->width - 1 - extents.width,
995
		       y + FONT_SIZE + PAD / 2.);
996
	cairo_show_text (chart->cr, buf);
997
    }
998
}
999

            
static void
usage (void)
{
	printf("Usage:\n");
	printf("  cairo-perf-chart [OPTION...] <result1> <result2>...<resultN>\n");
	printf("\n");
	printf("Help Options:\n");
	printf("  --help, --?\tShow help options\n");
	printf("\n");
	printf("Application Options:\n");
	printf("  --html\tOutput an HTML table comparing the results\n");
	printf("  --height=\tSet the height of the output graph"\
			" (default 480)\n");
	printf("  --width=\tSet the width of the output graph"\
			" (default 640)\n");
	printf("  --name\tSet the name of graph series."\
			" This only sets the name for the\n\t\tfirst result file."\
			" The graph series is usually set using the\n\t\tfile name for"\
			" the results file.\n");
	printf("\n");
	printf("Example:\n");
	printf("  cairo-perf-chart --width=1024 --height=768 run1 run2 run3\n");
	return;
}
int
main (int	  argc,
      const char *argv[])
{
    cairo_surface_t *surface;
    struct chart chart;
    test_report_t *t;
    int i;
    chart.use_html = 0;
    chart.width = 640;
    chart.height = 480;
    chart.reports = xcalloc (argc-1, sizeof (cairo_perf_report_t));
    chart.names = xcalloc (argc-1, sizeof (cairo_perf_report_t));
    chart.num_reports = 0;
    for (i = 1; i < argc; i++) {
	if (strcmp (argv[i], "--html") == 0) {
	    chart.use_html = 1;
	} else if (strncmp (argv[i], "--width=", 8) == 0) {
	    chart.width = atoi (argv[i] + 8);
	} else if (strncmp (argv[i], "--height=", 9) == 0) {
	    chart.height = atoi (argv[i] + 9);
	} else if (strcmp (argv[i], "--name") == 0) {
	    if (i + 1 < argc)
		chart.names[chart.num_reports] = argv[++i];
	} else if (strncmp (argv[i], "--name=", 7) == 0) {
	    chart.names[chart.num_reports] = argv[i] + 7;
	} else if ((strcmp (argv[i], "--help") == 0) ||
		(strcmp (argv[i], "--?") == 0)) {
		usage();
		return 0;
	} else {
	    cairo_perf_report_load (&chart.reports[chart.num_reports++],
				    argv[i], i,
				    test_report_cmp_name);
	}
    }
    for (chart.relative = 0; chart.relative <= 1; chart.relative++) {
	surface = cairo_image_surface_create (CAIRO_FORMAT_ARGB32,
					      chart.width,
					      chart.height + (FONT_SIZE + PAD) + 2*PAD);
	chart.cr = cairo_create (surface);
	cairo_surface_destroy (surface);
	cairo_set_source_rgb (chart.cr, 0, 0, 0);
	cairo_paint (chart.cr);
	find_ranges (&chart);
	for (i = 0; i < chart.num_tests; i++)
	    test_background (&chart, i);
	if (chart.relative) {
	    add_relative_lines (&chart);
	    add_slower_faster_guide (&chart);
	} else
	    add_absolute_lines (&chart);
	cairo_save (chart.cr);
	cairo_rectangle (chart.cr, 0, 0, chart.width, chart.height);
	cairo_clip (chart.cr);
	cairo_perf_reports_compare (&chart, !chart.relative);
	cairo_restore (chart.cr);
	add_base_line (&chart);
	add_legend (&chart);
	cairo_surface_write_to_png (cairo_get_target (chart.cr),
				    chart.relative ? "relative.png" : "absolute.png");
	cairo_destroy (chart.cr);
    }
    /* Pointless memory cleanup, (would be a great place for talloc) */
    for (i = 0; i < chart.num_reports; i++) {
	for (t = chart.reports[i].tests; t->name; t++) {
	    free (t->samples);
	    free (t->backend);
	    free (t->name);
	}
	free (chart.reports[i].tests);
	free (chart.reports[i].configuration);
    }
    free (chart.names);
    free (chart.reports);
    return 0;
}