/* 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 #include #include "odin.h" #include "client.h" #define _(x) x typedef struct _timeline_data TimelineData; struct _timeline_data { guint time; gint alloc_bias; guint64 bytes_allocated; guint64 bytes_freed; TimelineData *next, *prev; }; struct _timeline { GtkWidget widget; TimelineData *data, *tail; guint max_time; guint64 max_bytes; guint64 max_active; gint max_blocks; cairo_pattern_t *red_yellow_green_pattern; cairo_pattern_t *red_black_green_pattern; cairo_pattern_t *transparent_white_pattern; }; static GType timeline_get_type (void); typedef struct _timeline_class { GtkWidgetClass parent_class; } TimelineClass; static GType timeline_get_type (void); G_DEFINE_TYPE (Timeline, timeline, GTK_TYPE_WIDGET) static void timeline_set_property (GObject *obj, guint id, const GValue *v, GParamSpec *spec) { Timeline *self = (Timeline *) obj; switch (id) { default: G_OBJECT_WARN_INVALID_PROPERTY_ID (obj, id, spec); break; } } static void timeline_get_property (GObject *obj, guint id, GValue *v, GParamSpec *spec) { Timeline *self = (Timeline *) obj; switch (id) { default: G_OBJECT_WARN_INVALID_PROPERTY_ID (obj, id, spec); break; } } static void timeline_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_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); } #define BORDER 4 static cairo_pattern_t * _red_yellow_green_pattern (Timeline *self, cairo_t *cr) { cairo_pattern_t *gradient; cairo_surface_t *surface; cairo_t *cr2; if (self->red_yellow_green_pattern != NULL) return self->red_yellow_green_pattern; surface = cairo_surface_create_similar (cairo_get_target (cr), CAIRO_CONTENT_COLOR, 8, self->widget.allocation.height); cr2 = cairo_create (surface); gradient = cairo_pattern_create_linear ( 0, BORDER, 0, self->widget.allocation.height - BORDER); cairo_pattern_add_color_stop_rgb (gradient, 0., 0.76, 0.1, 0); cairo_pattern_add_color_stop_rgb (gradient, .40, 0.76, 0.1, 0); cairo_pattern_add_color_stop_rgb (gradient, .45, 0.76, 0.76, 0); cairo_pattern_add_color_stop_rgb (gradient, .55, 0.76, 0.76, 0.); cairo_pattern_add_color_stop_rgb (gradient, .60, 0, 0.76, 0.1); cairo_pattern_add_color_stop_rgb (gradient, 1., 0, 0.76, 0.1); cairo_set_source (cr2, gradient); cairo_pattern_destroy (gradient); cairo_paint (cr2); cairo_destroy (cr2); self->red_yellow_green_pattern = cairo_pattern_create_for_surface (surface); cairo_surface_destroy (surface); cairo_pattern_set_extend (self->red_yellow_green_pattern, CAIRO_EXTEND_REPEAT); return self->red_yellow_green_pattern; } static cairo_pattern_t * _transparent_white_pattern (Timeline *self, cairo_t *cr) { cairo_pattern_t *gradient; cairo_surface_t *surface; cairo_t *cr2; if (self->transparent_white_pattern != NULL) return self->transparent_white_pattern; surface = cairo_surface_create_similar (cairo_get_target (cr), CAIRO_CONTENT_COLOR_ALPHA, 8, self->widget.allocation.height); cr2 = cairo_create (surface); gradient = cairo_pattern_create_linear ( 0, 0, 0, self->widget.allocation.height); cairo_pattern_add_color_stop_rgba (gradient, 0.0, 1, 1, 1, 0); cairo_pattern_add_color_stop_rgba (gradient, 0.5, 1, 1, 1, 1); cairo_pattern_add_color_stop_rgba (gradient, 1.0, 1, 1, 1, 0); cairo_set_source (cr2, gradient); cairo_pattern_destroy (gradient); cairo_paint (cr2); cairo_destroy (cr2); self->transparent_white_pattern = cairo_pattern_create_for_surface (surface); cairo_surface_destroy (surface); cairo_pattern_set_extend (self->transparent_white_pattern, CAIRO_EXTEND_REPEAT); return self->transparent_white_pattern; } static cairo_pattern_t * _red_black_green_pattern (Timeline *self, cairo_t *cr) { cairo_pattern_t *gradient; cairo_surface_t *surface; cairo_t *cr2; if (self->red_black_green_pattern != NULL) return self->red_black_green_pattern; surface = cairo_surface_create_similar (cairo_get_target (cr), CAIRO_CONTENT_COLOR, 8, self->widget.allocation.height); cr2 = cairo_create (surface); gradient = cairo_pattern_create_linear ( 0, 0, 0, self->widget.allocation.height); cairo_pattern_add_color_stop_rgb (gradient, 0.0, 1, 0, 0); cairo_pattern_add_color_stop_rgb (gradient, 0.5, 0, 0, 0); cairo_pattern_add_color_stop_rgb (gradient, 1.0, 0, 1, 0); cairo_set_source (cr2, gradient); cairo_pattern_destroy (gradient); cairo_paint (cr2); cairo_destroy (cr2); self->red_black_green_pattern = cairo_pattern_create_for_surface (surface); cairo_surface_destroy (surface); cairo_pattern_set_extend (self->red_black_green_pattern, CAIRO_EXTEND_REPEAT); return self->red_black_green_pattern; } static gboolean timeline_expose (GtkWidget *widget, GdkEventExpose *ev) { Timeline *self = (Timeline *) widget; TimelineData *data, *first; cairo_t *cr; double dashes[1] = {4.}; guint last_time; guint64 last_value; gdouble last_x; cr = gdk_cairo_create (widget->window); gdk_cairo_region (cr, ev->region); cairo_clip (cr); first = NULL; if (self->data != NULL) first = self->data->next; if (first != NULL) { cairo_save (cr); { cairo_translate (cr, BORDER, widget->allocation.height - BORDER); cairo_scale (cr, (widget->allocation.width - 2*BORDER) / (gdouble) self->max_time, -(widget->allocation.height - 2*BORDER) / (gdouble) (2 * self->max_bytes)); cairo_translate (cr, 0, self->max_bytes); /* bytes allocated */ cairo_move_to (cr, 0, 0); cairo_line_to (cr, 0, first->bytes_allocated); last_time = last_value = 0; for (data = first; data != NULL; data = data->next) { guint64 bytes = data->prev->bytes_allocated; guint64 value = data->bytes_allocated - bytes; cairo_curve_to (cr, .5 * (data->time - last_time) + last_time, last_value, .5 * (data->time - last_time) + last_time, value, data->time, value); last_time = data->time; last_value = value; } cairo_line_to (cr, self->max_time, 0); cairo_close_path (cr); cairo_save (cr); { cairo_identity_matrix (cr); cairo_set_source (cr, _red_black_green_pattern (self, cr)); cairo_fill (cr); } cairo_restore (cr); cairo_scale (cr, 1, -1); /* bytes freed */ cairo_move_to (cr, 0, 0); cairo_line_to (cr, 0, first->bytes_freed); last_time = last_value = 0; for (data = first; data != NULL; data = data->next) { guint64 bytes = data->prev->bytes_freed; guint64 value = data->bytes_freed - bytes; cairo_curve_to (cr, .5 * (data->time - last_time) + last_time, last_value, .5 * (data->time - last_time) + last_time, value, data->time, value); last_time = data->time; last_value = value; } cairo_line_to (cr, self->max_time, 0); cairo_close_path (cr); cairo_save (cr); { cairo_identity_matrix (cr); cairo_set_source (cr, _red_black_green_pattern (self, cr)); cairo_fill (cr); } cairo_restore (cr); /* bytes delta */ cairo_move_to (cr, 0, 0); cairo_line_to (cr, 0, first->bytes_allocated - first->bytes_freed); last_time = last_value = 0; for (data = first; data != NULL; data = data->next) { gint bytes = data->prev->bytes_allocated - data->prev->bytes_freed; gint value = data->bytes_allocated - data->bytes_freed - bytes; if (value < 0) value = 0; cairo_curve_to (cr, .5 * (data->time - last_time) + last_time, last_value, .5 * (data->time - last_time) + last_time, value, data->time, value); last_time = data->time; last_value = value; } cairo_line_to (cr, last_time, 0); cairo_scale (cr, 1, -1); cairo_move_to (cr, 0, 0); cairo_line_to (cr, 0, first->bytes_allocated - first->bytes_freed); last_time = last_value = 0; for (data = first; data != NULL; data = data->next) { gint bytes = data->prev->bytes_allocated - data->prev->bytes_freed; gint value = data->bytes_allocated - data->bytes_freed - bytes; if (value < 0) value = 0; cairo_curve_to (cr, .5 * (data->time - last_time) + last_time, last_value, .5 * (data->time - last_time) + last_time, value, data->time, value); last_time = data->time; last_value = value; } cairo_line_to (cr, last_time, 0); cairo_close_path (cr); cairo_save (cr); { cairo_identity_matrix (cr); cairo_set_source (cr, _transparent_white_pattern (self, cr)); cairo_fill (cr); } cairo_restore (cr); } cairo_restore (cr); cairo_set_source (cr, _red_yellow_green_pattern (self, cr)); cairo_set_line_width (cr, 2.0); cairo_save (cr); { cairo_translate (cr, BORDER, widget->allocation.height - BORDER); cairo_scale (cr, (widget->allocation.width - 2*BORDER) / (gdouble) self->max_time, -(widget->allocation.height - 2*BORDER) / (gdouble) (2 * self->max_blocks)); cairo_translate (cr, 0, self->max_blocks); /* allocation bias */ cairo_move_to (cr, 0, first->prev->alloc_bias); for (data = first; data != NULL; data = data->next) { gint last_bias = data->prev->alloc_bias; cairo_line_to (cr, data->time, data->alloc_bias - last_bias); } cairo_save (cr); { cairo_identity_matrix (cr); cairo_stroke (cr); } cairo_restore (cr); } cairo_restore (cr); /* active bytes */ cairo_save (cr); { cairo_translate (cr, BORDER, widget->allocation.height - BORDER); cairo_scale (cr, (widget->allocation.width - 2*BORDER) / (gdouble) self->max_time, -(widget->allocation.height - 2*BORDER) / (gdouble) (2 * self->max_active)); cairo_translate (cr, 0, self->max_active); cairo_set_source_rgb (cr, 1, 1, 1); /* struts */ last_x = 0; for (data = first; data->next != NULL; data = data->next) { gdouble x; x = floor ((widget->allocation.width - 2*BORDER) / (gdouble) self->max_time * data->time) - .5; /* maintain a minimum of 10 pixels between struts */ if (x - last_x < 10.) continue; last_x = x; x *= self->max_time / (gdouble) (widget->allocation.width - 2*BORDER); cairo_move_to (cr, x, 0); cairo_line_to (cr, x, data->bytes_allocated - data->bytes_freed); cairo_save (cr); { cairo_identity_matrix (cr); cairo_set_dash (cr, dashes, 1, 0.); cairo_set_line_width (cr, 1.); cairo_stroke (cr); } cairo_restore (cr); } /* bytes allocated */ cairo_move_to (cr, 0, 0); for (data = first; data != NULL; data = data->next) { cairo_line_to (cr, data->time, data->bytes_allocated - data->bytes_freed); } cairo_identity_matrix (cr); cairo_stroke (cr); } cairo_restore (cr); /* draw a fine-line to show zero */ cairo_move_to (cr, BORDER, widget->allocation.height/2 + .5); cairo_line_to (cr, widget->allocation.width - BORDER, widget->allocation.height/2 + .5); cairo_set_line_width (cr, 1.); cairo_set_dash (cr, dashes, 1, 0.); cairo_set_source_rgb (cr, 0., 0., 0.); cairo_stroke_preserve (cr); cairo_set_dash (cr, dashes, 1, 4.); cairo_set_source_rgb (cr, 1., 1., 1.); cairo_stroke(cr); } cairo_destroy (cr); return FALSE; } static void timeline_unrealize (GtkWidget *widget) { Timeline *self = (Timeline *) widget; if (self->red_yellow_green_pattern != NULL) { cairo_pattern_destroy (self->red_yellow_green_pattern); self->red_yellow_green_pattern = NULL; } if (self->transparent_white_pattern != NULL) { cairo_pattern_destroy (self->transparent_white_pattern); self->transparent_white_pattern = NULL; } if (self->red_black_green_pattern != NULL) { cairo_pattern_destroy (self->red_black_green_pattern); self->red_black_green_pattern = NULL; } GTK_WIDGET_CLASS (timeline_parent_class)->unrealize (widget); } static void timeline_size_allocate (GtkWidget *widget, GdkRectangle *allocation) { Timeline *self = (Timeline *) widget; if (self->red_yellow_green_pattern != NULL) { cairo_pattern_destroy (self->red_yellow_green_pattern); self->red_yellow_green_pattern = NULL; } if (self->transparent_white_pattern != NULL) { cairo_pattern_destroy (self->transparent_white_pattern); self->transparent_white_pattern = NULL; } if (self->red_black_green_pattern != NULL) { cairo_pattern_destroy (self->red_black_green_pattern); self->red_black_green_pattern = NULL; } GTK_WIDGET_CLASS (timeline_parent_class)->size_allocate (widget, allocation); } static void timeline_class_init (TimelineClass *klass) { GObjectClass *object_class = (GObjectClass *) klass; GtkWidgetClass *widget_class = (GtkWidgetClass *) klass; object_class->set_property = timeline_set_property; object_class->get_property = timeline_get_property; widget_class->realize = timeline_realize; widget_class->unrealize = timeline_unrealize; widget_class->expose_event = timeline_expose; widget_class->size_allocate = timeline_size_allocate; } static void timeline_init (Timeline *self) { } GtkWidget * timeline_new (void) { return g_object_new (timeline_get_type(), NULL); } void timeline_reset (Timeline *tl) { tl->data = tl->tail = NULL; tl->max_time = 0; tl->max_bytes = 0; tl->max_active = 0; tl->max_blocks = 0; } void timeline_add_datum (Timeline *tl, Client *client, guint time, Allocator *A) { TimelineData *data; TimelineData *prev; guint64 bytes; guint64 n_allocs, n_frees; gint bias; if (A == NULL) return; data = client_perm_alloc (client, sizeof (TimelineData)); prev = tl->tail; if (tl->tail != NULL) { g_assert (time >= tl->tail->time); tl->tail->next = data; } else tl->data = data; data->prev = tl->tail; tl->tail = data; tl->max_time = time; data->time = time; data->next = NULL; data->bytes_allocated = 0; data->bytes_freed = 0; n_allocs = 0; n_frees = 0; while (A != NULL) { if (A->time_tail->time <= time) { data->bytes_allocated += A->time_tail->bytes; data->bytes_freed += A->time_tail->freed; n_allocs += A->time_tail->n_allocs; n_frees += A->time_tail->n_frees; } A = A->next; } g_assert (data->bytes_allocated >= data->bytes_freed); g_assert (n_allocs >= n_frees); bytes = data->bytes_allocated - data->bytes_freed; if (bytes > tl->max_active) tl->max_active = bytes; bytes = prev ? prev->bytes_allocated : 0; if (data->bytes_allocated - bytes > tl->max_bytes) tl->max_bytes = data->bytes_allocated - bytes; bytes = prev ? prev->bytes_freed : 0; if (data->bytes_freed - bytes > tl->max_bytes) tl->max_bytes = data->bytes_freed - bytes; data->alloc_bias = n_allocs - n_frees; bias = prev ? prev->alloc_bias : 0; bias = data->alloc_bias - bias; if (bias > tl->max_blocks) tl->max_blocks = bias; else if (-bias > tl->max_blocks) tl->max_blocks = -bias; gtk_widget_queue_draw (&tl->widget); }