MINOR: activity: declare the storage for memory usage statistics

We'll need to store for each call place, the pointer to the caller
(the return address to be more exact as with free() it's not uncommon
to see tail calls), the number of calls to alloc/free and the total
alloc/free bytes. realloc() will be counted either as alloc or free
depending on the balance of the size before vs after.

We store 1024+1 entries. The first ones are used as hashes and the
last one for collisions.

When profiling is enabled via the CLI, all the stats are reset.
diff --git a/src/activity.c b/src/activity.c
index 18dd6ae..5162c22 100644
--- a/src/activity.c
+++ b/src/activity.c
@@ -10,6 +10,7 @@
  *
  */
 
+#include <malloc.h>
 #include <haproxy/activity-t.h>
 #include <haproxy/api.h>
 #include <haproxy/cfgparse.h>
@@ -30,6 +31,33 @@
 /* One struct per function pointer hash entry (256 values, 0=collision) */
 struct sched_activity sched_activity[256] __attribute__((aligned(64))) = { };
 
+
+#if USE_MEMORY_PROFILING
+/* determine the number of buckets to store stats */
+#define MEMPROF_HASH_BITS 10
+#define MEMPROF_HASH_BUCKETS (1U << MEMPROF_HASH_BITS)
+
+/* stats:
+ *   - malloc increases alloc
+ *   - free increases free (if non null)
+ *   - realloc increases either depending on the size change.
+ * when the real size is known (malloc_usable_size()), it's used in free_tot
+ * and alloc_tot, otherwise the requested size is reported in alloc_tot and
+ * zero in free_tot.
+ */
+struct memprof_stats {
+	const void *caller;
+	unsigned long long alloc_calls;
+	unsigned long long free_calls;
+	unsigned long long alloc_tot;
+	unsigned long long free_tot;
+};
+
+/* last one is for hash collisions ("others") and has no caller address */
+struct memprof_stats memprof_stats[MEMPROF_HASH_BUCKETS + 1] = { };
+
+#endif // USE_MEMORY_PROFILING
+
 /* Updates the current thread's statistics about stolen CPU time. The unit for
  * <stolen> is half-milliseconds.
  */
@@ -68,7 +96,35 @@
 		return 1;
 
 	if (strcmp(args[2], "memory") == 0) {
+#ifdef USE_MEMORY_PROFILING
+		if (strcmp(args[3], "on") == 0) {
+			unsigned int old = profiling;
+			int i;
+
+			while (!_HA_ATOMIC_CAS(&profiling, &old, old | HA_PROF_MEMORY))
+				;
+
+			/* also flush current profiling stats */
+			for (i = 0; i < sizeof(memprof_stats) / sizeof(memprof_stats[0]); i++) {
+				HA_ATOMIC_STORE(&memprof_stats[i].alloc_calls, 0);
+				HA_ATOMIC_STORE(&memprof_stats[i].free_calls, 0);
+				HA_ATOMIC_STORE(&memprof_stats[i].alloc_tot, 0);
+				HA_ATOMIC_STORE(&memprof_stats[i].free_tot, 0);
+				HA_ATOMIC_STORE(&memprof_stats[i].caller, NULL);
+			}
+		}
+		else if (strcmp(args[3], "off") == 0) {
+			unsigned int old = profiling;
+
+			while (!_HA_ATOMIC_CAS(&profiling, &old, old & ~HA_PROF_MEMORY))
+				;
+		}
+		else
+			return cli_err(appctx, "Expects either 'on' or 'off'.\n");
+		return 1;
+#else
 		return cli_err(appctx, "Memory profiling not compiled in.\n");
+#endif
 	}
 
 	if (strcmp(args[2], "tasks") != 0)