summaryrefslogtreecommitdiff
path: root/perf/cairo-perf-graph-files.c
diff options
context:
space:
mode:
Diffstat (limited to 'perf/cairo-perf-graph-files.c')
-rw-r--r--perf/cairo-perf-graph-files.c604
1 files changed, 604 insertions, 0 deletions
diff --git a/perf/cairo-perf-graph-files.c b/perf/cairo-perf-graph-files.c
new file mode 100644
index 000000000..1fd99e4ad
--- /dev/null
+++ b/perf/cairo-perf-graph-files.c
@@ -0,0 +1,604 @@
+/*
+ * Copyright © 2008 Chris Wilson
+ *
+ * Permission to use, copy, modify, distribute, and sell this software
+ * and its documentation for any purpose is hereby granted without
+ * fee, provided that the above copyright notice appear in all copies
+ * and that both that copyright notice and this permission notice
+ * appear in supporting documentation, and that the name of the
+ * copyright holders not be used in advertising or publicity
+ * pertaining to distribution of the software without specific,
+ * written prior permission. The copyright holders make no
+ * representations about the suitability of this software for any
+ * purpose. It is provided "as is" without express or implied
+ * warranty.
+ *
+ * THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS
+ * SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
+ * FITNESS, IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY
+ * SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN
+ * AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING
+ * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
+ * SOFTWARE.
+ *
+ * Authors: Chris Wilson <chris@chris-wilson.co.uk>
+ */
+
+#include "cairo-perf.h"
+#include "cairo-perf-graph.h"
+
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <errno.h>
+#include <fcntl.h>
+
+#include <cairo.h>
+
+static void
+usage (const char *argv0)
+{
+ char const *basename = strrchr (argv0, '/');
+ basename = basename ? basename+1 : argv0;
+ g_printerr ("Usage: %s [options] file1 file2 [...]\n\n", basename);
+ g_printerr ("Draws a graph illustrating the change in performance over a series.\n");
+ exit(1);
+}
+
+enum {
+ CASE_SHOWN,
+ CASE_INCONSISTENT,
+ CASE_BACKEND,
+ CASE_CONTENT,
+ CASE_NAME,
+ CASE_SIZE,
+ CASE_FG_COLOR,
+ CASE_DATA,
+ CASE_NCOLS
+};
+
+static GtkTreeStore *
+cases_to_store (test_case_t *cases)
+{
+ GtkTreeStore *store;
+ GtkTreeIter backend_iter;
+ GtkTreeIter content_iter;
+ const char *backend = NULL;
+ const char *content = NULL;
+
+ store = gtk_tree_store_new (CASE_NCOLS,
+ G_TYPE_BOOLEAN, /* shown */
+ G_TYPE_BOOLEAN, /* inconsistent */
+ G_TYPE_STRING, /* backend */
+ G_TYPE_STRING, /* content */
+ G_TYPE_STRING, /* name */
+ G_TYPE_INT, /* size */
+ GDK_TYPE_COLOR, /* fg color */
+ G_TYPE_POINTER); /* data */
+ while (cases->backend != NULL) {
+ GtkTreeIter iter;
+
+ if (backend == NULL || strcmp (backend, cases->backend)) {
+ gtk_tree_store_append (store, &backend_iter, NULL);
+ gtk_tree_store_set (store, &backend_iter,
+ CASE_SHOWN, TRUE,
+ CASE_BACKEND, cases->backend,
+ -1);
+ backend = cases->backend;
+ content = NULL;
+ }
+ if (content == NULL || strcmp (content, cases->content)) {
+ gtk_tree_store_append (store, &content_iter, &backend_iter);
+ gtk_tree_store_set (store, &content_iter,
+ CASE_SHOWN, TRUE,
+ CASE_BACKEND, cases->backend,
+ CASE_CONTENT, cases->content,
+ -1);
+ content = cases->content;
+ }
+
+ gtk_tree_store_append (store, &iter, &content_iter);
+ gtk_tree_store_set (store, &iter,
+ CASE_SHOWN, TRUE,
+ CASE_BACKEND, cases->backend,
+ CASE_CONTENT, cases->content,
+ CASE_NAME, cases->name,
+ CASE_SIZE, cases->size,
+ CASE_FG_COLOR, &cases->color,
+ CASE_DATA, cases,
+ -1);
+ cases++;
+ }
+
+ return store;
+}
+
+struct _app_data {
+ GtkWidget *window;
+
+ test_case_t *cases;
+ cairo_perf_report_t *reports;
+ int num_reports;
+
+ GtkTreeStore *case_store;
+
+ GIOChannel *git_io;
+ GtkTextBuffer *git_buffer;
+
+ GtkWidget *gv;
+};
+
+static void
+recurse_set_shown (GtkTreeModel *model,
+ GtkTreeIter *parent,
+ gboolean shown)
+{
+ GtkTreeIter iter;
+
+ if (gtk_tree_model_iter_children (model, &iter, parent)) do {
+ test_case_t *c;
+
+ gtk_tree_model_get (model, &iter, CASE_DATA, &c, -1);
+ if (c == NULL) {
+ recurse_set_shown (model, &iter, shown);
+ } else if (shown != c->shown) {
+ c->shown = shown;
+ gtk_tree_store_set (GTK_TREE_STORE (model), &iter,
+ CASE_SHOWN, shown,
+ CASE_INCONSISTENT, FALSE,
+ -1);
+ }
+ } while (gtk_tree_model_iter_next (model, &iter));
+}
+
+static gboolean
+children_consistent (GtkTreeModel *model,
+ GtkTreeIter *parent)
+{
+ GtkTreeIter iter;
+ gboolean first = TRUE;
+ gboolean first_active;
+
+ if (gtk_tree_model_iter_children (model, &iter, parent)) do {
+ gboolean active, inconsistent;
+
+ gtk_tree_model_get (model, &iter,
+ CASE_INCONSISTENT, &inconsistent,
+ CASE_SHOWN, &active,
+ -1);
+ if (inconsistent)
+ return FALSE;
+
+ if (first) {
+ first_active = active;
+ first = FALSE;
+ } else if (active != first_active)
+ return FALSE;
+ } while (gtk_tree_model_iter_next (model, &iter));
+
+ return TRUE;
+}
+
+static void
+check_consistent (GtkTreeModel *model,
+ GtkTreeIter *child)
+{
+ GtkTreeIter parent;
+
+ if (gtk_tree_model_iter_parent (model, &parent, child)) {
+ gtk_tree_store_set (GTK_TREE_STORE (model), &parent,
+ CASE_INCONSISTENT,
+ ! children_consistent (model, &parent),
+ -1);
+ check_consistent (model, &parent);
+ }
+}
+
+static void
+show_case_toggled (GtkCellRendererToggle *cell,
+ gchar *str,
+ struct _app_data *app)
+{
+ GtkTreeModel *model;
+ GtkTreePath *path;
+ GtkTreeIter iter;
+ test_case_t *c;
+ gboolean active;
+
+ active = ! gtk_cell_renderer_toggle_get_active (cell);
+
+ model = GTK_TREE_MODEL (app->case_store);
+
+ path = gtk_tree_path_new_from_string (str);
+ gtk_tree_model_get_iter (model, &iter, path);
+ gtk_tree_path_free (path);
+
+ gtk_tree_store_set (app->case_store, &iter,
+ CASE_SHOWN, active,
+ CASE_INCONSISTENT, FALSE,
+ -1);
+ gtk_tree_model_get (model, &iter, CASE_DATA, &c, -1);
+ if (c != NULL) {
+ if (active == c->shown)
+ return;
+
+ c->shown = active;
+ } else {
+ recurse_set_shown (model, &iter, active);
+ }
+ check_consistent (model, &iter);
+
+ graph_view_update_visible ((GraphView *) app->gv);
+}
+
+static gboolean
+git_read (GIOChannel *io,
+ GIOCondition cond,
+ struct _app_data *app)
+{
+ int fd;
+
+ fd = g_io_channel_unix_get_fd (io);
+ do {
+ char buf[4096];
+ int len;
+ GtkTextIter end;
+
+ len = read (fd, buf, sizeof (buf));
+ if (len <= 0) {
+ int err = len ? errno : 0;
+ switch (err) {
+ case EAGAIN:
+ case EINTR:
+ return TRUE;
+ default:
+ g_io_channel_unref (app->git_io);
+ app->git_io = NULL;
+ return FALSE;
+ }
+ }
+
+ gtk_text_buffer_get_end_iter (app->git_buffer, &end);
+ gtk_text_buffer_insert (app->git_buffer, &end, buf, len);
+ } while (TRUE);
+}
+
+static void
+do_git (struct _app_data *app,
+ char **argv)
+{
+ gint output;
+ GError *error = NULL;
+ GtkTextIter start, stop;
+ long flags;
+
+ if (! g_spawn_async_with_pipes (NULL, argv, NULL,
+ G_SPAWN_SEARCH_PATH |
+ G_SPAWN_STDERR_TO_DEV_NULL |
+ G_SPAWN_FILE_AND_ARGV_ZERO,
+ NULL, NULL, NULL,
+ NULL, &output, NULL,
+ &error))
+ {
+ g_error ("spawn failed: %s", error->message);
+ }
+
+ if (app->git_io) {
+ g_io_channel_shutdown (app->git_io, FALSE, NULL);
+ g_io_channel_unref (app->git_io);
+ }
+
+ gtk_text_buffer_get_bounds (app->git_buffer, &start, &stop);
+ gtk_text_buffer_delete (app->git_buffer, &start, &stop);
+
+ flags = fcntl (output, F_GETFL);
+ if ((flags & O_NONBLOCK) == 0)
+ fcntl (output, F_SETFL, flags | O_NONBLOCK);
+
+ app->git_io = g_io_channel_unix_new (output);
+ g_io_add_watch (app->git_io, G_IO_IN | G_IO_HUP, (GIOFunc) git_read, app);
+}
+
+static void
+gv_report_selected (GraphView *gv,
+ int i,
+ struct _app_data *app)
+{
+ cairo_perf_report_t *report;
+ char *hyphen;
+
+ if (i == -1)
+ return;
+
+ report = &app->reports[i];
+ hyphen = strchr (report->configuration, '-');
+ if (hyphen != NULL) {
+ int len = hyphen - report->configuration;
+ char *id = g_malloc (len + 1);
+ char *argv[5];
+
+ memcpy (id, report->configuration, len);
+ id[len] = '\0';
+
+ argv[0] = (char *) "git";
+ argv[1] = (char *) "git";
+ argv[2] = (char *) "show";
+ argv[3] = id;
+ argv[4] = NULL;
+
+ do_git (app, argv);
+ g_free (id);
+ }
+}
+
+static GtkWidget *
+window_create (test_case_t *cases,
+ cairo_perf_report_t *reports,
+ int num_reports)
+{
+ GtkWidget *window, *table, *w;
+ GtkWidget *tv, *sw;
+ GtkTreeStore *store;
+ GtkTreeViewColumn *column;
+ GtkCellRenderer *renderer;
+ struct _app_data *data;
+
+
+ data = g_new0 (struct _app_data, 1);
+ data->cases = cases;
+ data->reports = reports;
+ data->num_reports = num_reports;
+
+ window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
+ gtk_window_set_title (GTK_WINDOW (window), "Cairo Performance Graph");
+ g_object_set_data_full (G_OBJECT (window),
+ "app-data", data, (GDestroyNotify)g_free);
+
+ data->window = window;
+
+ table = gtk_table_new (2, 2, FALSE);
+
+ /* legend & show/hide lines (categorised) */
+ tv = gtk_tree_view_new ();
+ store = cases_to_store (cases);
+ data->case_store = store;
+ gtk_tree_view_set_model (GTK_TREE_VIEW (tv), GTK_TREE_MODEL (store));
+
+ renderer = gtk_cell_renderer_toggle_new ();
+ column = gtk_tree_view_column_new_with_attributes (NULL,
+ renderer,
+ "active", CASE_SHOWN,
+ "inconsistent", CASE_INCONSISTENT,
+ NULL);
+ gtk_tree_view_append_column (GTK_TREE_VIEW (tv), column);
+ g_signal_connect (renderer, "toggled",
+ G_CALLBACK (show_case_toggled), data);
+
+ renderer = gtk_cell_renderer_text_new ();
+ column = gtk_tree_view_column_new_with_attributes ("Backend",
+ renderer,
+ "text", CASE_BACKEND,
+ NULL);
+ gtk_tree_view_append_column (GTK_TREE_VIEW (tv), column);
+
+ renderer = gtk_cell_renderer_text_new ();
+ column = gtk_tree_view_column_new_with_attributes ("Content",
+ renderer,
+ "text", CASE_CONTENT,
+ NULL);
+ gtk_tree_view_append_column (GTK_TREE_VIEW (tv), column);
+
+ renderer = gtk_cell_renderer_text_new ();
+ column = gtk_tree_view_column_new_with_attributes ("Test",
+ renderer,
+ "text", CASE_NAME,
+ "foreground-gdk", CASE_FG_COLOR,
+ NULL);
+ gtk_tree_view_append_column (GTK_TREE_VIEW (tv), column);
+
+ renderer = gtk_cell_renderer_text_new ();
+ column = gtk_tree_view_column_new_with_attributes ("Size",
+ renderer,
+ "text", CASE_SIZE,
+ NULL);
+ gtk_tree_view_append_column (GTK_TREE_VIEW (tv), column);
+
+ gtk_tree_view_set_rules_hint (GTK_TREE_VIEW (tv), TRUE);
+ g_object_unref (store);
+
+ sw = gtk_scrolled_window_new (NULL, NULL);
+ gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (sw),
+ GTK_POLICY_NEVER,
+ GTK_POLICY_AUTOMATIC);
+ gtk_container_add (GTK_CONTAINER (sw), tv);
+ gtk_widget_show (tv);
+ gtk_table_attach (GTK_TABLE (table), sw,
+ 0, 1, 0, 2,
+ GTK_FILL, GTK_FILL,
+ 4, 4);
+ gtk_widget_show (sw);
+
+ /* the performance chart */
+ w = graph_view_new ();
+ data->gv = w;
+ g_signal_connect (w, "report-selected",
+ G_CALLBACK (gv_report_selected), data);
+ graph_view_set_reports ((GraphView *)w, cases, reports, num_reports);
+ gtk_table_attach (GTK_TABLE (table), w,
+ 1, 2, 0, 1,
+ GTK_FILL | GTK_EXPAND, GTK_FILL | GTK_EXPAND,
+ 4, 4);
+ gtk_widget_show (w);
+
+ /* interesting information - presumably the commit log */
+ w = gtk_text_view_new ();
+ data->git_buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (w));
+ sw = gtk_scrolled_window_new (NULL, NULL);
+ gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (sw),
+ GTK_POLICY_NEVER,
+ GTK_POLICY_AUTOMATIC);
+ gtk_container_add (GTK_CONTAINER (sw), w);
+ gtk_widget_show (w);
+ gtk_table_attach (GTK_TABLE (table), sw,
+ 1, 2, 1, 2,
+ GTK_FILL, GTK_FILL | GTK_EXPAND,
+ 4, 4);
+ gtk_widget_show (sw);
+
+ gtk_container_add (GTK_CONTAINER (window), table);
+ gtk_widget_show (table);
+
+ return window;
+}
+
+static void
+name_to_color (const char *name,
+ GdkColor *color)
+{
+ gint v = g_str_hash (name);
+
+ color->red = ((v >> 0) & 0xff) / 384. * 0xffff;
+ color->green = ((v >> 8) & 0xff) / 384. * 0xffff;
+ color->blue = ((v >> 16) & 0xff) / 384. * 0xffff;
+}
+
+static test_case_t *
+test_cases_from_reports (cairo_perf_report_t *reports,
+ int num_reports)
+{
+ test_case_t *cases, *c;
+ test_report_t **tests;
+ int i, j;
+ int num_tests;
+
+ num_tests = 0;
+ for (i = 0; i < num_reports; i++) {
+ for (j = 0; reports[i].tests[j].name != NULL; j++)
+ ;
+ if (j > num_tests)
+ num_tests = j;
+ }
+
+ cases = xcalloc (num_tests+1, sizeof (test_case_t));
+ tests = xmalloc (num_reports * sizeof (test_report_t *));
+ for (i = 0; i < num_reports; i++)
+ tests[i] = reports[i].tests;
+
+ c = cases;
+ while (1) {
+ int seen_non_null;
+ test_report_t *min_test;
+
+ /* We expect iterations values of 0 when multiple raw reports
+ * for the same test have been condensed into the stats of the
+ * first. So we just skip these later reports that have no
+ * stats. */
+ seen_non_null = 0;
+ for (i = 0; i < num_reports; i++) {
+ while (tests[i]->name && tests[i]->stats.iterations == 0)
+ tests[i]++;
+ if (tests[i]->name)
+ seen_non_null++;
+ }
+
+ if (seen_non_null < 2)
+ break;
+
+ /* Find the minimum of all current tests, (we have to do this
+ * in case some reports don't have a particular test). */
+ for (i = 0; i < num_reports; i++) {
+ if (tests[i]->name) {
+ min_test = tests[i];
+ break;
+ }
+ }
+ for (++i; i < num_reports; i++) {
+ if (tests[i]->name &&
+ test_report_cmp_backend_then_name (tests[i], min_test) < 0)
+ {
+ min_test = tests[i];
+ }
+ }
+
+ c->min_test = min_test;
+ c->backend = min_test->backend;
+ c->content = min_test->content;
+ c->name = min_test->name;
+ c->size = min_test->size;
+ c->baseline = min_test->stats.min_ticks;
+ c->min = c->max = 1.;
+ c->shown = TRUE;
+ name_to_color (c->name, &c->color);
+
+ for (i = 0; i < num_reports; i++) {
+ if (tests[i]->name &&
+ test_report_cmp_backend_then_name (tests[i], min_test) == 0)
+ {
+ tests[i]++;
+ break;
+ }
+ }
+
+ for (++i; i < num_reports; i++) {
+ if (tests[i]->name &&
+ test_report_cmp_backend_then_name (tests[i], min_test) == 0)
+ {
+ double v = tests[i]->stats.min_ticks / c->baseline;
+ if (v < c->min)
+ c->min = v;
+ if (v > c->max)
+ c->max = v;
+ tests[i]++;
+ }
+ }
+
+ c++;
+ }
+ free (tests);
+
+ return cases;
+}
+int
+main (int argc,
+ char *argv[])
+{
+ cairo_perf_report_t *reports;
+ test_case_t *cases;
+ test_report_t *t;
+ int i;
+ GtkWidget *window;
+
+ gtk_init (&argc, &argv);
+
+ if (argc < 3)
+ usage (argv[0]);
+
+ reports = xmalloc ((argc-1) * sizeof (cairo_perf_report_t));
+ for (i = 1; i < argc; i++ )
+ cairo_perf_report_load (&reports[i-1], argv[i], i, NULL);
+
+ cases = test_cases_from_reports (reports, argc-1);
+
+ window = window_create (cases, reports, argc-1);
+ g_signal_connect (window, "delete-event",
+ G_CALLBACK (gtk_main_quit), NULL);
+ gtk_widget_show (window);
+
+ gtk_main ();
+
+ /* Pointless memory cleanup, (would be a great place for talloc) */
+ free (cases);
+ for (i = 0; i < argc-1; i++) {
+ for (t = reports[i].tests; t->name; t++) {
+ free (t->samples);
+ free (t->backend);
+ free (t->name);
+ }
+ free (reports[i].tests);
+ free (reports[i].configuration);
+ }
+ free (reports);
+
+ return 0;
+}