From 7a040a60328b5ab455a6be937c7c8f98e5c22e7f Mon Sep 17 00:00:00 2001 From: Christian Hergert Date: Thu, 4 Nov 2021 21:08:23 -0700 Subject: [PATCH] memprof: add section for allocation leaks These are defined by allocation records that do not have a corresponding release record. --- src/libsysprof-ui/sysprof-memprof-page.c | 9 + src/libsysprof-ui/sysprof-memprof-page.ui | 9 + src/libsysprof/sysprof-memprof-profile.c | 234 ++++++++++++++++++++++ src/libsysprof/sysprof-memprof-profile.h | 13 ++ 4 files changed, 265 insertions(+) diff --git a/src/libsysprof-ui/sysprof-memprof-page.c b/src/libsysprof-ui/sysprof-memprof-page.c index 9dd42a88..d4764768 100644 --- a/src/libsysprof-ui/sysprof-memprof-page.c +++ b/src/libsysprof-ui/sysprof-memprof-page.c @@ -62,6 +62,7 @@ typedef struct GtkRadioButton *summary; GtkRadioButton *all_allocs; GtkRadioButton *temp_allocs; + GtkRadioButton *leaked_allocs_button; GtkLabel *temp_allocs_count; GtkLabel *num_allocs; GtkLabel *leaked_allocs; @@ -1029,6 +1030,8 @@ mode_notify_active (SysprofMemprofPage *self, do_allocs (self, SYSPROF_MEMPROF_MODE_ALL_ALLOCS); else if (button == priv->temp_allocs) do_allocs (self, SYSPROF_MEMPROF_MODE_TEMP_ALLOCS); + else if (button == priv->leaked_allocs_button) + do_allocs (self, SYSPROF_MEMPROF_MODE_LEAKED_ALLOCS); } } @@ -1147,6 +1150,7 @@ sysprof_memprof_page_class_init (SysprofMemprofPageClass *klass) gtk_widget_class_bind_template_child_private (widget_class, SysprofMemprofPage, temp_allocs_count); gtk_widget_class_bind_template_child_private (widget_class, SysprofMemprofPage, num_allocs); gtk_widget_class_bind_template_child_private (widget_class, SysprofMemprofPage, leaked_allocs); + gtk_widget_class_bind_template_child_private (widget_class, SysprofMemprofPage, leaked_allocs_button); gtk_widget_class_bind_template_child_private (widget_class, SysprofMemprofPage, peak_allocs); bindings = gtk_binding_set_by_class (klass); @@ -1182,6 +1186,11 @@ sysprof_memprof_page_init (SysprofMemprofPage *self) G_CALLBACK (mode_notify_active), self, G_CONNECT_SWAPPED); + g_signal_connect_object (priv->leaked_allocs_button, + "notify::active", + G_CALLBACK (mode_notify_active), + self, + G_CONNECT_SWAPPED); g_signal_connect_object (priv->summary, "notify::active", G_CALLBACK (mode_notify_active), diff --git a/src/libsysprof-ui/sysprof-memprof-page.ui b/src/libsysprof-ui/sysprof-memprof-page.ui index c123ab3c..0a522b1e 100644 --- a/src/libsysprof-ui/sysprof-memprof-page.ui +++ b/src/libsysprof-ui/sysprof-memprof-page.ui @@ -44,6 +44,15 @@ summary + + + Leaked Allocations + false + true + false + summary + + diff --git a/src/libsysprof/sysprof-memprof-profile.c b/src/libsysprof/sysprof-memprof-profile.c index 30984106..33da6bc5 100644 --- a/src/libsysprof/sysprof-memprof-profile.c +++ b/src/libsysprof/sysprof-memprof-profile.c @@ -410,6 +410,31 @@ compare_alloc (gconstpointer a, return 0; } +static gint +compare_alloc_pid_addr_time (gconstpointer a, + gconstpointer b) +{ + const Alloc *aptr = a; + const Alloc *bptr = b; + + if (aptr->pid < bptr->pid) + return -1; + else if (aptr->pid > bptr->pid) + return 1; + + if (aptr->addr < bptr->addr) + return -1; + else if (aptr->addr > bptr->addr) + return 1; + + if (aptr->time < bptr->time) + return -1; + else if (aptr->time > bptr->time) + return 1; + else + return 0; +} + static guint get_bucket (gint64 size) { @@ -741,6 +766,211 @@ temp_allocs_worker (Generate *g) } } +static void +leaked_allocs_worker (Generate *g) +{ + g_autoptr(GArray) leak_allocs = NULL; + g_autoptr(GArray) all_allocs = NULL; + StackNode *node; + SysprofCaptureFrameType type; + guint64 frame_num = 0; + + g_assert (g != NULL); + g_assert (g->reader != NULL); + + leak_allocs = g_array_new (FALSE, FALSE, sizeof (Alloc)); + all_allocs = g_array_new (FALSE, FALSE, sizeof (Alloc)); + + sysprof_capture_reader_reset (g->reader); + + while (sysprof_capture_reader_peek_type (g->reader, &type)) + { + if G_UNLIKELY (type == SYSPROF_CAPTURE_FRAME_PROCESS) + { + const SysprofCaptureProcess *pr; + + if (!(pr = sysprof_capture_reader_read_process (g->reader))) + break; + + if (!g_hash_table_contains (g->cmdlines, GINT_TO_POINTER (pr->frame.pid))) + { + g_autofree gchar *cmdline = g_strdup_printf ("[%s]", pr->cmdline); + g_hash_table_insert (g->cmdlines, + GINT_TO_POINTER (pr->frame.pid), + (gchar *)g_string_chunk_insert_const (g->symbols, cmdline)); + } + } + else if (type == SYSPROF_CAPTURE_FRAME_ALLOCATION) + { + const SysprofCaptureAllocation *ev; + Alloc a; + + if (!(ev = sysprof_capture_reader_read_allocation (g->reader))) + break; + + frame_num++; + + /* Short-circuit if we don't care about this frame */ + if (!sysprof_selection_contains (g->selection, ev->frame.time)) + continue; + + a.pid = ev->frame.pid; + a.tid = ev->tid; + a.time = ev->frame.time; + a.addr = ev->alloc_addr; + a.size = ev->alloc_size; + a.frame_num = frame_num; + + g_array_append_val (all_allocs, a); + } + else + { + if (!sysprof_capture_reader_skip (g->reader)) + break; + } + } + + if (all_allocs->len == 0) + return; + + /* Order so we can find frees right after alloc */ + g_array_sort (all_allocs, compare_alloc_pid_addr_time); + + for (guint i = 0; i < all_allocs->len; i++) + { + const Alloc *a = &g_array_index (all_allocs, Alloc, i); + const Alloc *next; + + /* free()s are <= 0 */ + if (a->size <= 0) + continue; + + if (i + 1 == all_allocs->len) + goto leaked; + + next = &g_array_index (all_allocs, Alloc, i+1); + if (a->addr == next->addr && a->pid == next->pid && next->size <= 0) + continue; + + leaked: + g_array_append_vals (leak_allocs, a, 1); + } + + if (leak_allocs->len == 0) + return; + + /* Now sort by frame number so we can walk the reader and get the stack + * for each allocation as we count frames. We can skip frames until we + * get to the matching frame_num for the next alloc. + * + * We sort in reverse so that we can just keep shortening the array as + * we match each frame to save having to keep a secondary position + * variable. + */ + g_array_sort (leak_allocs, compare_frame_num_reverse); + sysprof_capture_reader_reset (g->reader); + frame_num = 0; + while (sysprof_capture_reader_peek_type (g->reader, &type)) + { + if (type == SYSPROF_CAPTURE_FRAME_ALLOCATION) + { + const SysprofCaptureAllocation *ev; + const Alloc *tail; + + if (!(ev = sysprof_capture_reader_read_allocation (g->reader))) + break; + + frame_num++; + + tail = &g_array_index (leak_allocs, Alloc, leak_allocs->len - 1); + + if G_UNLIKELY (tail->frame_num == frame_num) + { + SysprofAddressContext last_context = SYSPROF_ADDRESS_CONTEXT_NONE; + const gchar *cmdline; + guint len = 5; + + node = stack_stash_add_trace (g->building, ev->addrs, ev->n_addrs, ev->alloc_size); + + for (const StackNode *iter = node; iter != NULL; iter = iter->parent) + len++; + + if (G_UNLIKELY (g->resolved->len < len)) + g_array_set_size (g->resolved, len); + + len = 0; + + for (const StackNode *iter = node; iter != NULL; iter = iter->parent) + { + SysprofAddressContext context = SYSPROF_ADDRESS_CONTEXT_NONE; + SysprofAddress address = iter->data; + const gchar *symbol = NULL; + + if (sysprof_address_is_context_switch (address, &context)) + { + if (last_context) + symbol = sysprof_address_context_to_string (last_context); + else + symbol = NULL; + + last_context = context; + } + else + { + for (guint i = 0; i < g->resolvers->len; i++) + { + SysprofSymbolResolver *resolver = g_ptr_array_index (g->resolvers, i); + GQuark tag = 0; + gchar *str; + + str = sysprof_symbol_resolver_resolve_with_context (resolver, + ev->frame.time, + ev->frame.pid, + last_context, + address, + &tag); + + if (str != NULL) + { + symbol = g_string_chunk_insert_const (g->symbols, str); + g_free (str); + + if (tag != 0 && !g_hash_table_contains (g->tags, symbol)) + g_hash_table_insert (g->tags, (gchar *)symbol, GSIZE_TO_POINTER (tag)); + + break; + } + } + } + + if (symbol != NULL) + g_array_index (g->resolved, SysprofAddress, len++) = POINTER_TO_U64 (symbol); + } + + if ((cmdline = g_hash_table_lookup (g->cmdlines, GINT_TO_POINTER (ev->frame.pid)))) + g_array_index (g->resolved, guint64, len++) = POINTER_TO_U64 (cmdline); + + g_array_index (g->resolved, guint64, len++) = POINTER_TO_U64 ("[Everything]"); + + stack_stash_add_trace (g->stash, + (gpointer)g->resolved->data, + len, + ev->alloc_size); + + g_array_set_size (leak_allocs, leak_allocs->len - 1); + + if (leak_allocs->len == 0) + break; + } + } + else + { + if (!sysprof_capture_reader_skip (g->reader)) + break; + } + } +} + static void sysprof_memprof_profile_generate_worker (GTask *task, gpointer source_object, @@ -781,6 +1011,10 @@ sysprof_memprof_profile_generate_worker (GTask *task, { summary_worker (g); } + else if (g->mode == SYSPROF_MEMPROF_MODE_LEAKED_ALLOCS) + { + leaked_allocs_worker (g); + } /* Release some data we don't need anymore */ g_clear_pointer (&g->resolved, g_array_unref); diff --git a/src/libsysprof/sysprof-memprof-profile.h b/src/libsysprof/sysprof-memprof-profile.h index 1a5badd3..28e4d915 100644 --- a/src/libsysprof/sysprof-memprof-profile.h +++ b/src/libsysprof/sysprof-memprof-profile.h @@ -33,11 +33,24 @@ G_BEGIN_DECLS #define SYSPROF_TYPE_MEMPROF_PROFILE (sysprof_memprof_profile_get_type()) +/** + * SysprofMemprofMode: + * @SYSPROF_MEMPROF_MODE_SUMMARY: The summary profile + * @SYSPROF_MEMPROF_MODE_ALL_ALLOCS: find all allocations + * @SYSPROF_MEMPROF_MODE_TEMP_ALLOCS: find temporary allocations + * @SYSPROF_MEMPROF_MODE_LEAKED_ALLOCS: find allocation leaks, Since 3.44 + * + * The memprof profile mode. + * + * Since 3.44 @SYSPROF_MEMPROF_MODE_LEAKED_ALLOCS is available + * to find leaked allocations. + */ typedef enum { SYSPROF_MEMPROF_MODE_SUMMARY = 0, SYSPROF_MEMPROF_MODE_ALL_ALLOCS = 1, SYSPROF_MEMPROF_MODE_TEMP_ALLOCS = 2, + SYSPROF_MEMPROF_MODE_LEAKED_ALLOCS = 3, } SysprofMemprofMode; typedef struct