[sysprof] memprof: add section for allocation leaks



commit 7a040a60328b5ab455a6be937c7c8f98e5c22e7f
Author: Christian Hergert <chergert redhat com>
Date:   Thu Nov 4 21:08:23 2021 -0700

    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 @@
                     <property name="group">summary</property>
                   </object>
                 </child>
+                <child>
+                  <object class="GtkRadioButton" id="leaked_allocs_button">
+                    <property name="label" translatable="yes">Leaked Allocations</property>
+                    <property name="draw-indicator">false</property>
+                    <property name="visible">true</property>
+                    <property name="active">false</property>
+                    <property name="group">summary</property>
+                  </object>
+                </child>
               </object>
             </child>
           </object>
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


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]