/* 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" #define BORDER 5 #define _(x) x typedef struct _summary_chart { GtkWidget widget; cairo_pattern_t *image; int image_width, image_height; AllocatorsStore *store; struct _sum_allocator others; guint dirty; } SummaryChart; typedef struct _summary_chart_class { GtkWidgetClass parent_class; } SummaryChartClass; static GType summary_chart_get_type (void); G_DEFINE_TYPE (SummaryChart, summary_chart, GTK_TYPE_WIDGET) enum { PROP_0 = 0, PROP_MODEL }; static gboolean _summary_chart_mark_dirty (SummaryChart *self) { gtk_widget_queue_draw (&self->widget); gtk_widget_trigger_tooltip_query (&self->widget); self->dirty = 0; return FALSE; } static void summary_chart_mark_dirty (SummaryChart *self) { if (self->dirty == 0) { self->dirty = gdk_threads_add_idle ( (GSourceFunc) _summary_chart_mark_dirty, self); } } static void summary_chart_set_model (SummaryChart *self, AllocatorsStore *store) { if (self->store != NULL) { g_signal_handlers_disconnect_matched (self->store, G_SIGNAL_MATCH_DATA, 0, 0, NULL, NULL, self); g_object_unref (self->store); self->store = NULL; } if (store != NULL) { g_signal_connect_swapped (store, "row-changed", G_CALLBACK (summary_chart_mark_dirty), self); self->store = g_object_ref (store); } summary_chart_mark_dirty (self); } static void summary_chart_set_property (GObject *obj, guint id, const GValue *v, GParamSpec *spec) { SummaryChart *self = (SummaryChart *) obj; switch (id) { case PROP_MODEL: summary_chart_set_model (self, g_value_get_object (v)); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (obj, id, spec); break; } } static void summary_chart_get_property (GObject *obj, guint id, GValue *v, GParamSpec *spec) { SummaryChart *self = (SummaryChart *) obj; switch (id) { case PROP_MODEL: g_value_set_object (v, self->store); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (obj, id, spec); break; } } static void summary_chart_realize (GtkWidget *widget) { GdkWindowAttr attributes; GTK_WIDGET_SET_FLAGS (widget, GTK_REALIZED); attributes.window_type = GDK_WINDOW_CHILD; attributes.x = widget->allocation.x; attributes.y = widget->allocation.y; attributes.width = widget->allocation.width; attributes.height = widget->allocation.height; attributes.wclass = GDK_INPUT_OUTPUT; attributes.visual = gtk_widget_get_visual (widget); attributes.colormap = gtk_widget_get_colormap (widget); attributes.event_mask = GDK_BUTTON_PRESS_MASK | GDK_POINTER_MOTION_MASK | GDK_ENTER_NOTIFY_MASK | GDK_LEAVE_NOTIFY_MASK | GDK_EXPOSURE_MASK; widget->window = gdk_window_new (gtk_widget_get_parent_window (widget), &attributes, GDK_WA_X | GDK_WA_Y | GDK_WA_VISUAL | GDK_WA_COLORMAP); gdk_window_set_user_data (widget->window, widget); widget->style = gtk_style_attach (widget->style, widget->window); gtk_style_set_background (widget->style, widget->window, GTK_STATE_NORMAL); } static void summary_chart_load_background (SummaryChart *self, cairo_surface_t *target) { static cairo_surface_t *background; if (background == NULL) background = cairo_image_surface_create_from_png (DATADIR G_DIR_SEPARATOR_S "odin.png"); if (cairo_surface_status (background) == CAIRO_STATUS_SUCCESS) { cairo_surface_t *surface; cairo_t *cr; cairo_status_t status; self->image_width = cairo_image_surface_get_width (background); self->image_height = cairo_image_surface_get_height (background); surface = cairo_surface_create_similar (target, cairo_surface_get_content (background), self->image_width, self->image_height); cr = cairo_create (surface); cairo_set_source_surface (cr, background, 0, 0); cairo_set_operator (cr, CAIRO_OPERATOR_SOURCE); cairo_paint (cr); status = cairo_status (cr); cairo_destroy (cr); if (status == CAIRO_STATUS_SUCCESS && cairo_surface_status (surface) == CAIRO_STATUS_SUCCESS) { self->image = cairo_pattern_create_for_surface (surface); if (cairo_pattern_status (self->image)) { cairo_pattern_destroy (self->image); self->image = NULL; } } cairo_surface_destroy (surface); } } static void summary_chart_screen_changed (GtkWidget *widget, GdkScreen *old_screen) { SummaryChart *self = (SummaryChart *) widget; PangoContext *ctx; PangoFontDescription *merge, *desc; if (GTK_WIDGET_CLASS (summary_chart_parent_class)->screen_changed) GTK_WIDGET_CLASS (summary_chart_parent_class)->screen_changed (widget, old_screen); ctx = gtk_widget_get_pango_context (&self->widget); merge = pango_context_get_font_description (ctx); desc = pango_font_description_new (); pango_font_description_set_family_static (desc, "sans"); pango_font_description_set_size (desc, PANGO_SCALE * 8); pango_font_description_set_weight (desc, PANGO_WEIGHT_NORMAL); pango_font_description_merge (desc, merge, FALSE); pango_context_set_font_description (ctx, desc); pango_font_description_free (desc); } static gboolean summary_chart_expose (GtkWidget *widget, GdkEventExpose *ev) { SummaryChart *self = (SummaryChart *) widget; cairo_t *cr; GPtrArray *allocators; guint64 total; guint min; guint n; gint r; gdouble theta, theta_offset, ww, hh; AllocatorsStoreSort sort; PangoContext *ctx; PangoLayout *text; PangoRectangle logical; sort = allocators_store_get_sort (self->store); cr = gdk_cairo_create (widget->window); gdk_cairo_region (cr, ev->region); cairo_clip (cr); total = 0; allocators = allocators_store_get_array (self->store); for (n = 0; n < allocators->len; n++) { struct _sum_allocator *sum = g_ptr_array_index (allocators, n); total += sort == ALLOCATORS_STORE_SORT_BY_SIZE ? sum->size : sum->count; } r = MIN (widget->allocation.width, widget->allocation.height)/2 - BORDER; min = total / (G_PI * r); theta = 0; for (n = 0; n < allocators->len; n++) { struct _sum_allocator *sum = g_ptr_array_index (allocators, n); if (sort == ALLOCATORS_STORE_SORT_BY_SIZE) { if (sum->size < min) break; theta += sum->size * 2 * G_PI / total; } else { if (sum->count < min) break; theta += sum->count * 2 * G_PI / total; } } theta_offset = -G_PI / 2 + (2 * G_PI - theta) / 2.; ww = .5 * widget->allocation.width; hh = .5 * widget->allocation.height; /* background */ cairo_arc (cr, ww, hh, r, 0, 2 * G_PI); cairo_set_source_rgb (cr, .7, .7, .7); if (self->image == NULL) summary_chart_load_background (self, cairo_get_target (cr)); if (self->image != NULL) { cairo_matrix_t matrix; cairo_fill_preserve (cr); cairo_clip (cr); cairo_matrix_init_translate (&matrix, .5 * self->image_width - ww, .5 * self->image_height - hh); cairo_pattern_set_matrix (self->image, &matrix); cairo_set_source (cr, self->image); cairo_paint (cr); cairo_reset_clip (cr); } else cairo_fill (cr); /* segments */ theta = theta_offset; for (n = 0; n < allocators->len; n++) { struct _sum_allocator *sum = g_ptr_array_index (allocators, n); gdouble dtheta, rgb[3]; if (sort == ALLOCATORS_STORE_SORT_BY_SIZE) { if (sum->size < min) break; dtheta = sum->size * 2 * G_PI / total; } else { if (sum->count < min) break; dtheta = sum->count * 2 * G_PI / total; } cairo_move_to (cr, ww, hh); cairo_arc (cr, ww, hh, r, theta, theta + dtheta); cairo_arc_negative (cr, ww, hh, r/4, theta + dtheta, theta); cairo_close_path (cr); hsv_to_rgb ((theta + dtheta / 2.) / (2 * G_PI) * 6, 1., .5, rgb); cairo_set_source_rgba (cr, rgb[0], rgb[1], rgb[2], .9); cairo_fill (cr); theta += dtheta; } /* outlines */ ctx = gtk_widget_get_pango_context (&self->widget); cairo_set_line_width (cr, 1.); theta = theta_offset; for (n = 0; n < allocators->len; n++) { struct _sum_allocator *sum = g_ptr_array_index (allocators, n); gdouble dtheta, rgb[3]; gdouble min_r, mid_r, max_r, arc_height; if (sort == ALLOCATORS_STORE_SORT_BY_SIZE) { if (sum->size < min) break; dtheta = sum->size * 2 * G_PI / total; } else { if (sum->count < min) break; dtheta = sum->count * 2 * G_PI / total; } cairo_new_path (cr); cairo_arc (cr, ww, hh, r, theta, theta + dtheta); cairo_arc_negative (cr, ww, hh, r/4, theta + dtheta, theta); cairo_close_path (cr); hsv_to_rgb ((theta + dtheta / 2.) / (2 * G_PI) * 6, 1., 1., rgb); cairo_set_source_rgb (cr, rgb[0], rgb[1], rgb[2]); cairo_stroke (cr); mid_r = r/4 + .5 * 3*r/4; text = pango_layout_new (ctx); pango_layout_set_text (text, sum->frame, -1); pango_layout_get_pixel_extents (text, NULL, &logical); min_r = mid_r - logical.width / 2.; max_r = mid_r + logical.width / 2.; arc_height = dtheta * min_r - 4; if (min_r < r/4 + 2 || max_r > r - 2 || logical.height > arc_height) { gdouble rr = 3*r/4 - 2*BORDER; if (logical.height > arc_height) { min_r = r - BORDER - (logical.height + 4) / dtheta; rr = MIN (rr, min_r); } pango_layout_set_width (text, PANGO_SCALE * rr); pango_layout_set_ellipsize (text, PANGO_ELLIPSIZE_END); pango_layout_get_pixel_extents (text, NULL, &logical); min_r = mid_r - logical.width / 2.; max_r = mid_r + logical.width / 2.; arc_height = dtheta * min_r - 4; } if (min_r > r/4 + 2 && max_r < r - 2 && logical.height <= arc_height) { gdouble angle = theta + dtheta/2.; cairo_save (cr); cairo_translate (cr, ww, hh); cairo_rotate (cr, angle); if (angle < G_PI/2) { min_r = mid_r - logical.width / 2.; cairo_translate (cr, min_r, - logical.height/2.); } else { max_r = mid_r + logical.width / 2.; cairo_translate (cr, max_r, + logical.height/2.); cairo_scale (cr, -1, -1); } cairo_set_source_rgb (cr, 0., 0., 0.); pango_cairo_show_layout (cr, text); cairo_restore (cr); } g_object_unref (text); theta += dtheta; } if (theta == theta_offset) cairo_arc (cr, ww, hh, r, 0, 2*G_PI); else cairo_arc (cr, ww, hh, r, theta, theta_offset); cairo_set_source_rgb (cr, 0, 0, 0); cairo_stroke (cr); cairo_destroy (cr); return FALSE; } static void summary_chart_unrealize (GtkWidget *widget) { SummaryChart *self = (SummaryChart *) widget; if (self->image != NULL) { cairo_pattern_destroy (self->image); self->image = NULL; } if (self->dirty) { g_source_remove (self->dirty); self->dirty = 0; } GTK_WIDGET_CLASS (summary_chart_parent_class)->unrealize (widget); } static struct _sum_allocator * _summary_chart_get_sum_for_cursor (SummaryChart *self, gint x, gint y) { GPtrArray *allocators; gdouble theta, theta_offset, cursor; gdouble dx, dy, r, R; guint64 total; guint n, min; AllocatorsStoreSort sort; sort = allocators_store_get_sort (self->store); allocators = allocators_store_get_array (self->store); if (allocators->len == 0) return NULL; dy = y - self->widget.allocation.height / 2.; dx = x - self->widget.allocation.width / 2.; r = hypot (dx, dy); R = MIN (self->widget.allocation.width, self->widget.allocation.height) / 2 - BORDER; if (r < R/4 || r > R) return NULL; cursor = atan2 (dy, dx); total = 0; for (n = 0; n < allocators->len; n++) { struct _sum_allocator *sum = g_ptr_array_index (allocators, n); total += sort == ALLOCATORS_STORE_SORT_BY_SIZE ? sum->size : sum->count; } min = total / (G_PI * R); theta = 0; for (n = 0; n < allocators->len; n++) { struct _sum_allocator *sum = g_ptr_array_index (allocators, n); if (sort == ALLOCATORS_STORE_SORT_BY_SIZE) { if (sum->size < min) break; theta += sum->size * 2 * G_PI / total; } else { if (sum->count < min) break; theta += sum->count * 2 * G_PI / total; } } theta_offset = -G_PI / 2 + (2 * G_PI - theta) / 2.; if (cursor < theta_offset) cursor += 2 * G_PI; theta = theta_offset; for (n = 0; n < allocators->len; n++) { struct _sum_allocator *sum = g_ptr_array_index (allocators, n); gdouble dtheta; if (sort == ALLOCATORS_STORE_SORT_BY_SIZE) { if (sum->size < min) break; dtheta = sum->size * 2 * G_PI / total; } else { if (sum->count < min) break; dtheta = sum->count * 2 * G_PI / total; } if (cursor >= theta && cursor < theta + dtheta) return sum; theta += dtheta; } self->others.size = 0; self->others.count = 0; for (; n < allocators->len; n++) { struct _sum_allocator *sum = g_ptr_array_index (allocators, n); self->others.size += sum->size; self->others.count += sum->count; } return &self->others; } 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_chart_query_tooltip (GtkWidget *widget, gint x, gint y, gboolean keyboard_tip, GtkTooltip *tooltip) { SummaryChart *self = (SummaryChart *) widget; struct _sum_allocator *sum; const Allocator *A; GtkWidget *label; PangoAttrList *attrs = NULL; GString *string; gchar *text; gchar bytes[160], blocks[80], calls[80]; gint len; if (x < 0 || x > widget->allocation.width) return FALSE; if (y < 0 || y > widget->allocation.height) return FALSE; sum = _summary_chart_get_sum_for_cursor (self, x, y); if (sum == NULL) return FALSE; len = g_snprintf (bytes + 80, 80, "%" G_GUINT64_FORMAT, sum->size); pretty_print_number (bytes + 80, len, bytes); len = g_snprintf (blocks + 40, 40, "%d", sum->count); pretty_print_number (blocks + 40, len, blocks); string = g_string_new (""); g_string_append_printf (string, "%s: %s bytes over %s allocations.", sum->frame, bytes, blocks); A = sum->largest; if (A != NULL) { const gchar *main_fn, *last; guint n, m, depth, n_allocs, last_allocs = 0; GSList *list; PangoRectangle ink_rect, logical_rect; ink_rect.x = ink_rect.y = 0; ink_rect.width = ink_rect.height = 12; logical_rect = ink_rect; n_allocs = A->time_tail->n_allocs; if (! allocators_store_is_cumulative (self->store) && A->time_tail->prev) { n_allocs -= A->time_tail->freed; } g_string_append (string, "\n\nMost 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; main_fn = app_get_main_function (app_get (widget)); attrs = pango_attr_list_new (); list = _sum_allocators_get_allocators (sum); last = NULL; last_allocs = 0; for (m = n; m < MIN (n + NUM_CALLERS, A->n_frames); m++) { if (A->frames[m]->function_srcloc != last) { GSList *l, *next, **prev; guint this_allocs = 0; gint fraction; PangoAttribute *attr; 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 (self->store) && 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_chart_class_init (SummaryChartClass *klass) { GObjectClass *object_class = (GObjectClass *) klass; GtkWidgetClass *widget_class = (GtkWidgetClass *) klass; object_class->set_property = summary_chart_set_property; object_class->get_property = summary_chart_get_property; widget_class->realize = summary_chart_realize; widget_class->unrealize = summary_chart_unrealize; widget_class->screen_changed = summary_chart_screen_changed; widget_class->expose_event = summary_chart_expose; widget_class->query_tooltip = summary_chart_query_tooltip; g_object_class_install_property (object_class, PROP_MODEL, g_param_spec_object ("model", _("model"), _("Model"), G_TYPE_OBJECT, //allocators_store_get_type (), G_PARAM_STATIC_NICK | G_PARAM_STATIC_BLURB | G_PARAM_STATIC_NAME | G_PARAM_READWRITE)); } static void summary_chart_init (SummaryChart *self) { self->others.frame = (gchar *) "Others"; gtk_widget_set_has_tooltip (&self->widget, TRUE); } GtkWidget * summary_chart_new (AllocatorsStore *store) { return g_object_new (summary_chart_get_type (), "model", store, NULL); }