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
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
25 * Authors: Chris Wilson <chris@chris-wilson.co.uk>
28 #include "cairo-perf.h"
29 #include "cairo-perf-graph.h"
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");
62 cases_to_store (test_case_t *cases)
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) {
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,
86 CASE_BACKEND, cases->backend,
88 backend = cases->backend;
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,
95 CASE_BACKEND, cases->backend,
96 CASE_CONTENT, cases->content,
98 content = cases->content;
101 gtk_tree_store_append (store, &iter, &content_iter);
102 gtk_tree_store_set (store, &iter,
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,
121 cairo_perf_report_t *reports;
124 GtkTreeStore *case_store;
127 GtkTextBuffer *git_buffer;
133 recurse_set_shown (GtkTreeModel *model,
139 if (gtk_tree_model_iter_children (model, &iter, parent)) do {
142 gtk_tree_model_get (model, &iter, CASE_DATA, &c, -1);
144 recurse_set_shown (model, &iter, shown);
145 } else if (shown != c->shown) {
147 gtk_tree_store_set (GTK_TREE_STORE (model), &iter,
149 CASE_INCONSISTENT, FALSE,
152 } while (gtk_tree_model_iter_next (model, &iter));
156 children_consistent (GtkTreeModel *model,
160 gboolean first = TRUE;
161 gboolean first_active;
163 if (gtk_tree_model_iter_children (model, &iter, parent)) do {
164 gboolean active, inconsistent;
166 gtk_tree_model_get (model, &iter,
167 CASE_INCONSISTENT, &inconsistent,
174 first_active = active;
176 } else if (active != first_active)
178 } while (gtk_tree_model_iter_next (model, &iter));
184 check_consistent (GtkTreeModel *model,
189 if (gtk_tree_model_iter_parent (model, &parent, child)) {
190 gtk_tree_store_set (GTK_TREE_STORE (model), &parent,
192 ! children_consistent (model, &parent),
194 check_consistent (model, &parent);
199 show_case_toggled (GtkCellRendererToggle *cell,
201 struct _app_data *app)
209 active = ! gtk_cell_renderer_toggle_get_active (cell);
211 model = GTK_TREE_MODEL (app->case_store);
213 path = gtk_tree_path_new_from_string (str);
214 gtk_tree_model_get_iter (model, &iter, path);
215 gtk_tree_path_free (path);
217 gtk_tree_store_set (app->case_store, &iter,
219 CASE_INCONSISTENT, FALSE,
221 gtk_tree_model_get (model, &iter, CASE_DATA, &c, -1);
223 if (active == c->shown)
228 recurse_set_shown (model, &iter, active);
230 check_consistent (model, &iter);
232 graph_view_update_visible ((GraphView *) app->gv);
236 git_read (GIOChannel *io,
238 struct _app_data *app)
242 fd = g_io_channel_unix_get_fd (io);
248 len = read (fd, buf, sizeof (buf));
250 int err = len ? errno : 0;
256 g_io_channel_unref (app->git_io);
262 gtk_text_buffer_get_end_iter (app->git_buffer, &end);
263 gtk_text_buffer_insert (app->git_buffer, &end, buf, len);
268 do_git (struct _app_data *app,
272 GError *error = NULL;
273 GtkTextIter start, stop;
276 if (! g_spawn_async_with_pipes (NULL, argv, NULL,
277 G_SPAWN_SEARCH_PATH |
278 G_SPAWN_STDERR_TO_DEV_NULL |
279 G_SPAWN_FILE_AND_ARGV_ZERO,
284 g_error ("spawn failed: %s", error->message);
288 g_io_channel_shutdown (app->git_io, FALSE, NULL);
289 g_io_channel_unref (app->git_io);
292 gtk_text_buffer_get_bounds (app->git_buffer, &start, &stop);
293 gtk_text_buffer_delete (app->git_buffer, &start, &stop);
295 flags = fcntl (output, F_GETFL);
296 if ((flags & O_NONBLOCK) == 0)
297 fcntl (output, F_SETFL, flags | O_NONBLOCK);
299 app->git_io = g_io_channel_unix_new (output);
300 g_io_add_watch (app->git_io, G_IO_IN | G_IO_HUP, (GIOFunc) git_read, app);
304 gv_report_selected (GraphView *gv,
306 struct _app_data *app)
308 cairo_perf_report_t *report;
314 report = &app->reports[i];
315 hyphen = strchr (report->configuration, '-');
316 if (hyphen != NULL) {
317 int len = hyphen - report->configuration;
318 char *id = g_malloc (len + 1);
321 memcpy (id, report->configuration, len);
324 argv[0] = (char *) "git";
325 argv[1] = (char *) "git";
326 argv[2] = (char *) "show";
336 window_create (test_case_t *cases,
337 cairo_perf_report_t *reports,
340 GtkWidget *window, *table, *w;
343 GtkTreeViewColumn *column;
344 GtkCellRenderer *renderer;
345 struct _app_data *data;
348 data = g_new0 (struct _app_data, 1);
350 data->reports = reports;
351 data->num_reports = num_reports;
353 window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
354 gtk_window_set_title (GTK_WINDOW (window), "Cairo Performance Graph");
355 g_object_set_data_full (G_OBJECT (window),
356 "app-data", data, (GDestroyNotify)g_free);
358 data->window = window;
360 table = gtk_table_new (2, 2, FALSE);
362 /* legend & show/hide lines (categorised) */
363 tv = gtk_tree_view_new ();
364 store = cases_to_store (cases);
365 data->case_store = store;
366 gtk_tree_view_set_model (GTK_TREE_VIEW (tv), GTK_TREE_MODEL (store));
368 renderer = gtk_cell_renderer_toggle_new ();
369 column = gtk_tree_view_column_new_with_attributes (NULL,
371 "active", CASE_SHOWN,
372 "inconsistent", CASE_INCONSISTENT,
374 gtk_tree_view_append_column (GTK_TREE_VIEW (tv), column);
375 g_signal_connect (renderer, "toggled",
376 G_CALLBACK (show_case_toggled), data);
378 renderer = gtk_cell_renderer_text_new ();
379 column = gtk_tree_view_column_new_with_attributes ("Backend",
381 "text", CASE_BACKEND,
383 gtk_tree_view_append_column (GTK_TREE_VIEW (tv), column);
385 renderer = gtk_cell_renderer_text_new ();
386 column = gtk_tree_view_column_new_with_attributes ("Content",
388 "text", CASE_CONTENT,
390 gtk_tree_view_append_column (GTK_TREE_VIEW (tv), column);
392 renderer = gtk_cell_renderer_text_new ();
393 column = gtk_tree_view_column_new_with_attributes ("Test",
396 "foreground-gdk", CASE_FG_COLOR,
398 gtk_tree_view_append_column (GTK_TREE_VIEW (tv), column);
400 renderer = gtk_cell_renderer_text_new ();
401 column = gtk_tree_view_column_new_with_attributes ("Size",
405 gtk_tree_view_append_column (GTK_TREE_VIEW (tv), column);
407 gtk_tree_view_set_rules_hint (GTK_TREE_VIEW (tv), TRUE);
408 g_object_unref (store);
410 sw = gtk_scrolled_window_new (NULL, NULL);
411 gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (sw),
413 GTK_POLICY_AUTOMATIC);
414 gtk_container_add (GTK_CONTAINER (sw), tv);
415 gtk_widget_show (tv);
416 gtk_table_attach (GTK_TABLE (table), sw,
420 gtk_widget_show (sw);
422 /* the performance chart */
423 w = graph_view_new ();
425 g_signal_connect (w, "report-selected",
426 G_CALLBACK (gv_report_selected), data);
427 graph_view_set_reports ((GraphView *)w, cases, reports, num_reports);
428 gtk_table_attach (GTK_TABLE (table), w,
430 GTK_FILL | GTK_EXPAND, GTK_FILL | GTK_EXPAND,
434 /* interesting information - presumably the commit log */
435 w = gtk_text_view_new ();
436 data->git_buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (w));
437 sw = gtk_scrolled_window_new (NULL, NULL);
438 gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (sw),
440 GTK_POLICY_AUTOMATIC);
441 gtk_container_add (GTK_CONTAINER (sw), w);
443 gtk_table_attach (GTK_TABLE (table), sw,
445 GTK_FILL, GTK_FILL | GTK_EXPAND,
447 gtk_widget_show (sw);
449 gtk_container_add (GTK_CONTAINER (window), table);
450 gtk_widget_show (table);
456 name_to_color (const char *name,
459 gint v = g_str_hash (name);
461 color->red = ((v >> 0) & 0xff) / 384. * 0xffff;
462 color->green = ((v >> 8) & 0xff) / 384. * 0xffff;
463 color->blue = ((v >> 16) & 0xff) / 384. * 0xffff;
467 test_cases_from_reports (cairo_perf_report_t *reports,
470 test_case_t *cases, *c;
471 test_report_t **tests;
476 for (i = 0; i < num_reports; i++) {
477 for (j = 0; reports[i].tests[j].name != NULL; j++)
483 cases = xcalloc (num_tests+1, sizeof (test_case_t));
484 tests = xmalloc (num_reports * sizeof (test_report_t *));
485 for (i = 0; i < num_reports; i++)
486 tests[i] = reports[i].tests;
491 test_report_t *min_test;
493 /* We expect iterations values of 0 when multiple raw reports
494 * for the same test have been condensed into the stats of the
495 * first. So we just skip these later reports that have no
498 for (i = 0; i < num_reports; i++) {
499 while (tests[i]->name && tests[i]->stats.iterations == 0)
505 if (seen_non_null < 2)
508 /* Find the minimum of all current tests, (we have to do this
509 * in case some reports don't have a particular test). */
510 for (i = 0; i < num_reports; i++) {
511 if (tests[i]->name) {
516 for (++i; i < num_reports; i++) {
517 if (tests[i]->name &&
518 test_report_cmp_backend_then_name (tests[i], min_test) < 0)
524 c->min_test = min_test;
525 c->backend = min_test->backend;
526 c->content = min_test->content;
527 c->name = min_test->name;
528 c->size = min_test->size;
529 c->baseline = min_test->stats.min_ticks;
530 c->min = c->max = 1.;
532 name_to_color (c->name, &c->color);
534 for (i = 0; i < num_reports; i++) {
535 if (tests[i]->name &&
536 test_report_cmp_backend_then_name (tests[i], min_test) == 0)
543 for (++i; i < num_reports; i++) {
544 if (tests[i]->name &&
545 test_report_cmp_backend_then_name (tests[i], min_test) == 0)
547 double v = tests[i]->stats.min_ticks / c->baseline;
566 cairo_perf_report_t *reports;
572 gtk_init (&argc, &argv);
577 reports = xmalloc ((argc-1) * sizeof (cairo_perf_report_t));
578 for (i = 1; i < argc; i++ )
579 cairo_perf_report_load (&reports[i-1], argv[i], i, NULL);
581 cases = test_cases_from_reports (reports, argc-1);
583 window = window_create (cases, reports, argc-1);
584 g_signal_connect (window, "delete-event",
585 G_CALLBACK (gtk_main_quit), NULL);
586 gtk_widget_show (window);
590 /* Pointless memory cleanup, (would be a great place for talloc) */
592 for (i = 0; i < argc-1; i++) {
593 for (t = reports[i].tests; t->name; t++) {
598 free (reports[i].tests);
599 free (reports[i].configuration);