/* 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 #include #include #include #include #include #include #include /* status macros */ #include "odin.h" #include "client.h" #include "block.h" #include "callgraph.h" #include "frames.h" #include "procmap.h" #include "shared-objects.h" #include "lwp-events.h" struct _app { GdkCursor *busy_cursor; guint busy_count; GtkWidget *window; GtkWidget *notebook; GtkWidget *summary_total; GtkWidget *summary_current; struct { GtkWidget *allocators; GtkWidget *block_map; } allocation_image; struct { GtkWidget *call_graph; GtkWidget *tree_map; GtkWidget *ring; } allocations; GtkWidget *spacetime; GtkWidget *procmap; GtkWidget *timeline; GtkWidget *statusbar; gboolean discard_log; GtkWidget *log_tv; GtkTextBuffer *log; GtkTextMark *log_end; Client client; GUnixSocket *lwp_unix; GTcpSocket *lwp_tcp; GTcpSocket *vg_events; GTcpSocket *vg_log; }; static void _client_reset (Client *client, App *app); static GQuark app_quark; static guint pow2_max (guint n, guint min) { while (n < min) n <<= 1; return n; } gpointer client_perm_alloc (Client *client, guint size) { gpointer mem; #ifndef G_ENABLE_DEBUG Chunk *c = client->perm_chunks; if (c == NULL || c->used + size > c->size) { guint len = pow2_max (4<<20, size); c = g_malloc (sizeof (Chunk) + len); c->size = len; c->used = 0; c->next = client->perm_chunks; client->perm_chunks = c; } #define align(x, y) (((x) + ((y)-1)) & -(y)) mem = c->mem + c->used; c->used += align (size, 2 * sizeof (gpointer)); #undef align #else mem = g_malloc (size); #endif return mem; } static gboolean readn (gzFile *file, gpointer data, gint len) { return gzread (file, data, len) == len; } static gboolean discardn (gzFile *file, guint len) { char buf[1024]; do { guint n = len > G_N_ELEMENTS (buf) ? G_N_ELEMENTS (buf) : len; if (! readn (file, buf, n)) return FALSE; len -= n; } while (len); return TRUE; } static gboolean discard_string (gzFile *file) { gint len; if (! readn (file, &len, sizeof (len))) return FALSE; return discardn (file, len); } static gboolean read_string (gzFile *file, gchar **bp, guint *rem, gchar **str) { gushort len; if (! readn (file, &len, sizeof (len))) return FALSE; if (len) { if (len >= *rem) { *str = g_malloc (len + 1); } else { *str = *bp; *bp += len + 1; *rem -= len + 1; } if (! readn (file, *str, len)) return FALSE; (*str)[len] = '\0'; } else *str = NULL; return TRUE; } static Allocator * _client_get_allocator (Client *client, gulong key) { guint index = (key ^ 6017773UL) % client->allocator_by_addr.size; Allocator *A = client->allocator_by_addr.nodes[index]; while (A != NULL && A->key != key) A = A->ht_next; return A; } static gboolean discard_allocator_time (gzFile *file) { AllocatorTime At; if (! readn (file, &At.bytes, sizeof (At.bytes)) || ! readn (file, &At.freed, sizeof (At.freed)) || ! readn (file, &At.max_size, sizeof (At.max_size)) || ! readn (file, &At.n_allocs, sizeof (At.n_allocs)) || ! readn (file, &At.n_reallocs, sizeof (At.n_reallocs)) || ! readn (file, &At.n_frees, sizeof (At.n_frees)) || ! readn (file, &At.n_realloced_blocks, sizeof (At.n_realloced_blocks)) || ! readn (file, &At.peak_blocks, sizeof (At.peak_blocks)) || ! readn (file, &At.peak_bytes, sizeof (At.peak_bytes)) || ! readn (file, &At.size_allocs, sizeof (At.size_allocs)) || ! readn (file, &At.faults.tid, sizeof (At.faults.tid))) { return FALSE; } At.faults.next = NULL; if (At.faults.tid != 0) { ThreadFaults *f = &At.faults; if (! readn (file, &f->fault_cnt, sizeof (f->fault_cnt)) || ! readn (file, &f->n_faults, sizeof (f->n_faults))) { return FALSE; } do { guint tid; if (! readn (file, &tid, sizeof (tid))) return FALSE; if (tid == 0) break; if (! readn (file, &f->fault_cnt, sizeof (f->fault_cnt)) || ! readn (file, &f->n_faults, sizeof (f->n_faults))) { return FALSE; } } while (TRUE); } return TRUE; } static void _client_add_allocator (Client *c, Allocator *A) { guint index; if (++c->allocator_by_addr.nnodes > 3 * c->allocator_by_addr.size) { Allocator **new_nodes; Allocator *node, *next; guint n, new_size, hv; new_size = g_spaced_primes_closest (c->allocator_by_addr.nnodes); new_nodes = g_new0 (Allocator *, new_size); for (n = 0; n < c->allocator_by_addr.size ; n++) { for (node = c->allocator_by_addr.nodes[n]; node != NULL; node = next) { next = node->ht_next; hv = (node->key ^ 6017773UL) % new_size; node->ht_next = new_nodes[hv]; new_nodes[hv] = node; } } g_free (c->allocator_by_addr.nodes); c->allocator_by_addr.nodes = new_nodes; c->allocator_by_addr.size = new_size; } index = (A->key ^ 6017773UL) % c->allocator_by_addr.size; A->ht_next = c->allocator_by_addr.nodes[index]; c->allocator_by_addr.nodes[index] = A; A->next = c->allocators; c->allocators = A; } static Allocator * read_allocator (App *app, gzFile *file, guint time) { gulong key; Allocator *A; AllocatorTime *At; guint magic; guint last; guint n; guint total; if (! readn (file, &key, sizeof (key))) return NULL; if (! readn (file, &magic, sizeof (magic)) || magic != 0xdeadbeef) return NULL; if (! readn (file, &last, sizeof (last))) return NULL; A = _client_get_allocator (&app->client, key); if (A == NULL) { A = client_perm_alloc (&app->client, sizeof (Allocator)); A->key = key; A->max_bytes = 0; At = &A->time[0]; A->time_tail = NULL; if (! readn (file, &A->n_frames, sizeof (A->n_frames))) return NULL; A->frames = client_perm_alloc (&app->client, sizeof (Frame *) * A->n_frames); if (! readn (file, A->frames, A->n_frames * sizeof (gulong))) return NULL; for (n = 0; n < A->n_frames; n++) { A->frames[n] = frames_get (&app->client.frames, ((gulong *) A->frames)[n]); g_return_val_if_fail (A->frames[n] != NULL, NULL); } A->alloc_fn_serial = 0; _client_add_allocator (&app->client, A); } else { g_assert (A->time_tail->time < time); if (last <= A->time_tail->time) return discard_allocator_time (file) ? A : NULL; At = client_perm_alloc (&app->client, sizeof (AllocatorTime)); A->time_tail->next = At; } At->prev = A->time_tail; A->time_tail = At; At->next = NULL; At->time = time; if (! readn (file, &At->bytes, sizeof (At->bytes)) || ! readn (file, &At->freed, sizeof (At->freed)) || ! readn (file, &At->max_size, sizeof (At->max_size)) || ! readn (file, &At->n_allocs, sizeof (At->n_allocs)) || ! readn (file, &At->n_reallocs, sizeof (At->n_reallocs)) || ! readn (file, &At->n_frees, sizeof (At->n_frees)) || ! readn (file, &At->n_realloced_blocks, sizeof (At->n_realloced_blocks)) || ! readn (file, &At->peak_blocks, sizeof (At->peak_blocks)) || ! readn (file, &At->peak_bytes, sizeof (At->peak_bytes)) || ! readn (file, &At->size_allocs, sizeof (At->size_allocs)) || ! readn (file, &At->faults.tid, sizeof (At->faults.tid))) { return NULL; } total = 0; At->max_size_alloc = 0; for (n = 0; n < G_N_ELEMENTS (At->size_allocs); n++) { if (At->size_allocs[n] != 0) At->max_size_alloc = n + 1; total += At->size_allocs[n]; } g_assert (total == At->n_allocs); At->faults.next = NULL; if (At->faults.tid != 0) { ThreadFaults *f = &At->faults; if (! readn (file, &f->fault_cnt, sizeof (f->fault_cnt)) || ! readn (file, &f->n_faults, sizeof (f->n_faults))) { return NULL; } do { guint tid; if (! readn (file, &tid, sizeof (tid))) return NULL; if (tid == 0) break; f->next = client_perm_alloc (&app->client, sizeof (ThreadFaults)); f = f->next; f->tid = tid; if (! readn (file, &f->fault_cnt, sizeof (f->fault_cnt)) || ! readn (file, &f->n_faults, sizeof (f->n_faults))) { return NULL; } } while (TRUE); } return A; } static Block * _client_block_alloc (Client *client) { Block *b; gsize size = sizeof (Block); #ifndef G_ENABLE_DEBUG Chunk *c; b = client->block_free_list; if (b != NULL) { client->block_free_list = b->next; return b; } c = client->block_chunks; if (c == NULL || c->used + size > c->size) { guint len = size * (32 << 10); c = g_malloc (sizeof (Chunk) + len); c->size = len; c->used = 0; c->next = client->block_chunks; client->block_chunks = c; } b = (Block *) (c->mem + c->used); c->used += size; #else b = g_malloc (size); #endif return b; } static Block * read_block (Client *client, gzFile *file, Block *blocks) { Block *b; gulong addr; gsize size; gulong allocator; Allocator *A; guint age; if (! readn (file, &addr, sizeof (addr)) || ! readn (file, &size, sizeof (size)) || ! readn (file, &age, sizeof (age)) || ! readn (file, &allocator, sizeof (allocator))) { return NULL; } A = _client_get_allocator (client, allocator); g_return_val_if_fail (A != NULL, NULL); b = _client_block_alloc (client); b->next = blocks; blocks = b; b->addr = addr; b->size = size; b->age = age; b->allocator = A; return blocks; } static const char * client_type_to_string (Client *client) { switch (client->type) { case LWP: return "lwp"; break; case VALGRIND: return "valgrind"; break; } return "b0rked"; } static void _app_set_client_name (App *app, const gchar *client) { char *str; if (app->client.name != NULL && strcmp (client, app->client.name) == 0) return; if (app->client.type != LWP) { const char *type = client_type_to_string (&app->client); str = g_strconcat (client, " - Óðinn [", type, "]", NULL); } else { str = g_strconcat (client, " - Óðinn", NULL); } gtk_window_set_title (GTK_WINDOW (app->window), str); g_free (str); g_free (app->client.name); app->client.name = g_strdup (client); } static guint _client_count_allocations (Client *client) { Allocator *A; guint n_allocs = 0; for (A = client->allocators; A != NULL; A = A->next) n_allocs += A->time_tail->n_allocs; return n_allocs; } static guint64 _client_count_allocated_bytes (Client *client) { Allocator *A; guint64 bytes = 0; for (A = client->allocators; A != NULL; A = A->next) bytes += A->time_tail->bytes; return bytes; } static gsize _client_count_active_bytes (Client *client) { Block *b; gsize bytes = 0; for (b = client->blocks; b != NULL; b = b->next) bytes += b->size; return bytes; } static guint _client_get_number_of_frames (Client *client) { return frames_get_unique_count (&client->frames); } static guint _client_get_number_of_allocators (Client *client) { return client->allocator_by_addr.nnodes; } static guint _client_get_number_of_blocks (Client *client) { return client->block_by_addr.nnodes; } static void app_update_status (App *app) { gchar bytes[160]; gchar n_allocs[80]; gchar n_allocators[80]; gchar n_blocks[80]; gchar active_bytes[80]; gchar n_frames[80]; gchar n_unique_frames[80]; gulong pagesize; gulong n_pages; gsize n_bytes; gfloat fragmentation; char *txt; gint len; if (_client_get_number_of_allocators (&app->client)) { len = g_snprintf (bytes, 80, "%" G_GUINT64_FORMAT, _client_count_allocated_bytes (&app->client)); pretty_print_number (bytes, len, bytes + 80); len = g_snprintf (n_allocs, 40, "%u", _client_count_allocations (&app->client)); pretty_print_number (n_allocs, len, n_allocs + 40); len = g_snprintf (n_allocators, 40, "%u", _client_get_number_of_allocators (&app->client)); pretty_print_number (n_allocators, len, n_allocators + 40); len = g_snprintf (n_blocks, 40, "%u", _client_get_number_of_blocks (&app->client)); pretty_print_number (n_blocks, len, n_blocks + 40); n_bytes = _client_count_active_bytes (&app->client); len = g_snprintf (active_bytes, 40, "%" G_GSIZE_FORMAT, n_bytes); pretty_print_number (active_bytes, len, active_bytes + 40); len = g_snprintf (n_frames, 40, "%u", app->client.call_graph->n_frames); pretty_print_number (n_frames, len, n_frames + 40); len = g_snprintf (n_unique_frames, 40, "%u", _client_get_number_of_frames (&app->client)); pretty_print_number (n_unique_frames, len, n_unique_frames + 40); pagesize = sysconf (_SC_PAGESIZE); n_pages = blocks_count_pages (app->client.blocks, pagesize, NULL); n_pages *= pagesize; fragmentation = (n_pages - n_bytes) * 100. / n_pages; txt = g_strdup_printf ("%s: %.1fs, %s bytes over %s allocations, %s allocators, %s active blocks [%s bytes (%.1f %%)], %s [%s unique] frames.", app->client.name, app->client.last / 1000., bytes + 80, n_allocs + 40, n_allocators + 40, n_blocks + 40, active_bytes + 40, fragmentation, n_frames + 40, n_unique_frames + 40); } else if (! app->client.terminated) { txt = g_strdup_printf ("%s: running.", app->client.name); } else txt = g_strdup_printf ("%s: terminated.", app->client.name); gtk_statusbar_pop (GTK_STATUSBAR (app->statusbar), 0); gtk_statusbar_push (GTK_STATUSBAR (app->statusbar), 0, txt); g_free (txt); } static void app_set_blocks (App *app, Block *blocks) { g_object_set (app->allocation_image.allocators, "blocks", blocks, NULL); g_object_set (app->allocation_image.block_map, "blocks", blocks, NULL); } static void _client_lookup_frames (Client *client) { Allocator *A; guint n; for (A = client->allocators; A != NULL; A = A->next) { if (A->alloc_fn_serial) break; for (n = 0; n < A->n_frames; n++) { A->frames[n] = frames_get (&client->frames, ((gulong *)A->frames)[n]); } } } static void _client_update_alloc_fn (Client *client) { Allocator *A; guint serial = frames_get_alloc_fns_serial (&client->frames); guint n; for (A = client->allocators; A != NULL; A = A->next) { if (A->alloc_fn_serial == serial) break; for (n = 0; n < A->n_frames - 1; n++) { if (! frames_is_alloc_fn (&client->frames, A->frames[n])) break; } A->alloc_fn = A->frames[n]->function; A->alloc_fn_serial = serial; } } static void _client_update_max_bytes (Client *client) { Allocator *A; for (A = client->allocators; A != NULL; A = A->next) { AllocatorTime *At = A->time_tail; guint64 bytes = At->bytes - At->freed; if (bytes > A->max_bytes) A->max_bytes = bytes; } } static void app_update_allocators (App *app, Client *client, guint time) { timeline_add_datum ((Timeline *) app->timeline, &app->client, time, app->client.allocators); spacetime_add_datum ((Spacetime *) app->spacetime, &app->client, time, app->client.allocators); } static void app_update_client (App *app, Client *client, guint time) { app_update_allocators (app, client, time); } static gboolean vg_read (gzFile *file, App *app) { guint count; gushort len; Block *blocks; gchar client[1024]; gint pid, time, exitcode; Chunk *c; if (! readn (file, client, 4) || strcmp (client, "MF0")) return FALSE; if (! readn (file, &len, sizeof (len))) return FALSE; g_return_val_if_fail ((guint) len < G_N_ELEMENTS (client), FALSE); if (! readn (file, client, len)) return FALSE; client[len] = '\0'; _app_set_client_name (app, client); if (! readn (file, &pid, sizeof (pid))) return FALSE; if (! readn (file, &exitcode, sizeof (exitcode))) return FALSE; if (! readn (file, &time, sizeof (time))) return FALSE; if (app->client.pid && pid && pid != app->client.pid) return FALSE; app->client.pid = pid; /* frames */ if (! readn (file, &count, sizeof (count))) return FALSE; while (count--) { gchar buf[4096], *bp = buf; gulong eip; gchar *function, *object, *name, *directory; gint line; guint rem = G_N_ELEMENTS (buf); if (! readn (file, &eip, sizeof (eip))) return FALSE; if (frames_has_ip (&app->client.frames, eip)) { if (! discard_string (file) || ! discard_string (file) || ! discard_string (file) || ! discard_string (file) || ! discardn (file, sizeof (line))) { return FALSE; } continue; } function = object = name = directory = NULL; if (! read_string (file, &bp, &rem, &function) || ! read_string (file, &bp, &rem, &object) || ! read_string (file, &bp, &rem, &name) || ! read_string (file, &bp, &rem, &directory)) { if (function < buf || function > bp) g_free (function); if (object < buf || object > bp) g_free (object); if (name < buf || name > bp) g_free (name); if (directory < buf || directory > bp) g_free (directory); return FALSE; } if (! readn (file, &line, sizeof (line))) return FALSE; frames_add (&app->client.frames, eip, object, function, name, directory, line); if (function < buf || function > bp) g_free (function); if (object < buf || object > bp) g_free (object); if (name < buf || name > bp) g_free (name); if (directory < buf || directory > bp) g_free (directory); } /* allocators */ if (! readn (file, &count, sizeof (count))) return FALSE; while (count--) { Allocator *A = read_allocator (app, file, time); if (A == NULL) return FALSE; } /* blocks */ if (! readn (file, &count, sizeof (count))) return FALSE; app_set_blocks (app, NULL); c = app->client.block_chunks; app->client.block_chunks = NULL; while (c != NULL) { Chunk *next = c->next; g_free (c); c = next; } blocks = NULL; while (count--) { Block *new_blocks = read_block (&app->client, file, blocks); if (new_blocks == NULL) return FALSE; blocks = new_blocks; } /* events */ if (! readn (file, &count, sizeof (count))) return FALSE; while (count--) { Event ev; gchar c; gulong allocator; if (! readn (file, &c, sizeof (c)) || ! readn (file, &ev.base.time, sizeof (ev.base.time)) || ! readn (file, &ev.base.tid, sizeof (ev.base.tid)) || ! readn (file, &allocator, sizeof (allocator))) { return FALSE; } ev.base.allocator = _client_get_allocator (&app->client, allocator); if (ev.base.allocator == NULL) return FALSE; ev.type = c; switch (ev.type) { case ALLOC: if (! readn (file, &ev.alloc.addr, sizeof (ev.alloc.addr)) || ! readn (file, &ev.alloc.size, sizeof (ev.alloc.size))) { return FALSE; } break; case REALLOC: if (! readn (file, &ev.realloc.old_addr, sizeof (ev.realloc.old_addr)) || ! readn (file, &ev.realloc.old_size, sizeof (ev.realloc.old_size)) || ! readn (file, &ev.realloc.new_addr, sizeof (ev.realloc.new_addr)) || ! readn (file, &ev.realloc.new_size, sizeof (ev.realloc.new_size))) { return FALSE; } break; case DEALLOC: if (! readn (file, &ev.dealloc.addr, sizeof (ev.dealloc.addr)) || ! readn (file, &ev.dealloc.size, sizeof (ev.dealloc.size))) { return FALSE; } break; } g_array_append_val (app->client.events, ev); } app->client.blocks = blocks; app_update_client (app, &app->client, time); app_update_status (app); return TRUE; } static void call_graph_selection_changed (GtkTreeSelection *selection, gpointer data) { GtkTreeModel *model; GtkTreeIter iter; CallGraphFrame *frame = NULL; App *app = data; if (gtk_tree_selection_get_selected (selection, &model, &iter)) gtk_tree_model_get (model, &iter, CG_DATA, &frame, -1); g_object_set (app->allocations.ring, "frame", frame, NULL); } static void allocators_selection_changed (GtkTreeSelection *selection, gpointer data) { GList *rows, *l; GSList *blocks = NULL; GtkTreeModel *model; Allocators *allocators; allocators = (Allocators *) gtk_tree_selection_get_tree_view (selection); rows = gtk_tree_selection_get_selected_rows (selection, &model); for (l = rows; l != NULL; l = g_list_next (l)) { GtkTreeIter iter; gtk_tree_model_get_iter (model, &iter, l->data); blocks = allocators_get_blocks_for_iter (allocators, &iter, blocks); gtk_tree_path_free (l->data); } g_list_free (rows); block_map_set_highlight (data, blocks); g_slist_free (blocks); } static void block_map_selection_changed (BlockMap *block_map, GParamSpec *spec, gpointer data) { gpointer blocks; g_object_get (G_OBJECT (block_map), "selection", &blocks, NULL); allocators_select_blocks (data, blocks); } static void call_graph_select_iter (GtkWidget *w, GtkTreeIter *iter, GtkTreeView *tv) { GtkTreeSelection *selection; GtkTreeModel *model; GtkTreePath *path; model = gtk_tree_view_get_model (tv); call_graph_store_update_tree_model ((CallGraphStore *) model); selection = gtk_tree_view_get_selection (tv); gtk_tree_selection_unselect_all (selection); path = gtk_tree_model_get_path (model, iter); gtk_tree_view_expand_to_path (tv, path); gtk_tree_view_scroll_to_cell (tv, path, NULL, FALSE, 0., 0.); gtk_tree_selection_select_path (selection, path); gtk_tree_path_free (path); } void app_set_busy (App *app, gboolean busy) { if (busy) { if (app->busy_count++ == 0) { gdk_window_set_cursor (app->window->window, app->busy_cursor); gdk_flush (); } } else { if (--app->busy_count == 0) gdk_window_set_cursor (app->window->window, NULL); } } static GtkWidget * main_window_create (App *app) { GtkWidget *hbox, *sw, *vbox, *w, *label; GtkTreeSelection *selection; app->busy_cursor = gdk_cursor_new (GDK_WATCH); app->allocation_image.allocators = allocators_new (); app->allocation_image.block_map = block_map_new (); app->allocations.call_graph = call_graph_new (); app->allocations.tree_map = call_graph_tree_map_new (); app->allocations.ring = call_graph_ring_new (); app->spacetime = spacetime_new (); app->summary_total = summary_new (TRUE); app->summary_current = summary_new (FALSE); selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (app->allocations.call_graph)); g_signal_connect (selection, "changed", G_CALLBACK (call_graph_selection_changed), app); g_signal_connect (app->allocations.tree_map, "selected", G_CALLBACK (call_graph_select_iter), app->allocations.call_graph); g_signal_connect (app->allocations.ring, "selected", G_CALLBACK (call_graph_select_iter), app->allocations.call_graph); selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (app->allocation_image.allocators)); g_signal_connect (selection, "changed", G_CALLBACK (allocators_selection_changed), app->allocation_image.block_map); g_signal_connect (app->allocation_image.block_map, "notify::selection", G_CALLBACK (block_map_selection_changed), app->allocation_image.allocators); app->window = gtk_window_new (GTK_WINDOW_TOPLEVEL); g_object_set_qdata (G_OBJECT (app->window), app_quark, app); vbox = gtk_vbox_new (FALSE, 2); gtk_container_add (GTK_CONTAINER (app->window), vbox); gtk_widget_show (vbox); app->notebook = gtk_notebook_new (); gtk_box_pack_start (GTK_BOX (vbox), app->notebook, TRUE, TRUE, 2); gtk_widget_show (app->notebook); w = timeline_new (); gtk_widget_set_size_request (w, 256, 128); gtk_box_pack_start (GTK_BOX (vbox), w, FALSE, FALSE, 2); gtk_widget_show (w); app->timeline = w; app->statusbar = gtk_statusbar_new (); gtk_box_pack_start (GTK_BOX (vbox), app->statusbar, FALSE, FALSE, 2); gtk_widget_show (app->statusbar); label = gtk_label_new ("Summary"); gtk_notebook_append_page (GTK_NOTEBOOK (app->notebook), app->summary_total, label); gtk_widget_show (label); gtk_widget_show (app->summary_total); label = gtk_label_new ("Current"); gtk_notebook_append_page (GTK_NOTEBOOK (app->notebook), app->summary_current, label); gtk_widget_show (label); gtk_widget_show (app->summary_current); hbox = gtk_hbox_new (FALSE, 2); label = gtk_label_new ("Allocation Map"); gtk_notebook_append_page (GTK_NOTEBOOK (app->notebook), hbox, label); gtk_widget_show (hbox); gtk_widget_show (label); sw = gtk_scrolled_window_new (NULL, NULL); gtk_widget_set_size_request (sw, -1, 256); gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (sw), GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC); w = app->allocation_image.allocators; gtk_container_add (GTK_CONTAINER (sw), w); gtk_widget_show (w); gtk_box_pack_start (GTK_BOX (hbox), sw, FALSE, FALSE, 2); gtk_widget_show (sw); w = app->allocation_image.block_map; gtk_widget_set_size_request (w, 256, 256); gtk_box_pack_start (GTK_BOX (hbox), w, TRUE, TRUE, 2); gtk_widget_show (w); hbox = gtk_hbox_new (FALSE, 2); vbox = gtk_vbox_new (FALSE, 2); gtk_box_pack_start (GTK_BOX (hbox), vbox, FALSE, TRUE, 2); gtk_widget_show (vbox); sw = gtk_scrolled_window_new (NULL, NULL); gtk_widget_set_size_request (sw, 768, 256); gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (sw), GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC); gtk_container_add (GTK_CONTAINER (sw), app->allocations.call_graph); gtk_widget_show (app->allocations.call_graph); gtk_box_pack_start (GTK_BOX (vbox), sw, TRUE, TRUE, 2); gtk_widget_show (sw); w = app->allocations.ring; gtk_widget_set_size_request (w, -1, 256); gtk_box_pack_start (GTK_BOX (vbox), w, FALSE, TRUE, 2); gtk_widget_show(w); w = app->allocations.tree_map; gtk_widget_set_size_request (w, 256, 256); gtk_box_pack_start (GTK_BOX (hbox), w, TRUE, TRUE, 2); gtk_widget_show (w); label = gtk_label_new ("All allocations"); gtk_notebook_append_page (GTK_NOTEBOOK (app->notebook), hbox, label); gtk_widget_show (hbox); gtk_widget_show (label); label = gtk_label_new ("Spacetime"); gtk_notebook_append_page (GTK_NOTEBOOK (app->notebook), app->spacetime, label); gtk_widget_show (app->spacetime); gtk_widget_show (label); return app->window; } static GtkTreeView * ensure_procmap (App *app) { GtkWidget *sw, *label; if (app->procmap != NULL) return (GtkTreeView *) app->procmap; app->procmap = procmap_new (); sw = gtk_scrolled_window_new (NULL, NULL); gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (sw), GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC); gtk_container_add (GTK_CONTAINER (sw), app->procmap); gtk_widget_show (app->procmap); label = gtk_label_new ("Process map"); gtk_notebook_append_page (GTK_NOTEBOOK (app->notebook), sw, label); gtk_widget_show (sw); gtk_widget_show (label); return (GtkTreeView *) app->procmap; } static gboolean vg_events_server_cb (GIOChannel *source, GIOCondition condition, gpointer data) { App *app = data; if (condition & G_IO_IN) { GTcpSocket *client = gnet_tcp_socket_server_accept (app->vg_events); GIOChannel *io; int fd; gzFile *file; gdk_window_set_cursor (app->window->window, app->busy_cursor); gdk_flush (); io = gnet_tcp_socket_get_io_channel (client); fd = g_io_channel_unix_get_fd (io); file = gzdopen (dup (fd), "r"); if (vg_read (file, app)) { if (! app->client.terminated) { GtkTreeModel *model = procmap_store_new (app->client.pid); gtk_tree_view_set_model (ensure_procmap (app), model); g_object_unref (model); } else call_graph_store_update_tree_model (app->client.call_graph); } gzclose (file); gdk_window_set_cursor (app->window->window, NULL); gnet_tcp_socket_delete (client); } if (condition & G_IO_HUP) return FALSE; return TRUE; } static gboolean _get_pid_cmd (GPid pid, char *cmd, int len) { glibtop_proc_state proc_state; glibtop_get_proc_state (&proc_state, (pid_t) pid); strncpy (cmd, proc_state.cmd, len); return cmd[0] != '\0'; } static Block * _client_get_block (Client *c, gpointer addr) { guint index = ((gulong) addr ^ 6017773UL) % c->block_by_addr.size; Block *b = c->block_by_addr.nodes[index]; while (b != NULL && b->addr != (gulong) addr) b = b->ht_next; return b; } static void _client_add_block (Client *c, Block *b) { guint index; if (++c->block_by_addr.nnodes > 3 * c->block_by_addr.size) { Block **new_nodes; Block *node, *next; guint n, new_size, hv; new_size = g_spaced_primes_closest (c->block_by_addr.nnodes); new_nodes = g_new0 (Block *, new_size); for (n = 0; n < c->block_by_addr.size ; n++) { for (node = c->block_by_addr.nodes[n]; node != NULL; node = next) { next = node->ht_next; hv = (node->addr ^ 6017773UL) % new_size; node->ht_prev = NULL; node->ht_next = new_nodes[hv]; if (new_nodes[hv] != NULL) new_nodes[hv]->ht_prev = node; new_nodes[hv] = node; } } g_free (c->block_by_addr.nodes); c->block_by_addr.nodes = new_nodes; c->block_by_addr.size = new_size; } index = (b->addr ^ 6017773UL) % c->block_by_addr.size; b->ht_prev = NULL; b->ht_next = c->block_by_addr.nodes[index]; if (c->block_by_addr.nodes[index] != NULL) c->block_by_addr.nodes[index]->ht_prev = b; c->block_by_addr.nodes[index] = b; b->prev = NULL; b->next = c->blocks; if (c->blocks != NULL) c->blocks->prev = b; c->blocks = b; } static AllocatorTime * _allocator_get_time (Client *client, Allocator *A, guint32 time) { AllocatorTime *At; if (G_UNLIKELY (A->time_tail->time == 0)) { A->time_tail->time = time; return A->time_tail; } if (A->time_tail->time == time) return A->time_tail; At = client_perm_alloc (client, sizeof (AllocatorTime)); /* we always accumulate (cf odin) */ memcpy (At, A->time_tail, sizeof (AllocatorTime)); At->next = NULL; A->time_tail->next = At; At->prev = A->time_tail; A->time_tail = At; At->time = time; return At; } static guint log2_ceil (guint n) { guint i = 1; while (n >>= 1) i++; return i; } static void _client_delete_block (Client *c, Allocator *A, gpointer addr, guint32 time, guint32 max_time) { Block *b; guint index; AllocatorTime *At; b = _client_get_block (c, addr); g_return_if_fail (b != NULL); A = b->allocator; At = _allocator_get_time (c, A, max_time); At->freed += b->size; At->n_frees++; if (b->prev != NULL) b->prev->next = b->next; else c->blocks = b->next; if (b->next != NULL) b->next->prev = b->prev; index = (b->addr ^ 6017773UL) % c->block_by_addr.size; if (b->ht_prev != NULL) b->ht_prev->ht_next = b->ht_next; else c->block_by_addr.nodes[index] = b->ht_next; if (b->ht_next != NULL) b->ht_next->ht_prev = b->ht_prev; c->block_by_addr.nnodes--; #ifndef G_ENABLE_DEBUG b->next = c->block_free_list; c->block_free_list = b; #else g_free (b); #endif } static void _client_new_block (Client *c, Allocator *A, gpointer addr, gsize size, guint32 time, guint32 max_time) { Block *b; AllocatorTime *At; guint index; b = _client_block_alloc (c); b->addr = (gulong) addr; b->size = size; b->age = time; b->allocator = A; At = _allocator_get_time (c, A, max_time); At->bytes += size; if (At->bytes > At->peak_bytes) At->peak_bytes = At->bytes; At->n_allocs++; if (At->n_allocs > At->peak_blocks) At->peak_blocks = At->n_allocs; index = log2_ceil (size); At->size_allocs[index]++; if (index >= At->max_size_alloc) At->max_size_alloc = index + 1; _client_add_block (c, b); } static void _client_move_block (Client *c, Allocator *A, gpointer old_addr, gpointer new_addr, gsize new_size, guint32 time, guint32 max_time) { Block *b; if (old_addr == NULL) return _client_new_block (c, A, new_addr, new_size, time, max_time); b = _client_get_block (c, old_addr); g_return_if_fail (b != NULL); if (new_addr != old_addr) { Allocator *original_allocator = b->allocator; _client_delete_block (c, A, old_addr, time, max_time); if (new_addr != NULL) _client_new_block (c, original_allocator, new_addr, new_size, time, max_time); } else { AllocatorTime *At; gsize old_size = b->size; At = _allocator_get_time (c, b->allocator, max_time); At->n_reallocs++; if (old_size != new_size) { guint index; b->size = new_size; At->bytes += (gssize) (new_size - old_size); if (At->bytes > At->peak_bytes) At->peak_bytes = At->bytes; index = log2_ceil (old_size); At->size_allocs[index]--; index = log2_ceil (new_size); At->size_allocs[index]--; if (index >= At->max_size_alloc) At->max_size_alloc = index + 1; } } } static void _client_init (Client *client, App *app) { client->active = TRUE; client->name = NULL; client->update = FALSE; shared_objects_init (&client->objects, client); frames_init (&client->frames, client); client->events = g_array_new (FALSE, FALSE, sizeof (Event)); client->strings.nnodes = 0; client->strings.size = 21089; client->strings.nodes = g_new0 (String *, client->strings.size); client->allocator_by_addr.size = 47431; client->allocator_by_addr.nnodes = 0; client->allocator_by_addr.nodes = g_new0 (Allocator *, client->allocator_by_addr.size); client->allocators = NULL; client->block_by_addr.size = 47431; client->block_by_addr.nnodes = 0; client->block_by_addr.nodes = g_new0 (Block *, client->block_by_addr.size); client->blocks = NULL; client->perm_chunks = NULL; client->block_chunks = NULL; client->block_free_list = NULL; client->call_graph = call_graph_store_new (); allocators_reset ((Allocators *) app->allocation_image.allocators); timeline_reset ((Timeline *) app->timeline); g_object_set (app->allocations.call_graph, "model", client->call_graph, NULL); g_object_set (app->allocations.tree_map, "model", client->call_graph, NULL); g_object_set (app->allocations.ring, "model", client->call_graph, NULL); } static void _client_fini (Client *client) { Chunk *c; if (! client->active) return; g_object_unref (client->call_graph); shared_objects_fini (&client->objects); frames_fini (&client->frames); g_free (client->strings.nodes); g_array_free (client->events, TRUE); g_free (client->allocator_by_addr.nodes); g_free (client->block_by_addr.nodes); g_free (client->name); c = client->block_chunks; while (c != NULL) { Chunk *next = c->next; g_free (c); c = next; } client->block_free_list = NULL; c = client->perm_chunks; while (c != NULL) { Chunk *next = c->next; g_free (c); c = next; } } static void _client_reset (Client *client, App *app) { _client_fini (client); _client_init (client, app); } static gboolean G_GNUC_UNUSED lwp_discard (gzFile *file) { gushort count; /* symbols */ if (! readn (file, &count, sizeof (count))) return FALSE; while (count--) { if (! discardn (file, sizeof (gulong)) || ! discard_string (file) || ! discard_string (file) || ! discard_string (file) || ! discard_string (file) || ! discardn (file, sizeof (guint))) { return FALSE; } } /* allocators */ if (! readn (file, &count, sizeof (count))) return FALSE; while (count--) { guint n_frames; if (! discardn (file, sizeof (gulong))) return FALSE; if (! readn (file, &n_frames, sizeof (n_frames))) return FALSE; if (! discardn (file, n_frames * sizeof (gulong))) return FALSE; } /* events */ if (! readn (file, &count, sizeof (count))) return FALSE; while (count--) { gboolean ok = TRUE; LWP_EventRecord ev; char c; ok &= readn (file, &c, 1); ev.type = c; ok &= discardn (file, sizeof (ev.time)); ok &= discardn (file, sizeof (ev.allocator)); switch (ev.type) { case LWP_INIT: case LWP_FINI: case LWP_DLOPEN: case LWP_DLCLOSE: break; case LWP_MALLOC: ok &= discardn (file, sizeof (ev.event.malloc.size)); ok &= discardn (file, sizeof (ev.event.malloc.addr)); break; case LWP_MEMALIGN: ok &= discardn (file, sizeof (ev.event.memalign.align)); ok &= discardn (file, sizeof (ev.event.memalign.size)); ok &= discardn (file, sizeof (ev.event.memalign.addr)); break; case LWP_REALLOC: ok &= discardn (file, sizeof (ev.event.realloc.size)); ok &= discardn (file, sizeof (ev.event.realloc.old_addr)); ok &= discardn (file, sizeof (ev.event.realloc.new_addr)); break; case LWP_FREE: ok &= discardn (file, sizeof (ev.event.free.addr)); break; } if (!ok) return FALSE; } return TRUE; } static gboolean lwp_read (gzFile *file, App *app) { gushort count; pid_t pid; guint32 time; gchar client[1024]; gboolean update; if (! readn (file, client, 4) || strcmp (client, "LWP")) return FALSE; if (! readn (file, &pid, sizeof (pid))) return FALSE; if (! readn (file, &time, sizeof (time))) return FALSE; if (app->client.pid && pid != app->client.pid) return FALSE; /* XXX lwp_discard (file); */ app->client.pid = pid; app->client.type = LWP; app->client.time = time; /* new objects */ if (! readn (file, &count, sizeof (count))) return FALSE; while (count--) { gchar buf[4096], *bp = buf; guint rem = G_N_ELEMENTS (buf); gchar *name = NULL; guint key; gboolean ok = TRUE; ok &= readn (file, &key, sizeof (key)); ok &= read_string (file, &bp, &rem, &name); if (ok) shared_object_add (&app->client.objects, key, name); if (name < buf || name > bp) g_free (name); if (! ok) return FALSE; } /* new symbols */ if (! readn (file, &count, sizeof (count))) return FALSE; while (count--) { SharedObject *so; gulong eip; guint key; gulong offset; if (! readn (file, &eip, sizeof (eip)) || ! readn (file, &key, sizeof (key)) || ! readn (file, &offset, sizeof (offset))) { return FALSE; } if (key) { so = shared_object_get (&app->client.objects, key); g_return_val_if_fail (so != NULL, FALSE); shared_object_queue_lookup_symbol (so, eip, offset); } else { /* unresolved symbol */ frames_add (&app->client.frames, eip, NULL, NULL, NULL, NULL, 0); } } /* new allocators */ if (! readn (file, &count, sizeof (count))) return FALSE; while (count--) { guint key; Allocator *A; if (! readn (file, &key, sizeof (key))) return FALSE; /* construct bare allocator... */ A = client_perm_alloc (&app->client, sizeof (Allocator)); A->key = key; A->max_bytes = 0; A->time[0].prev = NULL; A->time[0].next = NULL; A->time_tail = &A->time[0]; memset (A->time_tail, 0, sizeof (AllocatorTime)); if (! readn (file, &A->n_frames, sizeof (A->n_frames))) return FALSE; A->frames = client_perm_alloc (&app->client, sizeof (Frame *) * A->n_frames); if (! readn (file, A->frames, A->n_frames * sizeof (gulong))) return FALSE; A->alloc_fn_serial = 0; _client_add_allocator (&app->client, A); } /* new events */ if (! readn (file, &count, sizeof (count))) return FALSE; update = FALSE; while (count--) { LWP_EventRecord ev; char c; Allocator *A = NULL; if (! readn (file, &c, 1) || ! readn (file, &ev.time, sizeof (ev.time)) || ! readn (file, &ev.allocator, sizeof (ev.allocator))) { return FALSE; } ev.type = c; if (ev.allocator) { A = _client_get_allocator (&app->client, ev.allocator); g_return_val_if_fail (A != NULL, FALSE); } switch (ev.type) { case LWP_INIT: _client_reset (&app->client, app); break; case LWP_FINI: app->client.terminated = TRUE; break; case LWP_DLOPEN: case LWP_DLCLOSE: break; case LWP_MALLOC: update = TRUE; readn (file, &ev.event.malloc.size, sizeof (ev.event.malloc.size)); readn (file, &ev.event.malloc.addr, sizeof (ev.event.malloc.addr)); _client_new_block (&app->client, A, ev.event.malloc.addr, ev.event.malloc.size, ev.time, time); break; case LWP_MEMALIGN: update = TRUE; readn (file, &ev.event.memalign.align, sizeof (ev.event.memalign.align)); readn (file, &ev.event.memalign.size, sizeof (ev.event.memalign.size)); readn (file, &ev.event.memalign.addr, sizeof (ev.event.memalign.addr)); _client_new_block (&app->client, A, ev.event.memalign.addr, ev.event.memalign.size, ev.time, time); break; case LWP_REALLOC: update = TRUE; readn (file, &ev.event.realloc.size, sizeof (ev.event.realloc.size)); readn (file, &ev.event.realloc.old_addr, sizeof (ev.event.realloc.old_addr)); readn (file, &ev.event.realloc.new_addr, sizeof (ev.event.realloc.new_addr)); _client_move_block (&app->client, A, ev.event.realloc.old_addr, ev.event.realloc.new_addr, ev.event.realloc.size, ev.time, time); break; case LWP_FREE: update = TRUE; readn (file, &ev.event.free.addr, sizeof (ev.event.free.addr)); if (ev.event.free.addr != NULL) _client_delete_block (&app->client, A, ev.event.free.addr, ev.time, time); break; } } app->client.update |= update; return TRUE; } typedef struct _timeout_source { GSource source; GTimeVal expiration; guint interval; } TimeoutSource; static void _timeout_set_expiration (TimeoutSource *timeout_source, GTimeVal *current_time) { guint seconds = timeout_source->interval / 1000; guint msecs = timeout_source->interval - seconds * 1000; timeout_source->expiration.tv_sec = current_time->tv_sec + seconds; timeout_source->expiration.tv_usec = current_time->tv_usec + msecs * 1000; if (timeout_source->expiration.tv_usec >= 1000000) { timeout_source->expiration.tv_usec -= 1000000; timeout_source->expiration.tv_sec++; } } static gboolean _timeout_prepare (GSource *source, gint *timeout) { TimeoutSource *timeout_source = (TimeoutSource *)source; GTimeVal current_time; glong sec; glong msec; g_source_get_current_time (source, ¤t_time); sec = timeout_source->expiration.tv_sec - current_time.tv_sec; msec = (timeout_source->expiration.tv_usec - current_time.tv_usec) / 1000; /* We do the following in a rather convoluted fashion to deal with * the fact that we don't have an integral type big enough to hold * the difference of two timevals in millseconds. */ if (sec < 0 || (sec == 0 && msec < 0)) msec = 0; else { glong interval_sec = timeout_source->interval / 1000; glong interval_msec = timeout_source->interval % 1000; if (msec < 0) { msec += 1000; sec -= 1; } if (sec > interval_sec || (sec == interval_sec && msec > interval_msec)) { /* The system time has been set backwards, so we * reset the expiration time to now + timeout_source->interval; * this at least avoids hanging for long periods of time. */ _timeout_set_expiration (timeout_source, ¤t_time); msec = MIN (G_MAXINT, timeout_source->interval); } else { msec = MIN (G_MAXINT, (guint)msec + 1000 * (guint)sec); } } *timeout = (gint)msec; return msec == 0; } static gboolean _timeout_check (GSource *source) { TimeoutSource *timeout_source = (TimeoutSource *)source; GTimeVal current_time; g_source_get_current_time (source, ¤t_time); return ((timeout_source->expiration.tv_sec < current_time.tv_sec) || ((timeout_source->expiration.tv_sec == current_time.tv_sec) && (timeout_source->expiration.tv_usec <= current_time.tv_usec))); } static gboolean _timeout_dispatch (GSource *source, GSourceFunc callback, gpointer user_data) { TimeoutSource *timeout_source = (TimeoutSource *)source; g_assert (callback != NULL); if (callback (user_data)) { GTimeVal current_time; g_source_get_current_time (source, ¤t_time); _timeout_set_expiration (timeout_source, ¤t_time); return TRUE; } else return FALSE; } static GSourceFuncs _timeout_funcs = { _timeout_prepare, _timeout_check, _timeout_dispatch, NULL }; static GSource * _new_timeout (guint interval, GSourceFunc function, gpointer data) { GSource *source = g_source_new (&_timeout_funcs, sizeof (TimeoutSource)); TimeoutSource *timeout_source = (TimeoutSource *)source; GTimeVal current_time; timeout_source->interval = interval; g_get_current_time (¤t_time); _timeout_set_expiration (timeout_source, ¤t_time); g_source_set_priority (source, G_PRIORITY_DEFAULT_IDLE); g_source_set_callback (source, function, data, NULL); g_source_attach (source, NULL); return source; } static void _reset_timeout (GSource *source, guint interval) { TimeoutSource *timeout_source = (TimeoutSource *)source; GTimeVal current_time; timeout_source->interval = interval; g_get_current_time (¤t_time); _timeout_set_expiration (timeout_source, ¤t_time); } static gboolean _update_client (App *app) { app_set_busy (app, TRUE); shared_objects_lookup_symbols (&app->client.objects); _client_lookup_frames (&app->client); _client_update_alloc_fn (&app->client); _client_update_max_bytes (&app->client); app_set_blocks (app, app->client.blocks); call_graph_store_update (app->client.call_graph, app, app->client.allocators, app->client.last); summary_update ((Summary *) app->summary_total, &app->client); summary_update ((Summary *) app->summary_current, &app->client); if (app->client.update) { timeline_add_datum ((Timeline *) app->timeline, &app->client, app->client.time, app->client.allocators); spacetime_add_datum ((Spacetime *) app->spacetime, &app->client, app->client.time, app->client.allocators); app->client.last = app->client.time; app->client.update = FALSE; } if (app->client.pid && ! app->client.terminated) { GtkTreeModel *model; char client[1024]; if (! _get_pid_cmd (app->client.pid, client, G_N_ELEMENTS (client))) strcpy (client, "«unknown»"); _app_set_client_name (app, client); model = procmap_store_new (app->client.pid); gtk_tree_view_set_model (ensure_procmap (app), model); g_object_unref (model); } else { call_graph_store_update_tree_model (app->client.call_graph); } app_update_status (app); g_source_destroy (app->client.timeout); app->client.timeout = NULL; app_set_busy (app, FALSE); return FALSE; } static gboolean lwp_unix_server_cb (GIOChannel *source, GIOCondition condition, gpointer data) { App *app = data; if (condition & G_IO_IN) { GUnixSocket *client; gboolean update = FALSE; client = gnet_unix_socket_server_accept (app->lwp_unix); do { GIOChannel *io = gnet_unix_socket_get_io_channel (client); int fd = g_io_channel_unix_get_fd (io); gzFile *file = gzdopen (dup (fd), "r"); if (lwp_read (file, app)) { update = TRUE; while (lwp_read (file, app)) ; } gzclose (file); gnet_unix_socket_delete (client); client = gnet_unix_socket_server_accept_nonblock (app->lwp_unix); } while (client != NULL); if (update) { if (app->client.timeout == NULL) app->client.timeout = _new_timeout (2000, (GSourceFunc) _update_client, app); } } if (condition & G_IO_HUP) return FALSE; return TRUE; } static gboolean lwp_tcp_server_cb (GIOChannel *source, GIOCondition condition, gpointer data) { App *app = data; if (condition & G_IO_IN) { GTcpSocket *client; gboolean update = FALSE; client = gnet_tcp_socket_server_accept (app->lwp_tcp); do { GIOChannel *io = gnet_tcp_socket_get_io_channel (client); int fd = g_io_channel_unix_get_fd (io); gzFile *file = gzdopen (dup (fd), "r"); if (lwp_read (file, app)) { update = TRUE; while (lwp_read (file, app)) ; } gzclose (file); gnet_tcp_socket_delete (client); client = gnet_tcp_socket_server_accept_nonblock (app->lwp_tcp); } while (client != NULL); if (update) { if (app->client.timeout == NULL) app->client.timeout = _new_timeout (2000, (GSourceFunc) _update_client, app); } } if (condition & G_IO_HUP) return FALSE; return TRUE; } static void scroll_to_mark (App *app) { if (GTK_WIDGET_DRAWABLE (app->log_tv)) gtk_text_view_scroll_mark_onscreen (GTK_TEXT_VIEW (app->log_tv), app->log_end); } static GtkTextBuffer * ensure_log (App *app) { GtkWidget *w, *sw, *label; GtkTextIter iter; if (app->discard_log) return NULL; if (app->log != NULL) return app->log; sw = gtk_scrolled_window_new (NULL, NULL); gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (sw), GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC); w = gtk_text_view_new (); gtk_text_view_set_editable (GTK_TEXT_VIEW (w), FALSE); gtk_text_view_set_cursor_visible (GTK_TEXT_VIEW (w), FALSE); gtk_container_add (GTK_CONTAINER (sw), w); gtk_widget_show (w); label = gtk_label_new ("Log"); gtk_notebook_append_page (GTK_NOTEBOOK (app->notebook), sw, label); gtk_widget_show (sw); gtk_widget_show (label); app->log = gtk_text_view_get_buffer (GTK_TEXT_VIEW (w)); app->log_tv = w; gtk_text_buffer_get_end_iter (app->log, &iter); app->log_end = gtk_text_buffer_create_mark (app->log, "end", &iter, FALSE); g_signal_connect_data (w, "map", G_CALLBACK (scroll_to_mark), app, NULL, G_CONNECT_AFTER | G_CONNECT_SWAPPED); g_signal_connect_swapped (app->log, "changed", G_CALLBACK (scroll_to_mark), app); return app->log; } static gboolean vg_log_client_cb (GIOChannel *source, GIOCondition condition, gpointer data) { App *app = data; if (condition & G_IO_IN) { GtkTextBuffer *log; int fd = g_io_channel_unix_get_fd (source); char buf[4096]; int ret; log = ensure_log (app); do { GtkTextIter iter; ret = read (fd, buf, sizeof (buf)); if (ret < 0) { const int err = errno; switch (err) { case EAGAIN: case EINTR: continue; default: break; } } if (log != NULL) { gtk_text_buffer_get_end_iter (log, &iter); gtk_text_buffer_insert (log, &iter, buf, ret); } } while (ret == sizeof (buf)); if (ret <= 0) condition |= G_IO_HUP; } if (condition & G_IO_HUP) return FALSE; return TRUE; } static gboolean vg_log_server_cb (GIOChannel *source, GIOCondition condition, gpointer data) { App *app = data; if (condition & G_IO_IN) { GTcpSocket *client = gnet_tcp_socket_server_accept (app->vg_log); GIOChannel *io; io = gnet_tcp_socket_get_io_channel (client); g_io_add_watch (io, G_IO_IN | G_IO_HUP, vg_log_client_cb, data); g_io_channel_unref (io); } return TRUE; } gboolean app_is_alloc_fn (App *app, Frame *f) { return frames_is_alloc_fn (&app->client.frames, f); } gboolean app_add_alloc_fn (App *app, const gchar *pattern, GError **error) { if (! frames_add_alloc_fn (&app->client.frames, pattern, error)) return FALSE; _client_update_alloc_fn (&app->client); /* XXX signal */ call_graph_store_filter (app->client.call_graph, app); summary_update ((Summary *) app->summary_total, &app->client); summary_update ((Summary *) app->summary_current, &app->client); if (app->client.blocks != NULL) app_set_blocks (app, app->client.blocks); return TRUE; } guint app_get_alloc_fns_serial (App *app) { return frames_get_alloc_fns_serial (&app->client.frames); } const gchar * app_get_main_function (App *app) { return client_add_string (&app->client, "main"); } App * app_get (GtkWidget *widget) { widget = gtk_widget_get_toplevel (widget); return g_object_get_qdata (G_OBJECT (widget), app_quark); } G_CONST_RETURN Event * app_find_prev_event_for_addr_range (App *app, guint time, gulong min, gulong max) { guint n; if (app->client.events->len == 0) return NULL; for (n = app->client.events->len; n-- > 0; ) { Event *event = &g_array_index (app->client.events, Event, n); if (event->base.time > time) continue; switch (event->type) { case ALLOC: if (event->alloc.addr + event->alloc.size < min || event->alloc.addr > max) { continue; } break; case REALLOC: if ((event->realloc.new_addr + event->realloc.new_size < max && event->realloc.new_addr > min) || (event->realloc.old_addr + event->realloc.old_size < max && event->realloc.old_addr > min)) { break; } continue; case DEALLOC: if (event->dealloc.addr + event->dealloc.size < min || event->dealloc.addr > max) { continue; } break; } return event; } return NULL; } static void reap_child (GPid pid, gint status, gpointer data) { App *app = data; if (WIFEXITED (status)) { g_print ("child exited with %d\n", WEXITSTATUS (status)); } else if (WIFSIGNALED (status)) { g_print ("child died with signal %d\n", WTERMSIG (status)); } g_spawn_close_pid (pid); app->client.terminated = TRUE; app_update_status (app); } static gboolean execute_cmdline_vg (App *app, char **argv, GError **error) { GInetAddr *addr; gchar *name; gint port; GPid child; gint argc = g_strv_length (argv); gchar **argvp = g_new (gchar *, argc + 8); int i, j; const gchar *env; gboolean ret; g_assert (app->client.pid == 0); j = 0; env = g_getenv ("VALGRIND"); if (env == NULL) env = "valgrind"; argvp[j++] = g_strdup (env); argvp[j++] = g_strdup ("valgrind"); argvp[j++] = g_strdup ("--tool=memfault"); addr = gnet_tcp_socket_get_local_inetaddr (app->vg_log); name = gnet_inetaddr_get_canonical_name (addr); port = gnet_inetaddr_get_port (addr); argvp[j++] = g_strdup_printf ("--log-socket=%s:%d", name, port); g_free (name); gnet_inetaddr_delete (addr); addr = gnet_tcp_socket_get_local_inetaddr (app->vg_events); name = gnet_inetaddr_get_canonical_name (addr); port = gnet_inetaddr_get_port (addr); argvp[j++] = g_strdup_printf ("--events=%s:%d", name, port); g_free (name); gnet_inetaddr_delete (addr); argvp[j++] = g_strdup ("--allow-malloc0=yes"); argvp[j++] = g_strdup ("--show-allocators=no"); for (i = 0; i < argc; i++) argvp[j++] = g_strdup (argv[i]); argvp[j] = NULL; ret = g_spawn_async (NULL, argvp, NULL, G_SPAWN_SEARCH_PATH | //G_SPAWN_STDOUT_TO_DEV_NULL | //G_SPAWN_STDERR_TO_DEV_NULL | G_SPAWN_DO_NOT_REAP_CHILD | G_SPAWN_FILE_AND_ARGV_ZERO, NULL, NULL, &child, error); if (ret) { app->client.type = VALGRIND; app->client.pid = child; app->client.terminated = FALSE; _client_init (&app->client, app); g_child_watch_add (child, reap_child, app); } g_strfreev (argvp); return ret; } static gboolean execute_cmdline_lwp (App *app, char **argv, gboolean attach_dbg, GError **error) { const gchar *lib = LIBDIR "/lwp.so"; gchar *path; gchar **env, **envp; GPid child; gboolean ret; gboolean have_preload, have_socket; gint n_client_env; gint n, m, i; g_assert (app->client.pid == 0); if (! g_file_test (lib, G_FILE_TEST_EXISTS)) { g_set_error (error, 0, 0, "Missing light-weight profiler: '%s' not found.", lib); return FALSE; } path = gnet_unix_socket_get_path (app->lwp_unix); n_client_env = 0; while (argv[n_client_env] != NULL) { if (strchr (argv[n_client_env], '=') == NULL || g_file_test (argv[n_client_env], G_FILE_TEST_EXISTS)) { break; } n_client_env++; } env = g_listenv (); envp = g_new (gchar *, g_strv_length (env) + n_client_env + 3); have_preload = have_socket = FALSE; for (i = n = 0; env[n] != NULL; n++) { gboolean found = FALSE; for (m = 0; ! found && m < n_client_env; m++) { char *equals = strchr (argv[m], '='); g_assert (equals != NULL); *equals = '\0'; found = strcmp (env[n], argv[m]) == 0; *equals = '='; } if (found) { /* override with the user's cmdline var */ if (strcmp (env[n], "LD_PRELOAD") == 0) have_preload = TRUE; continue; } if (strcmp (env[n], "LD_PRELOAD") == 0) { const gchar *preload = g_getenv ("LD_PRELOAD"); if (strstr (preload, lib) == NULL) envp[i] = g_strdup_printf ("LD_PRELOAD=%s:%s", lib, preload); else envp[i] = g_strconcat ("LD_PRELOAD=", preload, NULL); have_preload = TRUE; } else if (strcmp (env[n], "LWP_PATH") == 0) { envp[i] = g_strconcat ("LWP_PATH=unix:", path, NULL); have_socket = TRUE; } else envp[i] = g_strconcat (env[n], "=", g_getenv (env[n]), NULL); i++; } if (! have_preload) envp[i++] = g_strconcat ("LD_PRELOAD=", lib, NULL); if (! have_socket) envp[i++] = g_strconcat ("LWP_PATH=unix:", path, NULL); while (n_client_env--) { char *equals = strchr (*argv, '='); g_assert (equals != NULL); *equals = '\0'; if (strcmp (*argv, "LD_PRELOAD") == 0) { envp[i++] = g_strdup_printf ("LD_PRELOAD=%s:%s", lib, equals+1); } else if (strcmp (*argv, "LWP_PATH") == 0) { /* ignore */ } else { *equals = '='; envp[i++] = g_strdup (*argv); } argv++; } envp[i] = NULL; g_strfreev (env); ret = g_spawn_async (NULL, argv, envp, G_SPAWN_SEARCH_PATH | //G_SPAWN_STDOUT_TO_DEV_NULL | //G_SPAWN_STDERR_TO_DEV_NULL | G_SPAWN_DO_NOT_REAP_CHILD, NULL, NULL, &child, error); if (ret) { if (attach_dbg) { gchar *cmd; cmd = g_strdup_printf ("gnome-terminal -x gdb --pid=%d", child); g_spawn_command_line_async (cmd, NULL); g_free (cmd); } app->client.type = LWP; app->client.pid = child; app->client.terminated = FALSE; g_child_watch_add (child, reap_child, app); } g_strfreev (envp); g_free (path); return ret; } int main (int argc, char **argv) { App app; GIOChannel *io; GtkWidget *window; GInetAddr *iface; gboolean have_child; GError *error = NULL; const gchar tmpl[] = "lwp.XXXXXX"; gchar *path; int fd; gboolean use_valgrind = FALSE; gboolean attach_dbg = FALSE; gchar **client_argv = NULL; const GOptionEntry options[] = { { "valgrind", 0, 0, G_OPTION_ARG_NONE, &use_valgrind, "Use the heavy weight odin valgrind skin to intercept the allocations.", NULL }, { "attach-dbg", 0, 0, G_OPTION_ARG_NONE, &attach_dbg, "Attach the debugger to the child process.", NULL }, { G_OPTION_REMAINING, 0, 0, G_OPTION_ARG_STRING_ARRAY, &client_argv, NULL, NULL }, { NULL } }; GOptionContext *context; memset (&app, 0, sizeof (app)); gnet_init (); glibtop_init (); g_set_application_name ("Óðinn"); context = g_option_context_new ("[client [args]]"); g_option_context_add_main_entries (context, options, NULL); g_option_context_add_group (context, gtk_get_option_group (TRUE)); g_option_context_parse (context, &argc, &argv, &error); g_option_context_free (context); if (error != NULL) { g_printerr ("Failed to parse command line arguments: %s\n", error->message); g_error_free (error); return 1; } app_quark = g_quark_from_static_string ("«App»"); app.discard_log = TRUE; window = main_window_create (&app); g_signal_connect (window, "delete-event", G_CALLBACK (gtk_main_quit), NULL); gtk_window_present (GTK_WINDOW (window)); /* force the realize... */ while (gtk_events_pending ()) gtk_main_iteration (); /* nasty race ahead... */ fd = g_file_open_tmp (tmpl, &path, &error); if (fd == -1) { g_printerr ("Unable to create temporary path for socket: '%s'\n", error->message); g_error_free (error); return 1; } close (fd); g_remove (path); /* lwp interface */ app.lwp_unix = gnet_unix_socket_server_new (path); g_free (path); io = gnet_unix_socket_get_io_channel (app.lwp_unix); g_io_add_watch (io, G_IO_IN, lwp_unix_server_cb, &app); g_io_channel_unref (io); iface = gnet_inetaddr_get_host_addr (); app.lwp_tcp = gnet_tcp_socket_server_new_full (iface, 5320); gnet_inetaddr_delete (iface); io = gnet_tcp_socket_get_io_channel (app.lwp_tcp); g_io_add_watch (io, G_IO_IN, lwp_tcp_server_cb, &app); g_io_channel_unref (io); /* restrict to IPv4 loopback */ iface = gnet_inetaddr_new ("127.0.0.1", 0); app.vg_events = gnet_tcp_socket_server_new_full (iface, 0); gnet_inetaddr_delete (iface); io = gnet_tcp_socket_get_io_channel (app.vg_events); g_io_add_watch (io, G_IO_IN, vg_events_server_cb, &app); g_io_channel_unref (io); iface = gnet_inetaddr_new ("127.0.0.1", 0); app.vg_log = gnet_tcp_socket_server_new_full (iface, 0); gnet_inetaddr_delete (iface); io = gnet_tcp_socket_get_io_channel (app.vg_log); g_io_add_watch (io, G_IO_IN, vg_log_server_cb, &app); g_io_channel_unref (io); have_child = TRUE; if (client_argv != NULL) { gboolean have_allocators = FALSE; if (g_file_test (client_argv[0], G_FILE_TEST_EXISTS && ! g_file_test (client_argv[0], G_FILE_TEST_IS_EXECUTABLE))) { if (have_allocators == FALSE){ GIOChannel *io = g_io_channel_new_file (client_argv[0], "r", NULL); if (io != NULL) { gzFile *file; file = gzdopen (dup (g_io_channel_unix_get_fd (io)), "r"); app_set_busy (&app, TRUE); have_allocators = lwp_read (file, &app); if (have_allocators) do { _update_client (&app); while (gtk_events_pending ()) gtk_main_iteration (); } while (lwp_read (file, &app)); _update_client (&app); gzclose (file); g_io_channel_unref (io); app_set_busy (&app, FALSE); } } if (have_allocators == FALSE){ GIOChannel *io = g_io_channel_new_file (client_argv[0], "r", NULL); if (io != NULL) { gzFile *file; file = gzdopen (dup (g_io_channel_unix_get_fd (io)), "r"); have_allocators = vg_read (file, &app); gzclose (file); g_io_channel_unref (io); } } if (have_allocators) call_graph_store_update_tree_model (app.client.call_graph); else g_warning ("Failed to load allocations from '%s'", client_argv[0]); } if (! have_allocators) { if (use_valgrind) execute_cmdline_vg (&app, client_argv, &error); else execute_cmdline_lwp (&app, client_argv, attach_dbg, &error); if (error != NULL) { g_warning ("Failed to execute child: %s", error->message); g_error_free (error); have_child = FALSE; } } g_strfreev (client_argv); } if (have_child) gtk_main (); gdk_cursor_unref (app.busy_cursor); _client_fini (&app.client); gnet_unix_socket_delete (app.lwp_unix); gnet_tcp_socket_delete (app.lwp_tcp); gnet_tcp_socket_delete (app.vg_events); gnet_tcp_socket_delete (app.vg_log); glibtop_close (); return 0; }