Upload Tizen2.0 source
[framework/graphics/cairo.git] / perf / cairo-perf-graph-files.c
1 /*
2  * Copyright © 2008 Chris Wilson
3  *
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.
15  *
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.
24  *
25  * Authors: Chris Wilson <chris@chris-wilson.co.uk>
26  */
27
28 #include "cairo-perf.h"
29 #include "cairo-perf-graph.h"
30
31 #include <stdlib.h>
32 #include <string.h>
33 #include <unistd.h>
34 #include <errno.h>
35 #include <fcntl.h>
36
37 #include <cairo.h>
38
39 static void
40 usage (const char *argv0)
41 {
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);
47 }
48
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
59 };
60
61 static GtkTreeStore *
62 cases_to_store (test_case_t *cases)
63 {
64     GtkTreeStore *store;
65     GtkTreeIter backend_iter;
66     GtkTreeIter content_iter;
67     const char *backend = NULL;
68     const char *content = NULL;
69
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;
81
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;
90         }
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;
99         }
100
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++;
112     }
113
114     return store;
115 }
116
117 struct _app_data {
118     GtkWidget *window;
119
120     test_case_t *cases;
121     cairo_perf_report_t *reports;
122     int num_reports;
123
124     GtkTreeStore *case_store;
125
126     GIOChannel *git_io;
127     GtkTextBuffer *git_buffer;
128
129     GtkWidget *gv;
130 };
131
132 static void
133 recurse_set_shown (GtkTreeModel *model,
134                    GtkTreeIter  *parent,
135                    gboolean      shown)
136 {
137     GtkTreeIter iter;
138
139     if (gtk_tree_model_iter_children (model, &iter, parent)) do {
140         test_case_t *c;
141
142         gtk_tree_model_get (model, &iter, CASE_DATA, &c, -1);
143         if (c == NULL) {
144             recurse_set_shown (model, &iter, shown);
145         } else if (shown != c->shown) {
146             c->shown = shown;
147             gtk_tree_store_set (GTK_TREE_STORE (model), &iter,
148                                 CASE_SHOWN, shown,
149                                 CASE_INCONSISTENT, FALSE,
150                                 -1);
151         }
152     } while (gtk_tree_model_iter_next (model, &iter));
153 }
154
155 static gboolean
156 children_consistent (GtkTreeModel *model,
157                      GtkTreeIter  *parent)
158 {
159     GtkTreeIter iter;
160     gboolean first = TRUE;
161     gboolean first_active;
162
163     if (gtk_tree_model_iter_children (model, &iter, parent)) do {
164         gboolean active, inconsistent;
165
166         gtk_tree_model_get (model, &iter,
167                             CASE_INCONSISTENT, &inconsistent,
168                             CASE_SHOWN, &active,
169                             -1);
170         if (inconsistent)
171             return FALSE;
172
173         if (first) {
174             first_active = active;
175             first = FALSE;
176         } else if (active != first_active)
177             return FALSE;
178     } while (gtk_tree_model_iter_next (model, &iter));
179
180     return TRUE;
181 }
182
183 static void
184 check_consistent (GtkTreeModel *model,
185                   GtkTreeIter  *child)
186 {
187     GtkTreeIter parent;
188
189     if (gtk_tree_model_iter_parent (model, &parent, child)) {
190         gtk_tree_store_set (GTK_TREE_STORE (model), &parent,
191                             CASE_INCONSISTENT,
192                             ! children_consistent (model, &parent),
193                             -1);
194         check_consistent (model, &parent);
195     }
196 }
197
198 static void
199 show_case_toggled (GtkCellRendererToggle *cell,
200                    gchar                 *str,
201                    struct _app_data      *app)
202 {
203     GtkTreeModel *model;
204     GtkTreePath *path;
205     GtkTreeIter iter;
206     test_case_t *c;
207     gboolean active;
208
209     active = ! gtk_cell_renderer_toggle_get_active (cell);
210
211     model = GTK_TREE_MODEL (app->case_store);
212
213     path = gtk_tree_path_new_from_string (str);
214     gtk_tree_model_get_iter (model, &iter, path);
215     gtk_tree_path_free (path);
216
217     gtk_tree_store_set (app->case_store, &iter,
218                         CASE_SHOWN, active,
219                         CASE_INCONSISTENT, FALSE,
220                         -1);
221     gtk_tree_model_get (model, &iter, CASE_DATA, &c, -1);
222     if (c != NULL) {
223         if (active == c->shown)
224             return;
225
226         c->shown = active;
227     } else {
228         recurse_set_shown (model, &iter, active);
229     }
230     check_consistent (model, &iter);
231
232     graph_view_update_visible ((GraphView *) app->gv);
233 }
234
235 static gboolean
236 git_read (GIOChannel       *io,
237           GIOCondition      cond,
238           struct _app_data *app)
239 {
240     int fd;
241
242     fd = g_io_channel_unix_get_fd (io);
243     do {
244         char buf[4096];
245         int len;
246         GtkTextIter end;
247
248         len = read (fd, buf, sizeof (buf));
249         if (len <= 0) {
250             int err = len ? errno : 0;
251             switch (err) {
252             case EAGAIN:
253             case EINTR:
254                 return TRUE;
255             default:
256                 g_io_channel_unref (app->git_io);
257                 app->git_io = NULL;
258                 return FALSE;
259             }
260         }
261
262         gtk_text_buffer_get_end_iter (app->git_buffer, &end);
263         gtk_text_buffer_insert (app->git_buffer, &end, buf, len);
264     } while (TRUE);
265 }
266
267 static void
268 do_git (struct _app_data  *app,
269         char             **argv)
270 {
271     gint output;
272     GError *error = NULL;
273     GtkTextIter start, stop;
274     long flags;
275
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,
280                                     NULL, NULL, NULL,
281                                     NULL, &output, NULL,
282                                     &error))
283     {
284         g_error ("spawn failed: %s", error->message);
285     }
286
287     if (app->git_io) {
288         g_io_channel_shutdown (app->git_io, FALSE, NULL);
289         g_io_channel_unref (app->git_io);
290     }
291
292     gtk_text_buffer_get_bounds (app->git_buffer, &start, &stop);
293     gtk_text_buffer_delete (app->git_buffer, &start, &stop);
294
295     flags = fcntl (output, F_GETFL);
296     if ((flags & O_NONBLOCK) == 0)
297         fcntl (output, F_SETFL, flags | O_NONBLOCK);
298
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);
301 }
302
303 static void
304 gv_report_selected (GraphView        *gv,
305                     int               i,
306                     struct _app_data *app)
307 {
308     cairo_perf_report_t *report;
309     char *hyphen;
310
311     if (i == -1)
312         return;
313
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);
319         char *argv[5];
320
321         memcpy (id, report->configuration, len);
322         id[len] = '\0';
323
324         argv[0] = (char *) "git";
325         argv[1] = (char *) "git";
326         argv[2] = (char *) "show";
327         argv[3] = id;
328         argv[4] = NULL;
329
330         do_git (app, argv);
331         g_free (id);
332     }
333 }
334
335 static GtkWidget *
336 window_create (test_case_t         *cases,
337                cairo_perf_report_t *reports,
338                int                  num_reports)
339 {
340     GtkWidget *window, *table, *w;
341     GtkWidget *tv, *sw;
342     GtkTreeStore *store;
343     GtkTreeViewColumn *column;
344     GtkCellRenderer *renderer;
345     struct _app_data *data;
346
347
348     data = g_new0 (struct _app_data, 1);
349     data->cases = cases;
350     data->reports = reports;
351     data->num_reports = num_reports;
352
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);
357
358     data->window = window;
359
360     table = gtk_table_new (2, 2, FALSE);
361
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));
367
368     renderer = gtk_cell_renderer_toggle_new ();
369     column = gtk_tree_view_column_new_with_attributes (NULL,
370             renderer,
371             "active", CASE_SHOWN,
372             "inconsistent", CASE_INCONSISTENT,
373             NULL);
374     gtk_tree_view_append_column (GTK_TREE_VIEW (tv), column);
375     g_signal_connect (renderer, "toggled",
376                       G_CALLBACK (show_case_toggled), data);
377
378     renderer = gtk_cell_renderer_text_new ();
379     column = gtk_tree_view_column_new_with_attributes ("Backend",
380             renderer,
381             "text", CASE_BACKEND,
382             NULL);
383     gtk_tree_view_append_column (GTK_TREE_VIEW (tv), column);
384
385     renderer = gtk_cell_renderer_text_new ();
386     column = gtk_tree_view_column_new_with_attributes ("Content",
387             renderer,
388             "text", CASE_CONTENT,
389             NULL);
390     gtk_tree_view_append_column (GTK_TREE_VIEW (tv), column);
391
392     renderer = gtk_cell_renderer_text_new ();
393     column = gtk_tree_view_column_new_with_attributes ("Test",
394             renderer,
395             "text", CASE_NAME,
396             "foreground-gdk", CASE_FG_COLOR,
397             NULL);
398     gtk_tree_view_append_column (GTK_TREE_VIEW (tv), column);
399
400     renderer = gtk_cell_renderer_text_new ();
401     column = gtk_tree_view_column_new_with_attributes ("Size",
402             renderer,
403             "text", CASE_SIZE,
404             NULL);
405     gtk_tree_view_append_column (GTK_TREE_VIEW (tv), column);
406
407     gtk_tree_view_set_rules_hint (GTK_TREE_VIEW (tv), TRUE);
408     g_object_unref (store);
409
410     sw = gtk_scrolled_window_new (NULL, NULL);
411     gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (sw),
412                                     GTK_POLICY_NEVER,
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,
417                       0, 1, 0, 2,
418                       GTK_FILL, GTK_FILL,
419                       4, 4);
420     gtk_widget_show (sw);
421
422     /* the performance chart */
423     w = graph_view_new ();
424     data->gv = w;
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,
429                       1, 2, 0, 1,
430                       GTK_FILL | GTK_EXPAND, GTK_FILL | GTK_EXPAND,
431                       4, 4);
432     gtk_widget_show (w);
433
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),
439                                     GTK_POLICY_NEVER,
440                                     GTK_POLICY_AUTOMATIC);
441     gtk_container_add (GTK_CONTAINER (sw), w);
442     gtk_widget_show (w);
443     gtk_table_attach (GTK_TABLE (table), sw,
444                       1, 2, 1, 2,
445                       GTK_FILL, GTK_FILL | GTK_EXPAND,
446                       4, 4);
447     gtk_widget_show (sw);
448
449     gtk_container_add (GTK_CONTAINER (window), table);
450     gtk_widget_show (table);
451
452     return window;
453 }
454
455 static void
456 name_to_color (const char *name,
457                GdkColor   *color)
458 {
459     gint v = g_str_hash (name);
460
461     color->red = ((v >>  0) & 0xff) / 384. * 0xffff;
462     color->green = ((v >>  8) & 0xff) / 384. * 0xffff;
463     color->blue = ((v >> 16) & 0xff) / 384. * 0xffff;
464 }
465
466 static test_case_t *
467 test_cases_from_reports (cairo_perf_report_t *reports,
468                          int                  num_reports)
469 {
470     test_case_t *cases, *c;
471     test_report_t **tests;
472     int i, j;
473     int num_tests;
474
475     num_tests = 0;
476     for (i = 0; i < num_reports; i++) {
477         for (j = 0; reports[i].tests[j].name != NULL; j++)
478             ;
479         if (j > num_tests)
480             num_tests = j;
481     }
482
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;
487
488     c = cases;
489     while (1) {
490         int seen_non_null;
491         test_report_t *min_test;
492
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
496          * stats. */
497         seen_non_null = 0;
498         for (i = 0; i < num_reports; i++) {
499             while (tests[i]->name && tests[i]->stats.iterations == 0)
500                 tests[i]++;
501             if (tests[i]->name)
502                 seen_non_null++;
503         }
504
505         if (seen_non_null < 2)
506             break;
507
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) {
512                 min_test = tests[i];
513                 break;
514             }
515         }
516         for (++i; i < num_reports; i++) {
517             if (tests[i]->name &&
518                 test_report_cmp_backend_then_name (tests[i], min_test) < 0)
519             {
520                 min_test = tests[i];
521             }
522         }
523
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.;
531         c->shown = TRUE;
532         name_to_color (c->name, &c->color);
533
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)
537             {
538                 tests[i]++;
539                 break;
540             }
541         }
542
543         for (++i; i < num_reports; i++) {
544             if (tests[i]->name &&
545                 test_report_cmp_backend_then_name (tests[i], min_test) == 0)
546             {
547                 double v = tests[i]->stats.min_ticks / c->baseline;
548                 if (v < c->min)
549                     c->min = v;
550                 if (v > c->max)
551                     c->max = v;
552                 tests[i]++;
553             }
554         }
555
556         c++;
557     }
558     free (tests);
559
560     return cases;
561 }
562 int
563 main (int   argc,
564       char *argv[])
565 {
566     cairo_perf_report_t *reports;
567     test_case_t *cases;
568     test_report_t *t;
569     int i;
570     GtkWidget *window;
571
572     gtk_init (&argc, &argv);
573
574     if (argc < 3)
575         usage (argv[0]);
576
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);
580
581     cases = test_cases_from_reports (reports, argc-1);
582
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);
587
588     gtk_main ();
589
590     /* Pointless memory cleanup, (would be a great place for talloc) */
591     free (cases);
592     for (i = 0; i < argc-1; i++) {
593         for (t = reports[i].tests; t->name; t++) {
594             free (t->samples);
595             free (t->backend);
596             free (t->name);
597         }
598         free (reports[i].tests);
599         free (reports[i].configuration);
600     }
601     free (reports);
602
603     return 0;
604 }