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
, GtkTreeIter
*parent
, gboolean shown
)
137 if (gtk_tree_model_iter_children (model
, &iter
, parent
)) do {
140 gtk_tree_model_get (model
, &iter
, CASE_DATA
, &c
, -1);
142 recurse_set_shown (model
, &iter
, shown
);
143 } else if (shown
!= c
->shown
) {
145 gtk_tree_store_set (GTK_TREE_STORE (model
), &iter
,
147 CASE_INCONSISTENT
, FALSE
,
150 } while (gtk_tree_model_iter_next (model
, &iter
));
154 children_consistent (GtkTreeModel
*model
, GtkTreeIter
*parent
)
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
,
171 first_active
= active
;
173 } else if (active
!= first_active
)
175 } while (gtk_tree_model_iter_next (model
, &iter
));
181 check_consistent (GtkTreeModel
*model
, GtkTreeIter
*child
)
185 if (gtk_tree_model_iter_parent (model
, &parent
, child
)) {
186 gtk_tree_store_set (GTK_TREE_STORE (model
), &parent
,
188 ! children_consistent (model
, &parent
),
190 check_consistent (model
, &parent
);
195 show_case_toggled (GtkCellRendererToggle
*cell
,
197 struct _app_data
*app
)
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
,
215 CASE_INCONSISTENT
, FALSE
,
217 gtk_tree_model_get (model
, &iter
, CASE_DATA
, &c
, -1);
219 if (active
== c
->shown
)
224 recurse_set_shown (model
, &iter
, active
);
226 check_consistent (model
, &iter
);
228 graph_view_update_visible ((GraphView
*) app
->gv
);
232 git_read (GIOChannel
*io
, GIOCondition cond
, struct _app_data
*app
)
236 fd
= g_io_channel_unix_get_fd (io
);
242 len
= read (fd
, buf
, sizeof (buf
));
244 int err
= len
? errno
: 0;
250 g_io_channel_unref (app
->git_io
);
256 gtk_text_buffer_get_end_iter (app
->git_buffer
, &end
);
257 gtk_text_buffer_insert (app
->git_buffer
, &end
, buf
, len
);
262 do_git (struct _app_data
*app
, char **argv
)
265 GError
*error
= NULL
;
266 GtkTextIter start
, stop
;
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
,
277 g_error ("spawn failed: %s", error
->message
);
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
);
297 gv_report_selected (GraphView
*gv
, int i
, struct _app_data
*app
)
299 cairo_perf_report_t
*report
;
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);
312 memcpy (id
, report
->configuration
, len
);
315 argv
[0] = (char *) "git";
316 argv
[1] = (char *) "git";
317 argv
[2] = (char *) "show";
327 window_create (test_case_t
*cases
,
328 cairo_perf_report_t
*reports
,
331 GtkWidget
*window
, *table
, *w
;
334 GtkTreeViewColumn
*column
;
335 GtkCellRenderer
*renderer
;
336 struct _app_data
*data
;
339 data
= g_new0 (struct _app_data
, 1);
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
,
362 "active", CASE_SHOWN
,
363 "inconsistent", CASE_INCONSISTENT
,
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",
372 "text", CASE_BACKEND
,
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",
379 "text", CASE_CONTENT
,
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",
387 "foreground-gdk", CASE_FG_COLOR
,
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",
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
),
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
,
411 gtk_widget_show (sw
);
413 /* the performance chart */
414 w
= graph_view_new ();
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
,
421 GTK_FILL
| GTK_EXPAND
, GTK_FILL
| GTK_EXPAND
,
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
),
431 GTK_POLICY_AUTOMATIC
);
432 gtk_container_add (GTK_CONTAINER (sw
), w
);
434 gtk_table_attach (GTK_TABLE (table
), sw
,
436 GTK_FILL
, GTK_FILL
| GTK_EXPAND
,
438 gtk_widget_show (sw
);
440 gtk_container_add (GTK_CONTAINER (window
), table
);
441 gtk_widget_show (table
);
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;
457 test_cases_from_reports (cairo_perf_report_t
*reports
,
460 test_case_t
*cases
, *c
;
461 test_report_t
**tests
;
466 for (i
= 0; i
< num_reports
; i
++) {
467 for (j
= 0; reports
[i
].tests
[j
].name
!= NULL
; 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
;
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
488 for (i
= 0; i
< num_reports
; i
++) {
489 while (tests
[i
]->name
&& tests
[i
]->stats
.iterations
== 0)
495 if (seen_non_null
< 2)
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
) {
506 for (++i
; i
< num_reports
; i
++) {
507 if (tests
[i
]->name
&&
508 test_report_cmp_backend_then_name (tests
[i
], min_test
) < 0)
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.;
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)
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
;
553 main (int argc
, char *argv
[])
555 cairo_perf_report_t
*reports
;
561 gtk_init (&argc
, &argv
);
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
);
579 /* Pointless memory cleanup, (would be a great place for talloc) */
581 for (i
= 0; i
< argc
-1; i
++) {
582 for (t
= reports
[i
].tests
; t
->name
; t
++) {
587 free (reports
[i
].tests
);
588 free (reports
[i
].configuration
);