MINOR: debug: add a new "debug dev memstats" command
Now when building with -DDEBUG_MEM_STATS, some malloc/calloc/strdup/realloc
stats are kept per file+line number and may be displayed and even reset on
the CLI using "debug dev memstats". This allows to easily track potential
leakers or abnormal usages.
diff --git a/src/debug.c b/src/debug.c
index 6f46591..e1a07f0 100644
--- a/src/debug.c
+++ b/src/debug.c
@@ -673,6 +673,107 @@
return 1;
}
+#if defined(DEBUG_MEM_STATS)
+/* CLI parser for the "debug dev memstats" command */
+static int debug_parse_cli_memstats(char **args, char *payload, struct appctx *appctx, void *private)
+{
+ extern __attribute__((__weak__)) struct mem_stats __start_mem_stats;
+ extern __attribute__((__weak__)) struct mem_stats __stop_mem_stats;
+
+ if (!cli_has_level(appctx, ACCESS_LVL_OPER))
+ return 1;
+
+ if (strcmp(args[3], "reset") == 0) {
+ struct mem_stats *ptr;
+
+ if (!cli_has_level(appctx, ACCESS_LVL_ADMIN))
+ return 1;
+
+ for (ptr = &__start_mem_stats; ptr < &__stop_mem_stats; ptr++) {
+ _HA_ATOMIC_STORE(&ptr->calls, 0);
+ _HA_ATOMIC_STORE(&ptr->size, 0);
+ }
+ return 1;
+ }
+
+ if (strcmp(args[3], "all") == 0)
+ appctx->ctx.cli.i0 = 1;
+
+ /* otherwise proceed with the dump from p0 to p1 */
+ appctx->ctx.cli.p0 = &__start_mem_stats;
+ appctx->ctx.cli.p1 = &__stop_mem_stats;
+ return 0;
+}
+
+/* CLI I/O handler for the "debug dev memstats" command. Dumps all mem_stats
+ * structs referenced by pointers located between p0 and p1. Dumps all entries
+ * if i0 > 0, otherwise only non-zero calls.
+ */
+static int debug_iohandler_memstats(struct appctx *appctx)
+{
+ struct stream_interface *si = appctx->owner;
+ struct mem_stats *ptr = appctx->ctx.cli.p0;
+ int ret = 1;
+
+ if (unlikely(si_ic(si)->flags & (CF_WRITE_ERROR|CF_SHUTW)))
+ goto end;
+
+ chunk_reset(&trash);
+
+ /* we have two inner loops here, one for the proxy, the other one for
+ * the buffer.
+ */
+ for (ptr = appctx->ctx.cli.p0; ptr != appctx->ctx.cli.p1; ptr++) {
+ const char *type;
+ const char *name;
+ const char *p;
+
+ if (!ptr->size && !ptr->calls && !appctx->ctx.cli.i0)
+ continue;
+
+ /* basename only */
+ for (p = name = ptr->file; *p; p++) {
+ if (*p == '/')
+ name = p + 1;
+ }
+
+ switch (ptr->type) {
+ case MEM_STATS_TYPE_CALLOC: type = "CALLOC"; break;
+ case MEM_STATS_TYPE_FREE: type = "FREE"; break;
+ case MEM_STATS_TYPE_MALLOC: type = "MALLOC"; break;
+ case MEM_STATS_TYPE_REALLOC: type = "REALLOC"; break;
+ case MEM_STATS_TYPE_STRDUP: type = "STRDUP"; break;
+ default: type = "UNSET"; break;
+ }
+
+ //chunk_printf(&trash,
+ // "%20s:%-5d %7s size: %12lu calls: %9lu size/call: %6lu\n",
+ // name, ptr->line, type,
+ // (unsigned long)ptr->size, (unsigned long)ptr->calls,
+ // (unsigned long)(ptr->calls ? (ptr->size / ptr->calls) : 0));
+
+ chunk_printf(&trash, "%s:%d", name, ptr->line);
+ while (trash.data < 25)
+ trash.area[trash.data++] = ' ';
+ chunk_appendf(&trash, "%7s size: %12lu calls: %9lu size/call: %6lu\n",
+ type,
+ (unsigned long)ptr->size, (unsigned long)ptr->calls,
+ (unsigned long)(ptr->calls ? (ptr->size / ptr->calls) : 0));
+
+ if (ci_putchk(si_ic(si), &trash) == -1) {
+ si_rx_room_blk(si);
+ appctx->ctx.cli.p0 = ptr;
+ ret = 0;
+ break;
+ }
+ }
+
+ end:
+ return ret;
+}
+
+#endif
+
#ifndef USE_THREAD_DUMP
/* This function dumps all threads' state to the trash. This version is the
@@ -810,6 +911,9 @@
{{ "debug", "dev", "hex", NULL }, "debug dev hex <addr> [len]: dump a memory area", debug_parse_cli_hex, NULL, NULL, NULL, ACCESS_EXPERT },
{{ "debug", "dev", "log", NULL }, "debug dev log [msg] ... : send this msg to global logs", debug_parse_cli_log, NULL, NULL, NULL, ACCESS_EXPERT },
{{ "debug", "dev", "loop", NULL }, "debug dev loop [ms] : loop this long", debug_parse_cli_loop, NULL, NULL, NULL, ACCESS_EXPERT },
+#if defined(DEBUG_MEM_STATS)
+ {{ "debug", "dev", "memstats", NULL }, "debug dev memstats [reset|all] : dump/reset memory statistics", debug_parse_cli_memstats, debug_iohandler_memstats, NULL, NULL, ACCESS_EXPERT },
+#endif
{{ "debug", "dev", "panic", NULL }, "debug dev panic : immediately trigger a panic", debug_parse_cli_panic, NULL, NULL, NULL, ACCESS_EXPERT },
{{ "debug", "dev", "stream",NULL }, "debug dev stream ... : show/manipulate stream flags", debug_parse_cli_stream,NULL, NULL, NULL, ACCESS_EXPERT },
{{ "debug", "dev", "tkill", NULL }, "debug dev tkill [thr] [sig] : send signal to thread", debug_parse_cli_tkill, NULL, NULL, NULL, ACCESS_EXPERT },