/* This file is part of odin, a memory profiler with fragmentation analysis. Copyright (C) 2007 Chris Wilson odin is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version. odin is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with odin. If not, see / The GNU General Public License is contained in the file COPYING. */ #include #include #include "odin.h" #include "client.h" #include "allocators.h" #include "summary.h" #ifndef _ #define _(x) x #endif typedef struct _summary_view { GtkTreeView tv; } SummaryView; typedef struct _summary_view_class { GtkTreeViewClass parent_class; } SummaryViewClass; static GType summary_view_get_type (void); G_DEFINE_TYPE (SummaryView, summary_view, GTK_TYPE_TREE_VIEW) static gboolean summary_view_button_press (GtkWidget *widget, GdkEventButton *ev) { SummaryView *self = (SummaryView *) widget; gboolean ret; if (ev->button == 3) { GtkTreePath *path; GtkTreeViewColumn *column; GtkTreeIter iter; gint cell_x, cell_y; if (gtk_tree_view_get_path_at_pos (&self->tv, ev->x, ev->y, &path, &column, &cell_x, &cell_y)) { GtkTreeModel *model = gtk_tree_view_get_model (&self->tv); struct _sum_allocator *sum; GtkWidget *dialog, *entry; gtk_tree_model_get_iter (model, &iter, path); gtk_tree_path_free (path); sum = iter.user_data; dialog = gtk_dialog_new_with_buttons ( "Add an allocation function", GTK_WINDOW (gtk_widget_get_toplevel (widget)), GTK_DIALOG_DESTROY_WITH_PARENT | GTK_DIALOG_NO_SEPARATOR, GTK_STOCK_OK, GTK_RESPONSE_ACCEPT, GTK_STOCK_CANCEL, GTK_RESPONSE_REJECT, NULL); entry = gtk_entry_new (); gtk_entry_set_activates_default (GTK_ENTRY (entry), TRUE); gtk_dialog_set_default_response (GTK_DIALOG (dialog), GTK_RESPONSE_ACCEPT); gtk_entry_set_text (GTK_ENTRY (entry), sum->frame); gtk_container_add ( GTK_CONTAINER (GTK_DIALOG (dialog)->vbox), entry); gtk_widget_show (entry); if (gtk_dialog_run (GTK_DIALOG (dialog)) == GTK_RESPONSE_ACCEPT) { const char *pattern = gtk_entry_get_text (GTK_ENTRY (entry)); GError *error = NULL; if (! app_add_alloc_fn (app_get (widget), pattern, &error)) { GtkWidget *msg = gtk_message_dialog_new ( GTK_WINDOW (gtk_widget_get_toplevel (widget)), GTK_DIALOG_DESTROY_WITH_PARENT | GTK_DIALOG_NO_SEPARATOR, GTK_MESSAGE_ERROR, GTK_BUTTONS_OK, "Failed to compile '%s' into a regex: %s", pattern, error->message); g_error_free (error); gtk_dialog_run (GTK_DIALOG (msg)); gtk_widget_destroy (msg); } } gtk_widget_destroy (dialog); } } ret = FALSE; if (GTK_WIDGET_CLASS (summary_view_parent_class)->button_press_event) ret = GTK_WIDGET_CLASS (summary_view_parent_class)->button_press_event (widget, ev); return ret; } static GSList * _sum_allocators_get_allocators (struct _sum_allocator *sum) { GSList *l, *list = NULL; guint n; for (n = 0; n < G_N_ELEMENTS (sum->ht); n++) { for (l = sum->ht[n]; l != NULL; l = g_slist_next (l)) list = g_slist_prepend (list, l->data); } return list; } static gboolean summary_view_query_tooltip (GtkWidget *widget, gint x, gint y, gboolean keyboard_tip, GtkTooltip *tooltip) { SummaryView * self = (SummaryView *) widget; GtkTreeModel *model = gtk_tree_view_get_model (&self->tv); PangoRectangle ink_rect, logical_rect; PangoAttrList *attrs; GtkWidget *label; GtkTreePath *path; GtkTreeViewColumn *column; GtkTreeIter iter; gint cell_x, cell_y; struct _sum_allocator *sum; const Allocator *A; GSList *list; guint last_allocs; GString *string; const gchar *main_fn; guint n, m, depth, n_allocs; const gchar *last; gint fraction; gchar *text; gchar calls[80]; gint len; gtk_tree_view_convert_widget_to_bin_window_coords (&self->tv, x, y, &x, &y); if (! gtk_tree_view_get_path_at_pos (&self->tv, x, y, &path, &column, &cell_x, &cell_y)) return FALSE; gtk_tree_view_set_tooltip_cell (&self->tv, tooltip, path, column, NULL); if (! gtk_tree_model_get_iter (model, &iter, path)) { gtk_tree_path_free (path); return FALSE; } sum = iter.user_data; gtk_tree_path_free (path); if (sum == NULL || sum->count == 0) return FALSE; A = sum->largest; n_allocs = A->time_tail->n_allocs; if (! allocators_store_is_cumulative ((AllocatorsStore *) model) && A->time_tail->prev) { n_allocs -= A->time_tail->freed; } string = g_string_new ("Most frequent allocation callsite, "); len = g_snprintf (calls + 40, 40, "%u", n_allocs); pretty_print_number (calls + 40, len, calls); g_string_append (string, calls); g_string_append_printf (string, " calls (%.1f%%)", n_allocs * 100. / sum->count); for (n = 0; n < A->n_frames; n++) if (A->frames[n]->function == A->alloc_fn) break; depth = n; ink_rect.x = ink_rect.y = 0; ink_rect.width = ink_rect.height = 12; logical_rect = ink_rect; attrs = pango_attr_list_new (); list = _sum_allocators_get_allocators (sum); main_fn = app_get_main_function (app_get (widget)); last = NULL; last_allocs = 0; for (m = n; m < MIN (n + NUM_CALLERS, A->n_frames); m++) { if (A->frames[m]->function_srcloc != last) { PangoAttribute *attr; GSList *l, *next, **prev; guint this_allocs = 0; for (l = list, prev = &list; l != NULL; l = next) { const Allocator *AA = l->data; next = g_slist_next (l); if (A->frames[m] != AA->frames[m]) { g_slist_free1 (l); *prev = next; } else { n_allocs = AA->time_tail->n_allocs; if (! allocators_store_is_cumulative ((AllocatorsStore *) model) && AA->time_tail->prev) { n_allocs -= AA->time_tail->freed; } this_allocs += n_allocs; prev = &l->next; } } g_string_append_c (string, '\n'); g_string_append_c (string, '\t'); fraction = (this_allocs * 1024 / sum->count) << 10; if (last_allocs) fraction |= this_allocs * 1024 / last_allocs - this_allocs / last_allocs; attr = pango_attr_shape_new_with_data (&ink_rect, &logical_rect, GINT_TO_POINTER (fraction), NULL, NULL); attr->start_index = string->len; g_string_append (string, BULLET); attr->end_index = string->len; g_string_append_c (string, '\t'); g_string_append (string, A->frames[m]->function_srcloc); pango_attr_list_insert (attrs, attr); last = A->frames[m]->function_srcloc; last_allocs = this_allocs; } else n++; if (A->frames[m]->function == main_fn) break; } g_slist_free (list); g_string_append (string, "\n\nAllocation stack:"); last = NULL; for (n = depth + 1; n-- > 0; ){ if (A->frames[n]->function_srcloc != last) { g_string_append_c (string, '\n'); g_string_append_c (string, '\t'); g_string_append (string, A->frames[n]->function_srcloc); last = A->frames[n]->function_srcloc; } } text = g_string_free (string, FALSE); label = summary_tooltip_label (text, attrs); gtk_tooltip_set_custom (tooltip, label); g_free (text); pango_attr_list_unref (attrs); return TRUE; } static void summary_view_class_init (SummaryViewClass *klass) { GtkWidgetClass *widget_class = (GtkWidgetClass *) klass; widget_class->button_press_event = summary_view_button_press; widget_class->query_tooltip = summary_view_query_tooltip; } static void summary_view_set_sort_by_size (SummaryView *self) { GtkTreeModel *model = gtk_tree_view_get_model (&self->tv); allocators_store_set_sort ((AllocatorsStore *) model, ALLOCATORS_STORE_SORT_BY_SIZE); } static void summary_view_set_sort_by_count (SummaryView *self) { GtkTreeModel *model = gtk_tree_view_get_model (&self->tv); allocators_store_set_sort ((AllocatorsStore *) model, ALLOCATORS_STORE_SORT_BY_COUNT); } static gboolean summary_view_search_equal_func (GtkTreeModel *model, gint column, const gchar *key, GtkTreeIter *iter, gpointer data) { struct _sum_allocator *sum = iter->user_data; if (sum->filtered) return FALSE; return strncmp (sum->frame, key, strlen (key)) != 0; } static void summary_view_init (SummaryView *self) { GtkTreeViewColumn *column; GtkCellRenderer *renderer; renderer = gtk_cell_renderer_text_new (); g_object_set (G_OBJECT (renderer), "ellipsize", PANGO_ELLIPSIZE_END, "ellipsize-set", TRUE, "width-chars", 50, NULL); column = gtk_tree_view_column_new_with_attributes ("Allocating Function", renderer, "text", ALLOCATORS_FRAME, NULL); gtk_tree_view_append_column (&self->tv, column); renderer = gtk_cell_renderer_text_new (); column = gtk_tree_view_column_new_with_attributes ("Count", renderer, NULL); gtk_cell_layout_set_cell_data_func (GTK_CELL_LAYOUT (column), renderer, cell_layout_pretty_print_uint, GUINT_TO_POINTER (ALLOCATORS_COUNT), NULL); gtk_tree_view_column_set_clickable (column, TRUE); gtk_tree_view_column_set_reorderable (column, TRUE); g_signal_connect_swapped (column, "clicked", G_CALLBACK (summary_view_set_sort_by_count), self); gtk_tree_view_append_column (&self->tv, column); renderer = gtk_cell_renderer_text_new (); column = gtk_tree_view_column_new_with_attributes ("Bytes", renderer, NULL); gtk_cell_layout_set_cell_data_func (GTK_CELL_LAYOUT (column), renderer, cell_layout_pretty_print_uint64, GUINT_TO_POINTER (ALLOCATORS_SIZE), NULL); gtk_tree_view_column_set_clickable (column, TRUE); gtk_tree_view_column_set_reorderable (column, TRUE); g_signal_connect_swapped (column, "clicked", G_CALLBACK (summary_view_set_sort_by_size), self); gtk_tree_view_append_column (&self->tv, column); gtk_tree_view_set_search_column (&self->tv, ALLOCATORS_FRAME); gtk_tree_view_set_enable_search (&self->tv, TRUE); gtk_tree_view_set_search_equal_func (&self->tv, summary_view_search_equal_func, NULL, NULL); gtk_widget_set_has_tooltip (GTK_WIDGET (self), TRUE); } GtkWidget * summary_view_new (AllocatorsStore *store) { return g_object_new (summary_view_get_type (), "model", store, NULL); }