MINOR: activity/memprofile: also monitor strdup() activity
authorWilly Tarreau <w@1wt.eu>
Thu, 21 Nov 2024 07:45:04 +0000 (08:45 +0100)
committerWilly Tarreau <w@1wt.eu>
Thu, 21 Nov 2024 18:58:06 +0000 (19:58 +0100)
Some memory profiling outputs have showed negative counters, very likely
due to some libs calling strdup(). Let's add it to the list of monitored
activities.

Actually even haproxy itself uses some. Having "profiling.memory on" in
the config reveals 35 call places.

include/haproxy/activity-t.h
src/activity.c

index b396dc0..74bf772 100644 (file)
@@ -42,6 +42,7 @@ enum memprof_method {
        MEMPROF_METH_MALLOC,
        MEMPROF_METH_CALLOC,
        MEMPROF_METH_REALLOC,
+       MEMPROF_METH_STRDUP,
        MEMPROF_METH_FREE,
        MEMPROF_METH_P_ALLOC, // pool_alloc()
        MEMPROF_METH_P_FREE,  // pool_free()
index 5a058f3..df2541b 100644 (file)
@@ -46,6 +46,7 @@ struct show_activity_ctx {
 #undef calloc
 #undef malloc
 #undef realloc
+#undef strdup
 #endif
 
 /* bit field of profiling options. Beware, may be modified at runtime! */
@@ -67,7 +68,7 @@ struct sched_activity sched_activity[SCHED_ACT_HASH_BUCKETS] __attribute__((alig
 #ifdef USE_MEMORY_PROFILING
 
 static const char *const memprof_methods[MEMPROF_METH_METHODS] = {
-       "unknown", "malloc", "calloc", "realloc", "free", "p_alloc", "p_free",
+       "unknown", "malloc", "calloc", "realloc", "strdup", "free", "p_alloc", "p_free",
 };
 
 /* last one is for hash collisions ("others") and has no caller address */
@@ -82,6 +83,7 @@ static THREAD_LOCAL int in_memprof = 0;
 static void *memprof_malloc_initial_handler(size_t size);
 static void *memprof_calloc_initial_handler(size_t nmemb, size_t size);
 static void *memprof_realloc_initial_handler(void *ptr, size_t size);
+static char *memprof_strdup_initial_handler(const char *s);
 static void  memprof_free_initial_handler(void *ptr);
 
 /* Fallback handlers for the main alloc/free functions. They are preset to
@@ -90,6 +92,7 @@ static void  memprof_free_initial_handler(void *ptr);
 static void *(*memprof_malloc_handler)(size_t size)               = memprof_malloc_initial_handler;
 static void *(*memprof_calloc_handler)(size_t nmemb, size_t size) = memprof_calloc_initial_handler;
 static void *(*memprof_realloc_handler)(void *ptr, size_t size)   = memprof_realloc_initial_handler;
+static char *(*memprof_strdup_handler)(const char *s)             = memprof_strdup_initial_handler;
 static void  (*memprof_free_handler)(void *ptr)                   = memprof_free_initial_handler;
 
 /* Used to force to die if it's not possible to retrieve the allocation
@@ -126,6 +129,10 @@ static void memprof_init()
        if (!memprof_realloc_handler)
                memprof_die("FATAL: realloc() function not found.\n");
 
+       memprof_strdup_handler  = get_sym_next_addr("strdup");
+       if (!memprof_strdup_handler)
+               memprof_die("FATAL: strdup() function not found.\n");
+
        memprof_free_handler    = get_sym_next_addr("free");
        if (!memprof_free_handler)
                memprof_die("FATAL: free() function not found.\n");
@@ -168,6 +175,17 @@ static void *memprof_realloc_initial_handler(void *ptr, size_t size)
        return memprof_realloc_handler(ptr, size);
 }
 
+static char *memprof_strdup_initial_handler(const char *s)
+{
+       if (in_memprof) {
+               /* probably that dlsym() needs strdup(), let's fail */
+               return NULL;
+       }
+
+       memprof_init();
+       return memprof_strdup_handler(s);
+}
+
 static void  memprof_free_initial_handler(void *ptr)
 {
        memprof_init();
@@ -295,6 +313,32 @@ void *realloc(void *ptr, size_t size)
        return ret;
 }
 
+/* This is the new global strdup() function. It must optimize for the normal
+ * case (i.e. profiling disabled) hence the first test to permit a direct jump.
+ * It must remain simple to guarantee the lack of reentrance. stdio is not
+ * possible there even for debugging. The reported size is the really allocated
+ * one as returned by malloc_usable_size(), because this will allow it to be
+ * compared to the one before realloc() or free(). This is a GNU and jemalloc
+ * extension but other systems may also store this size in ptr[-1].
+ */
+char *strdup(const char *s)
+{
+       struct memprof_stats *bin;
+       size_t size;
+       char *ret;
+
+       if (likely(!(profiling & HA_PROF_MEMORY)))
+               return memprof_strdup_handler(s);
+
+       ret = memprof_strdup_handler(s);
+       size = malloc_usable_size(ret) + sizeof(void *);
+
+       bin = memprof_get_bin(__builtin_return_address(0), MEMPROF_METH_STRDUP);
+       _HA_ATOMIC_ADD(&bin->alloc_calls, 1);
+       _HA_ATOMIC_ADD(&bin->alloc_tot, size);
+       return ret;
+}
+
 /* This is the new global free() function. It must optimize for the normal
  * case (i.e. profiling disabled) hence the first test to permit a direct jump.
  * It must remain simple to guarantee the lack of reentrance. stdio is not