2009-11-18 Chris Toshok <toshok@ximian.com>
[moon.git] / cairo / perf / cairo-perf-graph-files.c
blob5122b9faf6c2718ed8d747567739586a71da7221
1 /*
2 * Copyright © 2008 Chris Wilson
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.
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.
25 * Authors: Chris Wilson <chris@chris-wilson.co.uk>
28 #include "cairo-perf.h"
29 #include "cairo-perf-graph.h"
31 #include <stdlib.h>
32 #include <string.h>
33 #include <unistd.h>
34 #include <errno.h>
35 #include <fcntl.h>
37 #include <cairo.h>
39 static void
40 usage (const char *argv0)
42 char const *basename = strrchr (argv0, '/');
43 basename = basename ? basename+1 : argv0;
44 g_printerr ("Usage: %s [options] file1 file2 [...]\n\n", basename);
45 g_printerr ("Draws a graph illustrating the change in performance over a series.\n");
46 exit(1);
49 enum {
50 CASE_SHOWN,
51 CASE_INCONSISTENT,
52 CASE_BACKEND,
53 CASE_CONTENT,
54 CASE_NAME,
55 CASE_SIZE,
56 CASE_FG_COLOR,
57 CASE_DATA,
58 CASE_NCOLS
61 static GtkTreeStore *
62 cases_to_store (test_case_t *cases)
64 GtkTreeStore *store;
65 GtkTreeIter backend_iter;
66 GtkTreeIter content_iter;
67 const char *backend = NULL;
68 const char *content = NULL;
70 store = gtk_tree_store_new (CASE_NCOLS,
71 G_TYPE_BOOLEAN, /* shown */
72 G_TYPE_BOOLEAN, /* inconsistent */
73 G_TYPE_STRING, /* backend */
74 G_TYPE_STRING, /* content */
75 G_TYPE_STRING, /* name */
76 G_TYPE_INT, /* size */
77 GDK_TYPE_COLOR, /* fg color */
78 G_TYPE_POINTER); /* data */
79 while (cases->backend != NULL) {
80 GtkTreeIter iter;
82 if (backend == NULL || strcmp (backend, cases->backend)) {
83 gtk_tree_store_append (store, &backend_iter, NULL);
84 gtk_tree_store_set (store, &backend_iter,
85 CASE_SHOWN, TRUE,
86 CASE_BACKEND, cases->backend,
87 -1);
88 backend = cases->backend;
89 content = NULL;
91 if (content == NULL || strcmp (content, cases->content)) {
92 gtk_tree_store_append (store, &content_iter, &backend_iter);
93 gtk_tree_store_set (store, &content_iter,
94 CASE_SHOWN, TRUE,
95 CASE_BACKEND, cases->backend,
96 CASE_CONTENT, cases->content,
97 -1);
98 content = cases->content;
101 gtk_tree_store_append (store, &iter, &content_iter);
102 gtk_tree_store_set (store, &iter,
103 CASE_SHOWN, TRUE,
104 CASE_BACKEND, cases->backend,
105 CASE_CONTENT, cases->content,
106 CASE_NAME, cases->name,
107 CASE_SIZE, cases->size,
108 CASE_FG_COLOR, &cases->color,
109 CASE_DATA, cases,
110 -1);
111 cases++;
114 return store;
117 struct _app_data {
118 GtkWidget *window;
120 test_case_t *cases;
121 cairo_perf_report_t *reports;
122 int num_reports;
124 GtkTreeStore *case_store;
126 GIOChannel *git_io;
127 GtkTextBuffer *git_buffer;
129 GtkWidget *gv;
132 static void
133 recurse_set_shown (GtkTreeModel *model, GtkTreeIter *parent, gboolean shown)
135 GtkTreeIter iter;
137 if (gtk_tree_model_iter_children (model, &iter, parent)) do {
138 test_case_t *c;
140 gtk_tree_model_get (model, &iter, CASE_DATA, &c, -1);
141 if (c == NULL) {
142 recurse_set_shown (model, &iter, shown);
143 } else if (shown != c->shown) {
144 c->shown = shown;
145 gtk_tree_store_set (GTK_TREE_STORE (model), &iter,
146 CASE_SHOWN, shown,
147 CASE_INCONSISTENT, FALSE,
148 -1);
150 } while (gtk_tree_model_iter_next (model, &iter));
153 static gboolean
154 children_consistent (GtkTreeModel *model, GtkTreeIter *parent)
156 GtkTreeIter iter;
157 gboolean first = TRUE;
158 gboolean first_active;
160 if (gtk_tree_model_iter_children (model, &iter, parent)) do {
161 gboolean active, inconsistent;
163 gtk_tree_model_get (model, &iter,
164 CASE_INCONSISTENT, &inconsistent,
165 CASE_SHOWN, &active,
166 -1);
167 if (inconsistent)
168 return FALSE;
170 if (first) {
171 first_active = active;
172 first = FALSE;
173 } else if (active != first_active)
174 return FALSE;
175 } while (gtk_tree_model_iter_next (model, &iter));
177 return TRUE;
180 static void
181 check_consistent (GtkTreeModel *model, GtkTreeIter *child)
183 GtkTreeIter parent;
185 if (gtk_tree_model_iter_parent (model, &parent, child)) {
186 gtk_tree_store_set (GTK_TREE_STORE (model), &parent,
187 CASE_INCONSISTENT,
188 ! children_consistent (model, &parent),
189 -1);
190 check_consistent (model, &parent);
194 static void
195 show_case_toggled (GtkCellRendererToggle *cell,
196 gchar *str,
197 struct _app_data *app)
199 GtkTreeModel *model;
200 GtkTreePath *path;
201 GtkTreeIter iter;
202 test_case_t *c;
203 gboolean active;
205 active = ! gtk_cell_renderer_toggle_get_active (cell);
207 model = GTK_TREE_MODEL (app->case_store);
209 path = gtk_tree_path_new_from_string (str);
210 gtk_tree_model_get_iter (model, &iter, path);
211 gtk_tree_path_free (path);
213 gtk_tree_store_set (app->case_store, &iter,
214 CASE_SHOWN, active,
215 CASE_INCONSISTENT, FALSE,
216 -1);
217 gtk_tree_model_get (model, &iter, CASE_DATA, &c, -1);
218 if (c != NULL) {
219 if (active == c->shown)
220 return;
222 c->shown = active;
223 } else {
224 recurse_set_shown (model, &iter, active);
226 check_consistent (model, &iter);
228 graph_view_update_visible ((GraphView *) app->gv);
231 static gboolean
232 git_read (GIOChannel *io, GIOCondition cond, struct _app_data *app)
234 int fd;
236 fd = g_io_channel_unix_get_fd (io);
237 do {
238 char buf[4096];
239 int len;
240 GtkTextIter end;
242 len = read (fd, buf, sizeof (buf));
243 if (len <= 0) {
244 int err = len ? errno : 0;
245 switch (err) {
246 case EAGAIN:
247 case EINTR:
248 return TRUE;
249 default:
250 g_io_channel_unref (app->git_io);
251 app->git_io = NULL;
252 return FALSE;
256 gtk_text_buffer_get_end_iter (app->git_buffer, &end);
257 gtk_text_buffer_insert (app->git_buffer, &end, buf, len);
258 } while (TRUE);
261 static void
262 do_git (struct _app_data *app, char **argv)
264 gint output;
265 GError *error = NULL;
266 GtkTextIter start, stop;
267 long flags;
269 if (! g_spawn_async_with_pipes (NULL, argv, NULL,
270 G_SPAWN_SEARCH_PATH |
271 G_SPAWN_STDERR_TO_DEV_NULL |
272 G_SPAWN_FILE_AND_ARGV_ZERO,
273 NULL, NULL, NULL,
274 NULL, &output, NULL,
275 &error))
277 g_error ("spawn failed: %s", error->message);
280 if (app->git_io) {
281 g_io_channel_shutdown (app->git_io, FALSE, NULL);
282 g_io_channel_unref (app->git_io);
285 gtk_text_buffer_get_bounds (app->git_buffer, &start, &stop);
286 gtk_text_buffer_delete (app->git_buffer, &start, &stop);
288 flags = fcntl (output, F_GETFL);
289 if ((flags & O_NONBLOCK) == 0)
290 fcntl (output, F_SETFL, flags | O_NONBLOCK);
292 app->git_io = g_io_channel_unix_new (output);
293 g_io_add_watch (app->git_io, G_IO_IN | G_IO_HUP, (GIOFunc) git_read, app);
296 static void
297 gv_report_selected (GraphView *gv, int i, struct _app_data *app)
299 cairo_perf_report_t *report;
300 char *hyphen;
302 if (i == -1)
303 return;
305 report = &app->reports[i];
306 hyphen = strchr (report->configuration, '-');
307 if (hyphen != NULL) {
308 int len = hyphen - report->configuration;
309 char *id = g_malloc (len + 1);
310 char *argv[5];
312 memcpy (id, report->configuration, len);
313 id[len] = '\0';
315 argv[0] = (char *) "git";
316 argv[1] = (char *) "git";
317 argv[2] = (char *) "show";
318 argv[3] = id;
319 argv[4] = NULL;
321 do_git (app, argv);
322 free (id);
326 static GtkWidget *
327 window_create (test_case_t *cases,
328 cairo_perf_report_t *reports,
329 int num_reports)
331 GtkWidget *window, *table, *w;
332 GtkWidget *tv, *sw;
333 GtkTreeStore *store;
334 GtkTreeViewColumn *column;
335 GtkCellRenderer *renderer;
336 struct _app_data *data;
339 data = g_new0 (struct _app_data, 1);
340 data->cases = cases;
341 data->reports = reports;
342 data->num_reports = num_reports;
344 window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
345 gtk_window_set_title (GTK_WINDOW (window), "Cairo Performance Graph");
346 g_object_set_data_full (G_OBJECT (window),
347 "app-data", data, (GDestroyNotify)g_free);
349 data->window = window;
351 table = gtk_table_new (2, 2, FALSE);
353 /* legend & show/hide lines (categorised) */
354 tv = gtk_tree_view_new ();
355 store = cases_to_store (cases);
356 data->case_store = store;
357 gtk_tree_view_set_model (GTK_TREE_VIEW (tv), GTK_TREE_MODEL (store));
359 renderer = gtk_cell_renderer_toggle_new ();
360 column = gtk_tree_view_column_new_with_attributes (NULL,
361 renderer,
362 "active", CASE_SHOWN,
363 "inconsistent", CASE_INCONSISTENT,
364 NULL);
365 gtk_tree_view_append_column (GTK_TREE_VIEW (tv), column);
366 g_signal_connect (renderer, "toggled",
367 G_CALLBACK (show_case_toggled), data);
369 renderer = gtk_cell_renderer_text_new ();
370 column = gtk_tree_view_column_new_with_attributes ("Backend",
371 renderer,
372 "text", CASE_BACKEND,
373 NULL);
374 gtk_tree_view_append_column (GTK_TREE_VIEW (tv), column);
376 renderer = gtk_cell_renderer_text_new ();
377 column = gtk_tree_view_column_new_with_attributes ("Content",
378 renderer,
379 "text", CASE_CONTENT,
380 NULL);
381 gtk_tree_view_append_column (GTK_TREE_VIEW (tv), column);
383 renderer = gtk_cell_renderer_text_new ();
384 column = gtk_tree_view_column_new_with_attributes ("Test",
385 renderer,
386 "text", CASE_NAME,
387 "foreground-gdk", CASE_FG_COLOR,
388 NULL);
389 gtk_tree_view_append_column (GTK_TREE_VIEW (tv), column);
391 renderer = gtk_cell_renderer_text_new ();
392 column = gtk_tree_view_column_new_with_attributes ("Size",
393 renderer,
394 "text", CASE_SIZE,
395 NULL);
396 gtk_tree_view_append_column (GTK_TREE_VIEW (tv), column);
398 gtk_tree_view_set_rules_hint (GTK_TREE_VIEW (tv), TRUE);
399 g_object_unref (store);
401 sw = gtk_scrolled_window_new (NULL, NULL);
402 gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (sw),
403 GTK_POLICY_NEVER,
404 GTK_POLICY_AUTOMATIC);
405 gtk_container_add (GTK_CONTAINER (sw), tv);
406 gtk_widget_show (tv);
407 gtk_table_attach (GTK_TABLE (table), sw,
408 0, 1, 0, 2,
409 GTK_FILL, GTK_FILL,
410 4, 4);
411 gtk_widget_show (sw);
413 /* the performance chart */
414 w = graph_view_new ();
415 data->gv = w;
416 g_signal_connect (w, "report-selected",
417 G_CALLBACK (gv_report_selected), data);
418 graph_view_set_reports ((GraphView *)w, cases, reports, num_reports);
419 gtk_table_attach (GTK_TABLE (table), w,
420 1, 2, 0, 1,
421 GTK_FILL | GTK_EXPAND, GTK_FILL | GTK_EXPAND,
422 4, 4);
423 gtk_widget_show (w);
425 /* interesting information - presumably the commit log */
426 w = gtk_text_view_new ();
427 data->git_buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (w));
428 sw = gtk_scrolled_window_new (NULL, NULL);
429 gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (sw),
430 GTK_POLICY_NEVER,
431 GTK_POLICY_AUTOMATIC);
432 gtk_container_add (GTK_CONTAINER (sw), w);
433 gtk_widget_show (w);
434 gtk_table_attach (GTK_TABLE (table), sw,
435 1, 2, 1, 2,
436 GTK_FILL, GTK_FILL | GTK_EXPAND,
437 4, 4);
438 gtk_widget_show (sw);
440 gtk_container_add (GTK_CONTAINER (window), table);
441 gtk_widget_show (table);
443 return window;
446 static void
447 name_to_color (const char *name, GdkColor *color)
449 gint v = g_str_hash (name);
451 color->red = ((v >> 0) & 0xff) / 384. * 0xffff;
452 color->green = ((v >> 8) & 0xff) / 384. * 0xffff;
453 color->blue = ((v >> 16) & 0xff) / 384. * 0xffff;
456 static test_case_t *
457 test_cases_from_reports (cairo_perf_report_t *reports,
458 int num_reports)
460 test_case_t *cases, *c;
461 test_report_t **tests;
462 int i, j;
463 int num_tests;
465 num_tests = 0;
466 for (i = 0; i < num_reports; i++) {
467 for (j = 0; reports[i].tests[j].name != NULL; j++)
469 if (j > num_tests)
470 num_tests = j;
473 cases = xcalloc (num_tests+1, sizeof (test_case_t));
474 tests = xmalloc (num_reports * sizeof (test_report_t *));
475 for (i = 0; i < num_reports; i++)
476 tests[i] = reports[i].tests;
478 c = cases;
479 while (1) {
480 int seen_non_null;
481 test_report_t *min_test;
483 /* We expect iterations values of 0 when multiple raw reports
484 * for the same test have been condensed into the stats of the
485 * first. So we just skip these later reports that have no
486 * stats. */
487 seen_non_null = 0;
488 for (i = 0; i < num_reports; i++) {
489 while (tests[i]->name && tests[i]->stats.iterations == 0)
490 tests[i]++;
491 if (tests[i]->name)
492 seen_non_null++;
495 if (seen_non_null < 2)
496 break;
498 /* Find the minimum of all current tests, (we have to do this
499 * in case some reports don't have a particular test). */
500 for (i = 0; i < num_reports; i++) {
501 if (tests[i]->name) {
502 min_test = tests[i];
503 break;
506 for (++i; i < num_reports; i++) {
507 if (tests[i]->name &&
508 test_report_cmp_backend_then_name (tests[i], min_test) < 0)
510 min_test = tests[i];
514 c->min_test = min_test;
515 c->backend = min_test->backend;
516 c->content = min_test->content;
517 c->name = min_test->name;
518 c->size = min_test->size;
519 c->baseline = min_test->stats.min_ticks;
520 c->min = c->max = 1.;
521 c->shown = TRUE;
522 name_to_color (c->name, &c->color);
524 for (i = 0; i < num_reports; i++) {
525 if (tests[i]->name &&
526 test_report_cmp_backend_then_name (tests[i], min_test) == 0)
528 tests[i]++;
529 break;
533 for (++i; i < num_reports; i++) {
534 if (tests[i]->name &&
535 test_report_cmp_backend_then_name (tests[i], min_test) == 0)
537 double v = tests[i]->stats.min_ticks / c->baseline;
538 if (v < c->min)
539 c->min = v;
540 if (v > c->max)
541 c->max = v;
542 tests[i]++;
546 c++;
548 free (tests);
550 return cases;
553 main (int argc, char *argv[])
555 cairo_perf_report_t *reports;
556 test_case_t *cases;
557 test_report_t *t;
558 int i;
559 GtkWidget *window;
561 gtk_init (&argc, &argv);
563 if (argc < 3)
564 usage (argv[0]);
566 reports = xmalloc ((argc-1) * sizeof (cairo_perf_report_t));
567 for (i = 1; i < argc; i++ )
568 cairo_perf_report_load (&reports[i-1], argv[i]);
570 cases = test_cases_from_reports (reports, argc-1);
572 window = window_create (cases, reports, argc-1);
573 g_signal_connect (window, "delete-event",
574 G_CALLBACK (gtk_main_quit), NULL);
575 gtk_widget_show (window);
577 gtk_main ();
579 /* Pointless memory cleanup, (would be a great place for talloc) */
580 free (cases);
581 for (i = 0; i < argc-1; i++) {
582 for (t = reports[i].tests; t->name; t++) {
583 free (t->samples);
584 free (t->backend);
585 free (t->name);
587 free (reports[i].tests);
588 free (reports[i].configuration);
590 free (reports);
592 return 0;